Build an android marker app using GeoJSON data

23 mins remaining
Build an android marker app using GeoJSON data
Intermediate
codeKotlin

This tutorial covers a common UX pattern for Android mapping apps, adding map markers from external data using GeoJSON and presenting additional information about the marker on tap with a slide-in UI panel using ModalBottomSheet.

Your final map will include markers over 3 coffee shops in Providence, which you can tap to find out more information about the business:

Prerequisites

To follow along with this guide you'll need:

  • Familiarity with Android development: Some familiarity with android development (Jetpack Compose, Kotlin, and Android Studio)
  • A Mapbox account: Sign up or login to a free account.
  • Android Studio: The latest version of Android Studio and its required dependencies like Gradle.
    • This tutorial was created with the Android mode of the editor. Make sure Android Studio shows Android in the view in your top left corner of your editor, as opposed to Project, Production, etc.
  • Marker Image XML: Download the provided marker XML, which contains a vector-based marker image, like an SVG. Download this XML file by clicking the first download button.
arrow-downDownload XML
  • GeoJSON File: Download the provided GeoJSON file, which includes spatial data for the marker locations and their associated properties like the coffee shop name and address, by clicking the second download button.
arrow-downDownload GeoJSON
Accessing the Full Sample Code

If you wish to copy the full code snippet in its entirety instead of following along with the tutorial, view the full code snippet here.

If you only copy the snippet, there will be two missing dependencies, make sure to download and add the XML and the GeoJSON files from above and add them to your project.

Follow the Maps SDK for Android install guide

Before starting this tutorial, it's recommend following the Get Started with the Maps SDK for Android.

This getting started guide will show you how to configure your credentials, add the Mapbox dependency to your project and add a map to your application.

Once you are able to successfully render a map, you can move onto the next step of this tutorial, Add a marker to your map.

Add a marker to your map

For this step, add a marker to your map by creating a PointAnnotation.

A PointAnnotation is a UI element that is fixed to a location on the map. For this step you will use a vector graphic of a map marker to create a PointAnnotation. To learn more about other types of annotations, see our Annotation Guide.

You can add the marker by following the steps below:

  1. Copy the code snippet below and replace the contents of your MainActivity.kt file (excluding your package import line on line 1).
  2. Download the ic_blue_marker.xml by clicking the blue download button if you haven't already.
arrow-downDownload XML
  1. Add the XML file to the app > res > drawables folder by dragging it in from your file explorer/finder window.
  2. Save your project and run the simulator.

Your should see the map zoomed over a marker in Providence, Rhode Island, USA as shown in the screenshot at the bottom of the page.

Troubleshooting
  • Make sure your don't delete your package import line. Your package import line should be at the top of your MainActivity.kt file and should be written as package com.example.INSERT-PROJECT-NAME-HERE`.
  • If you cannot drag the XML file into editor you can also add the marker file to your project through your file explorer/finder window by going to PROJECTNAME/app/src/main/res/drawables and dragging the file in there.
MainActivity.kt
 
import android.os.Bundle
 
import androidx.activity.ComponentActivity
 
import androidx.activity.compose.setContent
 
import androidx.compose.foundation.layout.fillMaxSize
 
import androidx.compose.ui.Modifier
 
import com.mapbox.geojson.Point
 
import com.mapbox.maps.extension.compose.MapboxMap
 
import com.mapbox.maps.extension.compose.animation.viewport.rememberMapViewportState
 
import com.mapbox.maps.extension.compose.annotation.generated.PointAnnotation
 
import com.mapbox.maps.extension.compose.annotation.rememberIconImage
 
