

# Create an Android app to use Amazon Location Service
<a name="qs-android"></a>

In this section, you will create an Android application with a map, the ability to search at a location and tracking in the foreground. First, you will create your Amazon Location resources, an Amazon Cognito identity and an API key for your application.

**Topics**
+ [Create Amazon Location resources for your app](qs-create-resources-android.md)
+ [Set up authentication for your Amazon Location application](qs-setup-authentication-android.md)
+ [Create the base Android application to use Amazon Location](qs-create-app-android.md)
+ [Add an Amazon Location interactive map to your application](qs-add-map-android.md)
+ [Add Amazon Location reverse geocoding search to your application](qs-add-search-android.md)
+ [Add Amazon Location tracking to your application](qs-add-tracking-android.md)

# Create Amazon Location resources for your app
<a name="qs-create-resources-android"></a>

If you do not already have them, you must create the Amazon Location resources that your application will use. Here, you create a map resource to display maps in your application, a place index to search for locations on the map, and a tracker to track an object across the map.

**To add location resources to your application**

1. Choose the map style that you want to use.

   1. In the Amazon Location console, on the [Maps](https://console.aws.amazon.com/location/maps/home) page, choose **Create map** to preview map styles.

   1. Add a **Name** and **Description** for the new map resource. Make a note of the name that you use for the map resource. You will need it when creating your script file later in the tutorial.

   1. We recommend you choose the HERE map style for your map.
**Note**  
Choosing a map style also chooses which map data provider that you will use. If your application is tracking or routing assets that you use in your business, such as delivery vehicles or employees, you may only use HERE as your geolocation provider. For more information, see section 82 of the [AWS service terms](https://aws.amazon.com/service-terms). 

   1. Agree to the **Amazon Location Terms and Conditions**, then choose **Create map**. You can interact with the map that you've chosen: zoom in, zoom out, or pan in any direction.

   1. Make a note of the Amazon Resource Name (ARN) that is shown for your new map resource. You'll use it to create the correct authentication later in this tutorial.

1. Choose the place index that you want to use.

   1. In the Amazon Location console on the [https://console.aws.amazon.com/location/places/home](https://console.aws.amazon.com/location/places/home) page, choose **Create place index**.

   1. Add a **Name** and **Description** for the new place index resource. Make a note of the name that you use for the place index resource. You will need it when creating your script file later in the tutorial.

   1. Choose a data provider.
**Note**  
In most cases, choose the data provider that matches the map provider that you already chose. This helps to ensure that the searches will match the maps.  
If your application is tracking or routing assets that you use in your business, such as delivery vehicles or employees, you may only use HERE as your geolocation provider. For more information, see section 82 of the [AWS service terms](https://aws.amazon.com/service-terms). 

   1. Choose the **Data storage option**. For this tutorial, the results are not stored, so you can choose ** No, single use only**.

   1. Agree to the **Amazon Location Terms and Conditions**, then choose **Create place index**.

   1. Make a note of the ARN that is shown for your new place index resource. You'll use it to create the correct authentication in the next section of this tutorial.

1. To create a tracker using the Amazon Location console.

   1.  Open the [Amazon Location Service console](https://console.aws.amazon.com/location/).

   1.  In the left navigation pane, choose **Trackers**.

   1.  Choose **Create tracker**.

   1.  Fill in the all the required fields.

   1.  Under **Position filtering**, we recommend you use the default setting: **TimeBased**.

   1.  Choose **Create tracker** to finish.

# Set up authentication for your Amazon Location application
<a name="qs-setup-authentication-android"></a>

The application that you create in this tutorial has anonymous usage, meaning that your users are not required to sign into AWS to use the application. However, the Amazon Location Service APIs require authentication to use. You can use either API keys or Amazon Cognito to provide authentication and authorization for anonymous users. This tutorial will use Amazon Cognito and API keys to authenticate your application.

**Note**  
For more information about using Amazon Cognito or API keys with Amazon Location Service, see [Grant access to Amazon Location Service](how-to-access.md).

The following tutorials show you how to set up authentication for the map, the place index, and tracker you created in as well setting up permissions for Amazon Location.

**Set up authentication**

1. Navigate to the [Amazon Location console](https://console.aws.amazon.com/location/home/) and select **API keys** from the left-hand menu.

1. Click on 'Create API key'. Remember that the API key must be in the same AWS account and region as the previously created Amazon Location Service resources.

1. Fill in the required details on the 'Create API key' page:
   + Name: Provide a name for your API key, like `MyAppKey`.
   + Resources: Choose the Amazon Location Service Map and Place index resources created earlier. You can add multiple resources by selecting 'Add Resource'. This allows the API key to be used with specified resources.
   +  Actions: Specify authorized actions for this API key. At a minimum, select `geo:GetMap` and `geo:SearchPlaceIndexForPosition` to ensure the tutorial functions as intended.
   + Optional you can add a Description, Expiration time, Tags, or a referrer for example `https://www.example.com` to limit the key's usage to a specific domain, enabling the tutorial to function only within that domain.

1. Click **Create API Key** to generate the API key.

1. Select **Show API Key** and copy the key value for example `v1.public.a1b2c3d4` for later use in the tutorial.



**Create an IAM policy for tracking**

1. Sign in to the IAM console at https://console.aws.amazon.com/iam/ with your user that has administrator permissions. 

1. In the navigation pane, choose Policies. 

1. In the content pane, choose Create policy. 

1. Choose the **JSON** option, then copy and paste this JSON policy into the JSON text box.

   ```
   {
           "Version": "2012-10-17",		 	 	 
           "Statement": [
               {
                   "Effect": "Allow",
                   "Action": [
                       "geo:GetMapTile",
                       "geo:GetMapStyleDescriptor",
                       "geo:GetMapSprites",
                       "geo:GetMapGlyphs",
                       "geo:SearchPlaceIndexForPosition",
                       "geo:GetDevicePositionHistory",
                       "geo:BatchUpdateDevicePosition"
                   ],
                   "Resource": [
                       "arn:aws:geo:{Region}:{Account}:map/{MapName}",
                       "arn:aws:geo:{Region}:{Account}:place-index/{IndexName}",
                       "arn:aws:geo:{Region}:{Account}:tracker/{TrackerName}"
                   ]
               }
           ]
       }
   ```

   This is a policy example for Tracking. To use the example for your own policy, replace the `Region`, `Account`, and **TrackerName** placeholders.
**Note**  
While unauthenticated identity pools are intended for exposure on unsecured internet sites, note that they will be exchanged for standard, time-limited AWS credentials.  
It's important to scope the IAM roles associated with unauthenticated identity pools appropriately. For more information about using and appropriately scoping policies in Amazon Cognito with Amazon Location Service, see [Granting access to Amazon Location Service](location/previous/developerguide/how-to-access.html).

1. On the Review and Create page, provide a name for the policy name field. Review the permissions granted by your policy, and then choose Create Policy to save your work.

The new policy appears in the list of managed policies and is ready to attach.

**Set up authentication for your tracking**

1. Set up authentication for your map application in the [Amazon Cognito console](https://console.aws.amazon.com/cognito/home/).

1. Open the **Identity pools** page.
**Note**  
The pool that you create must be in the same AWS account and AWS Region as the Amazon Location Service resources that you created in the previous section.

1. Choose **Create Identity pool**.

1. Starting with the **Configure identity pool trust** step. For user access authentication, select **Guest access**, and press next.

1. On the **Configure permissions** page select the **Use an existing IAM role** and enter the name of the IAM role you created in the previous step. When ready press next to move on to the next step.

1. On the **Configure properties** page, provide a name for your identity pool. Then press **Next**.

1. On the **Review and create** page, review all the information present then press **Create identity pool**.

1. Open the **Identity pools** page, and select the identity pool you just created. Then copy or write down the IdentityPoolId that you will use later in your browser script.

# Create the base Android application to use Amazon Location
<a name="qs-create-app-android"></a>

In this tutorial, you will create an Android application that embeds a map and allows the user to find what's at a location on the map.

First, create an empty Kotlin application using Android Studio's new project wizard.

**To create an empty application (AndroidStudio)**

1. Start AndroidStudio. Open the menu, and choose **File**, **New**, **New Project**.

1. From the **Phone and Tablet** tab, select **Empty Activity**, and then choose **Next**.

1. Choose a **Name**, **Package name**, and **Save location** for your application.

1. In the menu for **Language**, select **Kotlin**.

1. Choose **Finish** to create your blank application.

# Add an Amazon Location interactive map to your application
<a name="qs-add-map-android"></a>

Now that you have created a basic application, you can add map control to your application. This tutorial uses API keys for managing the map view. The map control itself is part of the [MapLibre Native library](https://github.com/maplibre/maplibre-native), with the API key and MapLibre, and the map data comes from Amazon Location.

To add a map to your application you will have to perform the following actions:
+ Add the MapLibre dependency to your project.
+ Set up the map view code with compose.
+ Write code to show the map.

Use the following procedure to add the map to your app:

1. Add the MapLibre dependency to your project

   1. In AndroidStudio, select the **View** menu, and choose **Tool Windows**, **Project**. This will open the Project window, which gives you access to all the files in your project.

   1. In the **Project** window, open **gradle** then open the `libs.versions.toml` file in the tree view. This will open the `libs.versions.toml` file for editing. Now add the below version and libraries data in the `libs.versions.toml` file.

      ```
      [versions]
       ...
       auth = "0.2.4"
       tracking = "0.2.4"
       
       [libraries]
       ...
       auth = { group = "software.amazon.location", name = "auth", version.ref = "auth" }
       tracking = { module = "software.amazon.location:tracking", version.ref = "tracking" }
      
       [plugins]
       ...
      ```

   1. After you finish editing the `libs.versions.toml` file, AndroidStudio must re-sync the project. At the top of the `libs.versions.toml` editing window, AndroidStudio prompts you to sync. Select 'Sync Now' to sync your project before continuing.

   1. In the Project window, open Gradle Scripts in the tree view and select the `build.gradle` file for your application module. This will open the `build.gradle` file for editing.

   1. At the bottom of the file, in the dependencies section, add the following dependency.

      ```
      dependencies {
          ...
          implementation(libs.org.maplibre.gl)
       }
      ```

   1. After you finish adding the Gradle dependencies, Android Studio must re-sync the project. At the top of the **build.gradle** editing window, Android Studio, select **Sync Now** to sync your project before continuing.

1. Now you will set up the map view code with compose. Use the following steps:

   1. From the Project window, open App, Java, *your package name* in the tree view, and go to the **ui** folder, inside the **ui** folder create a **view** directory.

   1. Inside **view** directory create a `MapLoadScreen.kt` file.

   1. Add the following code to your `MapLoadScreen.kt` file.

      ```
      import androidx.compose.foundation.layout.Box
      import androidx.compose.foundation.layout.fillMaxHeight
      import androidx.compose.foundation.layout.fillMaxWidth
      import androidx.compose.runtime.Composable
      import androidx.compose.ui.Modifier
      import androidx.compose.ui.viewinterop.AndroidView
      import org.maplibre.android.maps.OnMapReadyCallback
       
       @Composable
       fun MapLoadScreen(
           mapReadyCallback: OnMapReadyCallback,
       ) {
           Box(
               modifier = Modifier
                   .fillMaxWidth()
                   .fillMaxHeight(),
           ) {
               MapView(mapReadyCallback)
           }
       }
       
       @Composable
       fun MapView(mapReadyCallback: OnMapReadyCallback) {
           AndroidView(
               factory = { context ->
                   val mapView = org.maplibre.android.maps.MapView(context)
                   mapView.onCreate(null)
                   mapView.getMapAsync(mapReadyCallback)
                   mapView
               },
           )
       }
      ```

1. Write code to show the map.

   1. Add the following code to your `MainActivity.kt` file.

      ```
      // ...other imports
      import org.maplibre.android.MapLibre
      import org.maplibre.android.camera.CameraPosition
      import org.maplibre.android.geometry.LatLng
      import org.maplibre.android.maps.MapLibreMap
      import org.maplibre.android.maps.OnMapReadyCallback
      import org.maplibre.android.maps.Style
       
       class MainActivity : ComponentActivity(), OnMapReadyCallback {
           private val region = "YOUR_AWS_REGION"
           private val mapName = "YOUR_AWS_MAP_NAME"
           private val apiKey = "YOUR_AWS_API_KEY"
           override fun onCreate(savedInstanceState: Bundle?) {
               MapLibre.getInstance(this)
               super.onCreate(savedInstanceState)
               setContent {
                   TestMapAppTheme {
                       Surface(
                           modifier = Modifier.fillMaxSize(),
                           color = MaterialTheme.colorScheme.background
                       ) {
                           MapLoadScreen(this)
                       }
                   }
               }
           }
       
           override fun onMapReady(map: MapLibreMap) {
               map.setStyle(
                   Style.Builder()
                       .fromUri(
                           "https://maps.geo.$region.amazonaws.com/maps/v0/maps/$mapName/style-descriptor?key=$apiKey"
                       ),
               ) {
                   map.uiSettings.isAttributionEnabled = true
                   map.uiSettings.isLogoEnabled = false
                   map.uiSettings.attributionGravity = Gravity.BOTTOM or Gravity.END
                   val initialPosition = LatLng(47.6160281982247, -122.32642111977668)
                   map.cameraPosition = CameraPosition.Builder()
                       .target(initialPosition)
                       .zoom(14.0)
                       .build()
               }
           }
       }
      ```

   1. Save the `MainActivity.kt` file. You can now build the application. To run it, you may have to set up a device to emulate it in AndroidStudio, or use the app on your device. Use this app to see how the map control behaves. You can pan by dragging it on the map and pinching it to zoom.

      In the next section, you will add a marker on the map and show the address of the location where the marker is as you move the map.

# Add Amazon Location reverse geocoding search to your application
<a name="qs-add-search-android"></a>

You will now add reverse geocoding search to your application, where you find the items at a location. To simplify using an Android app, we will search the center of the screen. To find a new location, move the map to where you want to search. We will place a marker at the centre of the map to show where we are searching.

Adding a reverse geocoding search will consist of two parts.
+ Add a marker at the centre of the screen to show the user where we are searching.
+ Add a text box for results, then search for what is at the marker's location and show it in the text box.

**To add a marker to your application**

1. Save this image to your project in the `app/res/drawable` folder as `red_marker.png` (you can also access the image from [GitHub](https://github.com/makeen-project/amazon-location-mobile-quickstart-android/tree/main/app/src/main/res/drawable). Alternatively, you can create your image. You can also use a .png file with transparency for the parts you don't want shown.

1. Add the following code to your **MapLoadScreen.kt** file.

   ```
   // ...other imports
   import androidx.compose.foundation.Image
   import androidx.compose.foundation.layout.size
   import androidx.compose.ui.Alignment
   import androidx.compose.ui.res.painterResource
   import androidx.compose.ui.unit.dp
   import com.amazon.testmapapp.R
   
   @Composable
   fun MapLoadScreen(
       mapReadyCallback: OnMapReadyCallback,
   ) {
       Box(
           modifier = Modifier
               .fillMaxWidth()
               .fillMaxHeight(),
       ) {
           MapView(mapReadyCallback)
           Box(
               modifier = Modifier
                   .align(Alignment.Center),
           ) {
               Image(
                   painter = painterResource(id = R.drawable.red_marker),
                   contentDescription = "marker",
                   modifier = Modifier
                       .size(40.dp)
                       .align(Alignment.Center),
               )
           }
       }
   }
   
   @Composable
   fun MapView(mapReadyCallback: OnMapReadyCallback) {
       AndroidView(
           factory = { context ->
               val mapView = org.maplibre.android.maps.MapView(context)
               mapView.onCreate(null)
               mapView.getMapAsync(mapReadyCallback)
               mapView
           },
       )
   }
   ```

1. Build and run your app to preview the functionality.

Your app now has a marker on the screen. In this case, it is a static image that doesn't move. It is used to show the centre of the map view, which is where we will search. In the following procedure, we will add the search at that location.

**To add reverse geocoding search at a location to your app**

1. In the **Project** window, open **gradle** to `libs.versions.toml` file in the tree view. This will open the `libs.versions.toml` file for editing. Now add the below version and libraries data in the `libs.versions.toml` file.

   ```
   [versions]
    ...
    okhttp = "4.12.0"
    
    [libraries]
    ...
    com-squareup-okhttp3 = { group = "com.squareup.okhttp3", name = "okhttp", version.ref = "okhttp" }
    
    [plugins]
    ...
   ```

1. After you finish editing the `libs.versions.toml` file, AndroidStudio must re-sync the project. At the top of the `libs.versions.toml` editing window, AndroidStudio prompts you to sync. Select 'Sync Now' to sync your project before continuing.

1. In the Project window, open Gradle Scripts in the tree view and select the `build.gradle` file for your application module. This will open the `build.gradle` file for editing.

1. At the bottom of the file, in the dependencies section, add the following dependency.

   ```
   dependencies {
       ...
       implementation(libs.com.squareup.okhttp3)
    }
   ```

1. After you finish editing the Gradle dependencies, AndroidStudio must re-sync the project. At the top of the `build.gradle` editing window, AndroidStudio prompts you to sync. Select **SyncNow** to sync your project before continuing.

1. Now in the tree view add the **data**, to the **request** directory, and create the `ReverseGeocodeRequest.kt` data class. Add the following code to the class.

   ```
   import com.google.gson.annotations.SerializedName
   
   data class ReverseGeocodeRequest(
       @SerializedName("Language")
       val language: String,
       @SerializedName("MaxResults")
       val maxResults: Int,
       @SerializedName("Position")
       val position: ListDouble
   )
   ```

1. Now in the tree view add the **data** to **response** directory, and create the `ReverseGeocodeResponse.kt` data class. Add the following code inside it.

   ```
   import com.google.gson.annotations.SerializedName
   
   data class ReverseGeocodeResponse(
       @SerializedName("Results")
       val results: ListResult
   )
   
   data class Result(
       @SerializedName("Place")
       val place: Place
   )
   
   data class Place(
       @SerializedName("Label")
       val label: String
   )
   ```

1. Now, From the Project window, open App, Java, *your package name* in the tree view, and go to the **ui** folder, inside **ui** folder create **viewModel** directory.

1. Inside **viewModel** directory create `MainViewModel.kt` file.

1. Add the following code to your `MainViewModel.kt` file.

   ```
   import androidx.compose.runtime.getValue
   import androidx.compose.runtime.mutableStateOf
   import androidx.compose.runtime.setValue
   import androidx.lifecycle.ViewModel
   import com.amazon.testmapapp.data.request.ReverseGeocodeRequest
   import com.amazon.testmapapp.data.response.ReverseGeocodeResponse
   import com.google.gson.Gson
   import java.io.IOException
   import okhttp3.Call
   import okhttp3.Callback
   import okhttp3.MediaType.Companion.toMediaTypeOrNull
   import okhttp3.OkHttpClient
   import okhttp3.Request
   import okhttp3.RequestBody.Companion.toRequestBody
   import okhttp3.Response
   import org.maplibre.android.geometry.LatLng
   import org.maplibre.android.maps.MapLibreMap
   
   class MainViewModel : ViewModel() {
       var label by mutableStateOf("")
       var isLabelAdded: Boolean by mutableStateOf(false)
       var client = OkHttpClient()
       var mapLibreMap: MapLibreMap? = null
   
       fun reverseGeocode(latLng: LatLng, apiKey: String) {
           val region = "YOUR_AWS_REGION"
           val indexName = "YOUR_AWS_PLACE_INDEX"
           val url =
               "https://places.geo.${region}.amazonaws.com/places/v0/indexes/${indexName}/search/position?key=${apiKey}"
   
           val requestBody = ReverseGeocodeRequest(
               language = "en",
               maxResults = 1,
               position = listOf(latLng.longitude, latLng.latitude)
           )
           val json = Gson().toJson(requestBody)
   
           val mediaType = "application/json".toMediaTypeOrNull()
           val request = Request.Builder()
               .url(url)
               .post(json.toRequestBody(mediaType))
               .build()
   
           client.newCall(request).enqueue(object : Callback {
               override fun onFailure(call: Call, e: IOException) {
                   e.printStackTrace()
               }
   
               override fun onResponse(call: Call, response: Response) {
                   if (response.isSuccessful) {
                       val jsonResponse = response.body?.string()
   
                       val reverseGeocodeResponse =
                           Gson().fromJson(jsonResponse, ReverseGeocodeResponse::class.java)
   
                       val responseLabel = reverseGeocodeResponse.results.firstOrNull()?.place?.label
   
                       if (responseLabel != null) {
                           label = responseLabel
                           isLabelAdded = true
                       }
                   }
               }
           })
       }
   }
   ```

1. If it's not open already, open the `MapLoadScreen.kt` file, as in the previous procedure. Add the following code. This will create a compose Text view where you will see reverse geocoding search results at the location.

   ```
   // ...other imports
   import androidx.compose.foundation.background
   import androidx.compose.foundation.layout.Arrangement
   import androidx.compose.foundation.layout.Column
   import androidx.compose.foundation.layout.Spacer
   import androidx.compose.foundation.layout.fillMaxSize
   import androidx.compose.foundation.layout.height
   import androidx.compose.foundation.layout.padding
   import androidx.compose.material3.Text
   import androidx.compose.ui.graphics.Color
   import androidx.compose.ui.platform.testTag
   import androidx.compose.ui.semantics.contentDescription
   import androidx.compose.ui.semantics.semantics
   import androidx.compose.ui.unit.sp
   import com.amazon.testmapapp.ui.viewModel.MainViewModel
   
   @Composable
   fun MapLoadScreen(
       mapReadyCallback: OnMapReadyCallback,
       mainViewModel: MainViewModel,
   ) {
       Box(
           modifier = Modifier
               .fillMaxWidth()
               .fillMaxHeight(),
       ) {
           MapView(mapReadyCallback)
           Box(
               modifier = Modifier
                   .align(Alignment.Center),
           ) {
               Image(
                   painter = painterResource(id = R.drawable.red_marker),
                   contentDescription = "marker",
                   modifier = Modifier
                       .size(40.dp)
                       .align(Alignment.Center),
               )
           }
           if (mainViewModel.isLabelAdded) {
               Column(
                   modifier = Modifier.fillMaxSize(),
                   verticalArrangement = Arrangement.Bottom
               ) {
                   Box(
                       modifier = Modifier
                           .fillMaxWidth()
                           .background(Color.White),
                   ) {
                       Text(
                           text = mainViewModel.label,
                           modifier = Modifier
                               .padding(16.dp)
                               .align(Alignment.Center)
                               .testTag("label")
                               .semantics {
                                   contentDescription = "label"
                               },
                           fontSize = 14.sp,
                       )
                   }
                   Spacer(modifier = Modifier.height(80.dp))
               }
           }
       }
   }
   
   @Composable
   fun MapView(mapReadyCallback: OnMapReadyCallback) {
       AndroidView(
           factory = { context ->
               val mapView = org.maplibre.android.maps.MapView(context)
               mapView.onCreate(null)
               mapView.getMapAsync(mapReadyCallback)
               mapView
           },
       )
   }
   ```

1. In the app, under java, in the **package name** folder in AndroidStudio, open the `MainActivity.kt` file. Modify the code as shown.

   ```
   // ...other imports
   import androidx.activity.viewModels
   import com.amazon.testmapapp.ui.viewModel.MainViewModel
   
    class MainActivity : ComponentActivity(), OnMapReadyCallback, MapLibreMap.OnCameraMoveStartedListener, MapLibreMap.OnCameraIdleListener {
    
        private val mainViewModel: MainViewModel by viewModels()
        private val region = "YOUR_AWS_REGION"
        private val mapName = "YOUR_AWS_MAP_NAME"
        private val apiKey = "YOUR_AWS_API_KEY"
        override fun onCreate(savedInstanceState: Bundle?) {
            MapLibre.getInstance(this)
            super.onCreate(savedInstanceState)
            setContent {
                TestMapAppTheme {
                    Surface(
                        modifier = Modifier.fillMaxSize(),
                        color = MaterialTheme.colorScheme.background
                    ) {
                        MapLoadScreen(this, mainViewModel)
                    }
                }
            }
        }
    
        override fun onMapReady(map: MapLibreMap) {
            map.setStyle(
                Style.Builder()
                    .fromUri(
                        "https://maps.geo.$region.amazonaws.com/maps/v0/maps/$mapName/style-descriptor?key=$apiKey"
                    ),
            ) {
                map.uiSettings.isAttributionEnabled = true
                map.uiSettings.isLogoEnabled = false
                map.uiSettings.attributionGravity = Gravity.BOTTOM or Gravity.END
                val initialPosition = LatLng(47.6160281982247, -122.32642111977668)
                map.cameraPosition = CameraPosition.Builder()
                    .target(initialPosition)
                    .zoom(14.0)
                    .build()
                    
                map.addOnCameraMoveStartedListener(this)
                map.addOnCameraIdleListener(this)
                map.cameraPosition.target?.let { latLng ->
                    mainViewModel.reverseGeocode(
                        LatLng(
                            latLng.latitude,
                            latLng.longitude
                        ), apiKey
                    )
                }
            }
        }
       override fun onCameraMoveStarted(p0: Int) {
           mainViewModel.label = ""
           mainViewModel.isLabelAdded = false
       }
   
       override fun onCameraIdle() {
           if (!mainViewModel.isLabelAdded) {
               mainViewModel.mapLibreMap?.cameraPosition?.target?.let { latLng ->
                   mainViewModel.reverseGeocode(
                       LatLng(
                           latLng.latitude,
                           latLng.longitude
                       ), apiKey
                   )
               }
           }
       }
    }
   ```

   This code works with the map view. A virtual camera position defines the map view in MapLibre. Moving the map can be thought of as moving that virtual camera.
   + ViewModel has a label variable: This variable sets data in compose text view.
   + **onMapReady**:This function is updated to register two new events.
   + The **onCameraMove** event happens whenever the user is moving the map. In general, when moving the map, we want to hide the search until the user is done moving the map.
   + The **onCameraIdle** event occurs when the user pauses moving the map. This event calls our reverse geocode function to search at the centre of the map.
   + `reverseGeocode(latLng: LatLng, apiKey: String)`: This function, called in the event onCameraIdle, searches at the centre of the map for a location and updates the label to show the results. It uses the camera target, which defines the centre of the map (where the camera is looking).

1. Save your files, and build and run your app to see if it works.

Your quick-start application with search functionality is complete.

# Add Amazon Location tracking to your application
<a name="qs-add-tracking-android"></a>

To add tracking to your sample application, follow these steps:

1. Add tracking and auth SDK dependencies to your project.

1. Include permission and service entries in your AndroidManifest.xml file.

1. Set up the start/stop tracking button code with compose.

1. Add code for creating a LocationTracker object and start and stop tracking.

1. Create a test route with Android Emulator.

1. **Add tracking and auth SDK dependencies to your project.**

   1. In the **Project** window, open **gradle** then open the `libs.versions.toml` file in the tree view. This will open the `libs.versions.toml` file for editing. Now add the below version and libraries data in the `libs.versions.toml` file.

      ```
      [versions]
       ...
       auth = "0.0.1"
       tracking = "0.0.1"
       
       [libraries]
       ...
       auth = { group = "software.amazon.location", name = "auth", version.ref = "auth" }
       tracking = { module = "software.amazon.location:tracking", version.ref = "tracking" }
      
       [plugins]
       ...
      ```

   1. After you finish editing the `libs.versions.toml` file, AndroidStudio must re-sync the project. At the top of the `libs.versions.toml` editing window, AndroidStudio prompts you to sync. Select 'Sync Now' to sync your project before continuing.

   1. In the Project window, open Gradle Scripts in the tree view and select the `build.gradle` file for your application module. This will open the `build.gradle` file for editing.

   1. At the bottom of the file, in the **dependencies** section, add the following dependency.

      ```
      dependencies {
          ...
          implementation(libs.auth)
          implementation(libs.tracking)
       }
      ```

   1. After you finish editing the Gradle dependencies, AndroidStudio must re-sync the project. At the top of the **build.gradle** editing window, AndroidStudio prompts you to sync. Select **SyncNow** to sync your project before continuing.

1. **Include permission and service entries in your AndroidManifest.xml file.**

   1. To include the correct permission and service entries in your `AndroidManifest.xml file`, update the file with the following code:

     ```
     <?xml version="1.0" encoding="utf-8"?>
     <manifest xmlns:android="http://schemas.android.com/apk/res/android"
         xmlns:tools="http://schemas.android.com/tools">
     
         <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
         <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
         <uses-permission android:name="android.permission.INTERNET"/>
         <application
             android:allowBackup="true"
             android:dataExtractionRules="@xml/data_extraction_rules"
             android:fullBackupContent="@xml/backup_rules"
             android:icon="@mipmap/ic_launcher"
             android:label="@string/app_name"
             android:roundIcon="@mipmap/ic_launcher_round"
             android:supportsRtl="true"
             android:theme="@style/Theme.AndroidQuickStartApp"
             tools:targetApi="31">
             <activity
                 android:name=".MainActivity"
                 android:exported="true"
                 android:label="@string/app_name"
                 android:theme="@style/Theme.AndroidQuickStartApp">
                 <intent-filter>
                     <action android:name="android.intent.action.MAIN" />
     
                     <category android:name="android.intent.category.LAUNCHER" />
                 </intent-filter>
             </activity>
         </application>
     
     </manifest>
     ```

1. **Set up the start/stop tracking button code with compose.** 

   1. Add two images of Play and Pause in **res** under **drawable** named as **ic\$1pause** and **ic\$1play**. You can also access the image from [GitHub](https://github.com/aws-geospatial/amazon-location-samples-android/tree/main/quick-start/res/drawable).

   1. If it's not open already, open the `MapLoadScreen.kt` file, as in the previous procedure. Add the following code. This will create a compose Button view where we can click on it to **start** and **stop** tracking.

      ```
      // ...other imports
      import androidx.compose.material3.Button
      import androidx.compose.material3.ButtonDefaults
      
      @Composable
      fun MapLoadScreen(
          mapReadyCallback: OnMapReadyCallback,
          mainViewModel: MainViewModel,
          onStartStopTrackingClick: () -> Unit
      ) {
          Box(
              modifier = Modifier
                  .fillMaxWidth()
                  .fillMaxHeight(),
          ) {
              MapView(mapReadyCallback)
              Box(
                  modifier = Modifier
                      .align(Alignment.Center),
              ) {
                  Image(
                      painter = painterResource(id = R.drawable.red_marker),
                      contentDescription = "marker",
                      modifier = Modifier
                          .size(40.dp)
                          .align(Alignment.Center),
                  )
              }
              if (mainViewModel.isLabelAdded) {
                  Column(
                      modifier = Modifier.fillMaxSize(),
                      verticalArrangement = Arrangement.Bottom
                  ) {
                      Box(
                          modifier = Modifier
                              .fillMaxWidth()
                              .background(Color.White),
                      ) {
                          Text(
                              text = mainViewModel.label,
                              modifier = Modifier
                                  .padding(16.dp)
                                  .align(Alignment.Center)
                                  .testTag("label")
                                  .semantics {
                                      contentDescription = "label"
                                  },
                              fontSize = 14.sp,
                          )
                      }
                      Spacer(modifier = Modifier.height(80.dp))
                  }
              }
              Column(
                  modifier = Modifier
                      .fillMaxSize()
                      .padding(bottom = 16.dp),
                  horizontalAlignment = Alignment.CenterHorizontally,
                  verticalArrangement = Arrangement.Bottom,
              ) {
                  Button(
                      onClick = onStartStopTrackingClick,
                      modifier = Modifier
                          .padding(horizontal = 16.dp)
                  ) {
                      Text(
                          text = if (mainViewModel.isLocationTrackingForegroundActive) "Stop tracking" else "Start tracking",
                          color = Color.Black
                      )
                      Spacer(modifier = Modifier.size(ButtonDefaults.IconSpacing))
                      Image(
                          painter = painterResource(id = if (mainViewModel.isLocationTrackingForegroundActive) R.drawable.ic_pause else R.drawable.ic_play),
                          contentDescription = if (mainViewModel.isLocationTrackingForegroundActive) "stop_tracking" else "start_tracking"
                      )
                  }
              }
          }
      }
      
      @Composable
      fun MapView(mapReadyCallback: OnMapReadyCallback) {
          AndroidView(
              factory = { context ->
                  val mapView = org.maplibre.android.maps.MapView(context)
                  mapView.onCreate(null)
                  mapView.getMapAsync(mapReadyCallback)
                  mapView
              },
          )
      }
      ```

1. **Add code for creating a LocationTracker object and start and stop tracking.** 

   1. Add the following code inside the `MainViewModel.kt` file.

      ```
      ...
      var isLocationTrackingForegroundActive: Boolean by mutableStateOf(false)
      var locationTracker: LocationTracker? = null
      ```

   1. Add the following code to your `MainActivity.kt` file.

      ```
      // ...other imports
      import software.amazon.location.auth.AuthHelper
      import software.amazon.location.auth.LocationCredentialsProvider
      import software.amazon.location.tracking.LocationTracker
      import software.amazon.location.tracking.aws.LocationTrackingCallback
      import software.amazon.location.tracking.config.LocationTrackerConfig
      import software.amazon.location.tracking.database.LocationEntry
      import software.amazon.location.tracking.filters.DistanceLocationFilter
      import software.amazon.location.tracking.filters.TimeLocationFilter
      import software.amazon.location.tracking.util.TrackingSdkLogLevel
      
      class MainActivity : ComponentActivity(), OnMapReadyCallback,
          MapLibreMap.OnCameraMoveStartedListener, MapLibreMap.OnCameraIdleListener {
      
          private val mainViewModel: MainViewModel by viewModels()
          private val poolId = "YOUR_AWS_IDENTITY_POOL_ID"
          private val trackerName = "YOUR_AWS_TRACKER_NAME"
          private val region = "YOUR_AWS_REGION"
          private val mapName = "YOUR_AWS_MAP_NAME"
          private val apiKey = "YOUR_AWS_API_KEY"
          private val coroutineScope = MainScope()
          private lateinit var locationCredentialsProvider: LocationCredentialsProvider
          private lateinit var authHelper: AuthHelper
      
          override fun onCreate(savedInstanceState: Bundle?) {
              MapLibre.getInstance(this)
              super.onCreate(savedInstanceState)
              setContent {
                  TestMapAppTheme {
                      Surface(
                          modifier = Modifier.fillMaxSize(),
                          color = MaterialTheme.colorScheme.background
                      ) {
                          MapLoadScreen(this, mainViewModel, onStartStopTrackingClick = {
                              if (mainViewModel.isLocationTrackingForegroundActive) {
                                  mainViewModel.isLocationTrackingForegroundActive = false
                                  mainViewModel.locationTracker?.stop()
                              } else {
                                  if (checkLocationPermission(this)) return@MapLoadScreen
                                  mainViewModel.isLocationTrackingForegroundActive = true
                                  mainViewModel.locationTracker?.start(locationTrackingCallback = object :
                                      LocationTrackingCallback {
                                      override fun onLocationAvailabilityChanged(locationAvailable: Boolean) {
                                      }
      
                                      override fun onLocationReceived(location: LocationEntry) {
                                      }
      
                                      override fun onUploadSkipped(entries: LocationEntry) {
                                      }
      
                                      override fun onUploadStarted(entries: ListLocationEntry) {
                                      }
      
                                      override fun onUploaded(entries: ListLocationEntry) {
                                      }
      
                                  })
                              }
                          })
                      }
                  }
              }
              authenticateUser()
          }
      
          private fun authenticateUser() {
              coroutineScope.launch {
                  authHelper = AuthHelper(applicationContext)
                  locationCredentialsProvider = authHelper.authenticateWithCognitoIdentityPool(
                      poolId,
                  )
                  locationCredentialsProvider.let {
                      val config = LocationTrackerConfig(
                          trackerName = trackerName,
                          logLevel = TrackingSdkLogLevel.DEBUG,
                          latency = 1000,
                          frequency = 5000,
                          waitForAccurateLocation = false,
                          minUpdateIntervalMillis = 5000,
                      )
                      mainViewModel.locationTracker = LocationTracker(
                          applicationContext,
                          it,
                          config,
                      )
                      mainViewModel.locationTracker?.enableFilter(TimeLocationFilter())
                      mainViewModel.locationTracker?.enableFilter(DistanceLocationFilter())
                  }
              }
          }
      
          private fun checkLocationPermission(context: Context) = ActivityCompat.checkSelfPermission(
              context,
              Manifest.permission.ACCESS_FINE_LOCATION,
          ) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(
              context,
              Manifest.permission.ACCESS_COARSE_LOCATION,
          ) != PackageManager.PERMISSION_GRANTED
      
          override fun onMapReady(map: MapLibreMap) {
              map.setStyle(
                  Style.Builder()
                      .fromUri(
                          "https://maps.geo.$region.amazonaws.com/maps/v0/maps/$mapName/style-descriptor?key=$apiKey"
                      ),
              ) {
                  mainViewModel.mapLibreMap = map
                  map.uiSettings.isAttributionEnabled = true
                  map.uiSettings.isLogoEnabled = false
                  map.uiSettings.attributionGravity = Gravity.BOTTOM or Gravity.END
                  val initialPosition = LatLng(47.6160281982247, -122.32642111977668)
                  map.cameraPosition = CameraPosition.Builder()
                      .target(initialPosition)
                      .zoom(14.0)
                      .build()
      
                  map.addOnCameraMoveStartedListener(this)
                  map.addOnCameraIdleListener(this)
                  map.cameraPosition.target?.let { latLng ->
                      mainViewModel.reverseGeocode(
                          LatLng(
                              latLng.latitude,
                              latLng.longitude
                          ), apiKey
                      )
                  }
              }
          }
      
          override fun onCameraMoveStarted(p0: Int) {
              mainViewModel.label = ""
              mainViewModel.isLabelAdded = false
          }
      
          override fun onCameraIdle() {
              if (!mainViewModel.isLabelAdded) {
                  mainViewModel.mapLibreMap?.cameraPosition?.target?.let { latLng ->
                      mainViewModel.reverseGeocode(
                          LatLng(
                              latLng.latitude,
                              latLng.longitude
                          ), apiKey
                      )
                  }
              }
          }
      }
      ```

      The above code shows how to create a `LocationTracker` object with `AuthHelper` and how to start and stop tracking with LocationTracker.
      + `authenticateUser()`: This method creates AuthHelper and LocationTracker objects.
      + `onStartStopTrackingClick`: This callback is triggered when the user clicks on the start/stop tracking button, which will start/stop tracking with Tracking SDK.

1. **Create a test route with Android Emulator.**

   1. **Open Emulator** by launching the AVD using Android Studio.

   1. **Open Extended Controls** by clicking on the **More** (three dots) icon in the emulator toolbar.

   1. **Open Location** by selecting **Location** from the sidebar.

   1. **Create route** with GPX data or by clicking on the map and choosing source and destination data.

   1. **Start Simulation** by clicking on **PLAY ROUTE** to begin simulating the GPS route.

   1. **Test Application** by running your application and observing how it handles the simulated route.

This is the full demo of the Android Quick Start application.

## What's next
<a name="qs-android-whats-next"></a>

You have completed the quick start tutorial, and should have an idea of how Amazon Location Service is used to build applications.

The source code for this application is available on [GitHub](https://github.com/aws-geospatial/amazon-location-samples-android/tree/main/quick-start).

To get more out of Amazon Location, you can check out the following resources:
+ Dive deeper into the [concepts of Amazon Location Service](how-it-works.md)
+ Get more information about [how to use Amazon Location features and functionality](using-amazon-location.md)
+ See how to expand on this sample and build more complex applications by looking at [code examples using Amazon Location](samples.md)