Tuesday, 9 January, 2018 UTC


At the recent AWS re:Invent conference in Las Vegas, we launched AWS AppSync, a fully managed GraphQL service with real-time and offline capabilities, and AWS Amplify, a JavaScript library that simplifies connecting to AWS resources with specific framework support for React and React Native. Together, these provide you the basis for building cloud-connected native mobile applications using JavaScript and React Native.
In this blog post, I’m going to take you through a set of tutorials where you use an existing React Native application to create cloud-based resources with AWS Mobile Hub, and then integrate those resources into the mobile app.

Before you begin

Before you get started, you need to set up your development computer for developing React Native applications. You’ll need:
  • macOS (for iOS development):
    • Xcode.
    • An iOS simulator.
    • The Xcode command line tools.
  • Android Studio (Windows and macOS – for Android development):
    • A suitable SDK and emulator.
  • Node.js.
  • A JavaScript editor such as Visual Studio Code or Nuclide.
  • An AWS account with permissions to create resources.
  • The React Native command line interface (CLI)
    • Install with npm install -g react-native-cli.
If in doubt, refer to the React Native installation instructions on their website.

Download the repository

We’ve created a notes app in React Native as a starter project. You can either clone the repository or download it.
  1. Go to the GitHub repository.
  2. Click Clone or download.
  3. Click Download ZIP.
  4. Unpack the ZIP file.
  5. Open a terminal in the new directory.
  6. Run yarn install.
  7. To build and run the app, run yarn run ios or yarn run android.
For Android, it’s a good idea to have the Android Emulator started before you run the application. You might see failures to start the emulator if you don’t. You can start the emulator from within Android Studio. If you’ve fully set up your environment, you can also use the emulator command (located in $ANDROID_HOME/tools).

Add analytics to the app

Analytics is an important part of any application—to measure usage, determine feature adoption, and serve as a major driver for user engagement activities. Do the following to add analytics.
  1. Sign in to the AWS Mobile Hub console.
  2. Create a new Mobile Hub project in the US East (N. Virginia) Region.
  3. Name the project, and then click Next.
  4. Choose React Native, and then click Add.
  5. Follow the on-screen instructions for installing the AWS Mobile CLI and connecting your Mobile Hub project to your app.
  6. Add the AWS Amplify library to your project:
    yarn add aws-amplify-react-native react-native link amazon-cognito-identity-js
  7. Add the following code snippet to App.js (right underneath the import statements):
    import Amplify from 'aws-amplify-react-native'; import awsconfig from './src/aws-exports'; Amplify.configure(awsconfig);
  8. Run the application.
  9. Switch to the AWS Mobile console for your project, click Analytics (on the top right corner of the page).
  10. Click Analytics on the left-hand menu.
It can take a couple of minutes for analytics to show up for the first time. If you have diagnostics available (for example, by running the debug version of the app in a suitable editor), then you see the console output that shows events being submitted.

Add authentication to the app

In this section, we add authentication to the app. Users are required to sign in to the app when the app starts. They’re also given the opportunity to create a new account and reset their password if they forget it.
Start by adding authentication to your Mobile Hub project.
  1. Sign in to the AWS Mobile Hub console.
  2. Select your Mobile Hub project.
  3. Scroll down until you see Add More Backend Features.
  4. Add the User Sign-in feature:
    • Choose Email and Password.
    • Click Create user pool.
  5. After the user pool is created, click Resources (on the top right corner of the page), then click the Amazon Cognito User Pools entry to open the Amazon Cognito console.
  6. Click Analytics on the left-hand menu.
  7. Click Add Analytics and campaigns.
  8. Fill in the form:
    • Amazon Cognito app client: Select the non-web version for your app.
    • Amazon Pinpoint project: Select your project.
    • Share user profile data with Amazon Pinpoint: Ensure this is checked.
    • IAM role: Type myproject-Cognito-Pinpoint-Integration, then click Create Role.
    • Click Save changes.
  9. Switch back to your terminal window and run awsmobile pull to get the latest configuration file.
  10. Edit the App.js file:
    • Edit the import for aws-amplify-react-native to look like this:
      import Amplify, { withAuthenticator } from 'aws-amplify-react-native';
    • Adjust the last line of the file to use withAuthenticator():
      export default withAuthenticator(App);
  11. Run the app.