import androidx.compose.ui.res.painterResource
 

 
public class MainActivity : ComponentActivity() {
 
override fun onCreate(savedInstanceState: Bundle?) {
 
super.onCreate(savedInstanceState)
 
setContent {
 

 
val markerImage = rememberIconImage(key = R.drawable.ic_blue_marker, painter = painterResource(R.drawable.ic_blue_marker))
 

 
MapboxMap(
 
Modifier.fillMaxSize(),
 
mapViewportState = rememberMapViewportState {
 
setCameraOptions {
 
zoom(14.0)
 
center(Point.fromLngLat(-71.41547, 41.821369))
 
pitch(0.0)
 
bearing(0.0)
 
}
 
},
 
){
 
PointAnnotation(point = Point.fromLngLat(-71.41547, 41.821369))
 
{
 
iconImage = markerImage
 
}
 
}
 
}
 
}
 
}

Add multiple markers with GeoJSON

Now that you've learned how to add a marker to your map, this step will show you how to use a GeoJSON file to add multiple markers to your map. GeoJSON is a data format that can store information about point-of-interests (POI) such as their coordinates, business name, address, phone number, etc. You can use the coordinates written in the GeoJSON file to determine where our markers will be placed.

To add a GeoJSON file to your project and create markers based on its data, follow these steps:

Step 1: Add the GeoJSON file to your project:

  • Download the GeoJSON file if you haven't already.
arrow-downDownload GeoJSON
  • In Android Studio, right click the app folder and select New > Folder > Assets Folder as seen in the screenshot below.
  • Drag in the GeoJSON file into the assets folder.

Now that the GeoJSON file has been added to your project, you can now add the markers.

Step 2: Read the data from the GeoJSON file and pass this information through to the markers to set their locations:

  • Add the import org.json.JSONObject dependency from the first section of the code snippet.
  • Delete your PointAnnotation code and replace it with the second highlighted code snippet below. This code reads the geojson file, parses it into a FeatureCollection type, then iterates over each Feature with a forEach loop. For each feature, it adds a PointAnnotation to the map.
  • Save your project and run the simulator.

The app should now show 3 markers hovering over three coffee shops in Providence, Rhode Island, as seen in the image at the bottom of the page.

Using Code Snippets

Throughout the rest of this tutorial, each of these code snippets with show hidden lines, when fully expanded can be copied and added to your project if you do not wish to individually update sections of the code.

If you follow this method, make sure your update your package import line at the top of your code each time.

Troubleshooting

If you have difficulty dragging in the GeoJSON file into the Android Studio editor, you can also add it through the finder window, by going to PROJECTFOLDER/app/src/main/assets.

MainActivity.kt
 
import com.mapbox.geojson.FeatureCollection
 
val geoJson = assets.open("coffee_shops.geojson").bufferedReader().use { it.readText() }
 
val featureCollection = FeatureCollection.fromJson(geoJson)
 

 
featureCollection.features()?.forEach { feature ->
 
val geometry = feature.geometry()
 
if (geometry is Point) {
 
val point = geometry
 

 
PointAnnotation(point = point) {
 
iconImage = markerImage
 
}
 
}
 
}

Show a toast on tap

