AWS Amplify is an open-source library that enables developers, and in our case mobile developers, to add a host of valuable functionality to applications including analytics, push notifications, storage, and authentication.
Amplify works not only with React Native, but also with Vue, Angular, Ionic, React web and really any JavaScript framework. In this tutorial, we will be demonstrating some of its core functionality within a React Native application.
The great thing about this library is that it abstracts away what used to be complex setup and configuration for all of these features into an easy-to-use module that we can add to our project by using NPM.
We'll cover AWS Amplify in three parts: authentication, storage, and analytics.
React Native Installation & Setup
If you would like to follow along, create a new React Native project using either Expo (Create React Native App) or the React Native CLI:
react-native init RNAmplify
or
create-react-native-app RNAmplify
Next, let's go ahead and install the aws-amplify
library using either yarn or npm:
yarn add aws-amplify
If you are using Expo, you can skip the next step (linking) as Expo already ships the native dependencies for Amazon Cognito support.
If you are not using Expo, we need to link the Cognito library (Amazon Cognito handles authentication) that was installed when we added aws-amplify
:
react-native link amazon-cognito-identity-js
AWS Setup
Now that the React Native project is created and configured, we need to set up the Amazon services that we will be interacting with.
Inside the directory of the new project, we will be creating a new Mobile Hub project for this tutorial. To create this project, we will be using the AWSMobile CLI, but feel free to use the console if you are a more advanced user. I've posted a YouTube video with a quick overview of the AWSMobile CLI if you want to learn more about it.
Now let's create a new Mobile Hub project in the root of our new project directory:
awsmobile init
After you've created your project and have your aws-exports file (this is automatically created for you by running awsmobile init
), we need to configure our React Native project with our AWS project using AWS Amplify.
To do so, we will go into App.js below the last imports and add the following three lines of code:
import Amplify, { Auth } from 'aws-amplify' import config from './aws-exports' Amplify.configure(config)
Authentication
Authentication with Amplify is done by using the Amazon Cognito service. We'll use this service to allow users to sign in and sign up to our application!
Adding Authentication Using the AWSMobile CLI
Let's add user signin and Amazon Cognito to our Mobile Hub project. In the root directory, run the following commands:
awsmobile user-signin enable awsmobile push
Now, we will have a new Amazon Cognito user pool set up and ready to go. (If you would like to view the details of this new service, go to the AWS Console, click on Cognito, and choose the name of the AWSMobile project name you created.)
Next, let's integrate Authentication with Amazon Cognito and AWS Amplify.
Auth Class
Let's begin by taking a look at the main class that you will be using to have full access to the Amazon Cognito services, the Auth
class.
The Auth
class has many methods that will allow you to do everything from signing up and signing in users to changing their password and everything in between.
It's also simple to work with two-factor authentication with AWS Amplify using the Auth
class, as we will see in the following example.
Example
Let's take a look at how you might go about signing up and signing in a user using Amazon Cognito and the Auth
class.
We can accomplish a solid sign-up and sign-in flow with relatively little work! We will be using the signUp
, confirmSignUp
, signIn
, and confirmSignIn
methods of the Auth
class.
In App.js, let's create a few methods that will handle user sign-up with two-factor authentication as well as some state to hold the user input:
state = { username: '', email: '', phone_number: '', password: '', authCode: '', user: {} } onChangeText = (key, value) => { this.setState({ [key]: value }) } signUp() { const { username, password, email, phone_number } = this.state Auth.signUp({ username, password, attributes: { phone_number, email } }) .then(() => console.log('user sign up success!!')) .catch(err => console.log('error signing up user: ', err)) } confirmSignUp() { Auth.confirmSignUp(this.state.username, this.state.authCode) .then(() => console.log('confirm user sign up success!!')) .catch(err => console.log('error confirming signing up user: ', err)) }
signUp
creates the initial sign-up request, which will send an SMS to the new user to confirm their number. confirmSignUp
takes the SMS code and the username and confirms the new user in Amazon Cognito.
We will also create a UI for the form input and a button, and wire the class methods to these UI elements. Update the render
method to the following:
render() { return (<View style={styles.container}><TextInput style={styles.input} placeholder='Username' onChangeText={val => this.onChangeText('username', val)} /><TextInput style={styles.input} placeholder='Password' secureTextEntry={true} onChangeText={val => this.onChangeText('password', val)} /><TextInput style={styles.input} placeholder='Email' onChangeText={val => this.onChangeText('email', val)} /><TextInput style={styles.input} placeholder='Phone Number' onChangeText={val => this.onChangeText('phone_number', val)} /><Button title='Sign Up' onPress={this.signUp.bind(this)} /><TextInput style={styles.input} placeholder='Confirmation Code' onChangeText={val => this.onChangeText('authCode', val)} /><Button title='Confirm Sign Up' onPress={this.confirmSignUp.bind(this)} /></View> ); }
Finally, we will update our styles
declaration so that we have a nicer UI:
const styles = StyleSheet.create({ container: { flex: 1, justifyContent: 'center', }, input: { height: 50, borderBottomWidth: 2, borderBottomColor: '#9E9E9E', margin: 10 } });
To view the final version of this file, click here.
Now, we should be able to sign up, get a confirmation code sent to our phone number, and confirm by typing in the confirmation code.
If you would like to view the details of this newly created user, go back to the AWS Console, click on Cognito, choose the name of the AWSMobile project name you created, and click on Users and groups in the General settings menu on the left.
You can take this further by implementing sign-in and confirm sign-in. Let's take a look at the methods for signIn
and confirmSignIn
:
signIn() { Auth.signIn(this.state.username, this.state.password) .then(user => { this.setState({ user }) console.log('user sign in success!!') }) .catch(err => console.log('error signing in user: ', err)) } confirmSignIn() { Auth.confirmSignIn(this.state.user, this.state.authCode) .then(() => console.log('confirm user sign in success!!')) .catch(err => console.log('error confirming signing in user: ', err)) }
Accessing the User's Data and Session
Once the user is signed in, we can then use Auth
to access user information!
We can call Auth.currentUserInfo()
to get the user's profile information (username, email, etc.) or Auth.currentAuthenticatedUser()
to get the user's idToken, JWT, and a lot of other useful information about the user's current logged-in session.
Analytics
AWS Amplify uses Amazon Pinpoint to handle metrics. When you create a new Mobile Hub project using either the CLI or Mobile Hub, you automatically have Amazon Pinpoint enabled, configured, and ready to go.
If you are not already familiar, Amazon Pinpoint is a service that not only allows developers to add Analytics to their mobile applications, but also lets them send push notifications, SMS messages, and emails to their users.
With AWS Amplify, we can send user session information as metrics to Amazon Pinpoint with a few lines of code.
Let's open the Amazon Pinpoint dashboard so we can view the events we are about to create. If you open your Mobile Hub project in the AWS console, choose Analytics in the top right corner, or go directly into Amazon Pinpoint from the console, and open the current project.
In the dark blue navigation bar on the left, there are four options: Analytics, Segments, Campaigns, and Direct. Choose Analytics.
Once we begin creating sending events, we will be able to see them in this console view.
Now we're ready to start tracking! In App.js, remove all of the code from the last example, leaving us with basically just a render method containing a container View with a Text greeting, no state, no class methods, and only a container style.
We also import Analytics
from aws-amplify
:
import React, { Component } from 'react'; import { StyleSheet, Text, Button, View } from 'react-native'; import Amplify, { Analytics } from 'aws-amplify' import config from './aws-exports' Amplify.configure(config) export default class App extends Component { render() { return ( <View style={styles.container}><Text>Analytics</Text></View> ); } } const styles = StyleSheet.create({ container: { flex: 1, justifyContent: 'center', alignItems: 'center' } });
Tracking Basic Events
One common metric that you may want to track is the number of times the user opens the app. Let's begin by creating an event that will track this.
In React Native, we have the AppState API, which will give us the current app state of active, background, or inactive. If the state is active, it means the user has opened the app. If the state is background, it means the app is running in the background and the user is either on the home screen or in another app, while inactive means the user is transitioning between active and foreground, multitasking, or is on a phone call.
When the app becomes active, let's send an event to our analytics that says the app was opened.
To do so, we will be calling the following event:
Analytics.record('App opened!')
You can view the API for this method in the official docs. The record
method takes three arguments: name (string), attributes (object, optional), and metrics (object, optional):
record(name: string, attributes?: EventAttributes, metrics?: EventMetrics): Promise<any>
Let's update the class to add an event listener when the component is mounted, and remove it when the component is destroyed. This listener will call _handleAppStateChange
whenever the app state changes:
componentDidMount() { AppState.addEventListener('change', this._handleAppStateChange); } componentWillUnmount() { AppState.removeEventListener('change', this._handleAppStateChange); }
Now, let's create the _handleAppStateChange
method:
_handleAppStateChange(appState) { if (appState === 'active') { Analytics.record('App opened!') } }
Now, we can move the app into the background, open it back up, and this should send an event to our Analytics dashboard. Note: To background the app on an iOS simulator or Android emulator, press Command-Shift-H.
To see this data in the dashboard, click on Events, and choose App opened! from the Events dropdown:
You'll also probably notice that you have other data available to you automatically from Mobile Hub, including session data, user sign up, and user sign in. I thought it was pretty cool that all this information is automatically recorded.
Tracking Detailed Events
Now let's take this to the next level. What if we wanted to track not only a user opening the app, but which user opened the app and how many times they opened the app?
We can easily do this by adding an attribute to the second metric!
Let's act as if we have a user signed in, and track a new event that we will call "User detail: App opened".
To do this, update the record event to the following:
Analytics.record('User detail - App opened!', { username: 'NaderDabit' })
Then, close and open the app a couple of times. We should now be able to see more detail about the event in our dashboard.
To do so, look to the right of the Event dropdown; there is an Attributes section. Here, we can drill down into the attributes for the event. In our case, we have the user name, so we can now filter this event by user name!
Usage Metrics
The final item we will track is the usage metrics. This is the third argument to record.
Let's add a metric that records the accrued time that the user has been in the app. We can do this pretty easily by setting a time value in the class, incrementing it every second, and then sending this information along to Amazon Pinpoint when the user opens the app:
// below class definition time = 0 componentDidMount() { this.startTimer() AppState.addEventListener('change', this._handleAppStateChange); } componentWillUnmount() { AppState.removeEventListener('change', this._handleAppStateChange); } _handleAppStateChange(appState) { if (appState === 'active') { Analytics.record('User detail - App opened!', { username: 'NaderDabit' }, { timeInApp: this.time }) } } startTimer() { setInterval(() => { this.time += 1 }, 1000) } // render method
Here, we've created a value of time and set it to 0. We've also added a new startTimer
method that will add 1 to the time value every second. In componentDidMount
, we call startTimer
which will increment the time value by 1 every second.
Now we can add a third argument to Analytics.record()
that will record this value as a metric!
Storage
Let's look at how we can use Amplify with Amazon S3 to add storage to our app.
To add S3 to your Mobile Hub project, run the following commands:
awsmobile user-files enable awsmobile push
Example Usage
AWS Amplify has a Storage API that we can import just as we have with the other APIs:
import { Storage } from 'aws-amplify
'
We can then call methods on Storage
like get
, put
, list
, and remove
to interact with items in our bucket.
When an S3 bucket is created, we will automatically have a default image in our bucket in the public folder; mine is called example-image.png. Let's see if we can use AWS Amplify to read and view this image from S3.
As I mentioned above, Storage
has a get
method that we will call to retrieve items, and the method to retrieve this image would look something like this:
Storage.get('example-image.png')
To demonstrate this functionality in our React Native app, let's create some functionality that retrieves this image from S3 and shows it to our user.
We will need to import Image
from React Native, as well as Storage
from aws-amplify
.
import React, { Component } from 'React' import { // previous imports Image } from 'react-native'; import Amplify, { Storage } from 'aws-amplify' // rest of code
Now, we will need to have some state to hold this image, as well as a method to retrieve the image and hold it in the state. Let's add the following to our class above the render method:
state = { url: '' } async getFile() { let name = 'example-image.png'; let fileUrl = await Storage.get(name); this.setState({ url: fileUrl }) }
Finally, let's add some UI elements to retrieve the image and render it to the UI:
render() { return (<View style={styles.container}><Text>Storage</Text><Button title="Get Image" onPress={this.getFile.bind(this)} /> { this.state.url !== '' && (<Image source={{ uri: this.state.url }} style={{ width: 300, height: 300 }} /> ) }</View> ); }
Now, we should be able to click the button and view the image from S3!
To view the final version of this file, click here.
Conclusion
Overall, AWS Amplify provides a really easy way to accomplish what used to be complex functionality with not much code, integrating seamlessly with many AWS services.
We did not cover push notifications, which were also recently added to AWS Amplify, but these will be covered in an upcoming post!
AWS Amplify is actively being maintained, so if you have any feature requests or ideas, feel free to comment, submit an issue or pull request, or just star or watch the project if you wish to be kept up to date with future features!
And in the meantime, check out some of our other posts about coding React Native apps.
- React NativeCode an App With GraphQL, React Native and AWS AppSync: The App
- Mobile DevelopmentTools for React Native Development
- React NativeGetting Started With a React Native App Template