Front-End Web & Mobile

Add Maps to your Android app with AWS Amplify Geo, powered by Amazon Location Service

This blog post was written by Erica Eaton – Software Development Engineer at AWS Amplify.

Today’s release of AWS Amplify Geo for Android allows developers to quickly and easily add customizable maps with markers and location search to their Android applications. The location APIs are powered by Amazon Location Service and map rendering is from the popular open-source map library, MapLibre.

Benefits

  • Add maps and search functionality to your Android application.
  • Leverages the cost-effectiveness and privacy benefits of Amazon Location Service.
  • Uses the popular open-source library, MapLibre GL Native, for on device rendering.

What we’ll build

Today, we’ll build an Android application that allows a user to find movie theaters and the closest restaurants to the movie theater they select, so they can quickly satisfy their hunger after a long movie. To achieve this, we’ll set up Amplify Geo with the Amplify MapLibre Adapter to render a map, allow a user to search for a movie theater, and display restaurants near that theater on the map.

Prerequisites

  • Install Android Studio 4.0 or higher
  • Install Android SDK API level 21 or higher
  • Get the latest version of the Amplify CLI by running npm install -g @aws-amplify/cli

1. Set up an Android App

Let’s get started by creating a new Android project. In Android Studio, choose Create New Project. Select Empty Activity as the project template and press Next.

New Android Studio Project

On the next screen, enter “Movies” for the name. Select Kotlin as the language and API 21 for the minimum SDK, then press Finish.

Creating new Project Named Movies

2. Set up Amplify Geo

Now that you have an Android project, you’ll install Amplify and set up Amplify Geo. In the terminal, navigate to your project directory and run amplify init. Respond to the prompts that appear using the following answers:

? Enter a name for the project
    `Movies`
? Initialize the project with the above configuration?
    `n`
? Enter a name for the environment
    `dev`
? Choose your default editor:
    `Android Studio`
? Choose the type of app that you're building
    `android`
? Where is your Res directory:
    `app/src/main/res`
? Select the authentication method you want to use:
    `AWS profile`
? Please choose the profile you want to use
    `default`

You’ll see a message that your project has been successfully initialized. Now let’s set up the Geo resources. First, you’ll add the map capability using the command amplify add geo.

? Select which capability you want to add:
    Map (visualize the geospatial data)
? geo category resources require auth (Amazon Cognito). Do you want to add auth now? (Y/n)
    Y
? Do you want to use the default authentication and security configuration?
    Default configuration
? How do you want users to be able to sign in?
    Username
? Do you want to configure advanced settings?
    No, I am done.
? Provide a name for the Map:
    You can use the default name or any name you'd like.
? Who can access this Map?
    Authorized and Guest users
? Do you want to configure advanced settings? (y/N)
    N

You now have maps configured for your project. The last Geo capability you need to add is location search. You’ll use amplify add geo again to do this.

? Select which capability you want to add:
    Location search (search by places, addresses, coordinates)
? Provide a name for the location search index (place index):
    You can use the default name or any name you'd like.
? Who can access this search index?
    Authorized and Guest users
? Do you want to configure advanced settings? (y/N)
    N

Now that your Amplify CLI setup is complete, do amplify push to provision the resources in the cloud. You’ve now setup all the resources you need for your Amplify project.

While the resources are being provisioned, add the Amplify dependencies to your app and configure Amplify.

First, add the following lines in your app-level build.gradle(Module: Movies.app) file to allow your app to use Java 8 features and add the necessary dependencies for Amplify Geo:

