Front-End Web & Mobile

Cross-Platform Mobile Tracking App with AWS Amplify and Amazon Location Service

Location-based tracking applications have become very popular in recent years with the rise of ride-sharing companies and augmented reality “catch them all” style games. But personnel tracking in areas such as policing, ambulance, firefighting and other departments has been an integral part of the intelligence required for dispatching and public safety management for a long time.

Unfortunately, organizations relying on location-based intelligence for personnel or fleet-tracking have been limited by the high cost of available solutions over the years. This cost is primarily driven by building, maintaining, and operating these solutions; which requires considerable domain expertise. AWS has recognized our customer’s need for a simpler, more cost effective, way to build location enabled applications and has answered the call with the recent release of the fully managed Amazon Location Service. With Amazon Location Service it is now possible to build end-to-end location enabled applications at a fraction of the previous cost.

One of the most important requirements of any location enabled application is the storage and real-time tracking of location based assets. The Amazon Location Service Tracking feature offers exactly that. It allows you to track the locations of your assets in a scalable, secure and easy to use way while integrating with other services and features of the AWS platform. It doesn’t matter whether you’re tracking ten assets or thousands, Amazon Location Service Tracker will scale elastically with the needs of your application.

In this blog post, you will learn how to create an end-to-end solution for tracking the location of mobile phones with AWS Amplify and Amazon Location Service, in a React Native & Expo cross-platform scaffold.

Overview of Solution

AWS Amplify provides libraries to integrate with Amazon Location Service for native Mobile development on Android or iOS, with libraries for the languages Kotlin or Swift. While native languages have access to the devices APIs to access GPS and other sensors, the JavaScript libraries rely on being implemented into a cross-platform framework. For this reason, there is no direct Amplify to Location Service library, but through this tutorial you will learn how to hook up your devices location with Amazon Location Service through AWS AppSync using AWS Amplify.

The following diagram illustrates the final high-level architecture you will build in the walk-through. It will allow you to push the device’s location into Amazon Location Services. This can be used as a stand-alone location enabled mobile app, or as a part of an end-to-end location based solution.

Final architecture

Figure 1: Final architecture

The grey shaded area in the diagram above contains all the components managed by the AWS Amplify framework. The mobile client will communicate with the back-end over HTTPS through an AWS AppSync GraphQL API. The API will expose two end-points. One is for registering mobile devices, associating the Amazon Location Tracker with the Device ID in an Amazon DynamoDB table. The other is for forwarding the location of the mobile device to Amazon Location Service. It is implemented with an AWS Lambda function.

Walk-through

This discussion will not cover the basics of setting up a React Native Expo App. Instruction on how to do this can be found on the Expo website.
You can also pull the code for this demonstration from AWS-Samples from GitHub.

Set up Amazon Location Services Tracker

Step 1: To set up a tracker in the Amazon Location Service console, select Trackers from the list of available features.

Amazon Location Service Console

Figure 2: Amazon Location Service Console

Step 2: From the Trackers screen, select the Create Tracker button to add a tracker to the list of My trackers.

Trackers console

Figure 3: Trackers console

Step 3: Give the tracker a “Name” and select the type of pricing plan you want to use. A description is optional.

Tracker creation

Figure 4: Tracker creation

For the purposes of this demonstration select simulated/sample location data, so that you can take advantage of the free tier during your first three months of request-based usage.

Step 4: Select Create tracker and that’s it. You have created a tracker for your mobile device to send its location data to.
For now, take note of the “Name” of your mobile tracker.

Tracker properties

Figure 5: Tracker properties

Set up AWS Amplify

To add AWS Amplify to your React Native Expo App, all you need to do is run the initialization command in the root of the mobile app, set a name for our app, and accept the defaults for all other settings.

> amplify init
? Enter a name for the project Location
The following configuration will be applied:
 
Project information
| Name: Location
| Environment: dev
| Default editor: Visual Studio Code        
| App type: javascript
| Javascript framework: react-native     
| Source Directory Path: src
| Distribution Directory Path: /
| Build Command: npm.cmd run-script build
| Start Command: npm.cmd run-script start
 
? Initialize the project with the above configuration? (Y/n) Y

This will add the Amplify folder containing the AWS resources configuration to your mobile app, and create the src folder with the aws-exports.js that you will need to tie together the mobile app with the back-end resources.

Add AppSync and DynamoDB

Next add the GraphQL API with AppSync and Amazon DynamoDB resources required to query and register your device into the solution.

AWS Amplify Resources and their intended workflow

Figure 6: AWS Amplify Resources and their intended workflow

To add a GraphQL API backed by DynamoDB for data storage, you run the command amplify add api and select GraphQL from the options. Provide a name for the API, and use API Key authentication to communicate with the GraphQL API.

In order to have the DynamoDB table provisioned and the accompanying GraphQL queries and mutations code generated for you, you need to specify a schema. In this example choose Single object with fields. Answer Y when asked if you want to edit the schema now.