The app prompts you to sign up or sign in. Go through the sign-up process. Note that you’ll receive a validation code as either an email or SMS text message. Then sign in with your newly created credentials.

Configure AWS AppSync

AWS AppSync allows you to expose data within Amazon DynamoDB, Amazon ElasticSearch Service, or other data sources so that it can be accessed using GraphQL. You need to sign up for the AWS AppSync preview and wait to be accepted before continuing.
  1. Open the AWS AppSync console.
  2. Click Create API.
    • Type a suitable name (like NotesApp).
    • Choose Custom Schema.
    • Click Create.
  3. Click Schema on the left-hand menu.
  4. Copy and paste the following GraphQL schema into the text window provided:
    type Note { noteId: ID! title: String! content: String! } type Query { getNote(noteId: ID!): Note allNote(count: Int, nextToken: String): [Note] } type Mutation { putNote(noteId: ID!, title: String!, content: String!): Note deleteNote(noteId: ID!): Note } schema { query:Query mutation:Mutation } 
  5. Click Save.
  6. Click Create Resources.
  7. To select a type, choose Note in the drop-down.
  8. Scroll to the bottom of the page, and then click Create.
  9. Wait for the DynamoDB “create table” operation to complete.
At this point, you can click Queries in the left-hand navigation to try out some operations. Replace the content of the Queries editor with the following:
query ListAllNotes { allNote { noteId, title } } query GetNote($noteId:ID!) { getNote(noteId:$noteId) { noteId, title, content } } mutation SaveNote($noteId:ID!,$title:String!,$content:String!) { putNote(noteId:$noteId, title:$title, content:$content) { noteId, title, content } } mutation DeleteNote($noteId:ID!) { deleteNote(noteId:$noteId) { noteId } } 
These are the GraphQL commands that our application will execute. Replace the content of the Query Variables section with the following:
{ "noteId": "4c34d384-b715-4258-9825-1d34e8e6003b", "title": "Console Test", "content": "A test from the console" } 
You can run each command by using the Play button at the top of the Queries editor. Then, you can select the query or mutation that you want to perform. For example, use SaveNote, then ListAllNotes to list the note you just saved. Don’t forget to change the noteId between successive SaveNote runs because the noteId must be unique.

Link AWS AppSync to your app

