From 9adde8be34a144ecd59d55c0c7728141c23eb5dd Mon Sep 17 00:00:00 2001 From: Elliot Hesp Date: Wed, 28 Feb 2018 13:30:54 +0000 Subject: [PATCH 01/25] [codorial] WIP authentication tutorial --- .../authentication-with-firebase/config.json | 21 ++ .../getting-started.md | 43 ++++ .../integrating-redux.md | 82 +++++++ .../project-structure.md | 124 +++++++++++ .../understanding-firebase-auth.md | 200 ++++++++++++++++++ 5 files changed, 470 insertions(+) create mode 100644 codorials/authentication-with-firebase/config.json create mode 100644 codorials/authentication-with-firebase/getting-started.md create mode 100644 codorials/authentication-with-firebase/integrating-redux.md create mode 100644 codorials/authentication-with-firebase/project-structure.md create mode 100644 codorials/authentication-with-firebase/understanding-firebase-auth.md diff --git a/codorials/authentication-with-firebase/config.json b/codorials/authentication-with-firebase/config.json new file mode 100644 index 00000000..e997db7c --- /dev/null +++ b/codorials/authentication-with-firebase/config.json @@ -0,0 +1,21 @@ +{ + "title": "Authentication with Firebase", + "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" + } + ] +} diff --git a/codorials/authentication-with-firebase/getting-started.md b/codorials/authentication-with-firebase/getting-started.md new file mode 100644 index 00000000..81fb50af --- /dev/null +++ b/codorials/authentication-with-firebase/getting-started.md @@ -0,0 +1,43 @@ +# Getting Started + +Welcome to the 'Authentication with Firebase' Codorial using the [react-native-firebase](https://rnfirebase.io) library. +The various steps of the Codorial will 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 considering integrate routing + using [react-navigation](https://reactnavigation.org/). + +## Prerequisites + +This Codorial assumes you know the basics of the following topics: + +- ES6 JavaScript. +- Starting your app using an emulator on Android/iOS. +- Understand how to setup a new Firebase project. +- Debugging with React Native. +- 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. + diff --git a/codorials/authentication-with-firebase/integrating-redux.md b/codorials/authentication-with-firebase/integrating-redux.md new file mode 100644 index 00000000..39950cc6 --- /dev/null +++ b/codorials/authentication-with-firebase/integrating-redux.md @@ -0,0 +1,82 @@ +# 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 certain 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: + +``` +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. + +``` +// src/store.js +import { createStore } from 'redux'; + +// Create a reducer (see below for explanation) +function reducer(state, action) { + return state; +} + +export default createStore(reducer); +``` + +> You may want to consider installing the [redux-logger](https://github.com/evgenyrodionov/redux-logger) library. + +### 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. +- `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 lirbary +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. + +```js +// 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 ( + + + + ); + } + } +} + +export default bootstrap; +``` + +Our app now has access to the power of Redux! diff --git a/codorials/authentication-with-firebase/project-structure.md b/codorials/authentication-with-firebase/project-structure.md new file mode 100644 index 00000000..1662f28b --- /dev/null +++ b/codorials/authentication-with-firebase/project-structure.md @@ -0,0 +1,124 @@ +# Project Structure + +Although it may seem trivial, having a good initial project structure ensures you 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 contains to key files, an `index.android.js` & a `index.ios.js` files which currently individually render a simple React component +with basic styling. Rather than having two separate files, we're going to create a single file so both Android & iOS use it. + +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 ( + + + Bootstrapped! + + + ); + } + } +} + +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!". + +TODO image + +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 ( + + + Bootstrapped! + + + ); + } + +} + +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 ( + + ); + } + } +} + +export default bootstrap; +``` diff --git a/codorials/authentication-with-firebase/understanding-firebase-auth.md b/codorials/authentication-with-firebase/understanding-firebase-auth.md new file mode 100644 index 00000000..ac72984c --- /dev/null +++ b/codorials/authentication-with-firebase/understanding-firebase-auth.md @@ -0,0 +1,200 @@ +# Understanding Firebase Authentication + +Before we dive into the logic of implementing authentication, it's first important to understand how the Firebase API, and how it handles authentication +with the various options we have. + +As we're also working in React, it's important to understand how Firebase's asynchronous API fits in with Reacts many lifecycle methods. +Luckily [react-native-firebase](https://rnfirebase.io) follows the Firebase web SDK API making this a breeze! + +## Enabling authentication + +Before we make a start, we need to tell Firebase that we plan on using authentication. We need to 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: + +TODO image + +You'll see we have a number of options here, however for purposes of this Codorial we'll be using "Email/Password" and "Facebook" as our providers. +Go ahead and enable these: + +TODO image + +> 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; + +- if a user is "signed in", our parameter will be a `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 method is immediately triggered when called. + +> 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 `createUserWithEmailAndPassword` 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().createUserWithEmailAndPassword('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 `signInWithEmailAndPassword`, which follows the exact same flow as `createUserWithEmailAndPassword`: + +```js +import firebase from 'react-native-firebase'; + +firebase.auth().signInWithEmailAndPassword('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: + +```js +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 ( + + Not signed in + + ); + } + + // Firebase has responded, and a user exists + return ( + + User signed in! {user.email} + + ); + } +} +``` + +### 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. +This can be easily implemented using React lifecycle methods and class properties: + +```js +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: false, + 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. From 87f47e2591dc2aba2d58fb29fde8c1e3b2211381 Mon Sep 17 00:00:00 2001 From: Elliot Hesp Date: Wed, 28 Feb 2018 15:02:13 +0000 Subject: [PATCH 02/25] [codorial] WIP fixes --- codorials/authentication-with-firebase/integrating-redux.md | 2 +- .../understanding-firebase-auth.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/codorials/authentication-with-firebase/integrating-redux.md b/codorials/authentication-with-firebase/integrating-redux.md index 39950cc6..07621bda 100644 --- a/codorials/authentication-with-firebase/integrating-redux.md +++ b/codorials/authentication-with-firebase/integrating-redux.md @@ -14,7 +14,7 @@ more important to manage your data. Lets go ahead by installing the core Redux library and the React bindings: -``` +```bash npm install --save redux react-redux ``` diff --git a/codorials/authentication-with-firebase/understanding-firebase-auth.md b/codorials/authentication-with-firebase/understanding-firebase-auth.md index ac72984c..4ca84643 100644 --- a/codorials/authentication-with-firebase/understanding-firebase-auth.md +++ b/codorials/authentication-with-firebase/understanding-firebase-auth.md @@ -1,9 +1,9 @@ # Understanding Firebase Authentication -Before we dive into the logic of implementing authentication, it's first important to understand how the Firebase API, and how it handles 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, it's important to understand how Firebase's asynchronous API fits in with Reacts many lifecycle methods. +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 From 1283320997cae2a2d26ca2006059f022ae310d57 Mon Sep 17 00:00:00 2001 From: Elliot Hesp Date: Wed, 28 Feb 2018 15:06:09 +0000 Subject: [PATCH 03/25] [codorial] WIP adding missing code type --- codorials/authentication-with-firebase/integrating-redux.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codorials/authentication-with-firebase/integrating-redux.md b/codorials/authentication-with-firebase/integrating-redux.md index 07621bda..6526fa88 100644 --- a/codorials/authentication-with-firebase/integrating-redux.md +++ b/codorials/authentication-with-firebase/integrating-redux.md @@ -21,7 +21,7 @@ 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'; From 7e952a8564a04a31c82b4f3cc04674032d2757bc Mon Sep 17 00:00:00 2001 From: Elliot Hesp Date: Wed, 28 Feb 2018 17:46:38 +0000 Subject: [PATCH 04/25] [codorial] WIP Add more config items --- codorials/authentication-with-firebase/config.json | 2 ++ codorials/authentication-with-firebase/integrating-redux.md | 4 ++-- codorials/authentication-with-firebase/project-structure.md | 2 +- .../understanding-firebase-auth.md | 2 +- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/codorials/authentication-with-firebase/config.json b/codorials/authentication-with-firebase/config.json index e997db7c..6acbe6ca 100644 --- a/codorials/authentication-with-firebase/config.json +++ b/codorials/authentication-with-firebase/config.json @@ -1,5 +1,7 @@ { "title": "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", diff --git a/codorials/authentication-with-firebase/integrating-redux.md b/codorials/authentication-with-firebase/integrating-redux.md index 6526fa88..77a37c78 100644 --- a/codorials/authentication-with-firebase/integrating-redux.md +++ b/codorials/authentication-with-firebase/integrating-redux.md @@ -40,13 +40,13 @@ export default createStore(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. +- `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 lirbary +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. diff --git a/codorials/authentication-with-firebase/project-structure.md b/codorials/authentication-with-firebase/project-structure.md index 1662f28b..151e2d11 100644 --- a/codorials/authentication-with-firebase/project-structure.md +++ b/codorials/authentication-with-firebase/project-structure.md @@ -5,7 +5,7 @@ an opinionated guide to how this might look, which will work across both Android ## Entry file -Every fresh React Native project contains to key files, an `index.android.js` & a `index.ios.js` files which currently individually render a simple React component +Every fresh React Native project contains two key files, an `index.android.js` & a `index.ios.js` files which currently individually render a simple React component with basic styling. Rather than having two separate files, we're going to create a single file so both Android & iOS use it. 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 diff --git a/codorials/authentication-with-firebase/understanding-firebase-auth.md b/codorials/authentication-with-firebase/understanding-firebase-auth.md index 4ca84643..3e24fbcb 100644 --- a/codorials/authentication-with-firebase/understanding-firebase-auth.md +++ b/codorials/authentication-with-firebase/understanding-firebase-auth.md @@ -85,7 +85,7 @@ firebase.auth().signInWithEmailAndPassword('jim.bob@gmail.com', 'supersecret!') console.log('Existing User', user); }) .catch((error) => { - console.error('Woops, something went wrong!, error); + console.error('Woops, something went wrong!', error); }); ``` From 3880cbd85d103b29f0b5331fc12aafd7fb3733fe Mon Sep 17 00:00:00 2001 From: Elliot Hesp Date: Wed, 28 Feb 2018 18:24:13 +0000 Subject: [PATCH 05/25] Add image assets --- .npmignore | 1 + .../assets/app-bootstrapped.jpg | Bin 0 -> 27641 bytes .../assets/auth-providers.jpg | Bin 0 -> 67072 bytes 3 files changed, 1 insertion(+) create mode 100644 codorials/authentication-with-firebase/assets/app-bootstrapped.jpg create mode 100644 codorials/authentication-with-firebase/assets/auth-providers.jpg diff --git a/.npmignore b/.npmignore index 17082b9a..8c5bd237 100644 --- a/.npmignore +++ b/.npmignore @@ -76,3 +76,4 @@ buddybuild_postclone.sh bin/test.js .github example +codorials diff --git a/codorials/authentication-with-firebase/assets/app-bootstrapped.jpg b/codorials/authentication-with-firebase/assets/app-bootstrapped.jpg new file mode 100644 index 0000000000000000000000000000000000000000..c9022632b31a27c79ad96cf0114e77e3c9e6cc01 GIT binary patch literal 27641 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%0A~BM6*ihB3_tfY}!OUtSGdq}!mKel%I{^Fx@39~( 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 literal 0 HcmV?d00001 From 54b94aef3b01443e79de716bb118b91ccbbbbe0b Mon Sep 17 00:00:00 2001 From: Elliot Hesp Date: Wed, 28 Feb 2018 18:25:48 +0000 Subject: [PATCH 06/25] Add example image --- codorials/authentication-with-firebase/project-structure.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codorials/authentication-with-firebase/project-structure.md b/codorials/authentication-with-firebase/project-structure.md index 151e2d11..28e04040 100644 --- a/codorials/authentication-with-firebase/project-structure.md +++ b/codorials/authentication-with-firebase/project-structure.md @@ -70,7 +70,7 @@ 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!". -TODO image +![Bootstrapped!](https://raw.githubusercontent.com/invertase/react-native-firebase/codorials/codorials/authentication-with-firebase/assets/app-bootstrapped.jpg) 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; From f44b0c915a07da54e76f1d31f646394a0975a167 Mon Sep 17 00:00:00 2001 From: Elliot Hesp Date: Wed, 28 Feb 2018 18:27:46 +0000 Subject: [PATCH 07/25] [codorial] WIP Image width test --- codorials/authentication-with-firebase/project-structure.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codorials/authentication-with-firebase/project-structure.md b/codorials/authentication-with-firebase/project-structure.md index 28e04040..793cf499 100644 --- a/codorials/authentication-with-firebase/project-structure.md +++ b/codorials/authentication-with-firebase/project-structure.md @@ -70,7 +70,7 @@ 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!](https://raw.githubusercontent.com/invertase/react-native-firebase/codorials/codorials/authentication-with-firebase/assets/app-bootstrapped.jpg) +![Bootstrapped!](https://raw.githubusercontent.com/invertase/react-native-firebase/codorials/codorials/authentication-with-firebase/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; From c4f8af27afd40de3d15b0b015213f0fb70f7360e Mon Sep 17 00:00:00 2001 From: Elliot Hesp Date: Wed, 28 Feb 2018 18:42:45 +0000 Subject: [PATCH 08/25] Add missing images --- .../understanding-firebase-auth.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/codorials/authentication-with-firebase/understanding-firebase-auth.md b/codorials/authentication-with-firebase/understanding-firebase-auth.md index 3e24fbcb..1956e30d 100644 --- a/codorials/authentication-with-firebase/understanding-firebase-auth.md +++ b/codorials/authentication-with-firebase/understanding-firebase-auth.md @@ -11,14 +11,12 @@ Luckily [react-native-firebase](https://rnfirebase.io) follows the Firebase web Before we make a start, we need to tell Firebase that we plan on using authentication. We need to 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: - -TODO image +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 purposes of this Codorial we'll be using "Email/Password" and "Facebook" as our providers. Go ahead and enable these: -TODO image +![Enabled Providers](https://raw.githubusercontent.com/invertase/react-native-firebase/codorials/codorials/authentication-with-firebase/assets/auth-providers.jpg) > If you don't have a Facebook app, simply enter dummy values. We'll cover this later on. From 48bbd71b3679f3c335f553900e922de5eabcdd29 Mon Sep 17 00:00:00 2001 From: Elliot Hesp Date: Thu, 1 Mar 2018 09:43:52 +0000 Subject: [PATCH 09/25] [codorial] WIP Add test relative images --- .../understanding-firebase-auth.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/codorials/authentication-with-firebase/understanding-firebase-auth.md b/codorials/authentication-with-firebase/understanding-firebase-auth.md index 1956e30d..26d58e96 100644 --- a/codorials/authentication-with-firebase/understanding-firebase-auth.md +++ b/codorials/authentication-with-firebase/understanding-firebase-auth.md @@ -18,6 +18,12 @@ Go ahead and enable these: ![Enabled Providers](https://raw.githubusercontent.com/invertase/react-native-firebase/codorials/codorials/authentication-with-firebase/assets/auth-providers.jpg) +![Enabled Providers](/assets/auth-providers.jpg) + +![Enabled Providers](assets/auth-providers.jpg) + +![](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 From 7963ddb66287f7b5bcef8be25cf6a2215a351edc Mon Sep 17 00:00:00 2001 From: Elliot Hesp Date: Thu, 1 Mar 2018 10:03:55 +0000 Subject: [PATCH 10/25] [codorial] WIP Use relative images --- .../app-navigation.md | 103 ++++++++++++++++++ .../authentication-with-firebase/config.json | 4 + .../integrating-redux.md | 2 +- .../project-structure.md | 2 +- .../understanding-firebase-auth.md | 6 - 5 files changed, 109 insertions(+), 8 deletions(-) create mode 100644 codorials/authentication-with-firebase/app-navigation.md diff --git a/codorials/authentication-with-firebase/app-navigation.md b/codorials/authentication-with-firebase/app-navigation.md new file mode 100644 index 00000000..b46a611b --- /dev/null +++ b/codorials/authentication-with-firebase/app-navigation.md @@ -0,0 +1,103 @@ +# 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. + +Authentication typically requires 3 screens; Login, Register & Forgot Password. + +## 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 ( + + + Login + + + ); + } +} +``` + +## 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 ; + } + +} + +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! + +TODO image + +## Styling the navigator + +As you can see, `react-navigation` provides a base view which is platform specific. + +.. + +## Pushing a new stack + diff --git a/codorials/authentication-with-firebase/config.json b/codorials/authentication-with-firebase/config.json index 6acbe6ca..1f3692c6 100644 --- a/codorials/authentication-with-firebase/config.json +++ b/codorials/authentication-with-firebase/config.json @@ -18,6 +18,10 @@ { "title": "Integrating Redux", "file": "integrating-redux" + }, + { + "title": "App Navigation", + "file": "app-navigation" } ] } diff --git a/codorials/authentication-with-firebase/integrating-redux.md b/codorials/authentication-with-firebase/integrating-redux.md index 77a37c78..04db370f 100644 --- a/codorials/authentication-with-firebase/integrating-redux.md +++ b/codorials/authentication-with-firebase/integrating-redux.md @@ -79,4 +79,4 @@ function bootstrap() { export default bootstrap; ``` -Our app now has access to the power of Redux! +Although noting will visually change, our app now has access to the power of Redux! diff --git a/codorials/authentication-with-firebase/project-structure.md b/codorials/authentication-with-firebase/project-structure.md index 793cf499..297d4f3f 100644 --- a/codorials/authentication-with-firebase/project-structure.md +++ b/codorials/authentication-with-firebase/project-structure.md @@ -70,7 +70,7 @@ 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!](https://raw.githubusercontent.com/invertase/react-native-firebase/codorials/codorials/authentication-with-firebase/assets/app-bootstrapped.jpg =300x*) +![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; diff --git a/codorials/authentication-with-firebase/understanding-firebase-auth.md b/codorials/authentication-with-firebase/understanding-firebase-auth.md index 26d58e96..64f25e37 100644 --- a/codorials/authentication-with-firebase/understanding-firebase-auth.md +++ b/codorials/authentication-with-firebase/understanding-firebase-auth.md @@ -16,14 +16,8 @@ Find the Authentication section and you'll be prompted with a number of options. You'll see we have a number of options here, however for purposes of this Codorial we'll be using "Email/Password" and "Facebook" as our providers. Go ahead and enable these: -![Enabled Providers](https://raw.githubusercontent.com/invertase/react-native-firebase/codorials/codorials/authentication-with-firebase/assets/auth-providers.jpg) - -![Enabled Providers](/assets/auth-providers.jpg) - ![Enabled Providers](assets/auth-providers.jpg) -![](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 From ef6d086a0e4c7f09b0da0fc89d8aa038410a4e47 Mon Sep 17 00:00:00 2001 From: Elliot Hesp Date: Thu, 1 Mar 2018 12:01:26 +0000 Subject: [PATCH 11/25] [codorial] WIP App navigation --- .../app-navigation.md | 104 +++++++++++++++++- .../assets/1-unauthenticated-nav.jpg | Bin 0 -> 27104 bytes .../assets/2-unauthenticated-nav.jpg | Bin 0 -> 30693 bytes .../assets/3-unauthenticated-push-pop.gif | Bin 0 -> 348727 bytes .../integrating-redux.md | 2 +- .../understanding-firebase-auth.md | 9 +- 6 files changed, 105 insertions(+), 10 deletions(-) create mode 100644 codorials/authentication-with-firebase/assets/1-unauthenticated-nav.jpg create mode 100644 codorials/authentication-with-firebase/assets/2-unauthenticated-nav.jpg create mode 100644 codorials/authentication-with-firebase/assets/3-unauthenticated-push-pop.gif diff --git a/codorials/authentication-with-firebase/app-navigation.md b/codorials/authentication-with-firebase/app-navigation.md index b46a611b..502b2b62 100644 --- a/codorials/authentication-with-firebase/app-navigation.md +++ b/codorials/authentication-with-firebase/app-navigation.md @@ -91,13 +91,109 @@ 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! -TODO image +![Basic Navigation](assets/1-unauthenticated-nav.jpg =300x*) ## Styling the navigator -As you can see, `react-navigation` provides a base view which is platform specific. +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! -## Pushing a new stack +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: + +```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 ( + + + Login + + + ); + } +} + +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/1-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 ( + +