Korišćenje Firebase-a u svrhe autentifikacije
Da bi aplikaciju zadržali jednostavnom, nećemo praviti stranu za registrovanje, već ukoliko se unese email koji ne postoji u bazi, aplikacija će nakon provere i pokušaja da prijavi (uloguje) korisnika taj, nepostojeći mejl, registrovati kao novog korisnika. Tako da, greška će se pojaviti samo u slučaju kada se ukuca pravilna (postojeća) email adresa a pogrešna šifra.
Krećemo tako što ćemo <Button> elementu (unutar LoginForm komponente) dodati prop onPress
čija vrednost će biti callback funkcija koja će se izvršavati na svaki klik dugmeta.
<CardSection>
<Button
onPress={ }
text='Log in' />
</CardSection>
Funkciju koju ćemo dodati biće pomoćna funkcija koju ćemo napisati unutar LoginForm komponente (najbolja praksa je da pomoćne komponente pišete iznad render()
metode). Potrebno je prvo importovati firebase
jer koristimo njegove funkcionalnosti
import firebase from 'firebase';
I zatim pišemo pomoćnu funkciju onButtonClick
onButtonClick() {
const { email, password } = this.state;
firebase.auth().signInWithEmailAndPassword(email, password);
}
Koristimo firebase-ovu auth().singInWithEmailAndPassword()
metodu i prosledjujemo joj email
ipassword
iz našeg state
objekta. I zatim ćemo ovu pomoćnu funkciju proslediti u onPress
<Button> komponente.
<CardSection>
<Button
onPress={ this.onButtonClick.bind(this) }
text='Log in' />
</CardSection>
Pošto je ovo callback funkcija i izvršiće se nekada u budućnosti potrebno je da "bind-ujemo" this
funkciji kako bi se this
predstavljao samo komponentu. (Da ne bind-ujemo ne bismo mogli koristi state
objekat unutar funkcije (const { .. } = this.state ), this
bi bio undefined
).
Funkcija firebase.auth().signInWithEmailAndPassword(email, password);
vraća Promise jer se izvešava asinhrono, jer ovim se pravi request na firebase server, i potrebno neko vreme da stigne odgovor. I kako bi bili svesni toga kada je odgovor stigao koristićemo Promise. Tako da ćemo to iskoristiti kako bi došli do željenog ponašanja naše Log in forme. (Ponašanje objasnjeno u prvom pasusu ovog dela)
Tako da ćemo ovo nadograditi sledećim kodom
firebase.auth().signInWithEmailAndPassword(email, password)
.catch(() => {
firebase.auth().createUserWithEmailAndPassword(email, password)
.catch(() => {
this.setState({ error: 'Authentication Failed' });
});
});
Ovde se dešava sledeće - nakon što je korisnik kliknuo na dugme Log in, aplikacija će prvo pokušati da sa vrednostima email
i password
uloguje korisnika. Ukoliko dodje do neke greške (to može biti neispravna email adresa ili šifra) tu grešku hvata prvi catch
unutar koga će aplikacija pokušati da registriju koristinika (firebase.auth().createUserWithEmailAndPassword(email, password)
). Ako se i tada dogodi greška to znači da email već postoji u bazi, a da je šifra pogrešna i zatim se ulazi u poslednji catch
pozivamo funckiju setState
koja za vrednost svojeg property-ja error
postavlja poruku "Authentication Failed". (Da naglasim da je potrebno sada pored email
i password
inicijalizovati i error
unutar state-a.
class LoginForm extends Component {
state = { email: '', password: '', error: '' };
...
}
Generalno ovakvo ponašanje se neće videti u realnim aplikacijama, ali držimo ovu aplikaciju što jednostavnijom.
Da bi korisnik saznao da je došlo do greške, treba je i prikazati. To ćemo učiniti tako što ćemo ubaciti jedan <Text> element iznad dugmeta i prikazati this.state.error
unutar njega. I kada nema greške state.error
će biti prazan i neće se ništa videti.
<Text style={ styles.errorStyle }>
{ this.state.error }
</Text>
Odmah ćemo definisati stil ovog teksta, jer želimo da bude crven i upadljiv.
const styles = {
errorStyle: {
fontSize: 20,
alignSelf: 'center',
color: 'red'
}
}
Ukoliko se dogodi greška i state.error
dobije odredjenu vrednost koja će se prikazati u ovo <Text> elemntu će ostati tu sve dok se vrednost state.error
ne resetuje. Zato je potrebno da pri svakom kliku dugmeta resetujemo tu vrednost, jer ukoliko korisnik prvo pogreši šifru a zatim je pravilno ukuca, error
treba da nestane. Zato ćemo samo unutar onButtonClick
metode resotovati taj state
.
onButtonClick() {
const { email, password } = this.state;
this.setState({ error: '' });
firebase.auth().signInWithEmailAndPassword(email, password)
.catch(() => {
firebase.auth().createUserWithEmailAndPassword(email, password)
.catch(() => {
this.setState({ error: 'Authentication Failed' });
});
});
}
Primetićete da kada probate da se prijavite nakon klika nema nikakvog načina da se korisnik obavesti da je kliknuo dugme i da se bilo šta dešava. O ovome treba da se povede dosta računa pogotovu jer se radi o mobilnoj aplikaciji, i mreža može biti dosta slabija, pogovoru 3G i 4G, gde ovakve vrste poziva mogu dosta dugo da traju. Zato ćemo mi napraviti novu komponentu, koju ćemo nazvati Loader
koji će biti mali klasičan spinner i koji će biti na mestu dugmeta dok se ne dobije odgovor od firebase-a prilikom prijave.
Tako da pravimo novi dokument unutar components
dadoteke sa imenom Loader.js i pošto je to kompoennta koja će se ili prikazivati (kada se čeka odgovor) ili ne, nemamo potrebe za korišćenjem state
pa će biti funkcionalna komponenta :
import React from 'react';
import { View, ActivityIndicator } from 'react-native';
const Loader = ({ size }) => {
return (
<View style={ styles.loaderStyle }>
<ActivityIndicator size={ size || 'large' }/>
</View>
);
};
const styles = {
loaderStyle: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
}
};
export default Loader;
Kako nam je potrebno za Loader nešto što se pokreće/okreće koristićemo React Native komponentu ActivityIndicator
, ime vam sve govori. On izgleda ovako
Dodaćemo mu style
tako da zauzima celu širinu i bude centriran i horizontalno i vertikalno. ActiviryIndicator
može da prima prop size
koji odredjuje njegovu veličinu. Kako bi ova komponenta bila reusable Loader
će primati ovaj prop od parent-a. Iskoristili smo ovde jedan mali "trik" size={ size || 'large' }
koji ustvari znači - Ako je prosledjen prop size
od strane parent-a iskoristiga, u suprotnom za vrednost property-ja size
iskoristi 'large'. Tako da ukoliko se ne kaže da je Loader
mali, uzeće vrednost 'large'.
Sada iskoristimo ovu komponentu unutar LoginForm
komponente.
Prvo ga import-ujemo
import Loader from './Loader';
Sledeći korak je da ga iskoristimo. Kada će se Loader
prikazivati? U trenutku kada se klikne da dugme "Log in" i biće vidljiv sve dok ne stigne odgovor od firebase-a. Zato ćemo koristi state
komponente. Napravićemo još jedan state
prop koji će se zvati loading
i koji će na početku imati vrednost false
. Kada se klikne na dugme dobiće vrednost true
i zatim kada se dobije odgovor od firebase-a vratiće se na false
. Tako da, zavisno od njegove vrednosti mi ćemo prikazivati Loader
komponentu ili ne.
state = { email: '', password: '', error: '', loading: false };
Unutar render
metode odradićemo dokonstruckiju state
(ovo radimo samo zbog "čistoće" koda)
const { loading } = this.state;
A zatim ćemo u poslednjoj CardSection
komponeni umesto Button
komponenti napisati sledeće
<CardSection>
{ loading ? <Loader size='small' /> : <Button text='Log in' onPress={ this.onButtonClick.bind(this) } /> }
</CardSection>
Ovo je još jedna ECMA6 feature na koji se treba navići, a ona označava sledeće:
Ako je loading === true
prikaži mi <Loader> komponentu a u suprotnom (smešta se iza :
) prikaži mi <Button> komponentu. Treba se navići na ove stvari jer se sve više koriste, a i mnogo su čistije of if
i else
.
Sada je još potrebno da izmenimo vrednosti state.loading
u odredjenim situacijama.
Prvo kada se klikne dugme "Log in" postavićemo da state.loading
bude true
. (dodajemo unutar onButtonClick
metode)
this.setState({ error: '', loading: true });
Tako da će se pored brisanja error
poruke na klik dugmeta i loading
postaviti na true
. Sada je potrebno da nakon vraćenog odgovora od strane firebase-a vratimo loading
na false
i na taj način sakrijemo Loader
komponentu.
Dva slučaja kada ćemo vratiti vrednost na false
jeste kada se uspešno izvrši firebase.auth().signInWithEmailAndPassword(email, password)
ilifirebase.auth().createUserWithEmailAndPassword(email, password)
. Do sada smo koristili samo catch()
metodu koja se poziva ukoliko dodje do neke greške, a sada ćemo iskoristi then()
metodu koja se izvršava ukoliko nema grešaka i u tom slučaju želimo da vratimo loading
na false
.
onButtonClick() {
const { email, password } = this.state;
this.setState({ error: '', loading: true });
firebase.auth().signInWithEmailAndPassword(email, password)
.then( this.setState({ loading: false }) )
.catch(() => {
firebase.auth().createUserWithEmailAndPassword(email, password)
.then( this.setState({ loading: false }) )
.catch( this.setState({ error: 'Authentication Failed', loading: false }) );
});
}
Na ovaj način smo pokriti to da bilo kakav odgovor firebase-a bio, mi sakrijemo Loader
komponentu. Još jednu stvar koju ćemo želeti da imao jeste to da se nakon uspešne prijave obrišu vrednosti iz naših input
komponenti a to ćemo uraditi jednostavnim čišćenjem state
-a. Tako da ćemo napisati jednu novu pomoćnu metodu
onLoginSuccess() {
this.setState({
email: '',
password: '',
error: '',
loading: false,
});
}
I iskoristi je unutar onButtonClick()
metode, radi čistog koda napisaćemo i pomoćnu funkciju u slučaju greške
onLoginFail() {
this.setState({ error: 'Authentication Failed', loading: false });
}
Iskoristimo ih unutar onButtonClick()
metode
onButtonClick() {
const { email, password } = this.state;
this.setState({ error: '', loading: true });
firebase.auth().signInWithEmailAndPassword(email, password)
.then( this.onLoginSuccess.bind(this) )
.catch(() => {
firebase.auth().createUserWithEmailAndPassword(email, password)
.then( this.onLoginSuccess.bind(this) )
.catch( this.onLoginFail.bind(this) );
});
}
Sada je kod i čistiji i čitljiviji.
LoginForm.js izlgeda sada ovako:
import React, { Component } from 'react';
import { Text } from 'react-native';
import firebase from 'firebase';
import Button from './Button';
import Card from './Card';
import CardSection from './CardSection';
import Input from './Input';
import Loader from './Loader';
class LoginForm extends Component {
state = { email: '', password: '', error: '', loading: false };
onButtonClick() {
const { email, password } = this.state;
this.setState({ error: '', loading: true });
firebase.auth().signInWithEmailAndPassword(email, password)
.then( this.onLoginSuccess.bind(this) )
.catch(() => {
firebase.auth().createUserWithEmailAndPassword(email, password)
.then( this.onLoginSuccess.bind(this) )
.catch( this.onLoginFail.bind(this) );
});
}
onLoginFail() {
this.setState({ error: 'Authentication Failed', loading: false });
}
onLoginSuccess() {
this.setState({
email: '',
password: '',
error: '',
loading: false,
});
}
render() {
const { loading } = this.state;
return (
<Card>
<CardSection>
<Input
placeholder= '[email protected]'
label= 'Email'
value={ this.state.email }
onChangeText={ email => this.setState({ email: email }) } />
</CardSection>
<CardSection>
<Input
secureTextEntry={ true }
placeholder= 'password'
label= 'Password'
value={ this.state.password }
onChangeText={ password => this.setState({ password: password }) } />
</CardSection>
<Text style={ styles.errorStyle }>
{ this.state.error }
</Text>
<CardSection>
{ loading ? <Loader size='small' /> : <Button text='Log in' onPress={ this.onButtonClick.bind(this) } /> }
</CardSection>
</Card>
);
}
}
const styles = {
errorStyle: {
fontSize: 20,
alignSelf: 'center',
color: 'red'
}
}
export default LoginForm;
Sada možete da reload-ujete svoj simualtor i testirate ponašanje forme. Kada popunite formu i kliknete na dugme dugme će nestati i pojaviće se Loader
komponenta. U slučaju da pogrešite šifru pojaviće se greška i vratiti dugme. Ukoliko zatim unesete tačne podatke i kliknete na dugme Loader
će se ponovo pojaviti i ukoliko prijava bude u redu, dugme se vraća na mesto i forma će se isprazniti.
Medjutim, to nije dovoljno za korisnike da znaju da su se uspešno prijavili tako da će naš sledeći korak biti upravo to. Nakon uspešne prijave prikazati korisniku nešo drugo a formu sakriti.