The next step is to link the data source that you’ve just created to the app you’re working on.
  1. Go to the AWS AppSync console.
  2. Choose your API.
  3. At the bottom of the page, choose React Native, and then download the AppSync.js file.
  4. Place the AppSync.js file into your ./src directory.
  5. Add the requisite libraries to your project:
    yarn add react-apollo graphql-tag aws-sdk aws-appsync aws-appsync-react
  6. Edit the ./android/app/src/main/AndroidManifest.xml file. Add the following permission (in the same place as the other permissions):
    <uses-permission android:name=”android.permission.ACCESS_NETWORK_STATE” />
  7. Create the GraphQL documents. Create a new file ./src/graphql.js with the following contents:
    import gql from 'graphql-tag'; import { graphql } from 'react-apollo'; export const ListAllNotes = gql`query ListAllNotes {     allNote {         noteId, title     } }`; export const GetNote = gql`query GetNote($noteId:ID!) {     getNote(noteId:$noteId) {         noteId, title, content     } }`; export const SaveNote = gql`mutation SaveNote($noteId:ID!,$title:String!,$content:String!) {     putNote(noteId:$noteId, title:$title, content:$content) {         noteId, title, content     } }`; export const DeleteNote = gql`mutation DeleteNote($noteId:ID!) {     deleteNote(noteId:$noteId) {         noteId     } }`; export const operations = {     ListAllNotes: graphql(ListAllNotes, {         options: {             fetchPolicy: 'network-only'         },         props: ({ data }) => {             return {                 loading: data.loading,                 notes: data.allNote             };         }     }),      GetNote: graphql(GetNote, {         options: props => {             return {                 fetchPolicy: 'network-only',                 variables: { noteId: props.navigation.state.params.noteId }             };         },         props: ({ data }) => {             return {                 loading: data.loading,                 note: data.getNote             }         }     }),     DeleteNote: graphql(DeleteNote, {         options: {             refetchQueries: [ { query: ListAllNotes } ]         },         props: props => ({             deleteNote: (note) => {                 return props.mutate({                     variables: note,                     optimisticResponse: {                         deleteNote: { ...note, __typename: 'Note' }                     }                 })             }         })     }),     SaveNote: graphql(SaveNote, {         options: {             refetchQueries: [ { query: ListAllNotes } ]         },         props: props => ({             saveNote: (note) => {                 return props.mutate({                     variables: note,                     optimisticResponse: {                         putNote: { ...note, __typename: 'Note' }                     }                 })             }         })     }) };
    Note that the GraphQL documents are identical to the ones that are used inside the AWS AppSync console for running the queries and mutations manually. This block binds the GraphQL queries and mutations to function props on the React components.
  8. Update App.js to instantiate the AppSync connection. Adjust the imports as follows:
    // import { Provider } from 'react-redux'; // import { PersistGate } from 'redux-persist/es/integration/react'; // import { persistor, store } from './src/redux/store'; import AWSAppSyncClient from 'aws-appsync'; import { Rehydrated } from 'aws-appsync-react'; import { ApolloProvider } from 'react-apollo'; import appsyncConfig from './src/config/AppSync'; const appsyncClient = new AWSAppSyncClient({ url: appsyncConfig.graphqlEndpoint, region: appsyncConfig.region, auth: { type: appsyncConfig.authenticationType, apiKey: appsyncConfig.apiKey } }); 
    The commented-out imports are used by React Redux to provide a local store. We no longer need them because we’re using a GraphQL-based store. If you’re integrating into your own app and still need access to the Redux store in addition to the GraphQL store, see the React Apollo documentation on how to do this.
  9. Replace the return value from the App component with the following:
     return ( <ApolloProvider client={appsyncClient}> <Rehydrated> <Navigator/> </Rehydrated> </ApolloProvider> );
  10. Update the screens to remove Redux connectivity in ./src/screens/NoteListScreen.js and ./src/screens/NoteDetailsScreen.js:
    • Comment out the Redux imports and replace them with AppSync imports (at the top of each file).
      // BEGIN-REDUX // import { connect } from 'react-redux'; // import actions from '../redux/actions'; // END-REDUX // BEGIN APPSYNC import { compose } from 'react-apollo'; import * as GraphQL from '../graphql'; // END APPSYNC 
    • Comment out the mapStateToProps, mapDispatchToProps, and connect blocks (at the bottom of each file, marked by //BEGIN-REDUX and //END-REDUX).
  11. Add the following code block to the bottom of the ./src/screens/NoteListScreen.js file, just before the export default line:
    const NoteListScreen = compose( GraphQL.operations.ListAllNotes, GraphQL.operations.DeleteNote )(NoteList);
  12. Similarly, add the following code block to the bottom of the ./src/screens/NoteDetailsScreen.js file:
    const NoteDetailsScreen = compose( GraphQL.operations.GetNote, GraphQL.operations.SaveNote )(NoteDetails);
  13. Run the application. Use the app to add and delete some notes.
  14. Go to the DynamoDB console and check the table for your connection. You should see a representation of the current set of notes.

Extra credit: Offline access

You can move from online access to offline access by changing the fetchPolicy within the operations block of ./src/graphql.js from ‘network-only’ (which is to say – it always querys the backend server) to one of the other possible options:
  • ‘cache-first’ always returns data from the cache if the data is available. Data is fetched from the network only if a cached result isn’t available.
  • ‘cache-and-network’ always queries the network for data, regardless of whether the full data is in your cache. It returns the cache if it’s available. This option is for returning data quickly, but keeping the cached data consistent with the server (note that it might result in some UI “flipping of data” issues).
  • ‘network-only’ ignores the cache.
  • ‘cache-only’ ignores the network.
The most reasonable option is ‘cache-and-network’ for this use case. For more details on using AWS AppSync in an offline mode, see the AWS AppSync documentation.

Wrap up

I hope you’ve enjoyed this walkthrough of adding AWS Amplify and AWS AppSync to your application. In combination, the AWS AppSync service and AWS Amplify library can provide cloud benefits to your application with minimal code. We’ll be spending more time on these topics in the future, so stay tuned!