android {
    compileOptions {
        // Support for Java 8 features
        coreLibraryDesugaringEnabled true
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}

dependencies {
    // Amplify dependencies
    implementation 'com.amplifyframework:core:1.35.2'
    implementation 'com.amplifyframework:aws-auth-cognito:1.35.2'
    implementation 'com.amplifyframework:aws-geo-location:1.0.0'
    implementation 'com.amplifyframework:maplibre-adapter:1.0.0'

    // Support for Java 8 features
    coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
}

Click “Sync now” in the notification bar at the top right to sync your project with the updated build.gradle file.

You’re almost done setting up Amplify Geo, you just need to configure Amplify in your app. Create a new Kotlin class called MoviesApp in the same directory as your MainActivity. Extend MoviesApp from android.app.Application. Add an onCreate function in the MoviesApp class that configures Amplify. When you’re done, your MoviesApp class should look like this:

class MoviesApp: Application() {

    override fun onCreate() {
        super.onCreate()

        try {
            Amplify.addPlugin(AWSCognitoAuthPlugin())
            Amplify.addPlugin(AWSLocationGeoPlugin())
            Amplify.configure(applicationContext)
            Log.i("MoviesApp", "Initialized Amplify")
        } catch (error: AmplifyException) {
            Log.e("MoviesApp", "Could not initialize Amplify", error)
        }
    }
}

To finish initializing Amplify in your app, add android:name=".MoviesApp" to the application element in your app’s AndroidManifest.xml file.

3. Display a Map in your App

With the setup complete, you’ll now add a map to your app. Since your map will be used to search for places, you’ll use AmplifyMapView, which provides built-in search capabilities. In the activity_main.xml file in the app/src/main/res/layout/ directory of your project, remove the existing contents and add the following:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:map="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <com.amplifyframework.geo.maplibre.view.AmplifyMapView
        android:id="@+id/mapView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        map:map_showZoomControls="true"
        map:map_zoomLevel="12"
        map:map_centerLatitude="47.62"
        map:map_centerLongitude="-122.33"
    />
</androidx.constraintlayout.widget.ConstraintLayout>

map:map_zoomLevel sets the initial zoom for the map (default is a zoom level of 14 that displays building-level detail). map:map_centerLatitude and map:map_centerLongitude set the initial latitude and longitude the map is centered on (default is 0 latitude and 0 longitude). You can change the zoomLevel, centerLatitude, and centerLongitude as you like.

In your app’s MainActivity, create a variable to store the instance of AmplifyMapView:

private val amplifyMapView by lazy {
    findViewById<AmplifyMapView>(R.id.mapView)
}

You now have an app that displays a map and allows users to search for places and it looks like this:

Amplify Map with Search on Android

Don’t stop here, continue on to the next step to allow users to search for movie theaters and find nearby restaurants.

4. Find the Closest Restaurants to a Movie Theater

By default, when a place (movie theater in this case) on the map is selected, AmplifyMapView will show a popup with more information about that place. To also search for restaurants near that movie theater, you need to listen for when a place is selected. Set the onPlaceSelect listener in the onCreate function of your app’s MainActivity:

amplifyMapView.onPlaceSelect { place, symbol ->
    // place is an instance of AmazonLocationPlace
    // symbol is an instance of Symbol from MapLibre
}

Now when a place on the map is selected, the informational popup from AmplifyMapView will still be shown, but the onPlaceSelect listener will also be invoked. To search for the 2 closest restaurants to a movie theater selected on the map, add the following code inside the onPlaceSelect listener you just added:

val searchQuery = "restaurant"
val position = place.coordinates
val options = GeoSearchByTextOptions.builder()
    .maxResults(2) // return 2 restaurants max
    .searchArea(SearchArea.near(position)) // search near the selected movie theater
    .build()

Amplify.Geo.searchByText(searchQuery, options,
    { Log.i("MoviesApp", "Searched for restaurants.") },
    { Log.e("MoviesApp", "Failed to search for restaurants", it) }
)

When the user searches for a movie theater and selects one on the map, your app now searches for the 2 closest restaurants to that movie theater. Those restaurants aren’t yet displayed on the map though. A new symbol (i.e a marker) needs to be added to the map for each of the 2 nearby restaurants. You’ll also add the place information to that symbol so it can be displayed when a user selects that restaurant. When you’re done, the onPlaceSelect listener will look as follows:

amplifyMapView.onPlaceSelect { place, symbol ->
    val searchQuery = "restaurant"
    val position = place.coordinates
    val options = GeoSearchByTextOptions.builder()
        .maxResults(2) // return 2 restaurants max
        .searchArea(SearchArea.near(position)) // search near the selected movie theater
        .build()

    Amplify.Geo.searchByText(searchQuery, options,
        { nearbyRestaurants ->
            this.runOnUiThread {
                nearbyRestaurants.places.forEach { restaurant ->
                    val currentRestaurant = restaurant as AmazonLocationPlace
                    amplifyMapView.mapView.getStyle { map, style ->
                        val restaurantLocation = currentRestaurant.coordinates.toLatLng()
                        amplifyMapView.mapView.symbolManager.create(
                            SymbolOptions()
                                .withIconImage("place-active")
                                .withLatLng(restaurantLocation)
                                .withData(currentRestaurant.toJsonElement())
                        )
                    }
                }
            }
        },
        { Log.e("MoviesApp", "Failed to search for restaurants", it) }
    )
}

It’s finally time to see the result of all your hard work. Run your app! In the search bar of the app, search for “movie theater”. You’ll see markers appear on the map for movie theaters. Click one of the markers. A popup will appear with information about that place and additional markers will be added to the map for nearby restaurants.

Map with Movie Theaters

Clean Up

Now that you’ve finished this walkthrough, if you don’t plan to use the movies app any further, delete the backend resources to avoid incurring unexpected costs from these resources.

Use the command amplify delete to delete the backend resources.

Conclusion

You have now built an app to search for places on a map and show restaurants near a place. In the process, you learned about multiple features Amplify Geo offers, including displaying a map in your app, searching for places near a location, and displaying location markers on the map.

Next Steps

Check out the Amplify Geo documentation to learn more about the features Amplify Geo offers. We’re working on new features for the Geo category, so keep an eye out on our Twitter and Discord communication channels. If you have any questions, let us know in #geo-help on Discord.