Now that you've spawned markers based on the GeoJSON data, next you'll add tap functionality and grab the properties data about the marker to display the business name of the coffee shop, by using the onClick method:

  1. Add the import android.widget.Toast dependency as seen in the first code snippet section.
  2. In the forEach loop, grab the name property of each coffee shop from its GeoJSON Feature, as seen in the second highlighted section in the code snippet below.
  3. Add code from the third code snippet inside the PointAnnotation function, to add interactionsState.onClicked. This function detects when a user taps on an annotation and allows you to execute some code. In this case, the code will create a Toast to show the tap has successfully occurred and that the associated properties data is available for display.
    • Make sure when you paste the interactionsState.onClicked{ code, the left bracket { is on the same line as interactionsState.onClicked, otherwise it may throw an error.
  4. Save your project and run the simulator.

Now when you click on the markers, the name of the respective coffee shop will appear in the toast at the bottom of the simulator. You can also see this in action in the video below.

MainActivity.kt
 
import android.widget.Toast
 
// Grabs the properties data from each marker and accesses the name of the coffee shop.
 
val properties = feature.properties()
 
val jsonObjectStoreName = properties?.get("name")?.asString
 
interactionsState.onClicked{
 
Toast.makeText(baseContext, "Tapped on $jsonObjectStoreName coffee shop", Toast.LENGTH_SHORT).show()
 
true
 
}

Create a Modal Bottom Sheet

The tap events executing and marker properties data available, the next step is to set up a ModalBottomSheet to appear when a marker is tapped. This UI element is a nice-looking dialog you can use to present data from the tapped feature. For now you can add the ModalBottomSheet with placeholder text so you can focus on getting the open and close logic the way you want it.

  1. Add the ModalBottomSheet dependencies as seen in the first snippet section below.
  2. Add the experimental permission to give our app permission to use ModalBottomSheet, above override fun onCreate as seen in the second snippet section.
    • ModalBottomSheet is an experimental class from Android Studio that may be changed in the future.
  3. Declare sheetState and showBottomSheet at the top of setContent to manage ModalBottomSheet and determine if the sheet should be opened or closed, as seen in the third snippet section.
  4. Replace your Toast code with showBottomSheet = true inside of the interactionsState.onClicked function, as seen in the fourth snippet section.
    • This will tell the sheet to open when the marker is tapped.
  5. After the map declaration, add the if statement as seen in the fifth code snippet section.
    • This will set up ModalBottomSheet with content when it opens, creating a Text element that reads Marker Tapped!.
  6. Save your file and run the simulator.

When you run the simulator, you should be able to click on a marker and have the ModalBottomSheet appear with text that says "Marker Clicked!". You can see this in action in the video at the bottom of the page.

MainActivity.kt
 
import androidx.compose.material3.ExperimentalMaterial3Api
 
import androidx.compose.material3.ModalBottomSheet
 
import androidx.compose.material3.Text
 
import androidx.compose.material3.rememberModalBottomSheetState
 
import androidx.compose.runtime.getValue
 
import androidx.compose.runtime.mutableStateOf
 
import androidx.compose.runtime.remember
 
import androidx.compose.runtime.setValue
 
@OptIn(ExperimentalMaterial3Api::class)
 
val sheetState = rememberModalBottomSheetState()
 
var showBottomSheet by remember { mutableStateOf(false) }
 
showBottomSheet = true
 
// Checks if a marker has been tapped, if so will open the ModalBottomSheet
 
if (showBottomSheet) {
 
ModalBottomSheet(
 
onDismissRequest = {
 
showBottomSheet = false
 
},
 
sheetState = sheetState
 
) {
 
Text("Marker Tapped!")
 
}
 
}

Present marker data in ModalBottomSheet

Now that the ModalBottomSheet appears on click, next you'll add text objects that grab from the marker data and populate the sheet. You'll need to create global variables the ModalBottomSheet can check when it renders and assign those values the relevant data when the marker is clicked.

To create and update these values, follow these steps:

  1. Add the new dependencies from the first section of the code snippet below.
  2. Create global variables for all 3 properties, above public class MainActivity : ComponentActivity() as seen in the second snippet section.
    • This will store our marker values so the text objects inside the ModalBottomSheet can update them properly.
  3. Grab the address and phone number data from the GeoJSON as seen in the third code snippet.
  4. Inside interactionsState.onClicked, update the global variables with the values you grabbed from the GeoJSON as seen in the fourth code snippet.
  5. Replace the Text element in ModalBottomSheet with the code from the last section of the snippet.
    • This will display the name, phone number and address of the coffee shop.
  6. Save your file and run the simulator.

Now, you can click on each of the markers and see their data appear in the ModalBottomSheet as seen in the video at the bottom of the page.

MainActivity.kt
 
import androidx.compose.foundation.layout.Column
 
import androidx.compose.foundation.layout.Spacer
 
import androidx.compose.foundation.layout.fillMaxWidth
 
import androidx.compose.foundation.layout.padding
 
import androidx.compose.ui.Alignment
 
import androidx.compose.ui.unit.dp
 
// Global variables to update ModalBottomSheet with data from GeoJSON when clicking on a marker.
 
val locationName = mutableStateOf("none")
 
val locationAddress = mutableStateOf("none")
 
val locationPhoneNumber = mutableStateOf("none")
 
val jsonObjectAddress = properties?.get("address")?.asString
 
val jsonObjectPhoneNumber = properties?.get("phone")?.asString
 
locationName.value = jsonObjectStoreName.toString()
 
locationAddress.value = jsonObjectAddress.toString()
 
locationPhoneNumber.value = jsonObjectPhoneNumber.toString()
 
// Aligns the contents of the ModalBottomSheet for better readability
 
Column(
 
modifier = Modifier.fillMaxWidth(),
 
horizontalAlignment = Alignment.CenterHorizontally
 
) {
 
// These values update when the marker is selected by querying the GeoJSON
 
// for the related data, and then updating the global variables so the text prints correctly here.
 
Text(locationName.value)
 
Text(locationAddress.value)
 
Text(locationPhoneNumber.value)
 

 
// Adds padding at the bottom of the ModalBottomSheet
 
Spacer(modifier = Modifier.padding(vertical = 50.dp))
 

 
}

Finished Product

You've now made a map with markers using GeoJSON data, and added tap interactivity to display properties to the user using the Mapbox Maps SDK for Android. This interaction is a foundation user experience of map-based apps that present point-based features, and you're ready to move on to more advanced functionality.

If you would like to view the finished code in its entirety, view the Finished Code Snippet at the bottom of the page.

Lastly, if you experienced any issues during the integration, here is a list of common troubleshooting solutions:

Troubleshooting Solutions
  • Make sure your don't delete your package import line when copying code snippets. Your package import line should be at the top of your MainActivity.kt file and should be package com.example.INSERT-PROJECT-NAME-HERE.
  • If you cannot drag a file into editor you can also add the file to your project through your file explorer/finder window and dragging the file in there.
  • If you're experiencing unexpected errors, try these solutions:
    • Click on File > Sync Project with Gradle Files
    • Restarting your editor
    • Updating your editor if you're not on the latest version of Android Studio.

Finished Code Snippet

MainActivity.kt
 
package com.example.geojsonapp
 

 
import android.os.Bundle
 
import androidx.activity.ComponentActivity
 
import androidx.activity.compose.setContent
 
import androidx.compose.foundation.layout.Column
 
import androidx.compose.foundation.layout.Spacer
 
import androidx.compose.foundation.layout.fillMaxSize
 
import androidx.compose.foundation.layout.fillMaxWidth
 
import androidx.compose.foundation.layout.padding
 
import androidx.compose.material3.ExperimentalMaterial3Api
 
import androidx.compose.material3.ModalBottomSheet
 
import androidx.compose.material3.Text
 
import androidx.compose.material3.rememberModalBottomSheetState
 
import androidx.compose.runtime.getValue
 
import androidx.compose.runtime.mutableStateOf
 
import androidx.compose.runtime.remember
 
import androidx.compose.runtime.setValue
 
import androidx.compose.ui.Alignment
 
import androidx.compose.ui.Modifier
 
import androidx.compose.ui.res.painterResource
 
import androidx.compose.ui.unit.dp
 
import com.mapbox.geojson.Point
 
import com.mapbox.maps.extension.compose.MapboxMap
 
import com.mapbox.maps.extension.compose.animation.viewport.rememberMapViewportState
 
import com.mapbox.maps.extension.compose.annotation.generated.PointAnnotation
 
import com.mapbox.maps.extension.compose.annotation.rememberIconImage
 
import com.mapbox.geojson.FeatureCollection
 

 
// Global variables to update ModalBottomSheet with data from GeoJSON when clicking on a marker.
 
val locationName = mutableStateOf("none")
 
val locationAddress = mutableStateOf("none")
 
val locationPhoneNumber = mutableStateOf("none")
 

 
public class MainActivity : ComponentActivity()
 
{
 
// ModalBottomSheet is an experimental class from Material3, this code may be changed in future updates.
 
@OptIn(ExperimentalMaterial3Api::class)
 
override fun onCreate(savedInstanceState: Bundle?) {
 
super.onCreate(savedInstanceState)
 

 
setContent {
 

 
val sheetState = rememberModalBottomSheetState()
 
var showBottomSheet by remember { mutableStateOf(false) }
 

 
// Grabs the image used for the marker
 
val markerImage = rememberIconImage(key = R.drawable.ic_blue_marker, painter = painterResource(R.drawable.ic_blue_marker))
 

 
// Creates a mapbox map in the ContentView
 
MapboxMap(
 
Modifier.fillMaxSize(),
 
mapViewportState = rememberMapViewportState {
 
setCameraOptions {
 
zoom(14.0)
 
center(Point.fromLngLat(-71.41547, 41.821369))
 
pitch(0.0)
 
bearing(0.0)
 
}
 
},
 
) {
 

 
// Grabs the GeoJSON file so its data can be accessed to create the markers
 
val geoJson = assets.open("coffee_shops.geojson").bufferedReader().use { it.readText() }
 
val featureCollection = FeatureCollection.fromJson(geoJson)
 

 
// Iterates through each feature in the FeatureCollection of the GeoJSON, and creates a marker for each.
 
featureCollection.features()?.forEach { feature ->
 
val geometry = feature.geometry()
 
if (geometry is Point) {
 
val point = geometry
 

 
// Grabs json data from the GeoJSON file and create jsonObject or jsonArray to access the values
 
val properties = feature.properties()
 
val jsonObjectStoreName = properties?.get("name")?.asString
 
val jsonObjectAddress = properties?.get("address")?.asString
 
val jsonObjectPhoneNumber = properties?.get("phone")?.asString
 

 
// Creates a PointAnnotation to act as the marker for each business.
 
PointAnnotation(point = point){
 

 
//Sets the marker image
 
iconImage = markerImage
 

 
// When a marker is tapped:
 
// - The data about the tapped marker is grabbed from the GeoJSON file
 
// - The data is then assigned to the global variables which overwrites the text objects in the ModalBottomSheet
 
interactionsState.onClicked {
 
locationName.value = jsonObjectStoreName.toString()
 
locationAddress.value = jsonObjectAddress.toString()
 
locationPhoneNumber.value = jsonObjectPhoneNumber.toString()
 
showBottomSheet = true
 
true
 
}
 
}
 
}
 
}
 
}
 

 
// Checks if a marker has been tapped, if so will open the ModalBottomSheet
 
if (showBottomSheet)
 
{
 
ModalBottomSheet(
 
onDismissRequest = {
 
showBottomSheet = false
 
},
 
sheetState = sheetState
 
){
 
// Contents of the ModalBottomSheet
 

 
// Aligns the contents of the ModalBottomSheet for better readability
 
Column(
 
modifier = Modifier.fillMaxWidth(),
 
horizontalAlignment = Alignment.CenterHorizontally
 
){
 
// These values update when the marker is selected by querying the GeoJSON
 
// for the related data, and then updating the global variables so the text prints correctly here.
 
Text(locationName.value)
 
Text(locationAddress.value)
 
Text(locationPhoneNumber.value)
 

 
// Adds padding at the bottom of the ModalBottomSheet
 
Spacer(modifier = Modifier.padding(vertical = 50.dp))
 

 
}
 
}
 
}
 
}
 
}
 
}

Next steps

Now that you've finished this tutorial, here are some other relevant resources:

Here are some ideas for improving this project if you would like to continue working on it:

  • Swap out the sample dataset for your own GeoJSON file. You can use geojson.io to create and edit your own GeoJSON file.
  • Adjust the map's initial camera view to show a different location.
  • Add a title panel to the app or other UI elements to make it a more polished product.
  • Improve the layout and styling of the data showing in the ModalBottomSheet to make it more visually appealing.
  • Instead of using a local GeoJSON file, load one via a network request.
Was this tutorial helpful?