> amplify add api
? Please select from one of the below mentioned services: GraphQL
? Provide API name: device
? Choose the default authorization type for the API API key
? Enter a description for the API key: public
? After how many days from now the API key should expire (1-365): 365
? Do you want to configure advanced settings for the GraphQL API No, I am done.
? Do you have an annotated GraphQL schema? No
? Choose a schema template: Single object with fields (e.g., “Todo” with ID, name, description)
? Do you want to edit the schema now? (y/N) Y

Next modify the default GraphQL schema: The generated model will be named Todo. Replace Todo, with Device, and add the fields you want to capture for the device.

type Device @model {
  id: ID!
  name: String!
  trackerName: String!
}

The name field will contain the readable, user-friendly name of the device, which will be linked to the auto-generated id.
The trackerName field will contain the name of the tracker you created (“MobileTracker”).

Once modified, save the file and run amplify push in the terminal using the Amplify CLI to provision the resources in your AWS account. Make sure to select yes, to generate the code for GraphQL API, and accept the defaults for all other settings.

? Do you want to generate code for your newly created GraphQL API (Y/n) Y
? Choose the code generation language target javascript
? Enter the file name pattern of graphql queries, mutations and subscriptions src\graphql\**\*.js
? Do you want to generate/update all possible GraphQL operations - queries, mutations and subscriptions Yes
? Enter maximum statement depth [increase from default if your schema is deeply nested] 2

Connect AppSync to Tracker

Now you have the API and storage required to register your device with the solution. The last thing you need to do before integrating with the mobile app is to configure an AWS Lambda function to receive inputs from your API and to update your tracker with the device id and location data.

There are three parts to this step:

  • Create a Lambda function
  • Give Lambda permission to tracker
  • Update GraphQL API to query the lambda

Create a Lambda function

To add a Lambda function, run the CLI command amplify add function.

> amplify add function 
? Select which capability you want to add: Lambda function (serverless function)
? Provide an AWS Lambda function name: updateLocation
? Choose the runtime that you want to use: NodeJS
? Choose the function template that you want to use: Hello World
? Do you want to configure advanced settings? No
? Do you want to edit the local lambda function now? (Y/n) Y

For this example you will use NodeJS as the runtime, which means you can continue to use JavaScript for the Lambda function.

Before you can edit the function’s source code, you will need to change your CLI directory from the root of the mobile app to the root of the Lambda source: if you have named the Lambda function the same as in the example above the directory will be ./amplify/backend/function/updateLocation/src.

In this folder, run the command:

npm install aws-sdk

which will download the AWS SDK for JavaScript to the node_modules in the Lambda function source.

Be sure to change directory back to the root of the mobile application once the AWS SDK has been downloaded.

Update the Lambda function index.js to import the AWS SDK and receive the device id, location data, and tracker name you want to send to the Amazon Location Service Tracker service.

exports.handler = async (event) => {
    
    var location = new AWS.Location();
    console.log("update loc");
    
    var params = {
        TrackerName: event.arguments.trackerName, /* required */
        Updates: [ /* required */
        {
            DeviceId: `${event.arguments.deviceId}`, /* required */
            Position: [ /* required */
                parseFloat(event.arguments.Longitude),
                parseFloat(event.arguments.Latitude)
                /* more items */
            ],
            SampleTime: new Date || 123456789 /* required */
        },
        /* more items */
        ]
    };
    
    try{
        const data = await location.batchUpdateDevicePosition(params).promise(); 
        console.log(data);           // successful response
        console.log("update loc: success");
    } catch (err){
        console.log("update loc: error");
        console.log(err);
    }

    return params
};

Here, you are using the location libraries batchUpdateDevicePosition API to upload one device at a time. However, the batchUpdateDevicePosition can be used to upload up to ten devices at once in order to handle multiple devices through a single request.

Give Lambda permission to tracker

In order for the Lambda function to be able to send data to the Location Service Tracker, you need to provide permission in the function’s execution policy.

Find and edit the CloudFormation template for the Lambda in the ./amplify/backend/function/updateLocation folder named updateLocation-cloudformation-template.json. Add the following:

"PolicyDocument": {
    "Version": "2012-10-17",
    "Statement": [
    {
        "Effect": "Allow",
        "Action": [
        "logs:CreateLogGroup",
        "logs:CreateLogStream",
        "logs:PutLogEvents",
        "geo:BatchUpdateDevicePosition"
        ],
        "Resource": [
        {
            "Fn::Sub": [
            "arn:aws:geo:*:${account}:tracker/*",
            {
                "account": {"Ref": "AWS::AccountId"}
            }
            ]
        },
        {
            "Fn::Sub": [
            "arn:aws:logs:${region}:${account}:log-group:/aws/lambda/${lambda}:log-stream:*",
            {
                "region": {"Ref": "AWS::Region"},
                "account": {"Ref": "AWS::AccountId"},
                "lambda": {"Ref": "LambdaFunction"}
            }
            ]
        }
        ]
    }
    ]
}

