> For the complete documentation index, see [llms.txt](https://docs.mapbox.com/help/ja/llms.txt)

# Implement Geofencing in an Android App

This tutorial teaches you how to implement geofencing in an Android app using the Mapbox Maps SDK for Android. Geofencing is a location-based service that allows you to define a virtual fence around a real-world geographic area. When a device enters, dwells in, or exits the perimeter, the app can trigger a notification or do other actions.

Your browser doesn't support HTML5 video. Open [link to the video](https://docs.mapbox.com/help/ja/help/ja/assets/medias/tutorials--android-geofencing-complete-7e17a4d4e85972126688c3206e3d6785.webm).

## Prerequisites

Before you begin, you will need:

-   **Mapbox account**: [Sign up](https://account.mapbox.com/auth/signup/) for free or [log in](https://console.mapbox.com/) if you already have an account.
-   **A project with the Maps SDK installed**: Follow the [Getting Started with the Maps SDK for Android](https://docs.mapbox.com/android/maps/guides/install/) guide.
-   **Familiarity with Android development**: Beginner experience with Jetpack Compose, Kotlin, and Android Studio.

This tutorial assumes you have a basic understanding of Kotlin and Android development and requires you to have setup the **Mapbox Maps SDK for Android** for a Jetpack Compose project as covered in our [Getting Started with Mapbox on Android](https://docs.mapbox.com/android/maps/guides/install/) guide. That guide walks you through adding the **Mapbox Maps SDK for Android** dependency (version 11.11.0 and later to use geofencing) and adding your public access token to your project's `mapbox_access_token.xml` file.

The Getting Started guide should leave you with a new Compose project open in Android with a globe displayed.

![Screenshot of an Android app showing a globe.](https://docs.mapbox.com/help/ja/assets/ideal-img/tutorials--android-geofencing-1.8df3c0d.480.png)

Your `MainActivity.kt` file should look like this:

```kotlin
package com.example.codelabsexample // replace with your package name

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

public class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MapboxMap(
                Modifier.fillMaxSize(),
                mapViewportState = rememberMapViewportState {
                    setCameraOptions {
                        zoom(2.0)
                        center(Point.fromLngLat(-98.0, 39.5))
                        pitch(0.0)
                        bearing(0.0)
                    }
                },
            )
        }
    }
}
```

## Add geofence data to your app

To set up geofences you need to add geographic data that defines the regions you want to watch. For this tutorial, we will use a GeoJSON file that defines several polygons in [Yellowstone National Park](https://en.wikipedia.org/wiki/Yellowstone_National_Park). In this section we add the GeoJSON file to our project and use it to visualize the polygons on the map using [GeoJSONData](https://docs.mapbox.com/android/maps/api/latest/mapbox-maps-android/com.mapbox.maps.extension.compose.style.sources/-geo-j-s-o-n-data/) and a [FillLayer](https://docs.mapbox.com/android/maps/api/latest/mapbox-maps-android/com.mapbox.maps.extension.style.layers.generated/-fill-layer/).

### Download the GeoJSON file and add it to your project

Download the Yellowstone National Park GeoJSON file and add it to your project as `yellowstone.geojson`. To add the file to your project, in the directory view right-click on `app` then select `New` > `Folder` > `Assets Folder`. Locate the `yellowstone.geojson` file on your computer. Copy that file and then paste it in the newly-created `assets` folder.

[Download GeoJSON](https://docs.mapbox.com/help/ja/help/ja/data/yellowstone.geojson)

### Display the GeoJSON polygons on the map

To start, adjust the initial camera viewport of the map to center on Yellowstone National Park (latitude: 44.5979, longitude: -110.6123) and zoom to level 9. Next add a [FillLayer](https://docs.mapbox.com/android/maps/api/latest/mapbox-maps-android/com.mapbox.maps.extension.compose.style.layers.generated/-fill-layer.html) to render the polygons on the map and set the [sourceState](https://docs.mapbox.com/android/maps/api/latest/mapbox-maps-android/com.mapbox.maps.extension.compose.style.sources/-source-state/) of the `FillLayer` to use [GeoJSONData](https://docs.mapbox.com/android/maps/api/latest/mapbox-maps-android/com.mapbox.maps.extension.compose.style.sources/-geo-j-s-o-n-data/) loaded from the `yellowstone.geojson` file. The code in your `setContent` block should look like the snippet below. With these updates you will need to import a few new classes. See the hidden lines below for a list of all the imports you need to add.

```kotlin
package com.example.codelabsexample

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.ui.Modifier
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.style.layers.generated.FillLayer
import com.mapbox.maps.extension.compose.style.sources.GeoJSONData
import com.mapbox.maps.extension.compose.style.sources.generated.rememberGeoJsonSourceState

public class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            val mapViewportState = rememberMapViewportState {
                setCameraOptions {
                    zoom(9.0)
                    center(Point.fromLngLat(-110.6123, 44.5979))
                    pitch(0.0)
                    bearing(0.0)
                }
            }
            MapboxMap(
                Modifier.fillMaxSize().padding(top = 20.dp),
                mapViewportState = mapViewportState
            ) {
                FillLayer(
                    sourceState = rememberGeoJsonSourceState {
                        data = GeoJSONData("asset://yellowstone.geojson")
                    }
                )
            }
        }
    }
}
```

We will adjust the styling later. For now, you should see a few black polygons rendered on the map.

![Screenshot of an Android app showing a map of Yellowstone National Park with black polygons.](https://docs.mapbox.com/help/ja/assets/ideal-img/tutorials--android-geofencing-2.80dc7ca.480.png)

## Show user location on the map

This step will allow your app to show the user's location on the map. To do this, you need to request location permissions and add a puck to the map to show the user's location. You do not need to show a user puck to use geofencing, but it is helpful in this example to see the user's location in the geofenced areas. You do need to request location permissions to use geofencing and show the user's location on the map in all circumstances.

### Enable location permissions

To use geofencing you need to request user's permission to access their precise location. Review our [User Location](https://docs.mapbox.com/android/maps/guides/user-location/) guide for complete information on requesting location permissions, responding to changes in location permission authorization, and displaying a user puck. For this tutorial, add the following code to your `AndroidManifest.xml` file to request location permissions.

```xml
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
```

### Request a user's location permission and show a user puck

You have now enabled your app to request location permissions. The next step is to ask the user to share their location when the application starts. To do this, add the `locationPermissions` variable to the end of your `MainActivity.kt` file. This variable contains the location permissions that we will request from the user.

Then use a `LaunchedEffect` to check and request location permissions. Add the launcher code to your `setContent { ... }` block below the `MapboxMap`. This code checks if the app has been granted location permissions. If not, it launches a permission request dialog for the user to accept or deny the permissions.

Finally, add a `MapEffect` inside the `MapboxMap` code to show the user's location on the map. The `MapEffect` updates the map's location puck settings to show the user's location. You will again need to import a few new classes. See the hidden lines below for a list of all the imports you need to add.

```kotlin
package com.example.codelabsexample

import android.content.pm.PackageManager
import android.os.Bundle
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.compose.setContent
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.core.content.ContextCompat
import com.mapbox.geojson.Point
import com.mapbox.maps.extension.compose.MapEffect
import com.mapbox.maps.extension.compose.MapboxMap
import com.mapbox.maps.extension.compose.animation.viewport.rememberMapViewportState
import com.mapbox.maps.extension.compose.style.layers.generated.FillLayer
import com.mapbox.maps.extension.compose.style.sources.GeoJSONData
import com.mapbox.maps.extension.compose.style.sources.generated.rememberGeoJsonSourceState
import com.mapbox.maps.plugin.locationcomponent.createDefault2DPuck
import com.mapbox.maps.plugin.locationcomponent.location

public class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            val mapViewportState = rememberMapViewportState {
                setCameraOptions {
                    zoom(9.0)
                    center(Point.fromLngLat(-110.6123, 44.5979))
                    pitch(0.0)
                    bearing(0.0)
                }
            }
            MapboxMap(
                Modifier.fillMaxSize().padding(top = 20.dp),
                mapViewportState = mapViewportState
            ) {
                FillLayer(
                    sourceState = rememberGeoJsonSourceState {
                        data = GeoJSONData("asset://yellowstone.geojson")
                    }
                )
                MapEffect(Unit) { mapView ->
                    mapView.location.updateSettings {
                        locationPuck = createDefault2DPuck(withBearing = true)
                        enabled = true
                    }
                }
            }
            val launcher = rememberLauncherForActivityResult(
                contract = ActivityResultContracts.RequestMultiplePermissions(),
            ) { permissionsMap ->
                val granted = permissionsMap.values.all { it }
                if (granted) {
                    // startGeofencing() you will write this function later
                } else {
                    Toast.makeText(
                        this@MainActivity,
                        "You need to accept location permissions for geofencing to function.",
                        Toast.LENGTH_SHORT
                    ).show()
                }
            }
            LaunchedEffect(Unit) {
                if (locationPermissions.all {
                        ContextCompat.checkSelfPermission(
                            this@MainActivity,
                            it
                        ) == PackageManager.PERMISSION_GRANTED
                    }
                ) {
                    // startGeofencing() you will write this function later
                } else {
                    launcher.launch(locationPermissions)
                }
            }
        }
    }
}

private val locationPermissions = arrayOf(
    android.Manifest.permission.ACCESS_FINE_LOCATION,
    android.Manifest.permission.ACCESS_COARSE_LOCATION
)
```

Now, that we've added a puck, you need to simulate the user's location in the Android Studio emulator to see the puck and to test the geofencing functionality. When your emulator is running select the "Extended Controls" button (three dots) in the emulator toolbar. In the "Location" tab, you can enter a latitude and longitude to simulate the user's location. For now, set the location to the center of Yellowstone National Park (latitude: 44.5979, longitude: -110.6123). Now, if you run the app, you should see a blue puck on the map showing the user's location.

![Screenshot of an Android app showing a map of Yellowstone National Park with a blue puck showing the user's location.](https://docs.mapbox.com/help/ja/assets/ideal-img/tutorials--android-geofencing-3.350d2ba.480.png)

## Watch geofence events

Now that you have added geographic data to your app and are showing the user's location on the map, you can add geofences and watch when the user [enters](https://docs.mapbox.com/android/maps/api/latest/mapbox-maps-android/com.mapbox.common.geofencing/-geofencing-observer/on-entry.html), [dwells in](https://docs.mapbox.com/android/maps/api/latest/mapbox-maps-android/com.mapbox.common.geofencing/-geofencing-observer/on-dwell.html), or [exits](https://docs.mapbox.com/android/maps/api/latest/mapbox-maps-android/com.mapbox.common.geofencing/-geofencing-observer/on-exit.html) the geofence.

### Add geofences to the map

To start adding geofences to your map, we first need to create the Geofencing service with the `GeofencingFactory`. We can then access the Geofencing service with `geofencing` variable. The Geofencing service handles managing geofences and notifying the app when the user enters, dwells in, or exits a geofence. Add the below code to your `MainActivity` class above the `onCreate` function. You will need to opt in to the `MapboxExperimental` annotation to use the Geofencing service.

```kotlin
package com.example.codelabsexample

import android.content.Context
import android.content.pm.PackageManager
import android.os.Bundle
import android.util.Log
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.compose.setContent
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.core.content.ContextCompat
import com.google.gson.JsonPrimitive
import com.mapbox.annotation.MapboxExperimental
import com.mapbox.common.geofencing.GeofencingError
import com.mapbox.common.geofencing.GeofencingEvent
import com.mapbox.common.geofencing.GeofencingFactory
import com.mapbox.common.geofencing.GeofencingObserver
import com.mapbox.common.geofencing.GeofencingPropertiesKeys
import com.mapbox.geojson.FeatureCollection
import com.mapbox.geojson.Point
import com.mapbox.maps.extension.compose.MapEffect
import com.mapbox.maps.extension.compose.MapboxMap
import com.mapbox.maps.extension.compose.animation.viewport.rememberMapViewportState
import com.mapbox.maps.extension.compose.style.layers.generated.FillLayer
import com.mapbox.maps.extension.compose.style.sources.GeoJSONData
import com.mapbox.maps.extension.compose.style.sources.generated.rememberGeoJsonSourceState
import com.mapbox.maps.plugin.locationcomponent.createDefault2DPuck
import com.mapbox.maps.plugin.locationcomponent.location
import java.io.IOException

@OptIn(MapboxExperimental::class)
public class MainActivity : ComponentActivity() {
    private val geofencing by lazy {
        GeofencingFactory.getOrCreate()
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            val mapViewportState = rememberMapViewportState {
                setCameraOptions {
                    zoom(9.0)
                    center(Point.fromLngLat(-110.6123, 44.5979))
                    pitch(0.0)
                    bearing(0.0)
                }
            }
            MapboxMap(
                Modifier.fillMaxSize().padding(top = 20.dp),
                mapViewportState = mapViewportState
            ) {
                FillLayer(
                    sourceState = rememberGeoJsonSourceState {
                        data = GeoJSONData("asset://yellowstone.geojson")
                    }
                )
                MapEffect(Unit) { mapView ->
                    mapView.location.updateSettings {
                        locationPuck = createDefault2DPuck(withBearing = true)
                        enabled = true
                    }
                }
            }
            val launcher = rememberLauncherForActivityResult(
                contract = ActivityResultContracts.RequestMultiplePermissions(),
            ) { permissionsMap ->
                val granted = permissionsMap.values.all { it }
                if (granted) {
                    startGeofencing()
                } else {
                    Toast.makeText(
                        this@MainActivity,
                        "You need to accept location permissions for geofencing to function.",
                        Toast.LENGTH_SHORT
                    ).show()
                }
            }
            LaunchedEffect(Unit) {
                if (locationPermissions.all {
                        ContextCompat.checkSelfPermission(
                            this@MainActivity,
                            it
                        ) == PackageManager.PERMISSION_GRANTED
                    }
                ) {
                    startGeofencing()
                } else {
                    launcher.launch(locationPermissions)
                }
            }
        }
    }

    private val observer: GeofencingObserver = object : GeofencingObserver {
        override fun onEntry(event: GeofencingEvent) {
            Log.d("YellowstoneApp", "onEntry() called with: feature id = ${event.feature.id()} at ${event.timestamp}")
        }

        override fun onDwell(event: GeofencingEvent) {
            Log.d("YellowstoneApp", "onDwell() called with: feature id = ${event.feature.id()} at ${event.timestamp}")
        }

        override fun onExit(event: GeofencingEvent) {
            Log.d("YellowstoneApp", "onExit() called with: feature id = ${event.feature.id()} at ${event.timestamp}")
        }

        override fun onError(error: GeofencingError) {
            Log.d("YellowstoneApp", "onError() called with: error = $error")
        }

        override fun onUserConsentChanged(isConsentGiven: Boolean) {
            Log.d("YellowstoneApp", "onUserConsentChanged() called with: isConsentGiven = $isConsentGiven")
        }
    }

    private fun startGeofencing() {
        Log.d("YellowstoneApp", "startGeofencing")
        /// Geofences are stored in database on disk.
        /// To make this example isolated and synchronized with the UI we delete existing feature from database.
        geofencing.clearFeatures {
            geofencing.addObserver(observer) { geofenceError ->
                Log.d("YellowstoneApp", "geofence.addObserver() error $geofenceError")
            }
        }
        decodeGeoJSON(this@MainActivity, "yellowstone.geojson")?.let { featureCollection ->
            featureCollection.features()?.forEach { feature ->
                // To receive dwell events we need to set a time.
                // After a user has spent that amount of time in the geofence
                // the Geofencing service will send a dwell event
                feature.addProperty(GeofencingPropertiesKeys.DWELL_TIME_KEY, JsonPrimitive(1)) // minutes
                geofencing.addFeature(feature) {}
            }
        }
    }
}

fun decodeGeoJSON(context: Context, fileName: String): FeatureCollection? = try {
    val json = context.assets.open(fileName).bufferedReader().use { it.readText() }
    FeatureCollection.fromJson(json)
} catch (e: IOException) {
    Log.e("YellowstoneApp", "Unable to parse $fileName")
    null
}

private val locationPermissions = arrayOf(
    android.Manifest.permission.ACCESS_FINE_LOCATION,
    android.Manifest.permission.ACCESS_COARSE_LOCATION
)
```

We will then add an `observer` which conforms to [GeofencingObserver](https://docs.mapbox.com/android/maps/api/latest/mapbox-maps-android/com.mapbox.common.geofencing/-geofencing-observer/) to watch for geofence events. The Geofencing service will call the observer when the user enters, dwells in, or exits a geofence. Further, we will add a function to start geofencing by adding an observer to the Geofencing service. Add the following code to your `MainActivity` class towards the bottom. Now that you've written the `startGeofencing()` function you can replace the `// startGeofencing()` comment in the `LaunchedEffect` block with a call to `startGeofencing()`.

```kotlin
package com.example.codelabsexample

import android.content.Context
import android.content.pm.PackageManager
import android.os.Bundle
import android.util.Log
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.compose.setContent
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.core.content.ContextCompat
import com.google.gson.JsonPrimitive
import com.mapbox.annotation.MapboxExperimental
import com.mapbox.common.geofencing.GeofencingError
import com.mapbox.common.geofencing.GeofencingEvent
import com.mapbox.common.geofencing.GeofencingFactory
import com.mapbox.common.geofencing.GeofencingObserver
import com.mapbox.common.geofencing.GeofencingPropertiesKeys
import com.mapbox.geojson.FeatureCollection
import com.mapbox.geojson.Point
import com.mapbox.maps.extension.compose.MapEffect
import com.mapbox.maps.extension.compose.MapboxMap
import com.mapbox.maps.extension.compose.animation.viewport.rememberMapViewportState
import com.mapbox.maps.extension.compose.style.layers.generated.FillLayer
import com.mapbox.maps.extension.compose.style.sources.GeoJSONData
import com.mapbox.maps.extension.compose.style.sources.generated.rememberGeoJsonSourceState
import com.mapbox.maps.plugin.locationcomponent.createDefault2DPuck
import com.mapbox.maps.plugin.locationcomponent.location
import java.io.IOException

@OptIn(MapboxExperimental::class)
public class MainActivity : ComponentActivity() {
    private val geofencing by lazy {
        GeofencingFactory.getOrCreate()
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            val mapViewportState = rememberMapViewportState {
                setCameraOptions {
                    zoom(9.0)
                    center(Point.fromLngLat(-110.6123, 44.5979))
                    pitch(0.0)
                    bearing(0.0)
                }
            }
            MapboxMap(
                Modifier.fillMaxSize().padding(top = 20.dp),
                mapViewportState = mapViewportState
            ) {
                FillLayer(
                    sourceState = rememberGeoJsonSourceState {
                        data = GeoJSONData("asset://yellowstone.geojson")
                    }
                )
                MapEffect(Unit) { mapView ->
                    mapView.location.updateSettings {
                        locationPuck = createDefault2DPuck(withBearing = true)
                        enabled = true
                    }
                }
            }
            val launcher = rememberLauncherForActivityResult(
                contract = ActivityResultContracts.RequestMultiplePermissions(),
            ) { permissionsMap ->
                val granted = permissionsMap.values.all { it }
                if (granted) {
                    startGeofencing()
                } else {
                    Toast.makeText(
                        this@MainActivity,
                        "You need to accept location permissions for geofencing to function.",
                        Toast.LENGTH_SHORT
                    ).show()
                }
            }
            LaunchedEffect(Unit) {
                if (locationPermissions.all {
                        ContextCompat.checkSelfPermission(
                            this@MainActivity,
                            it
                        ) == PackageManager.PERMISSION_GRANTED
                    }
                ) {
                    startGeofencing()
                } else {
                    launcher.launch(locationPermissions)
                }
            }
        }
    }

    private val observer: GeofencingObserver = object : GeofencingObserver {
        override fun onEntry(event: GeofencingEvent) {
            Log.d("YellowstoneApp", "onEntry() called with: feature id = ${event.feature.id()} at ${event.timestamp}")
        }

        override fun onDwell(event: GeofencingEvent) {
            Log.d("YellowstoneApp", "onDwell() called with: feature id = ${event.feature.id()} at ${event.timestamp}")
        }

        override fun onExit(event: GeofencingEvent) {
            Log.d("YellowstoneApp", "onExit() called with: feature id = ${event.feature.id()} at ${event.timestamp}")
        }

        override fun onError(error: GeofencingError) {
            Log.d("YellowstoneApp", "onError() called with: error = $error")
        }

        override fun onUserConsentChanged(isConsentGiven: Boolean) {
            Log.d("YellowstoneApp", "onUserConsentChanged() called with: isConsentGiven = $isConsentGiven")
        }
    }

    private fun startGeofencing() {
        Log.d("YellowstoneApp", "startGeofencing")
        /// Geofences are stored in database on disk.
        /// To make this example isolated and synchronized with the UI we delete existing feature from database.
        geofencing.clearFeatures {
            geofencing.addObserver(observer) { geofenceError ->
                Log.d("YellowstoneApp", "geofence.addObserver() error $geofenceError")
            }
        }
        decodeGeoJSON(this@MainActivity, "yellowstone.geojson")?.let { featureCollection ->
            featureCollection.features()?.forEach { feature ->
                // To receive dwell events we need to set a time.
                // After a user has spent that amount of time in the geofence
                // the Geofencing service will send a dwell event
                feature.addProperty(GeofencingPropertiesKeys.DWELL_TIME_KEY, JsonPrimitive(1)) // minutes
                geofencing.addFeature(feature) {}
            }
        }
    }
}

fun decodeGeoJSON(context: Context, fileName: String): FeatureCollection? = try {
    val json = context.assets.open(fileName).bufferedReader().use { it.readText() }
    FeatureCollection.fromJson(json)
} catch (e: IOException) {
    Log.e("YellowstoneApp", "Unable to parse $fileName")
    null
}

private val locationPermissions = arrayOf(
    android.Manifest.permission.ACCESS_FINE_LOCATION,
    android.Manifest.permission.ACCESS_COARSE_LOCATION
)
```

### Add geofences to the Geofencing service

Now that we have the Geofencing service and observer set up, we can add geofences to the Geofencing service. We will start by writing a helper function to decode our GeoJSON file into a `FeatureCollection`. The `FeatureCollection` will contain the geographic data that defines the geofences. Add the following code `decodeGeoJSON(context:, fileName:)` function to your `MainActivity.kt` file towards the bottom.

```kotlin
package com.example.codelabsexample

import android.content.Context
import android.content.pm.PackageManager
import android.os.Bundle
import android.util.Log
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.compose.setContent
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.core.content.ContextCompat
import com.google.gson.JsonPrimitive
import com.mapbox.annotation.MapboxExperimental
import com.mapbox.common.geofencing.GeofencingError
import com.mapbox.common.geofencing.GeofencingEvent
import com.mapbox.common.geofencing.GeofencingFactory
import com.mapbox.common.geofencing.GeofencingObserver
import com.mapbox.common.geofencing.GeofencingPropertiesKeys
import com.mapbox.geojson.FeatureCollection
import com.mapbox.geojson.Point
import com.mapbox.maps.extension.compose.MapEffect
import com.mapbox.maps.extension.compose.MapboxMap
import com.mapbox.maps.extension.compose.animation.viewport.rememberMapViewportState
import com.mapbox.maps.extension.compose.style.layers.generated.FillLayer
import com.mapbox.maps.extension.compose.style.sources.GeoJSONData
import com.mapbox.maps.extension.compose.style.sources.generated.rememberGeoJsonSourceState
import com.mapbox.maps.plugin.locationcomponent.createDefault2DPuck
import com.mapbox.maps.plugin.locationcomponent.location
import java.io.IOException

@OptIn(MapboxExperimental::class)
public class MainActivity : ComponentActivity() {
    private val geofencing by lazy {
        GeofencingFactory.getOrCreate()
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            val mapViewportState = rememberMapViewportState {
                setCameraOptions {
                    zoom(9.0)
                    center(Point.fromLngLat(-110.6123, 44.5979))
                    pitch(0.0)
                    bearing(0.0)
                }
            }
            MapboxMap(
                Modifier.fillMaxSize().padding(top = 20.dp),
                mapViewportState = mapViewportState
            ) {
                FillLayer(
                    sourceState = rememberGeoJsonSourceState {
                        data = GeoJSONData("asset://yellowstone.geojson")
                    }
                )
                MapEffect(Unit) { mapView ->
                    mapView.location.updateSettings {
                        locationPuck = createDefault2DPuck(withBearing = true)
                        enabled = true
                    }
                }
            }
            val launcher = rememberLauncherForActivityResult(
                contract = ActivityResultContracts.RequestMultiplePermissions(),
            ) { permissionsMap ->
                val granted = permissionsMap.values.all { it }
                if (granted) {
                    startGeofencing()
                } else {
                    Toast.makeText(
                        this@MainActivity,
                        "You need to accept location permissions for geofencing to function.",
                        Toast.LENGTH_SHORT
                    ).show()
                }
            }
            LaunchedEffect(Unit) {
                if (locationPermissions.all {
                        ContextCompat.checkSelfPermission(
                            this@MainActivity,
                            it
                        ) == PackageManager.PERMISSION_GRANTED
                    }
                ) {
                    startGeofencing()
                } else {
                    launcher.launch(locationPermissions)
                }
            }
        }
    }

    private val observer: GeofencingObserver = object : GeofencingObserver {
        override fun onEntry(event: GeofencingEvent) {
            Log.d("YellowstoneApp", "onEntry() called with: feature id = ${event.feature.id()} at ${event.timestamp}")
        }

        override fun onDwell(event: GeofencingEvent) {
            Log.d("YellowstoneApp", "onDwell() called with: feature id = ${event.feature.id()} at ${event.timestamp}")
        }

        override fun onExit(event: GeofencingEvent) {
            Log.d("YellowstoneApp", "onExit() called with: feature id = ${event.feature.id()} at ${event.timestamp}")
        }

        override fun onError(error: GeofencingError) {
            Log.d("YellowstoneApp", "onError() called with: error = $error")
        }

        override fun onUserConsentChanged(isConsentGiven: Boolean) {
            Log.d("YellowstoneApp", "onUserConsentChanged() called with: isConsentGiven = $isConsentGiven")
        }
    }

    private fun startGeofencing() {
        Log.d("YellowstoneApp", "startGeofencing")
        /// Geofences are stored in database on disk.
        /// To make this example isolated and synchronized with the UI we delete existing feature from database.
        geofencing.clearFeatures {
            geofencing.addObserver(observer) { geofenceError ->
                Log.d("YellowstoneApp", "geofence.addObserver() error $geofenceError")
            }
        }
        decodeGeoJSON(this@MainActivity, "yellowstone.geojson")?.let { featureCollection ->
            featureCollection.features()?.forEach { feature ->
                // To receive dwell events we need to set a time.
                // After a user has spent that amount of time in the geofence
                // the Geofencing service will send a dwell event
                feature.addProperty(GeofencingPropertiesKeys.DWELL_TIME_KEY, JsonPrimitive(1)) // minutes
                geofencing.addFeature(feature) {}
            }
        }
    }
}

fun decodeGeoJSON(context: Context, fileName: String): FeatureCollection? = try {
    val json = context.assets.open(fileName).bufferedReader().use { it.readText() }
    FeatureCollection.fromJson(json)
} catch (e: IOException) {
    Log.e("YellowstoneApp", "Unable to parse $fileName")
    null
}

private val locationPermissions = arrayOf(
    android.Manifest.permission.ACCESS_FINE_LOCATION,
    android.Manifest.permission.ACCESS_COARSE_LOCATION
)
```

Now, we can call the `decodeGeoJSON` function to decode the `yellowstone.geojson` file into a `FeatureCollection`. We will then iterate over the features in the `FeatureCollection` and add them to the Geofencing service. The Geofencing service will watch those features and trigger events when the user enters, dwells in, or exits the geofenced area. To receive dwell events, we need to set a time using the [GeofencingPropertiesKeys.dwellTimeKey](https://docs.mapbox.com/android/maps/api/latest/mapbox-maps-android/com.mapbox.common.geofencing/-geofencing-properties-keys/?query=object%20GeofencingPropertiesKeys/) property on each feature. After a user has spent that amount of time in the geofence, the Geofencing service will send a dwell event. Add the below code to your `startGeofencing()` function, which should look like this:

```kotlin
package com.example.codelabsexample

import android.content.Context
import android.content.pm.PackageManager
import android.os.Bundle
import android.util.Log
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.compose.setContent
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.core.content.ContextCompat
import com.google.gson.JsonPrimitive
import com.mapbox.annotation.MapboxExperimental
import com.mapbox.common.geofencing.GeofencingError
import com.mapbox.common.geofencing.GeofencingEvent
import com.mapbox.common.geofencing.GeofencingFactory
import com.mapbox.common.geofencing.GeofencingObserver
import com.mapbox.common.geofencing.GeofencingPropertiesKeys
import com.mapbox.geojson.FeatureCollection
import com.mapbox.geojson.Point
import com.mapbox.maps.extension.compose.MapEffect
import com.mapbox.maps.extension.compose.MapboxMap
import com.mapbox.maps.extension.compose.animation.viewport.rememberMapViewportState
import com.mapbox.maps.extension.compose.style.layers.generated.FillLayer
import com.mapbox.maps.extension.compose.style.sources.GeoJSONData
import com.mapbox.maps.extension.compose.style.sources.generated.rememberGeoJsonSourceState
import com.mapbox.maps.plugin.locationcomponent.createDefault2DPuck
import com.mapbox.maps.plugin.locationcomponent.location
import java.io.IOException

@OptIn(MapboxExperimental::class)
public class MainActivity : ComponentActivity() {
    private val geofencing by lazy {
        GeofencingFactory.getOrCreate()
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            val mapViewportState = rememberMapViewportState {
                setCameraOptions {
                    zoom(9.0)
                    center(Point.fromLngLat(-110.6123, 44.5979))
                    pitch(0.0)
                    bearing(0.0)
                }
            }
            MapboxMap(
                Modifier.fillMaxSize().padding(top = 20.dp),
                mapViewportState = mapViewportState
            ) {
                FillLayer(
                    sourceState = rememberGeoJsonSourceState {
                        data = GeoJSONData("asset://yellowstone.geojson")
                    }
                )
                MapEffect(Unit) { mapView ->
                    mapView.location.updateSettings {
                        locationPuck = createDefault2DPuck(withBearing = true)
                        enabled = true
                    }
                }
            }
            val launcher = rememberLauncherForActivityResult(
                contract = ActivityResultContracts.RequestMultiplePermissions(),
            ) { permissionsMap ->
                val granted = permissionsMap.values.all { it }
                if (granted) {
                    startGeofencing()
                } else {
                    Toast.makeText(
                        this@MainActivity,
                        "You need to accept location permissions for geofencing to function.",
                        Toast.LENGTH_SHORT
                    ).show()
                }
            }
            LaunchedEffect(Unit) {
                if (locationPermissions.all {
                        ContextCompat.checkSelfPermission(
                            this@MainActivity,
                            it
                        ) == PackageManager.PERMISSION_GRANTED
                    }
                ) {
                    startGeofencing()
                } else {
                    launcher.launch(locationPermissions)
                }
            }
        }
    }

    private val observer: GeofencingObserver = object : GeofencingObserver {
        override fun onEntry(event: GeofencingEvent) {
            Log.d("YellowstoneApp", "onEntry() called with: feature id = ${event.feature.id()} at ${event.timestamp}")
        }

        override fun onDwell(event: GeofencingEvent) {
            Log.d("YellowstoneApp", "onDwell() called with: feature id = ${event.feature.id()} at ${event.timestamp}")
        }

        override fun onExit(event: GeofencingEvent) {
            Log.d("YellowstoneApp", "onExit() called with: feature id = ${event.feature.id()} at ${event.timestamp}")
        }

        override fun onError(error: GeofencingError) {
            Log.d("YellowstoneApp", "onError() called with: error = $error")
        }

        override fun onUserConsentChanged(isConsentGiven: Boolean) {
            Log.d("YellowstoneApp", "onUserConsentChanged() called with: isConsentGiven = $isConsentGiven")
        }
    }

    private fun startGeofencing() {
        Log.d("YellowstoneApp", "startGeofencing")
        /// Geofences are stored in database on disk.
        /// To make this example isolated and synchronized with the UI we delete existing feature from database.
        geofencing.clearFeatures {
            geofencing.addObserver(observer) { geofenceError ->
                Log.d("YellowstoneApp", "geofence.addObserver() error $geofenceError")
            }
        }
        decodeGeoJSON(this@MainActivity, "yellowstone.geojson")?.let { featureCollection ->
            featureCollection.features()?.forEach { feature ->
                // To receive dwell events we need to set a time.
                // After a user has spent that amount of time in the geofence
                // the Geofencing service will send a dwell event
                feature.addProperty(GeofencingPropertiesKeys.DWELL_TIME_KEY, JsonPrimitive(1)) // minutes
                geofencing.addFeature(feature) {}
            }
        }
    }
}

fun decodeGeoJSON(context: Context, fileName: String): FeatureCollection? = try {
    val json = context.assets.open(fileName).bufferedReader().use { it.readText() }
    FeatureCollection.fromJson(json)
} catch (e: IOException) {
    Log.e("YellowstoneApp", "Unable to parse $fileName")
    null
}

private val locationPermissions = arrayOf(
    android.Manifest.permission.ACCESS_FINE_LOCATION,
    android.Manifest.permission.ACCESS_COARSE_LOCATION
)
```

## Add sample location data

Next, we will add sample location data with a GPX file to imitate a user traveling around the [Yellowstone Grand Loop Road](https://en.wikipedia.org/wiki/Grand_Loop_Road).

### Download the GPX file to simulate a user traveling around Yellowstone National Park

Download the Yellowstone Grand Loop Road GPX file. Then, in the Android Studio emulator, select the "Extended Controls" button (three dots) in the emulator toolbar. In the "Location" tab, select "Import GPX/KML" and pick the GPX file you downloaded. Press "play route" to simulate the user traveling around the Grand Loop Road in Yellowstone National Park.

[Download GPX](https://docs.mapbox.com/help/ja/help/ja/data/yellowstone_grand_loop_road.gpx)

Your browser doesn't support HTML5 video. Open [link to the video](https://docs.mapbox.com/help/ja/help/ja/assets/medias/tutorials--android-geofencing-polygons-efe9ce72fb7450ea96aef8bce799f775.webm).

## Receive geofence events in your app

Now that you have added sample location data to your app and are receiving geofence events in the console, you can use these events to trigger notifications and style changes in your app. First, let's create a data class to store the geofence event data. This class will store the type of event, the feature that triggered the event, the name of the geofence, and the timestamp of the event. It will also provide a formatted description of the event type.

```kotlin
package com.example.codelabsexample

import android.content.Context
import android.content.pm.PackageManager
import android.os.Bundle
import android.util.Log
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.compose.setContent
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.core.content.ContextCompat
import com.google.gson.JsonPrimitive
import com.mapbox.annotation.MapboxExperimental
import com.mapbox.common.geofencing.GeofencingError
import com.mapbox.common.geofencing.GeofencingEvent
import com.mapbox.common.geofencing.GeofencingFactory
import com.mapbox.common.geofencing.GeofencingObserver
import com.mapbox.common.geofencing.GeofencingPropertiesKeys
import com.mapbox.geojson.Feature
import com.mapbox.geojson.FeatureCollection
import com.mapbox.geojson.Point
import com.mapbox.maps.extension.compose.MapEffect
import com.mapbox.maps.extension.compose.MapboxMap
import com.mapbox.maps.extension.compose.animation.viewport.rememberMapViewportState
import com.mapbox.maps.extension.compose.style.ColorValue
import com.mapbox.maps.extension.compose.style.DoubleValue
import com.mapbox.maps.extension.compose.style.layers.generated.FillLayer
import com.mapbox.maps.extension.compose.style.sources.GeoJSONData
import com.mapbox.maps.extension.compose.style.sources.generated.rememberGeoJsonSourceState
import com.mapbox.maps.extension.style.expressions.generated.Expression.Companion.match
import com.mapbox.maps.plugin.locationcomponent.createDefault2DPuck
import com.mapbox.maps.plugin.locationcomponent.location
import java.io.IOException
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale

@OptIn(MapboxExperimental::class)
public class MainActivity : ComponentActivity() {
    private val geofencing by lazy {
        GeofencingFactory.getOrCreate()
    }
    val lastEvent: MutableState<GeofenceEvent?> = mutableStateOf(null)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            val mapViewportState = rememberMapViewportState {
                setCameraOptions {
                    zoom(9.0)
                    center(Point.fromLngLat(-110.6123, 44.5979))
                    pitch(0.0)
                    bearing(0.0)
                }
            }
            MapboxMap(
                Modifier.fillMaxSize().padding(top = 20.dp),
                mapViewportState = mapViewportState
            ) {
                FillLayer(
                    sourceState = rememberGeoJsonSourceState {
                        data = GeoJSONData("asset://yellowstone.geojson")
                    }
                ) {
                    fillColor = ColorValue(
                        match {
                            id()
                            literal(lastEvent.value?.feature?.id().toString())
                            match {
                                literal(lastEvent.value?.type?.description.toString())
                                stop {
                                    literal("entry")
                                    rgb(7.0, 144.0, 30.0) // green
                                }
                                stop {
                                    literal("exit")
                                    rgb(173.0, 17.0, 5.0) // red
                                }
                                stop {
                                    literal("dwell")
                                    rgb(17.0, 97.0, 195.0) // blue
                                }
                                rgb(119.0, 119.0, 119.0) // gray
                            }
                            rgb(119.0, 119.0, 119.0) // gray
                        }
                    )
                    fillOpacity = DoubleValue(0.7)
                }
                MapEffect(Unit) { mapView ->
                    mapView.location.updateSettings {
                        locationPuck = createDefault2DPuck(withBearing = true)
                        enabled = true
                    }
                }
            }
            val launcher = rememberLauncherForActivityResult(
                contract = ActivityResultContracts.RequestMultiplePermissions(),
            ) { permissionsMap ->
                val granted = permissionsMap.values.all { it }
                if (granted) {
                    startGeofencing()
                } else {
                    Toast.makeText(
                        this@MainActivity,
                        "You need to accept location permissions for geofencing to function.",
                        Toast.LENGTH_SHORT
                    ).show()
                }
            }
            LaunchedEffect(Unit) {
                if (locationPermissions.all {
                        ContextCompat.checkSelfPermission(
                            this@MainActivity,
                            it
                        ) == PackageManager.PERMISSION_GRANTED
                    }
                ) {
                    startGeofencing()
                } else {
                    launcher.launch(locationPermissions)
                }
            }
            lastEvent.value?.let {
                Toast.makeText(
                    this@MainActivity,
                    "${it.type.formatted} ${it.geofenceName} at ${it.timestamp}",
                    Toast.LENGTH_SHORT
                ).show()
            }
        }
    }

    private val observer: GeofencingObserver = object : GeofencingObserver {
        override fun onEntry(event: GeofencingEvent) {
            lastEvent.value = GeofenceEvent(GeofenceEvent.GeofenceEventType.ENTRY, event)
        }

        override fun onDwell(event: GeofencingEvent) {
            lastEvent.value = GeofenceEvent(GeofenceEvent.GeofenceEventType.DWELL, event)
        }

        override fun onExit(event: GeofencingEvent) {
            lastEvent.value = GeofenceEvent(GeofenceEvent.GeofenceEventType.EXIT, event)
        }

        override fun onError(error: GeofencingError) {
            Log.d("YellowstoneApp", "onError() called with: error = $error")
        }

        override fun onUserConsentChanged(isConsentGiven: Boolean) {
            Log.d("YellowstoneApp", "onUserConsentChanged() called with: isConsentGiven = $isConsentGiven")
        }
    }

    private fun startGeofencing() {
        Log.d("YellowstoneApp", "startGeofencing")
        /// Geofences are stored in database on disk.
        /// To make this example isolated and synchronized with the UI we delete existing feature from database.
        geofencing.clearFeatures {
            geofencing.addObserver(observer) { geofenceError ->
                Log.d("YellowstoneApp", "geofence.addObserver() error $geofenceError")
            }
        }
        decodeGeoJSON(this@MainActivity, "yellowstone.geojson")?.let { featureCollection ->
            featureCollection.features()?.forEach { feature ->
                // To receive dwell events we need to set a time.
                // After a user has spent that amount of time in the geofence
                // the Geofencing service will send a dwell event
                feature.addProperty(GeofencingPropertiesKeys.DWELL_TIME_KEY, JsonPrimitive(1)) // minutes
                geofencing.addFeature(feature) {}
            }
        }
    }
}

@OptIn(MapboxExperimental::class)
data class GeofenceEvent(
    val type: GeofenceEventType,
    val feature: Feature,
    val geofenceName: String,
    val timestamp: String
) {
    enum class GeofenceEventType(val description: String, val formatted: String) {
        ENTRY("entry", "Entered"),
        DWELL("dwell", "Dwelled in"),
        EXIT("exit", "Exited")
    }

    constructor(type: GeofenceEventType, event: GeofencingEvent) : this(
        type = type,
        feature = event.feature,
        geofenceName = event.feature.getStringProperty("name") ?: "unknown geofence",
        timestamp = event.timestamp.format()
    )

    companion object {
        private fun Date.format(): String {
            val formatter = SimpleDateFormat("h:mm a", Locale.getDefault()) // 12-hour format with AM/PM
            return formatter.format(this)
        }
    }
}

fun decodeGeoJSON(context: Context, fileName: String): FeatureCollection? = try {
    val json = context.assets.open(fileName).bufferedReader().use { it.readText() }
    FeatureCollection.fromJson(json)
} catch (e: IOException) {
    Log.e("YellowstoneApp", "Unable to parse $fileName")
    null
}

private val locationPermissions = arrayOf(
    android.Manifest.permission.ACCESS_FINE_LOCATION,
    android.Manifest.permission.ACCESS_COARSE_LOCATION
)
```

Inside your `MainActivity` class, add a property to store the last geofence event. This property will be used to store the most recent geofence event that occurred.

```kotlin
package com.example.codelabsexample

import android.content.Context
import android.content.pm.PackageManager
import android.os.Bundle
import android.util.Log
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.compose.setContent
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.core.content.ContextCompat
import com.google.gson.JsonPrimitive
import com.mapbox.annotation.MapboxExperimental
import com.mapbox.common.geofencing.GeofencingError
import com.mapbox.common.geofencing.GeofencingEvent
import com.mapbox.common.geofencing.GeofencingFactory
import com.mapbox.common.geofencing.GeofencingObserver
import com.mapbox.common.geofencing.GeofencingPropertiesKeys
import com.mapbox.geojson.Feature
import com.mapbox.geojson.FeatureCollection
import com.mapbox.geojson.Point
import com.mapbox.maps.extension.compose.MapEffect
import com.mapbox.maps.extension.compose.MapboxMap
import com.mapbox.maps.extension.compose.animation.viewport.rememberMapViewportState
import com.mapbox.maps.extension.compose.style.ColorValue
import com.mapbox.maps.extension.compose.style.DoubleValue
import com.mapbox.maps.extension.compose.style.layers.generated.FillLayer
import com.mapbox.maps.extension.compose.style.sources.GeoJSONData
import com.mapbox.maps.extension.compose.style.sources.generated.rememberGeoJsonSourceState
import com.mapbox.maps.extension.style.expressions.generated.Expression.Companion.match
import com.mapbox.maps.plugin.locationcomponent.createDefault2DPuck
import com.mapbox.maps.plugin.locationcomponent.location
import java.io.IOException
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale

@OptIn(MapboxExperimental::class)
public class MainActivity : ComponentActivity() {
    private val geofencing by lazy {
        GeofencingFactory.getOrCreate()
    }
    val lastEvent: MutableState<GeofenceEvent?> = mutableStateOf(null)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            val mapViewportState = rememberMapViewportState {
                setCameraOptions {
                    zoom(9.0)
                    center(Point.fromLngLat(-110.6123, 44.5979))
                    pitch(0.0)
                    bearing(0.0)
                }
            }
            MapboxMap(
                Modifier.fillMaxSize().padding(top = 20.dp),
                mapViewportState = mapViewportState
            ) {
                FillLayer(
                    sourceState = rememberGeoJsonSourceState {
                        data = GeoJSONData("asset://yellowstone.geojson")
                    }
                ) {
                    fillColor = ColorValue(
                        match {
                            id()
                            literal(lastEvent.value?.feature?.id().toString())
                            match {
                                literal(lastEvent.value?.type?.description.toString())
                                stop {
                                    literal("entry")
                                    rgb(7.0, 144.0, 30.0) // green
                                }
                                stop {
                                    literal("exit")
                                    rgb(173.0, 17.0, 5.0) // red
                                }
                                stop {
                                    literal("dwell")
                                    rgb(17.0, 97.0, 195.0) // blue
                                }
                                rgb(119.0, 119.0, 119.0) // gray
                            }
                            rgb(119.0, 119.0, 119.0) // gray
                        }
                    )
                    fillOpacity = DoubleValue(0.7)
                }
                MapEffect(Unit) { mapView ->
                    mapView.location.updateSettings {
                        locationPuck = createDefault2DPuck(withBearing = true)
                        enabled = true
                    }
                }
            }
            val launcher = rememberLauncherForActivityResult(
                contract = ActivityResultContracts.RequestMultiplePermissions(),
            ) { permissionsMap ->
                val granted = permissionsMap.values.all { it }
                if (granted) {
                    startGeofencing()
                } else {
                    Toast.makeText(
                        this@MainActivity,
                        "You need to accept location permissions for geofencing to function.",
                        Toast.LENGTH_SHORT
                    ).show()
                }
            }
            LaunchedEffect(Unit) {
                if (locationPermissions.all {
                        ContextCompat.checkSelfPermission(
                            this@MainActivity,
                            it
                        ) == PackageManager.PERMISSION_GRANTED
                    }
                ) {
                    startGeofencing()
                } else {
                    launcher.launch(locationPermissions)
                }
            }
            lastEvent.value?.let {
                Toast.makeText(
                    this@MainActivity,
                    "${it.type.formatted} ${it.geofenceName} at ${it.timestamp}",
                    Toast.LENGTH_SHORT
                ).show()
            }
        }
    }

    private val observer: GeofencingObserver = object : GeofencingObserver {
        override fun onEntry(event: GeofencingEvent) {
            lastEvent.value = GeofenceEvent(GeofenceEvent.GeofenceEventType.ENTRY, event)
        }

        override fun onDwell(event: GeofencingEvent) {
            lastEvent.value = GeofenceEvent(GeofenceEvent.GeofenceEventType.DWELL, event)
        }

        override fun onExit(event: GeofencingEvent) {
            lastEvent.value = GeofenceEvent(GeofenceEvent.GeofenceEventType.EXIT, event)
        }

        override fun onError(error: GeofencingError) {
            Log.d("YellowstoneApp", "onError() called with: error = $error")
        }

        override fun onUserConsentChanged(isConsentGiven: Boolean) {
            Log.d("YellowstoneApp", "onUserConsentChanged() called with: isConsentGiven = $isConsentGiven")
        }
    }

    private fun startGeofencing() {
        Log.d("YellowstoneApp", "startGeofencing")
        /// Geofences are stored in database on disk.
        /// To make this example isolated and synchronized with the UI we delete existing feature from database.
        geofencing.clearFeatures {
            geofencing.addObserver(observer) { geofenceError ->
                Log.d("YellowstoneApp", "geofence.addObserver() error $geofenceError")
            }
        }
        decodeGeoJSON(this@MainActivity, "yellowstone.geojson")?.let { featureCollection ->
            featureCollection.features()?.forEach { feature ->
                // To receive dwell events we need to set a time.
                // After a user has spent that amount of time in the geofence
                // the Geofencing service will send a dwell event
                feature.addProperty(GeofencingPropertiesKeys.DWELL_TIME_KEY, JsonPrimitive(1)) // minutes
                geofencing.addFeature(feature) {}
            }
        }
    }
}

@OptIn(MapboxExperimental::class)
data class GeofenceEvent(
    val type: GeofenceEventType,
    val feature: Feature,
    val geofenceName: String,
    val timestamp: String
) {
    enum class GeofenceEventType(val description: String, val formatted: String) {
        ENTRY("entry", "Entered"),
        DWELL("dwell", "Dwelled in"),
        EXIT("exit", "Exited")
    }

    constructor(type: GeofenceEventType, event: GeofencingEvent) : this(
        type = type,
        feature = event.feature,
        geofenceName = event.feature.getStringProperty("name") ?: "unknown geofence",
        timestamp = event.timestamp.format()
    )

    companion object {
        private fun Date.format(): String {
            val formatter = SimpleDateFormat("h:mm a", Locale.getDefault()) // 12-hour format with AM/PM
            return formatter.format(this)
        }
    }
}

fun decodeGeoJSON(context: Context, fileName: String): FeatureCollection? = try {
    val json = context.assets.open(fileName).bufferedReader().use { it.readText() }
    FeatureCollection.fromJson(json)
} catch (e: IOException) {
    Log.e("YellowstoneApp", "Unable to parse $fileName")
    null
}

private val locationPermissions = arrayOf(
    android.Manifest.permission.ACCESS_FINE_LOCATION,
    android.Manifest.permission.ACCESS_COARSE_LOCATION
)
```

Update the `observer` implementation to store the last geofence event in the `lastEvent` property. The `onEntry`, `onDwell`, and `onExit` methods will now create a new `GeofenceEvent` object and store it in the `lastEvent` property.

```kotlin
package com.example.codelabsexample

import android.content.Context
import android.content.pm.PackageManager
import android.os.Bundle
import android.util.Log
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.compose.setContent
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.core.content.ContextCompat
import com.google.gson.JsonPrimitive
import com.mapbox.annotation.MapboxExperimental
import com.mapbox.common.geofencing.GeofencingError
import com.mapbox.common.geofencing.GeofencingEvent
import com.mapbox.common.geofencing.GeofencingFactory
import com.mapbox.common.geofencing.GeofencingObserver
import com.mapbox.common.geofencing.GeofencingPropertiesKeys
import com.mapbox.geojson.Feature
import com.mapbox.geojson.FeatureCollection
import com.mapbox.geojson.Point
import com.mapbox.maps.extension.compose.MapEffect
import com.mapbox.maps.extension.compose.MapboxMap
import com.mapbox.maps.extension.compose.animation.viewport.rememberMapViewportState
import com.mapbox.maps.extension.compose.style.ColorValue
import com.mapbox.maps.extension.compose.style.DoubleValue
import com.mapbox.maps.extension.compose.style.layers.generated.FillLayer
import com.mapbox.maps.extension.compose.style.sources.GeoJSONData
import com.mapbox.maps.extension.compose.style.sources.generated.rememberGeoJsonSourceState
import com.mapbox.maps.extension.style.expressions.generated.Expression.Companion.match
import com.mapbox.maps.plugin.locationcomponent.createDefault2DPuck
import com.mapbox.maps.plugin.locationcomponent.location
import java.io.IOException
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale

@OptIn(MapboxExperimental::class)
public class MainActivity : ComponentActivity() {
    private val geofencing by lazy {
        GeofencingFactory.getOrCreate()
    }
    val lastEvent: MutableState<GeofenceEvent?> = mutableStateOf(null)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            val mapViewportState = rememberMapViewportState {
                setCameraOptions {
                    zoom(9.0)
                    center(Point.fromLngLat(-110.6123, 44.5979))
                    pitch(0.0)
                    bearing(0.0)
                }
            }
            MapboxMap(
                Modifier.fillMaxSize().padding(top = 20.dp),
                mapViewportState = mapViewportState
            ) {
                FillLayer(
                    sourceState = rememberGeoJsonSourceState {
                        data = GeoJSONData("asset://yellowstone.geojson")
                    }
                ) {
                    fillColor = ColorValue(
                        match {
                            id()
                            literal(lastEvent.value?.feature?.id().toString())
                            match {
                                literal(lastEvent.value?.type?.description.toString())
                                stop {
                                    literal("entry")
                                    rgb(7.0, 144.0, 30.0) // green
                                }
                                stop {
                                    literal("exit")
                                    rgb(173.0, 17.0, 5.0) // red
                                }
                                stop {
                                    literal("dwell")
                                    rgb(17.0, 97.0, 195.0) // blue
                                }
                                rgb(119.0, 119.0, 119.0) // gray
                            }
                            rgb(119.0, 119.0, 119.0) // gray
                        }
                    )
                    fillOpacity = DoubleValue(0.7)
                }
                MapEffect(Unit) { mapView ->
                    mapView.location.updateSettings {
                        locationPuck = createDefault2DPuck(withBearing = true)
                        enabled = true
                    }
                }
            }
            val launcher = rememberLauncherForActivityResult(
                contract = ActivityResultContracts.RequestMultiplePermissions(),
            ) { permissionsMap ->
                val granted = permissionsMap.values.all { it }
                if (granted) {
                    startGeofencing()
                } else {
                    Toast.makeText(
                        this@MainActivity,
                        "You need to accept location permissions for geofencing to function.",
                        Toast.LENGTH_SHORT
                    ).show()
                }
            }
            LaunchedEffect(Unit) {
                if (locationPermissions.all {
                        ContextCompat.checkSelfPermission(
                            this@MainActivity,
                            it
                        ) == PackageManager.PERMISSION_GRANTED
                    }
                ) {
                    startGeofencing()
                } else {
                    launcher.launch(locationPermissions)
                }
            }
            lastEvent.value?.let {
                Toast.makeText(
                    this@MainActivity,
                    "${it.type.formatted} ${it.geofenceName} at ${it.timestamp}",
                    Toast.LENGTH_SHORT
                ).show()
            }
        }
    }

    private val observer: GeofencingObserver = object : GeofencingObserver {
        override fun onEntry(event: GeofencingEvent) {
            lastEvent.value = GeofenceEvent(GeofenceEvent.GeofenceEventType.ENTRY, event)
        }

        override fun onDwell(event: GeofencingEvent) {
            lastEvent.value = GeofenceEvent(GeofenceEvent.GeofenceEventType.DWELL, event)
        }

        override fun onExit(event: GeofencingEvent) {
            lastEvent.value = GeofenceEvent(GeofenceEvent.GeofenceEventType.EXIT, event)
        }

        override fun onError(error: GeofencingError) {
            Log.d("YellowstoneApp", "onError() called with: error = $error")
        }

        override fun onUserConsentChanged(isConsentGiven: Boolean) {
            Log.d("YellowstoneApp", "onUserConsentChanged() called with: isConsentGiven = $isConsentGiven")
        }
    }

    private fun startGeofencing() {
        Log.d("YellowstoneApp", "startGeofencing")
        /// Geofences are stored in database on disk.
        /// To make this example isolated and synchronized with the UI we delete existing feature from database.
        geofencing.clearFeatures {
            geofencing.addObserver(observer) { geofenceError ->
                Log.d("YellowstoneApp", "geofence.addObserver() error $geofenceError")
            }
        }
        decodeGeoJSON(this@MainActivity, "yellowstone.geojson")?.let { featureCollection ->
            featureCollection.features()?.forEach { feature ->
                // To receive dwell events we need to set a time.
                // After a user has spent that amount of time in the geofence
                // the Geofencing service will send a dwell event
                feature.addProperty(GeofencingPropertiesKeys.DWELL_TIME_KEY, JsonPrimitive(1)) // minutes
                geofencing.addFeature(feature) {}
            }
        }
    }
}

@OptIn(MapboxExperimental::class)
data class GeofenceEvent(
    val type: GeofenceEventType,
    val feature: Feature,
    val geofenceName: String,
    val timestamp: String
) {
    enum class GeofenceEventType(val description: String, val formatted: String) {
        ENTRY("entry", "Entered"),
        DWELL("dwell", "Dwelled in"),
        EXIT("exit", "Exited")
    }

    constructor(type: GeofenceEventType, event: GeofencingEvent) : this(
        type = type,
        feature = event.feature,
        geofenceName = event.feature.getStringProperty("name") ?: "unknown geofence",
        timestamp = event.timestamp.format()
    )

    companion object {
        private fun Date.format(): String {
            val formatter = SimpleDateFormat("h:mm a", Locale.getDefault()) // 12-hour format with AM/PM
            return formatter.format(this)
        }
    }
}

fun decodeGeoJSON(context: Context, fileName: String): FeatureCollection? = try {
    val json = context.assets.open(fileName).bufferedReader().use { it.readText() }
    FeatureCollection.fromJson(json)
} catch (e: IOException) {
    Log.e("YellowstoneApp", "Unable to parse $fileName")
    null
}

private val locationPermissions = arrayOf(
    android.Manifest.permission.ACCESS_FINE_LOCATION,
    android.Manifest.permission.ACCESS_COARSE_LOCATION
)
```

### Display geofence events in your app

Now that you are storing the last geofence event in your `Geofencing` class, you can display it in your app. Add the below code to your `setContent` block to show a toast message when the last geofence event changes. This will display a toast message with the type of event, the name of the geofence, and the timestamp of the event.

```kotlin
package com.example.codelabsexample

import android.content.Context
import android.content.pm.PackageManager
import android.os.Bundle
import android.util.Log
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.compose.setContent
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.core.content.ContextCompat
import com.google.gson.JsonPrimitive
import com.mapbox.annotation.MapboxExperimental
import com.mapbox.common.geofencing.GeofencingError
import com.mapbox.common.geofencing.GeofencingEvent
import com.mapbox.common.geofencing.GeofencingFactory
import com.mapbox.common.geofencing.GeofencingObserver
import com.mapbox.common.geofencing.GeofencingPropertiesKeys
import com.mapbox.geojson.Feature
import com.mapbox.geojson.FeatureCollection
import com.mapbox.geojson.Point
import com.mapbox.maps.extension.compose.MapEffect
import com.mapbox.maps.extension.compose.MapboxMap
import com.mapbox.maps.extension.compose.animation.viewport.rememberMapViewportState
import com.mapbox.maps.extension.compose.style.ColorValue
import com.mapbox.maps.extension.compose.style.DoubleValue
import com.mapbox.maps.extension.compose.style.layers.generated.FillLayer
import com.mapbox.maps.extension.compose.style.sources.GeoJSONData
import com.mapbox.maps.extension.compose.style.sources.generated.rememberGeoJsonSourceState
import com.mapbox.maps.extension.style.expressions.generated.Expression.Companion.match
import com.mapbox.maps.plugin.locationcomponent.createDefault2DPuck
import com.mapbox.maps.plugin.locationcomponent.location
import java.io.IOException
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale

@OptIn(MapboxExperimental::class)
public class MainActivity : ComponentActivity() {
    private val geofencing by lazy {
        GeofencingFactory.getOrCreate()
    }
    val lastEvent: MutableState<GeofenceEvent?> = mutableStateOf(null)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            val mapViewportState = rememberMapViewportState {
                setCameraOptions {
                    zoom(9.0)
                    center(Point.fromLngLat(-110.6123, 44.5979))
                    pitch(0.0)
                    bearing(0.0)
                }
            }
            MapboxMap(
                Modifier.fillMaxSize().padding(top = 20.dp),
                mapViewportState = mapViewportState
            ) {
                FillLayer(
                    sourceState = rememberGeoJsonSourceState {
                        data = GeoJSONData("asset://yellowstone.geojson")
                    }
                ) {
                    fillColor = ColorValue(
                        match {
                            id()
                            literal(lastEvent.value?.feature?.id().toString())
                            match {
                                literal(lastEvent.value?.type?.description.toString())
                                stop {
                                    literal("entry")
                                    rgb(7.0, 144.0, 30.0) // green
                                }
                                stop {
                                    literal("exit")
                                    rgb(173.0, 17.0, 5.0) // red
                                }
                                stop {
                                    literal("dwell")
                                    rgb(17.0, 97.0, 195.0) // blue
                                }
                                rgb(119.0, 119.0, 119.0) // gray
                            }
                            rgb(119.0, 119.0, 119.0) // gray
                        }
                    )
                    fillOpacity = DoubleValue(0.7)
                }
                MapEffect(Unit) { mapView ->
                    mapView.location.updateSettings {
                        locationPuck = createDefault2DPuck(withBearing = true)
                        enabled = true
                    }
                }
            }
            val launcher = rememberLauncherForActivityResult(
                contract = ActivityResultContracts.RequestMultiplePermissions(),
            ) { permissionsMap ->
                val granted = permissionsMap.values.all { it }
                if (granted) {
                    startGeofencing()
                } else {
                    Toast.makeText(
                        this@MainActivity,
                        "You need to accept location permissions for geofencing to function.",
                        Toast.LENGTH_SHORT
                    ).show()
                }
            }
            LaunchedEffect(Unit) {
                if (locationPermissions.all {
                        ContextCompat.checkSelfPermission(
                            this@MainActivity,
                            it
                        ) == PackageManager.PERMISSION_GRANTED
                    }
                ) {
                    startGeofencing()
                } else {
                    launcher.launch(locationPermissions)
                }
            }
            lastEvent.value?.let {
                Toast.makeText(
                    this@MainActivity,
                    "${it.type.formatted} ${it.geofenceName} at ${it.timestamp}",
                    Toast.LENGTH_SHORT
                ).show()
            }
        }
    }

    private val observer: GeofencingObserver = object : GeofencingObserver {
        override fun onEntry(event: GeofencingEvent) {
            lastEvent.value = GeofenceEvent(GeofenceEvent.GeofenceEventType.ENTRY, event)
        }

        override fun onDwell(event: GeofencingEvent) {
            lastEvent.value = GeofenceEvent(GeofenceEvent.GeofenceEventType.DWELL, event)
        }

        override fun onExit(event: GeofencingEvent) {
            lastEvent.value = GeofenceEvent(GeofenceEvent.GeofenceEventType.EXIT, event)
        }

        override fun onError(error: GeofencingError) {
            Log.d("YellowstoneApp", "onError() called with: error = $error")
        }

        override fun onUserConsentChanged(isConsentGiven: Boolean) {
            Log.d("YellowstoneApp", "onUserConsentChanged() called with: isConsentGiven = $isConsentGiven")
        }
    }

    private fun startGeofencing() {
        Log.d("YellowstoneApp", "startGeofencing")
        /// Geofences are stored in database on disk.
        /// To make this example isolated and synchronized with the UI we delete existing feature from database.
        geofencing.clearFeatures {
            geofencing.addObserver(observer) { geofenceError ->
                Log.d("YellowstoneApp", "geofence.addObserver() error $geofenceError")
            }
        }
        decodeGeoJSON(this@MainActivity, "yellowstone.geojson")?.let { featureCollection ->
            featureCollection.features()?.forEach { feature ->
                // To receive dwell events we need to set a time.
                // After a user has spent that amount of time in the geofence
                // the Geofencing service will send a dwell event
                feature.addProperty(GeofencingPropertiesKeys.DWELL_TIME_KEY, JsonPrimitive(1)) // minutes
                geofencing.addFeature(feature) {}
            }
        }
    }
}

@OptIn(MapboxExperimental::class)
data class GeofenceEvent(
    val type: GeofenceEventType,
    val feature: Feature,
    val geofenceName: String,
    val timestamp: String
) {
    enum class GeofenceEventType(val description: String, val formatted: String) {
        ENTRY("entry", "Entered"),
        DWELL("dwell", "Dwelled in"),
        EXIT("exit", "Exited")
    }

    constructor(type: GeofenceEventType, event: GeofencingEvent) : this(
        type = type,
        feature = event.feature,
        geofenceName = event.feature.getStringProperty("name") ?: "unknown geofence",
        timestamp = event.timestamp.format()
    )

    companion object {
        private fun Date.format(): String {
            val formatter = SimpleDateFormat("h:mm a", Locale.getDefault()) // 12-hour format with AM/PM
            return formatter.format(this)
        }
    }
}

fun decodeGeoJSON(context: Context, fileName: String): FeatureCollection? = try {
    val json = context.assets.open(fileName).bufferedReader().use { it.readText() }
    FeatureCollection.fromJson(json)
} catch (e: IOException) {
    Log.e("YellowstoneApp", "Unable to parse $fileName")
    null
}

private val locationPermissions = arrayOf(
    android.Manifest.permission.ACCESS_FINE_LOCATION,
    android.Manifest.permission.ACCESS_COARSE_LOCATION
)
```

With this addition, you should see the last geofence event displayed at the bottom of the screen.

![Screenshot of an Android app showing a map of Yellowstone National Park with the last geofence event displayed at the bottom of the screen.](https://docs.mapbox.com/help/ja/assets/ideal-img/tutorials--android-geofencing-4.ede12e1.480.png)

### Style the geofenced area based on the event type

Update the `FillLayer` to change the color of the geofenced area based on the type of event. To do this, we will use [Mapbox Expressions](https://docs.mapbox.com/android/maps/guides/styles/style-layers/). We will use the `match` expression to match the feature ID with the last event's feature ID. If they match, we will use a nested `match` expression to match the event type and set the color. The colors will be as follows:

-   Green for entry
-   Red for exit
-   Blue for dwell
-   Gray for no event

Additionally, update the `fillOpacity` to 0.7 to make the color slightly transparent.

```kotlin
package com.example.codelabsexample

import android.content.Context
import android.content.pm.PackageManager
import android.os.Bundle
import android.util.Log
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.compose.setContent
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.core.content.ContextCompat
import com.google.gson.JsonPrimitive
import com.mapbox.annotation.MapboxExperimental
import com.mapbox.common.geofencing.GeofencingError
import com.mapbox.common.geofencing.GeofencingEvent
import com.mapbox.common.geofencing.GeofencingFactory
import com.mapbox.common.geofencing.GeofencingObserver
import com.mapbox.common.geofencing.GeofencingPropertiesKeys
import com.mapbox.geojson.Feature
import com.mapbox.geojson.FeatureCollection
import com.mapbox.geojson.Point
import com.mapbox.maps.extension.compose.MapEffect
import com.mapbox.maps.extension.compose.MapboxMap
import com.mapbox.maps.extension.compose.animation.viewport.rememberMapViewportState
import com.mapbox.maps.extension.compose.style.ColorValue
import com.mapbox.maps.extension.compose.style.DoubleValue
import com.mapbox.maps.extension.compose.style.layers.generated.FillLayer
import com.mapbox.maps.extension.compose.style.sources.GeoJSONData
import com.mapbox.maps.extension.compose.style.sources.generated.rememberGeoJsonSourceState
import com.mapbox.maps.extension.style.expressions.generated.Expression.Companion.match
import com.mapbox.maps.plugin.locationcomponent.createDefault2DPuck
import com.mapbox.maps.plugin.locationcomponent.location
import java.io.IOException
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale

@OptIn(MapboxExperimental::class)
public class MainActivity : ComponentActivity() {
    private val geofencing by lazy {
        GeofencingFactory.getOrCreate()
    }
    val lastEvent: MutableState<GeofenceEvent?> = mutableStateOf(null)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            val mapViewportState = rememberMapViewportState {
                setCameraOptions {
                    zoom(9.0)
                    center(Point.fromLngLat(-110.6123, 44.5979))
                    pitch(0.0)
                    bearing(0.0)
                }
            }
            MapboxMap(
                Modifier.fillMaxSize().padding(top = 20.dp),
                mapViewportState = mapViewportState
            ) {
                FillLayer(
                    sourceState = rememberGeoJsonSourceState {
                        data = GeoJSONData("asset://yellowstone.geojson")
                    }
                ) {
                    fillColor = ColorValue(
                        match {
                            id()
                            literal(lastEvent.value?.feature?.id().toString())
                            match {
                                literal(lastEvent.value?.type?.description.toString())
                                stop {
                                    literal("entry")
                                    rgb(7.0, 144.0, 30.0) // green
                                }
                                stop {
                                    literal("exit")
                                    rgb(173.0, 17.0, 5.0) // red
                                }
                                stop {
                                    literal("dwell")
                                    rgb(17.0, 97.0, 195.0) // blue
                                }
                                rgb(119.0, 119.0, 119.0) // gray
                            }
                            rgb(119.0, 119.0, 119.0) // gray
                        }
                    )
                    fillOpacity = DoubleValue(0.7)
                }
                MapEffect(Unit) { mapView ->
                    mapView.location.updateSettings {
                        locationPuck = createDefault2DPuck(withBearing = true)
                        enabled = true
                    }
                }
            }
            val launcher = rememberLauncherForActivityResult(
                contract = ActivityResultContracts.RequestMultiplePermissions(),
            ) { permissionsMap ->
                val granted = permissionsMap.values.all { it }
                if (granted) {
                    startGeofencing()
                } else {
                    Toast.makeText(
                        this@MainActivity,
                        "You need to accept location permissions for geofencing to function.",
                        Toast.LENGTH_SHORT
                    ).show()
                }
            }
            LaunchedEffect(Unit) {
                if (locationPermissions.all {
                        ContextCompat.checkSelfPermission(
                            this@MainActivity,
                            it
                        ) == PackageManager.PERMISSION_GRANTED
                    }
                ) {
                    startGeofencing()
                } else {
                    launcher.launch(locationPermissions)
                }
            }
            lastEvent.value?.let {
                Toast.makeText(
                    this@MainActivity,
                    "${it.type.formatted} ${it.geofenceName} at ${it.timestamp}",
                    Toast.LENGTH_SHORT
                ).show()
            }
        }
    }

    private val observer: GeofencingObserver = object : GeofencingObserver {
        override fun onEntry(event: GeofencingEvent) {
            lastEvent.value = GeofenceEvent(GeofenceEvent.GeofenceEventType.ENTRY, event)
        }

        override fun onDwell(event: GeofencingEvent) {
            lastEvent.value = GeofenceEvent(GeofenceEvent.GeofenceEventType.DWELL, event)
        }

        override fun onExit(event: GeofencingEvent) {
            lastEvent.value = GeofenceEvent(GeofenceEvent.GeofenceEventType.EXIT, event)
        }

        override fun onError(error: GeofencingError) {
            Log.d("YellowstoneApp", "onError() called with: error = $error")
        }

        override fun onUserConsentChanged(isConsentGiven: Boolean) {
            Log.d("YellowstoneApp", "onUserConsentChanged() called with: isConsentGiven = $isConsentGiven")
        }
    }

    private fun startGeofencing() {
        Log.d("YellowstoneApp", "startGeofencing")
        /// Geofences are stored in database on disk.
        /// To make this example isolated and synchronized with the UI we delete existing feature from database.
        geofencing.clearFeatures {
            geofencing.addObserver(observer) { geofenceError ->
                Log.d("YellowstoneApp", "geofence.addObserver() error $geofenceError")
            }
        }
        decodeGeoJSON(this@MainActivity, "yellowstone.geojson")?.let { featureCollection ->
            featureCollection.features()?.forEach { feature ->
                // To receive dwell events we need to set a time.
                // After a user has spent that amount of time in the geofence
                // the Geofencing service will send a dwell event
                feature.addProperty(GeofencingPropertiesKeys.DWELL_TIME_KEY, JsonPrimitive(1)) // minutes
                geofencing.addFeature(feature) {}
            }
        }
    }
}

@OptIn(MapboxExperimental::class)
data class GeofenceEvent(
    val type: GeofenceEventType,
    val feature: Feature,
    val geofenceName: String,
    val timestamp: String
) {
    enum class GeofenceEventType(val description: String, val formatted: String) {
        ENTRY("entry", "Entered"),
        DWELL("dwell", "Dwelled in"),
        EXIT("exit", "Exited")
    }

    constructor(type: GeofenceEventType, event: GeofencingEvent) : this(
        type = type,
        feature = event.feature,
        geofenceName = event.feature.getStringProperty("name") ?: "unknown geofence",
        timestamp = event.timestamp.format()
    )

    companion object {
        private fun Date.format(): String {
            val formatter = SimpleDateFormat("h:mm a", Locale.getDefault()) // 12-hour format with AM/PM
            return formatter.format(this)
        }
    }
}

fun decodeGeoJSON(context: Context, fileName: String): FeatureCollection? = try {
    val json = context.assets.open(fileName).bufferedReader().use { it.readText() }
    FeatureCollection.fromJson(json)
} catch (e: IOException) {
    Log.e("YellowstoneApp", "Unable to parse $fileName")
    null
}

private val locationPermissions = arrayOf(
    android.Manifest.permission.ACCESS_FINE_LOCATION,
    android.Manifest.permission.ACCESS_COARSE_LOCATION
)
```

## Run your app

Your application is now complete! Run your app on a simulator, restarting the Grand Loop Road GPX file to simulate a user traveling around Yellowstone National Park. As the user moves along the road, you should see the geofenced areas change color based on the type of event. The last geofence event should be displayed at the bottom of the screen.

Your browser doesn't support HTML5 video. Open [link to the video](https://docs.mapbox.com/help/ja/help/ja/assets/medias/tutorials--android-geofencing-complete-7e17a4d4e85972126688c3206e3d6785.webm).

Your final code should look like this:

```kotlin
package com.example.codelabsexample

import android.content.Context
import android.content.pm.PackageManager
import android.os.Bundle
import android.util.Log
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.compose.setContent
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.core.content.ContextCompat
import com.google.gson.JsonPrimitive
import com.mapbox.annotation.MapboxExperimental
import com.mapbox.common.geofencing.GeofencingError
import com.mapbox.common.geofencing.GeofencingEvent
import com.mapbox.common.geofencing.GeofencingFactory
import com.mapbox.common.geofencing.GeofencingObserver
import com.mapbox.common.geofencing.GeofencingPropertiesKeys
import com.mapbox.geojson.Feature
import com.mapbox.geojson.FeatureCollection
import com.mapbox.geojson.Point
import com.mapbox.maps.extension.compose.MapEffect
import com.mapbox.maps.extension.compose.MapboxMap
import com.mapbox.maps.extension.compose.animation.viewport.rememberMapViewportState
import com.mapbox.maps.extension.compose.style.ColorValue
import com.mapbox.maps.extension.compose.style.DoubleValue
import com.mapbox.maps.extension.compose.style.layers.generated.FillLayer
import com.mapbox.maps.extension.compose.style.sources.GeoJSONData
import com.mapbox.maps.extension.compose.style.sources.generated.rememberGeoJsonSourceState
import com.mapbox.maps.extension.style.expressions.generated.Expression.Companion.match
import com.mapbox.maps.plugin.locationcomponent.createDefault2DPuck
import com.mapbox.maps.plugin.locationcomponent.location
import java.io.IOException
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale

@OptIn(MapboxExperimental::class)
public class MainActivity : ComponentActivity() {
    private val geofencing by lazy {
        GeofencingFactory.getOrCreate()
    }
    val lastEvent: MutableState<GeofenceEvent?> = mutableStateOf(null)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            val mapViewportState = rememberMapViewportState {
                setCameraOptions {
                    zoom(9.0)
                    center(Point.fromLngLat(-110.6123, 44.5979))
                    pitch(0.0)
                    bearing(0.0)
                }
            }
            MapboxMap(
                Modifier.fillMaxSize().padding(top = 20.dp),
                mapViewportState = mapViewportState
            ) {
                FillLayer(
                    sourceState = rememberGeoJsonSourceState {
                        data = GeoJSONData("asset://yellowstone.geojson")
                    }
                ) {
                    fillColor = ColorValue(
                        match {
                            id()
                            literal(lastEvent.value?.feature?.id().toString())
                            match {
                                literal(lastEvent.value?.type?.description.toString())
                                stop {
                                    literal("entry")
                                    rgb(7.0, 144.0, 30.0) // green
                                }
                                stop {
                                    literal("exit")
                                    rgb(173.0, 17.0, 5.0) // red
                                }
                                stop {
                                    literal("dwell")
                                    rgb(17.0, 97.0, 195.0) // blue
                                }
                                rgb(119.0, 119.0, 119.0) // gray
                            }
                            rgb(119.0, 119.0, 119.0) // gray
                        }
                    )
                    fillOpacity = DoubleValue(0.7)
                }
                MapEffect(Unit) { mapView ->
                    mapView.location.updateSettings {
                        locationPuck = createDefault2DPuck(withBearing = true)
                        enabled = true
                    }
                }
            }
            val launcher = rememberLauncherForActivityResult(
                contract = ActivityResultContracts.RequestMultiplePermissions(),
            ) { permissionsMap ->
                val granted = permissionsMap.values.all { it }
                if (granted) {
                    startGeofencing()
                } else {
                    Toast.makeText(
                        this@MainActivity,
                        "You need to accept location permissions for geofencing to function.",
                        Toast.LENGTH_SHORT
                    ).show()
                }
            }
            LaunchedEffect(Unit) {
                if (locationPermissions.all {
                        ContextCompat.checkSelfPermission(
                            this@MainActivity,
                            it
                        ) == PackageManager.PERMISSION_GRANTED
                    }
                ) {
                    startGeofencing()
                } else {
                    launcher.launch(locationPermissions)
                }
            }
            lastEvent.value?.let {
                Toast.makeText(
                    this@MainActivity,
                    "${it.type.formatted} ${it.geofenceName} at ${it.timestamp}",
                    Toast.LENGTH_SHORT
                ).show()
            }
        }
    }

    private val observer: GeofencingObserver = object : GeofencingObserver {
        override fun onEntry(event: GeofencingEvent) {
            lastEvent.value = GeofenceEvent(GeofenceEvent.GeofenceEventType.ENTRY, event)
        }

        override fun onDwell(event: GeofencingEvent) {
            lastEvent.value = GeofenceEvent(GeofenceEvent.GeofenceEventType.DWELL, event)
        }

        override fun onExit(event: GeofencingEvent) {
            lastEvent.value = GeofenceEvent(GeofenceEvent.GeofenceEventType.EXIT, event)
        }

        override fun onError(error: GeofencingError) {
            Log.d("YellowstoneApp", "onError() called with: error = $error")
        }

        override fun onUserConsentChanged(isConsentGiven: Boolean) {
            Log.d("YellowstoneApp", "onUserConsentChanged() called with: isConsentGiven = $isConsentGiven")
        }
    }

    private fun startGeofencing() {
        Log.d("YellowstoneApp", "startGeofencing")
        /// Geofences are stored in database on disk.
        /// To make this example isolated and synchronized with the UI we delete existing feature from database.
        geofencing.clearFeatures {
            geofencing.addObserver(observer) { geofenceError ->
                Log.d("YellowstoneApp", "geofence.addObserver() error $geofenceError")
            }
        }
        decodeGeoJSON(this@MainActivity, "yellowstone.geojson")?.let { featureCollection ->
            featureCollection.features()?.forEach { feature ->
                // To receive dwell events we need to set a time.
                // After a user has spent that amount of time in the geofence
                // the Geofencing service will send a dwell event
                feature.addProperty(GeofencingPropertiesKeys.DWELL_TIME_KEY, JsonPrimitive(1)) // minutes
                geofencing.addFeature(feature) {}
            }
        }
    }
}

@OptIn(MapboxExperimental::class)
data class GeofenceEvent(
    val type: GeofenceEventType,
    val feature: Feature,
    val geofenceName: String,
    val timestamp: String
) {
    enum class GeofenceEventType(val description: String, val formatted: String) {
        ENTRY("entry", "Entered"),
        DWELL("dwell", "Dwelled in"),
        EXIT("exit", "Exited")
    }

    constructor(type: GeofenceEventType, event: GeofencingEvent) : this(
        type = type,
        feature = event.feature,
        geofenceName = event.feature.getStringProperty("name") ?: "unknown geofence",
        timestamp = event.timestamp.format()
    )

    companion object {
        private fun Date.format(): String {
            val formatter = SimpleDateFormat("h:mm a", Locale.getDefault()) // 12-hour format with AM/PM
            return formatter.format(this)
        }
    }
}

fun decodeGeoJSON(context: Context, fileName: String): FeatureCollection? = try {
    val json = context.assets.open(fileName).bufferedReader().use { it.readText() }
    FeatureCollection.fromJson(json)
} catch (e: IOException) {
    Log.e("YellowstoneApp", "Unable to parse $fileName")
    null
}

private val locationPermissions = arrayOf(
    android.Manifest.permission.ACCESS_FINE_LOCATION,
    android.Manifest.permission.ACCESS_COARSE_LOCATION
)
```

## Next steps

Congratulations! You have successfully implemented geofencing in an Android app using the **Mapbox Maps SDK for Android**. You can now trigger notifications and style changes based on geofence events.

### What we covered

-   Adding GeoJSON data to your project, visualizing it on the map
-   Initializing the geofencing service and adding geofences to watch
-   Imitating a user's location with sample location data and watching geofence events
-   Displaying geofence events in your app and styling the geofenced area based on the event type

### Learn more

If you'd like an additional challenge try implementing the following features:

-   Change the color of the polygons based on whether this is the first, second, or third geofence event.
-   Implement the ability to remove geofences using the `.removeFeature(identifier:callback:)` method.
-   Add another geofence region to the map and watch events for that region as well.

For more information on the Mapbox Maps SDK for Android, read the [Mapbox Maps SDK for Android documentation](https://docs.mapbox.com/android/maps/). Additionally, you can explore our other geofencing examples.

> **Related content (example): [Create geofence zone around user's location](https://docs.mapbox.com/android/maps/examples/android-view/simple-geofencing/)**
> 
> This example shows the usage of the Mapbox Maps SDK for Android [Geofencing API](https://docs.mapbox.com/android/maps/api/latest/mapbox-maps-android/com.mapbox.common.geofencing//) to create a geofence zone for a radius around the user's location, updating its color based on [events](https://docs.mapbox.com/android/maps/api/latest/mapbox-maps-android/com.mapbox.common.geofencing/-geofencing-event//) such as entering, dwelling, or leaving the geofence zone.

> **Related content (example): [Create geofence zone on tapped area using Isochrone API](https://docs.mapbox.com/android/maps/examples/android-view/extended-geofencing/)**
> 
> This example demonstrates the usage of the [Geofencing API](https://docs.mapbox.com/android/maps/api/latest/mapbox-maps-android/com.mapbox.common.geofencing//) using the Mapbox Maps SDK for Android with a custom polygon. The polygon is sourced from the [Mapbox Isochrone API](https://docs.mapbox.com/api/navigation/isochrone/) to get a travel-time polygon for the user's current location. The Geofencing API handles [events](https://docs.mapbox.com/android/maps/api/latest/mapbox-maps-android/com.mapbox.common.geofencing/-geofencing-event//), displaying notifications for entry, dwell, and exit.