This commit is contained in:
Salakar 2018-04-21 15:55:13 +01:00
commit fb1a9a8f5a
26 changed files with 1285 additions and 10 deletions

4
.gitignore vendored
View File

@ -86,4 +86,6 @@ local.properties
**/ios/ReactNativeFirebaseDemo.xcworkspace/
dist
version.js
.nyc_output
.nyc_output
ios.coverage.json
tests/ios/Fabric.framework/Fabric

View File

@ -80,3 +80,4 @@ buddybuild_postclone.sh
bin/test.js
.github
example
codorials

View File

@ -6,8 +6,8 @@ First, thank you for considering contributing to react-native-firebase! It's peo
We welcome any type of contribution, not only code. You can help with
* **QA**: file bug reports, the more details you can give the better (e.g. screenshots with the console open)
* **Docs**: improve reference coverage, add more examples, fix any typos or anything else you can spot. At the top of every page on our docs site you can click the `Edit` pencil to go to that pages markdown file, or view the [Docs Repo](https://github.com/invertase/react-native-firebase-docs) directly
* **QA**: file bug reports, the more details you can give the better (e.g. platform versions, screenshots sdk versions & logs)
* **Docs**: improve reference coverage, add more examples, fix typos or anything else you can spot. At the top of every page on our docs site you can click the `Edit` pencil to go to that pages markdown file, or view the [Docs Repo](https://github.com/invertase/react-native-firebase-docs) directly
* **Marketing**: writing blog posts, howto's, printing stickers, ...
* **Community**: presenting the project at meetups, organizing a dedicated meetup for the local community, ...
* **Code**: take a look at the [open issues](issues). Even if you can't write code, commenting on them, showing that you care about a given issue matters. It helps us triage them.
@ -20,12 +20,12 @@ Working on your first Pull Request? You can learn how from this _free_ series, [
## Submitting code
Any code change should be submitted as a pull request. The description should explain what the code does and give steps to execute it. The pull request should also contain tests.
Any code change should be submitted as a pull request. The description should explain what the code does and give steps to execute it. The pull request should also contain tests where possible.
## Code review process
The bigger the pull request, the longer it will take to review and merge. Try to break down large pull requests in smaller chunks that are easier to review and merge.
It is also always helpful to have some context for your pull request. What was the purpose? Why does it matter to you?
It is also always helpful to have some context for your pull request. What was the purpose? Why does it matter to you? Tag in any linked issues.
## Financial contributions

View File

@ -560,7 +560,9 @@ public class RNFirebaseNotificationManager {
}
private Uri getSound(String sound) {
if (sound.equalsIgnoreCase("default")) {
if (sound.contains("://")) {
return Uri.parse(sound);
} else if (sound.equalsIgnoreCase("default")) {
return RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
} else {
int soundResourceId = getResourceId("raw", sound);

View File

@ -20,7 +20,7 @@
"ios:pod:install": "cd ios && rm -rf ReactNativeFirebaseDemo.xcworkspace && pod install && cd .."
},
"dependencies": {
"bridge": "^0.2.4",
"bridge": "^0.2.5",
"detox": "^7.2.0",
"escape-string-regexp": "^1.0.5",
"fbjs": "^0.8.16",

View File

@ -0,0 +1,188 @@
# App Navigation
React Navigation has gone through many cycles of navigation implementations and has been a pain point for developers for a good while.
A current "go to" navigation library is called [react-navigation](https://reactnavigation.org/). It's pure JavaScript implementation
which performs well and provides a solid foundation for navigation on both Android and iOS.
In this step we'll be focusing on adding a Login & Register screen to our app.
## Installation
Simply install the dependency via NPM, no native installation is needed:
```bash
npm install --save react-navigation
```
## Navigation Stacks
Navigation on an app typically works in stacks, where a user can navigate to a new screen (pushing a new screen onto the stack), or backwards (popping
a screen off the stack).
What's great about this concept is that we can create multiple instances of a stack, for example a stack for unauthenticated users and another for
authenticated ones.
To create a new stack, we import the `StackNavigator` from `react-navigation`. In it's basic form, the first item of the `StackNavigator` object
acts as our initial screen on the stack. Lets create a new directory and component for our unauthenticated state:
```js
// src/screens/unauthenticated/index.js
import { StackNavigator } from 'react-navigation';
import Login from './Login';
import Register from './Register';
export default StackNavigator({
Login: {
screen: Login,
},
Register: {
screen: Register,
},
});
```
In both the `Login` & `Register` files, create a basic React component (change Login to Register where appropriate):
```jsx
// src/screens/unauthenticated/Login.js
// src/screens/unauthenticated/Register.js
import React, { Component } from 'react';
import { View, Text } from 'react-native';
class Login extends Component {
render() {
return (
<View>
<Text>Login</Text>
</View>
);
}
}
```
## Using the stack
StackNavigator returns a React component which can be rendered in our app. If we go back to our `src/App.js` component, we can now return
the stack:
```jsx
// src/App.js
import React, { Component } from 'react';
import UnauthenticatedStack from './screens/unauthenticated';
class App extends Component {
render() {
return <UnauthenticatedStack />;
}
}
export default App;
```
Our `UnauthenticatedStack` component will now show the `Login` component as it's the first item in the `StackNavigator`. Reload your app and you
should have your `Login` component rendering!
![Basic Navigation](assets/1-unauthenticated-nav.jpg =300x\*)
## Styling the navigator
As you can see, `react-navigation` provides basic styling to mimic the feel of Android's [Material Design](https://material.io). The
library provides a simple, React like API to style and control your app.
> If you're using iOS, the functionality will remain the same however the basic styling will represent that of the iOS interface instead!
For this example we're going to add a title to our screen and liven up the colors - there's loads more you can do with `react-navigation` though,
just check out their in-depth [documentation](https://reactnavigation.org/docs/getting-started.html).
Lets go ahead and style the screen, using a class static `navigationOptions` object which lets `react-navigation` access our screen component styling:
```jsx
// src/screens/unauthenticated/Login.js
import React, { Component } from 'react';
import { View, Text } from 'react-native';
class Login extends Component {
// Add our react-navigation static method:
static navigationOptions = {
title: 'Login',
headerStyle: {
backgroundColor: '#E6853E',
},
headerTintColor: '#fff',
};
render() {
return (
<View>
<Text>Login</Text>
</View>
);
}
}
export default Login;
```
With this basic config you'll end up with an Android looking app with minimal configuration. Whats better is that `react-navigation` will also
take care of any back buttons and screen animations when navigating through the stack, pretty nifty.
![Styled Navigation](assets/2-unauthenticated-nav.jpg =300x\*)
## Pushing to a new stack
Pushing a new screen onto the stack is a common practice on mobile apps, however requires a slightly different mindset if you're from a web development
background. The basics of a stack allow you to `push` and `pop` where screens effectively overlay each other. The user cannot change stack item
unless you give them the ability to (compared to a website where the user could manually enter a different URL). This allows for greater
control over what a user is able to push/pop to.
Each component we assign to our `StackNavigator` gets cloned by `react-navigation` with a prop called `navigation` which gives us full control over
all of the navigation functionality we'll need.
* To "push" to a new screen we call the `navigate` method with the screen name we defined as the object key within `StackNavigator`.
* To "pop", or go back to the previous screen on the stack we call the `goBack` method.
Lets add a simple button to push to the `Register` screen we defined:
```jsx
// src/screens/unauthenticated/Login.js
import React, { Component } from 'react';
import { View, Button } from 'react-native';
class Login extends Component {
static navigationOptions = {
title: 'Login',
headerStyle: {
backgroundColor: '#E6853E',
},
headerTintColor: '#fff',
};
// Call this method on the button press
_register = () => {
this.props.navigation.navigate('Register');
};
render() {
return (
<View>
<Button onPress={this._register} title="Register Now!" />
</View>
);
}
}
export default Login;
```
Go ahead and click the button, you'll be pushed to a new screen. By pressing the back arrow on the header, `react-navigation` will automatically
call the `goBack` method for us:
![Transition!](assets/3-unauthenticated-push-pop.gif =300x\*)
> To style the `Register` page, simply add it's own `navigationOptions` static config!

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 341 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 219 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 374 KiB

View File

@ -0,0 +1,28 @@
# Conclusion
Hopefully from this Codorial you have seen the power of Firebase - allowing you to concentrate on your application and not worry about other complicated
topics, such as authentication. However understanding how to integrate this into your main business logic can seem challenging at first. This Codorial
has brought together a number of separate libaries which can be applied to any application context.
## Starter Kits
Invertase has provided a couple of starter kits to get your app up and running quicker:
### React Native Firebase Starter
https://github.com/invertase/react-native-firebase-starter
The React Native Firebase Starter is a free, pre-setup React Native app which integrates the [react-native-firebase](https://github.com/invertase/react-native-firebase)
modules. It's perfect if you want to get up and running with no native module installation on both Android & iOS.
### React Native Authentication Starter Kit for Firebase
https://rnfirebase.io/kits/auth-starter
The React Native Authentication Starter Kit for Firebase is a premium starter kit for Android & iOS with a fully integrated authentication flow out of the box.
It features email/password, Facebook, Google and Phone Number login, routing with [react-navigation](https://reactnavigation.org/) and Google Analytics for Firebase.
## Hire Us
The developers behind react-native-firebase at Invertase are available for hire on your next project, big or small. If you'd like to find out more,
head over to our website and drop us a contact message: https://invertase.io/

View File

@ -0,0 +1,47 @@
{
"title": "React Native authentication with Firebase",
"description": "Create a React Native app from scratch, implementing authentication with react-native-firebase. This Codorial covers navigation, firebase, redux and more!",
"tags": ["react-native", "redux", "react-redux", "firebase", "firebase-auth", "react-native-firebase", "react-navigation"],
"steps": [
{
"title": "Getting Started",
"file": "getting-started"
},
{
"title": "Project Structure",
"file": "project-structure"
},
{
"title": "Understanding Firebase Auth",
"file": "understanding-firebase-auth"
},
{
"title": "Integrating Redux",
"file": "integrating-redux"
},
{
"title": "App Navigation",
"file": "app-navigation"
},
{
"title": "Handling Authentication State",
"file": "handling-authentication-state"
},
{
"title": "Creating a sign-in form",
"file": "creating-a-signin-form"
},
{
"title": "Facebook Login",
"file": "facebook-login"
},
{
"title": "Google Login",
"file": "google-login"
},
{
"title": "Conclusion",
"file": "conclusion"
}
]
}

View File

@ -0,0 +1,163 @@
# Creating a sign-in form
We can now get onto allowing the user to login with their email and password. First off we need to create a dummy user for testing. This can be
done via the Firebase console on the 'Authentication' tab. Lets go ahead and create one now:
![Add new user](assets/add-user.jpg)
## Handling user input
React Native provides us with a [`TextInput`](https://facebook.github.io/react-native/docs/textinput.html) component, which renders the web
equivalent of an `input` box in our app. `TextInput` components are 'uncontrolled' meaning we have to explicitly give it a value and handle
updates the user enters. We're going to do this via component state, however you could also do this via our Redux store which is an option
other React developers would go down.
```jsx
// src/screens/unauthenticated/Login.js
import React, { Component } from 'react';
import { View, TextInput } from 'react-native';
class Login extends Component {
static navigationOptions = {
title: 'Login',
headerStyle: {
backgroundColor: '#E6853E',
},
headerTintColor: '#fff',
};
constructor() {
super();
this.state = {
email: '',
password: '',
};
}
_updateEmail = email => {
this.setState({ email });
};
_updatePassword = password => {
this.setState({ password });
};
render() {
return (
<View>
<TextInput
placeholder={'Email Address'}
onChangeText={this._updateEmail}
value={this.state.email}
/>
<TextInput
placeholder={'Password'}
onChangeText={this._updatePassword}
value={this.state.password}
/>
</View>
);
}
}
export default Login;
```
If you reload your app, you will see two plain `TextInput` boxes which can accept input. As these are updated, the `onChangeText` prop is triggered
which then updates state for that specific value. The inputs then individually update whenever their `value` from state changes:
![TextInput Changes](assets/textinput-update.gif =300x\*)
> If you want to hide your users password, use the `secureTextEntry` prop.
## Communicating with Firebase
Now we've got our users input readily available in state, we can use the values to send to Firebase! First off we need a trigger to do this:
```jsx
// src/screens/unauthenticated/Login.js
import React, { Component } from 'react';
import { View, TextInput, Button } from 'react-native';
...
_signIn = () => {
};
render() {
return (
<View>
<TextInput
placeholder={'Email Address'}
onChangeText={this._updateEmail}
value={this.state.email}
/>
<TextInput
placeholder={'Password'}
onChangeText={this._updatePassword}
value={this.state.password}
/>
<Button
title={'Sign In'}
onPress={this._signIn}
/>
</View>
);
}
```
As mentioned in 'Understanding Firebase Auth', we can call the `signInAndRetrieveDataWithEmailAndPassword` method on the Firebase API within our `_signIn` method,
using the state values.
```js
// src/screens/unauthenticated/Login.js
import firebase from 'react-native-firebase';
...
_signIn = () => {
// extract the values from state
const { email, password } = this.state;
firebase.auth().signInAndRetrieveDataWithEmailAndPassword(email, password)
.catch((error) => {
console.error(error);
});
};
```
The Firebase call will catch any errors (see list [here](https://rnfirebase.io/docs/v3.2.x/auth/reference/auth#signInAndRetrieveDataWithEmailAndPassword))
which may occur, such as a bad email address or incorrect email/password combination.
You may notice we don't listen our for the success response from the call (via the `.then`). As you may remember, back in our `App` component
our listener using `onAuthStateChanged` will pick up any successful sign in that occurs - and you guessed it, update the Redux store with
our users details, which causes `App` to re-render with our new `AuthenticatedStack`!
The `Welcome` component implemented below on the `AuthenticatedStack` contains a button which calls `firebase.auth().signOut()`, which triggers the
reverse motion of showing the `UnauthenticatedStack` to the user - simple!
![Sign In](assets/signin.gif =300x\*)
> You may notice there's a delay when pressing the "Sign In" button and the `onAuthStateChanged` lister firing. An app should always give feedback
> to a user when an action is happening. This can be accomplished through state/redux using many of the React Native components available such as
> [`ActivityIndicator`](https://facebook.github.io/react-native/docs/activityindicator.html). You'll also want to handle any errors back from
> Firebase to show to your user!
## Register / Forgot Password
The logic for registering or submitting a forgot password request is exactly the same as our sign in logic. Simply follow the same pattern
as we've done above using `TextInput` components and state. Once you've got the desired information, call Firebase with the methods you need, such as
[`createUserAndRetrieveDataWithEmailAndPassword`](https://rnfirebase.io/docs/v3.2.x/auth/reference/auth#createUserAndRetrieveDataWithEmailAndPassword)
or [`sendPasswordResetEmail`](https://rnfirebase.io/docs/v3.2.x/auth/reference/auth#sendPasswordResetEmail)!
Remember to allow the user to navigate between these screens using the `navigate` method provided by `react-navigation`.
> You could also try implementing [`TabNavigation`](https://reactnavigation.org/docs/tab-based-navigation.html) to allow horizontal navigation between
> screens which is another common navigation pattern.

View File

@ -0,0 +1,115 @@
# Facebook Login
Rather than users signing in to your app with an email and password, Firebase provides the opportunity to integrate with a number of login providers
(or even your own!). It does this by creating a `credential` from an [OAuth](https://oauth.net/) request which your login provider returns, such as an
`accessToken`.
The Firebase API allows us to call `signInAndRetrieveDataWithCredential` with a generated credential. You guessed it, just as we accomplished in
'Creating a sign in form', the method triggers `onAuthStateChanged` if the request was a success - super simple!
## Installing `react-native-fbsdk`
Luckily as Facebook own React Native, they provide a handy wrapper around their own SDK to integrate with React Native, called [`react-native-fbsdk`](https://github.com/facebook/react-native-fbsdk).
```bash
npm install --save react-native-fbsdk
```
To save explaining how to install this library, refer to their [documentation](https://developers.facebook.com/docs/react-native) on how to
install the library into your React Native project on both Android & iOS.
## Creating a credential
A credential can be generated by first obtaining an `accessToken` from Facebook. This is returned once a user successfully signs in via their
Facebook app or popup (which the `react-native-fbsdk` handles).
In our `Login.js` component, go ahead and create a new method called `_facebookLogin`:
```js
// src/screens/unauthenticated/Login.js
import { AccessToken, LoginManager } from 'react-native-fbsdk'; // import AccessToken and LoginManager
...
_facebookLogin = async () => {
try {
const result = await LoginManager.logInWithReadPermissions(['public_profile', 'email']);
if (result.isCancelled) {
throw new Error('User cancelled request'); // Handle this however fits the flow of your app
}
console.log(`Login success with permissions: ${result.grantedPermissions.toString()}`);
// get the access token
const data = await AccessToken.getCurrentAccessToken();
if (!data) {
throw new Error('Something went wrong obtaining the users access token'); // Handle this however fits the flow of your app
}
// create a new firebase credential with the token
const credential = firebase.auth.FacebookAuthProvider.credential(data.accessToken);
// login with credential
await firebase.auth().signInAndRetrieveDataWithCredential(credential);
} catch (e) {
console.error(e);
}
};
```
There's quite a few steps involved here, which require asynchronous calls to both Facebook and Firebase so it's important to give feedback to your
user whilst this process is running. There's also a number of errors which can occur, caused by the user (such as rejecting the request) or
Firebase not having the Facebook provider enabled, so ensure the error is shown back to the user.
> You may notice here we make use of [`async/await`](https://ponyfoo.com/articles/understanding-javascript-async-await). This allows us to keep our code
> feeling synchronous and handle any false positive errors we want to catch without worry about promise chaining.
### Triggering the method
Quite simply, just like the `_signIn` method we call the `_facebookLogin` method with a custom `Button`:
```jsx
// src/screens/unauthenticated/Login.js
...
render() {
return (
<View>
<TextInput
placeholder={'Email Address'}
onChangeText={this._updateEmail}
value={this.state.email}
/>
<TextInput
placeholder={'Password'}
onChangeText={this._updatePassword}
value={this.state.password}
/>
<Button
title={'Sign In'}
onPress={this._signIn}
/>
<Button
title={'Sign In with Facebook'}
onPress={this._facebookLogin}
/>
</View>
);
}
```
## Updating your Facebook app ID & secret
Back in step 'Understanding Firebase Authentication', we enabled Firebase as a login provider on Facebook, however may have entered dummy values.
The `react-native-fbsdk` requires you to assign an app ID to your install as mentioned in their [quickstart guide](https://developers.facebook.com/quickstarts/?platform=android).
Ensure that the app you choose also has the credentials on the Firebase console, otherwise you'll get an error back from Firebase when attempting to
sign in with the generated credential.

View File

@ -0,0 +1,42 @@
# Getting Started
Welcome to the 'Authentication with Firebase' Codorial, using [React Native](http://facebook.github.io/react-native/) and [react-native-firebase](https://rnfirebase.io).
Over the Codorial we'll cover how to setup your application to require both email/password login and social login using Facebook,
to handling the users authenticated state using the popular [redux](https://redux.js.org/introduction) library whilst also integrating routing
using [react-navigation](https://reactnavigation.org/).
## Prerequisites
This Codorial assumes you know the basics of the following topics:
* React JS.
* ES6 JavaScript.
* Starting an app using an emulator on Android/iOS.
* Understand how to setup a new Firebase project.
* Managing your project using Android Stuido and/or XCode.
* Installation of the [react-native-firebase](https://rnfirebase.io) library (see "Creating a base project" below).
This Codorial was created with React Native version `0.53.0`.
## Creating a base project
This project will take a bare bones React Native setup and explain every step required to implement a solid authentication flow in your application.
To start we need a base project to work from.
Both options below require you to setup a new Firebase project and add the configuration file to your project - check out the [documentation](https://rnfirebase.io/docs/v3.2.x/installation/initial-setup) on how to do that if needed.
### Option 1: Using `react-native init`
You can quickly create a base React Native project using `react-native init` by following the React Native [documentation](http://facebook.github.io/react-native/docs/getting-started.html).
> Ensure you follow the "Building Projects with Native Code" tab, as the project won't work using Expo due to requiring native modules.
Once installed, you need to install the [react-native-firebase](https://rnfirebase.io/docs/v3.2.x/installation/initial-setup) library. Ensure you've
also installed the Authentication module on your platform ([Android](https://rnfirebase.io/docs/v3.2.x/auth/android) or [iOS](https://rnfirebase.io/docs/v3.2.x/auth/ios))!
### Option 2: Using [react-native-firebase-starter](https://github.com/invertase/react-native-firebase-starter)
A starter kit has been created to help you get up and running with minimal setup needed. If you're new to React Native this will be perfect starting point.
> Keep in mind every Firebase module is installed in this starter kit. You can refer to the react-native-firebase [documentation](https://rnfirebase.io/docs) if you want to remove
> any unwanted modules.

View File

@ -0,0 +1,48 @@
# Google Login
Much like Facebook loginin, Firebase provides the ability to accept and sign in with a Google credential.
## Installing `react-native-google-signin`
To sign in with Google, we recommend using `react-native-google-signin`. This provides a native way of obtaining the users
Google accounts and their required `accessToken`.
```
npm install react-native-google-signin --save
react-native link react-native-google-signin
```
## Creating a credential
To generate a new credential for the user, simply call the asynchronous `signIn` method `react-native-google-signin` provides and create a new
credential from the `firebase.auth.GoogleAuthProvider`:
```js
import { GoogleSignin } from 'react-native-google-signin';
// Calling this function will open Google for login.
export const googleLogin = async () => {
try {
// Add any configuration settings here:
await GoogleSignin.configure();
const data = await GoogleSignin.signIn();
// create a new firebase credential with the token
const credential = firebase.auth.GoogleAuthProvider.credential(
data.idToken,
data.accessToken
);
// login with credential
const currentUser = await firebase
.auth()
.signInAndRetrieveDataWithCredential(credential);
} catch (e) {
console.error(e);
}
};
```
The flow here is exactly the same as the rest of our app; logging in with the newly created credential will trigger our `onAuthStateChanged` lister to fire with
the new user, logging us into the application.

View File

@ -0,0 +1,236 @@
# Handling Authentication State
Now we've got a basic navigation stack in place along with Redux, we can combine the two together to handle the users authenticated state.
## Listen for authentication state changes
As mentioned in "Understanding Firebase Auth", we can listen for auth state changes via `onAuthStateChanged`. As our app will require authentication
to view the main content, we can conditionally render the 'unauthenticated' `StackNavigator` if the user is signed out in our `src/App.js`. Lets go
ahead and add the boilerplate code to get this in motion:
```jsx
// src/App.js
import React, { Component } from 'react';
import firebase from 'react-native-firebase';
import UnauthenticatedStack from './screens/unauthenticated';
class App extends Component {
constructor() {
super();
this.state = {
loading: true,
};
}
componentDidMount() {
// Listen for user auth state changes
firebase.auth().onAuthStateChanged(user => {
this.setState({
loading: false,
});
});
}
render() {
// Render a blank screen whilst we wait for Firebase.
// The listener generally trigger immediately so it will be too fast for the user to see
if (this.state.loading) {
return null;
}
return <UnauthenticatedStack />;
}
}
export default App;
```
## Updating Redux with the user state
Rather than passing our `user` into component `state`, we're going to add it into into Redux instead. Firebase does provide direct access to the user
via `firebase.auth().currentUser`, however as our app complexity grows we may want to integrate parts of the users data (for example the `uid`) into
other parts of our Redux store. By storing the user in Redux, it is guaranteed that the user details will keep in-sync throughout our Redux store.
### Dispatching Actions
Another common Redux concept is called 'Dispatching Actions'. An action is an event with a unique name, which our reducer can listen out for and react
to the action. Every action requires a `type` property and can pass any additional data along which the reducer needs to handle the action. Lets go ahead
and create an `actions.js` file, where we'll define our first action:
```js
// src/actions.js
// define our action type as a exportable constant
export const USER_STATE_CHANGED = 'USER_STATE_CHANGED';
// define our action function
export function userStateChanged(user) {
return {
type: USER_STATE_CHANGED, // required
user: user ? user.toJSON() : null, // the response from Firebase: if a user exists, pass the serialized data down, else send a null value.
};
}
```
To dispatch this action we need to again make use of `react-redux`. As our `App.js` has been provided the Redux store via the `Provider`
component within `index.js`, we can use a [higher order component (HOC)](https://reactjs.org/docs/higher-order-components.html) called `connect` to provide the component with access to Redux:
```jsx
// src/App.js
import { connect } from 'react-redux';
...
export default connect()(App);
```
The `connect` HOC clones the given component with a function prop called `dispatch`. The `dispatch` function then takes an action, which when called 'dispatches'
it to Redux. Lets jump back into our `App.js` and dispatch our action when `onAuthStateChanged` is triggered:
```jsx
// src/App.js
// import our userStateChanged action
import { userStateChanged } from './actions';
...
componentDidMount() {
firebase.auth().onAuthStateChanged((user) => {
// dispatch the imported action using the dispatch prop:
this.props.dispatch(userStateChanged(user));
this.setState({
loading: false,
});
});
}
```
> You may want to consider implementing [`mapDispatchToProps`](https://github.com/reactjs/react-redux/blob/master/docs/api.md) to keep the action
> usage reusable & cleaner.
Now every time `onAuthStateChanged` is triggered by Firebase, our Redux action will be dispatched regardless of whether a user is signed in our out!
### Reducing state
Back on step 'Integrating Redux' we setup a very basic Redux store. In order for us to latch onto the dispatched action we need to listen out
for the events being sent to the reducer. To do this we import the action type which we exported within `actions.js` and conditionally
return new state when that action is dispatched:
```jsx
// src/store.js
import { createStore } from 'redux';
// import the action type
import { USER_STATE_CHANGED } from './actions';
function reducer(state = {}, action) {
// When USER_STATE_CHANGED is dispatched, update the store with new state
if (action.type === USER_STATE_CHANGED) {
return {
user: action.user,
};
}
return state;
}
export default createStore(reducer);
```
You may notice here that we return a brand new object rather than modifying the existing state. This is because Redux state is
[immutable](https://facebook.github.io/immutable-js/). In order for Redux to know whether state has actually changed, it needs to compare the
previous state with a new one.
> As your Redux state grows in complexity, it may be worth breaking your store out into multiple reducers. This can easily be achieved using
> [combineReducers](https://redux.js.org/api-reference/combinereducers) from the `redux` package.
### Subscribing to Redux state
Now our action is updating the store whenever it's dispatched, we can subscribe to specific parts of the data which we need in our React
components. The power of using `react-redux` is that it allows us to subscribe to data within our store and update the component whenever that
data changes - we do this via a function known as `mapStateToProps`. This function is passed as the first argument of our `connect` HOC and gets
given the current Redux state. It returns an object, which is cloned as props into our component. Here's how it works:
```js
function mapStateToProps(state) {
return {
isUserAuthenticated: !!state.user,
};
}
export default connect(mapStateToProps)(App);
```
With this code, our `App` component will receive a prop called `isUserAuthenticated`, which in our case will be a `true` or `false` value based on
whether the `state.user` object exists or not. Every time Redux state changes, this logic is run. What's handy is that if the result of any
prop has changed, the component will be updated with the new data. If none of the props-to-be have changed, the component doesn't update.
> Keep in mind that if you return a complex `Array` or `object`, `react-redux` will only shallow compare them. Even if your state does not change
> the component will still be re-rendered with the same data which can cause performance issues in our app if not handled. Therefore it is wise
> to break components out to only subscribe to specific parts of primitive state (such as `strings`, `booleans` etc).
As our `App` component contains our routes, any change in the `isUserAuthenticated` value will cause the entire app to re-render - which in this case
is fine as we're conditionally changing navigation stacks. Lets implement that logic:
```jsx
// src/App.js
import React, { Component } from 'react';
import firebase from 'react-native-firebase';
import { connect } from 'react-redux';
import UnauthenticatedStack from './screens/unauthenticated';
import AuthenticatedStack from './screens/authenticated';
import { userStateChanged } from './actions';
class App extends Component {
constructor() {
super();
this.state = {
loading: true,
};
}
componentDidMount() {
firebase.auth().onAuthStateChanged(user => {
this.props.dispatch(userStateChanged(user));
this.setState({
loading: false,
});
});
}
render() {
// Render a blank screen whilst we wait for Firebase.
// The listener generally trigger immediately so it will be too fast for the user to see
if (this.state.loading) {
return null;
}
if (!this.props.isUserAuthenticated) {
return <UnauthenticatedStack />;
}
return <AuthenticatedStack />;
}
}
function mapStateToProps(state) {
return {
isUserAuthenticated: !!state.user,
};
}
export default connect(mapStateToProps)(App);
```
As you can see in our `render` method, if the `isUserAuthenticated` value is `false`, we render our `UnauthenticatedStack`. If it's `true` we can
render a new stack, in this case called `AuthenticatedStack` which is waiting for you to setup!

View File

@ -0,0 +1,84 @@
# Integrating Redux
Redux has become somewhat of a buzz word in the React community, and is generally used in most projects without thought. This Codorial
won't go into details on what it is as their own [documentation](https://redux.js.org/introduction/motivation) does a wonderful job at explaining
what it's for and why to use it.
_TLDR;_ Redux provides your app with a single "state" (data), which can be accessed by any component. You can subscribe to this data to cause
a component update whenever something changes, even if it's deeply nested.
Although the end product of this Codorial certainly doesn't require Redux to function, as your app grows in complexity Redux becomes more and
more important to manage your data.
## Installing Redux
Lets go ahead by installing the core Redux library and the React bindings:
```bash
npm install --save redux react-redux
```
Now within our projects `src` directory, create a `store.js` file. This file will contain all of our Redux logic, however you may want to break
this out into multiple directories as your projects grows in complexity.
```js
// src/store.js
import { createStore } from 'redux';
// Create a reducer with empty state (see below for explanation)
function reducer(state = {}, action) {
return state;
}
export default createStore(reducer);
```
By default state is `null`, however we're setting it to an empty `object` (`state = {}`) so we can attempt to access
shallow nested properties even if they don't exist.
> You may want to consider installing the [redux-logger](https://github.com/evgenyrodionov/redux-logger) library to improve
> your Redux experience.
### Reducer
A reducer is a simple JavaScript function which takes two arguments: `state` & `action`. The idea of a reducer is to take "some data" from an `action`
and return new state.
* `state` is any sort of data, which cannot be altered (immutable). A reducer must return a new value each time. More on this later.
* `action` is an object containing a `type`, and any unreduced data. More on this later.
## Integrating Redux into the app
Our Redux store is now ready to be used. `react-redux` provides us with a `Provider` component which "provides" any children
with access to the store via [context](https://reactjs.org/docs/context.html). Luckily we don't need to worry about this too much as the library
takes care of the hard work!
Back within our original bootstrap file, we'll wrap the `App` component in the `Provider` component, so our business logic has access to Redux.
```jsx
// src/index.js
import React, { Component } from 'react';
import { Provider } from 'react-redux'; // Import the Provider component
import App from './App';
import store from './store';
function bootstrap() {
// Init any external libraries here!
return class extends Component {
render() {
return (
<Provider store={store}>
<App />
</Provider>
);
}
};
}
export default bootstrap;
```
Although noting will visually change, our app now has access to the power of Redux!

View File

@ -0,0 +1,113 @@
# Project Structure
Although it may seem trivial, having a good initial project structure ensures your code will be clean and reusable.
The following step gives an opinionated guide to how this might look, which will work across both Android & iOS.
## Entry file
Every fresh React Native project a key file, an `index.js`, which currently renders a simple React component
with basic styling. Rather than keeping our business logic within this file, we're going to keep it contained in it's own
directory.
We'll achieve this by creating a `src` directory where our own code for the app will live. Create the directory with an `index.js` file, so your
project structure resembles the following:
```
- node_modules/
- android/
- ios/
- src/
-- index.js
- index.js
```
Now we can reference our bootstrap file in the `index.js` file, so both our platform share the same entry point:
```js
// index.js
import { AppRegistry } from 'react-native';
import bootstrap from './src';
AppRegistry.registerComponent('RNFirebaseStarter', () => bootstrap());
```
## Bootstrapping your project
You may have noticed before, but the `bootstrap` import is a function. This allows us to setup or initialize any external modules before our
React based application kick starts (such as [react-native-i18n](https://github.com/AlexanderZaytsev/react-native-i18n)).
Lets go ahead and setup our bootstrap file:
```js
// src/index.js
import React, { Component } from 'react';
import { View, Text } from 'react-native';
function bootstrap() {
// Init any external libraries here!
return class extends Component {
render() {
return (
<View>
<Text>Bootstrapped!</Text>
</View>
);
}
};
}
export default bootstrap;
```
Although this function simply returns a basic React component, later we'll be able to see the power of having a bootstrap file which
consumes our entire application.
Go ahead and boot up your app onto your emulator. You should simply be presented with a plain screen with the words "Bootstrapped!".
![Bootstrapped!](assets/app-bootstrapped.jpg =300x\*)
Although a good starting point, we want to separate we'll our business logic out of the bootstrap file, keeping it purely for app
initialization purposes. This can simply be done by creating a basic React component called `App.js`, which will also live in the `src` directory;
```js
// src/App.js
import React, { Component } from 'react';
import { View, Text } from 'react-native';
class App extends Component {
render() {
return (
<View>
<Text>Bootstrapped!</Text>
</View>
);
}
}
export default App;
```
Now we can reference this component within our bootstrap setup and return it from the bootstrap component:
```js
// src/index.js
import React, { Component } from 'react';
import App from './App';
function bootstrap() {
// Init any external libraries here!
return class extends Component {
render() {
return <App />;
}
};
}
export default bootstrap;
```

View File

@ -0,0 +1,206 @@
# Understanding Firebase Authentication
Before we dive into the logic of implementing authentication, it's first important to understand the Firebase API, and how it handles authentication
with the various options we have.
As we're also working in React, we'll cover how Firebase's asynchronous API fits in with Reacts lifecycle methods.
Luckily [react-native-firebase](https://rnfirebase.io) follows the Firebase web SDK API making this a breeze!
## Enabling authentication
We need to tell Firebase that we plan on using authentication and also enable a couple of the many login providers
which Firebase supports. Head over to the [Firebase console](https://console.firebase.google.com/u/0/) and select the project you're using.
Find the Authentication section and you'll be prompted with a number of options. To get started, we want to select the "SIGN-IN METHOD" tab.
You'll see we have a number of options here, however for the purposes of this Codorial we'll be using "Email/Password" and "Facebook" as our providers.
Go ahead and enable these:
![Enabled Providers](assets/auth-providers.jpg)
> If you don't have a Facebook app, simply enter dummy values. We'll cover this later on.
## Listening to the users authentication state
The Firebase API provides a simple yet powerful listener, which triggers when some event changes with the user.
This can be as obvious the user signing out or as subtle as the user validating their email address. Whatever the event, it triggers the same method: `onAuthStateChanged`.
```js
import firebase from 'react-native-firebase';
firebase.auth().onAuthStateChanged(user => {
console.log(user);
});
```
The callback for the `onAuthStateChanged` method returns a single parameter, commonly referred to as `user`.
The concept here is simple;
* the method is first called once Firebase responds, then any time user state changes thereafter.
* if a user is "signed in", our parameter will be a [`User`](https://firebase.google.com/docs/reference/js/firebase.User) `class`, containing all sorts of information we know about the user,
from their e-mail address to any social provider IDs they may have signed in through.
* if the user signed out, the parameter will be `null` value.
> The `user` class provides a `.toJSON()` method to serialize the users details if required.
### Handling authentication state when the app closes
A common question we get is how to handle the users authenticated state when the app closes/restarts so they don't have to keep logging in each
time they open the app. Luckily this is all handled through Firebase so you don't have to worry about a thing - they'll only be signed out if they
choose to, or the app is uninstalled.
## Creating a new account
Creating a new account on Firebase is very easy. Another method called `createUserAndRetrieveDataWithEmailAndPassword` is available which does exactly what it
says on the tin! This is an asynchronous promise which will throw an exception if something is wrong (such as email taken, or password too short).
Creating a user will also sign them in at the same time.
```js
import firebase from 'react-native-firebase';
firebase
.auth()
.createUserAndRetrieveDataWithEmailAndPassword(
'jim.bob@gmail.com',
'supersecret!'
)
.then(user => {
console.log('New User', user);
})
.catch(error => {
console.error('Woops, something went wrong!', error);
});
```
What's great about this is we don't need to know about the user within the `.then`, as any `onAuthStateChanged` listener would get triggered with our new
users details - how awesome is that.
## Signing into an existing account
Unsurprisingly, Firebase offers a method called `signInAndRetrieveDataWithEmailAndPassword`, which follows the exact same flow as `createUserAndRetrieveDataWithEmailAndPassword`:
```js
import firebase from 'react-native-firebase';
firebase
.auth()
.signInAndRetrieveDataWithEmailAndPassword(
'jim.bob@gmail.com',
'supersecret!'
)
.then(user => {
console.log('Existing User', user);
})
.catch(error => {
console.error('Woops, something went wrong!', error);
});
```
## Using with React
Firebase on it's own is super simple, however when using in a React environment there's some gotchas you need to be mindful of.
### Handling state changes
For any React component to update, a state or prop change needs to occur. As our Firebase auth methods are asynchronous we cannot rely on
the data being available on component mount. To solve this issue, we can make use of state:
```jsx
import React, { Component } from 'react';
import { View, Text } from 'react-native';
import firebase from 'react-native-firebase';
class App extends React.Component {
constructor() {
super();
this.state = {
loading: false,
user: null,
};
}
componentDidMount() {
firebase.auth().onAuthStateChanged(user => {
if (user) {
this.setState({
user: user.toJSON(), // serialize the user class
loading: false,
});
} else {
this.setState({
loading: false,
});
}
});
}
render() {
const { loading, user } = this.state;
// Firebase hasn't responded yet
if (loading) return null;
// Firebase has responded, but no user exists
if (!user) {
return (
<View>
<Text>Not signed in</Text>
</View>
);
}
// Firebase has responded, and a user exists
return (
<View>
<Text>User signed in! {user.email}</Text>
</View>
);
}
}
```
### Subscribing/Un-subscribing from listeners
When subscribing to a new listener, such as `onAuthStateChanged`, a new reference to it is made in memory which has no knowledge of the
React environment. If a component within your app mounts and subscribes, the method will still trigger even if your component unmounted.
If this happens and you're updating state, you'll get a yellow box warning.
To get around this, Firebase returns an unsubscribe function to every subscriber method, which when calls removes the subscription from memory.
This can be easily implemented using React lifecycle methods and class properties:
```jsx
import React, { Component } from 'react';
import { View, Text } from 'react-native';
import firebase from 'react-native-firebase';
class App extends React.Component {
constructor() {
super();
this.unsubscribe = null; // Set a empty class method
this.state = {
loading: true,
user: null,
};
}
componentDidMount() {
// Assign the class method to the unsubscriber response
this.unsubscribe = firebase.auth().onAuthStateChanged((user) => {
// handle state changes
});
}
componentWillUnmount() {
// Call the unsubscriber if it has been set
if (this.unsubscribe) {
this.unsubscribe();
}
}
```
## Further reading
The above examples just scratch the surface of whats available with Firebase auth. Firebase itself provides some in-depth documentation
on authentication and the many different implementation paths you can follow.

2
package-lock.json generated
View File

@ -1,6 +1,6 @@
{
"name": "react-native-firebase",
"version": "4.0.3",
"version": "4.0.4",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View File

@ -1,6 +1,6 @@
{
"name": "react-native-firebase",
"version": "4.0.3",
"version": "4.0.4",
"author": "Invertase <contact@invertase.io> (http://invertase.io)",
"description": "A well tested, feature rich Firebase implementation for React Native, supporting iOS & Android. Individual module support for Admob, Analytics, Auth, Crash Reporting, Cloud Firestore, Database, Dynamic Links, Messaging (FCM), Remote Config, Storage and Performance.",
"main": "dist/index.js",
@ -33,7 +33,7 @@
"jest": {
"preset": "jest-react-native",
"setupFiles": [],
"unmockedModulePathPatterns": ["./node_modules/react", "./node_modules/react-native", "./node_modues/react-native-mock", "./node_modules/react-addons-test-utils"]
"unmockedModulePathPatterns": ["./node_modules/react", "./node_modules/react-native", "./node_modules/react-native-mock", "./node_modules/react-addons-test-utils"]
},
"license": "APACHE-2.0",
"keywords": ["react", "admob", "auth", "config", "digits", "fabric", "phone-auth", "sms", "firestore", "cloud-firestore", "datastore", "remote-config", "transactions", "react-native", "react-native-firebase", "firebase", "fcm", "apn", "gcm", "analytics", "messaging", "database", "android", "ios", "crash", "firestack", "performance", "firestore", "dynamic-links", "crashlytics"],