Now you have provided the execution policy to allow geo:BatchUpdateDevicePosition for any tracker in the account.

Update GraphQL API to query using Lambda

Now modify your GraphQL model schema.graphql file, in the ./amplify/backend/api/device folder, with the following, at the bottom of your schema:

type Mutation {
  updateLocation(trackerName: String, deviceId: String, Latitude: String, Longitude: String): String @function(name: "updateLocation-${env}")
}

By adding a mutation function with input variables and joining it to our Lambda function with “@function”, when you call the mutation, the input variables will be sent to your Lambda function through the AppSync GraphQL API.

Run amplify push and be sure to select yes to update the generated code. This deploys your new Lambda function and updates to your GraphQL API. Thus completing the backend setup of AWS resources using AWS Amplify.

Now you have the resources you need to send location data to your tracker, from your mobile app.

Mobile App tracker

All that’s left to do is import and configure the Amplify resources into your mobile app.

To import and configure Amplify, as well as set up the functions you need for the mobile app to register and send location data to your tracker, you will follow a coding pattern known as the repository pattern.

Repository pattern illustration

Figure 7: Repository pattern illustration

The repository pattern is a simple strategy used for abstracting the application logic from the technical details of data access. It allows you to replace data sources without touching the application logic.

You will create a simple repository class file called device.js in the src folder that AWS Amplify created when you initialized Amplify.
The Device class contains all the functions necessary for your mobile app to register a device with the solution, store the generated device id on the device, and send the device location to your tracker.

To register a device with the solution, look at the screens/setup.js code in to see how the setup screen calls the Device class register function.

To send the mobile devices location to your tracker, all you need to do is import the expo-location library, which provides access to the devices location API, and import the Device class to query the AWS Amplify back-end.

The following code snippets are all a part of the main screen logic, but have been broken up to explain each step.

In the useEffect function, start by initializing the device service, which will query the Amplify backend to retrieve the tracker and device name according to the Device ID saved to the mobiles local storage:

useEffect(() => {

    (async () => {

      deviceSvc.init()
      .then((resp)=> {
          if(resp !== null){
              setTracker(resp.trackerName);
              setDeviceName(resp.name);
              setDeviceId(resp.id);
          }
      });

...

In order to use the devices Location API, you must first receive permission from the app user. This is handled by the Location libraries requestForegroundPermissionsAsync function

...

let { status } = await Location.requestForegroundPermissionsAsync();
if (status !== 'granted') {
    setErrorMsg('Permission to access location was denied');
    return;
} else {

...

Once the user has granted permission for the app to access the device location API, get the current device position with the getCurrentPositionAsync function. The callback data provided is then used with the Device service setLocation function, to send the location data to Amazon Location Service Tracker through AppSync.

...
        
let isLocationServicesEnabled = await Location.hasServicesEnabledAsync();
let locationProviderStatus = await Location.getProviderStatusAsync();
console.log(`loc status: ${JSON.stringify(locationProviderStatus)}`);

if(isLocationServicesEnabled){
      //Get loc
    let loc = await Location.getCurrentPositionAsync({accuracy: Location.Accuracy.Highest});
    if(loc.coords !== null){
        console.log(`loc: ${JSON.stringify(loc)}`);
        setLocation(loc);
        deviceSvc.setLocation(loc.coords.latitude, loc.coords.longitude);
    }

...

Finally, in order to track when the devices location changes, use the watchPositionAsync function to watch for a change in location up to 1 meter every 30 seconds. If the location has changed outside of 1 meter, send the location data to the Device service setLocation function.

...

          //Watch loc
          await Location.watchPositionAsync({
              enableHighAccuracy: true,
              distanceInterval: 1,
              timeInterval: 30000}, newLoc => {
                console.log(`new loc: ${JSON.stringify(newLoc)}`);
                setLocation(newLoc);
                deviceSvc.setLocation(newLoc.coords.latitude, newLoc.coords.longitude);
              });
        }
      }

    })();

}, []);

In this solution, the app must be in the foreground (active) to access the Location API. The location watcher is used to watch for changes to the location position, instead of a less efficient timeout query . For this demonstration you are checking for a change every 30 seconds, and execute an update if the location has change outside of 1 meter.

Clean up

The tracker does not incur any charges if it is not used, however, if you want to delete it, you can do so on the Amazon Location Service console.

Conclusion

In this walk-through you have created an Amazon Location Service Tracker and learned how to use AWS Amplify for JavaScript to deploy an integrated solution which sends location data to Amazon Location Service Tracker using a GraphQL API through AWS AppSync.

Authors

Aaron Sempf, Parter Solutions Architect
Aaron is a Senior Partner Solutions Architect, in the Global Systems Integrators team. When not working with AWS GSI partners, he can be found coding prototypes for autonomous robots, IoT devices, and distributed solutions.

Florian Seidel, Global Solutions Architect
Florian is a Solutions Architect in the EMEA Automotive Team at AWS. He has worked on location based services in the automotive industry for the last three years and has many years of experience in software engineering and software architecture.