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

# Custom screens

This guide shows how to create a custom Android Auto screen by extending the [`Screen`](https://developer.android.com/reference/androidx/car/app/Screen) class and returning a template from `onGetTemplate()`. Use Mapbox components to add map rendering, camera behavior, and map controls without implementing them yourself.

This guide combines three modes in one screen:

-   **Free Drive**: Follow the user's location without a route.
-   **Route Preview**: Display a route on the map and let the user start navigation.
-   **Active Guidance**: Show turn-by-turn instructions and a stop button.

It requires **Car API Level 7 or higher** because route preview uses `MapWithContentTemplate`. To keep the example small, it previews one route at a time and leaves search and route fetching to your app.

![A Android Auto screenshot of a custom screen](https://docs.mapbox.com/android/ja/assets/ideal-img/navigation-androidauto-custom-screen.581444a.480.png)

## Create a screen

The screen uses the `MapboxCarMap` and `MapboxNavigation` already configured by your app. Map observers provide the camera, route line, guidance markers, and navigation information. Register them while the screen is resumed and unregister them when it pauses.

```kotlin
import androidx.car.app.CarContext
import androidx.car.app.Screen
import androidx.car.app.annotations.RequiresCarApi
import androidx.car.app.model.Action
import androidx.car.app.model.ActionStrip
import androidx.car.app.model.Header
import androidx.car.app.model.ItemList
import androidx.car.app.model.ListTemplate
import androidx.car.app.model.Row
import androidx.car.app.model.Template
import androidx.car.app.navigation.model.MapController
import androidx.car.app.navigation.model.MapWithContentTemplate
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import com.mapbox.maps.extension.androidauto.MapboxCarMap
import com.mapbox.maps.extension.androidauto.MapboxCarMapObserver
import com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI
import com.mapbox.navigation.base.route.NavigationRoute
import com.mapbox.navigation.core.MapboxNavigation
import com.mapbox.navigation.ui.androidauto.action.MapboxMapActionStrip
import com.mapbox.navigation.ui.androidauto.navigation.CarActiveGuidanceMarkers
import com.mapbox.navigation.ui.androidauto.navigation.CarCameraMode
import com.mapbox.navigation.ui.androidauto.navigation.CarNavigationCamera
import com.mapbox.navigation.ui.androidauto.navigation.CarNavigationInfoProvider
import com.mapbox.navigation.ui.androidauto.navigation.CarNavigationTemplates
import com.mapbox.navigation.ui.androidauto.preview.CarRouteLineRenderer
import com.mapbox.navigation.ui.androidauto.preview.CarRouteLineRendererOptions
import com.mapbox.navigation.ui.androidauto.routes.CarRoutesProvider
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow

@RequiresCarApi(7)
@OptIn(ExperimentalPreviewMapboxNavigationAPI::class)
class CustomNavigationScreen(
    carContext: CarContext,
    private val mapboxCarMap: MapboxCarMap,
    private val mapboxNavigation: MapboxNavigation,
) : Screen(carContext) {
    private enum class Mode { FREE_DRIVE, PREVIEW, ACTIVE_GUIDANCE }

    private var mode = Mode.FREE_DRIVE
    private val routes = MutableStateFlow<List<NavigationRoute>>(emptyList())
    private val routesProvider = object : CarRoutesProvider {
        override val navigationRoutes: Flow<List<NavigationRoute>> = routes
    }
    private val camera = CarNavigationCamera(
        initialCarCameraMode = CarCameraMode.FOLLOWING,
        alternativeCarCameraMode = CarCameraMode.OVERVIEW,
        carRoutesProvider = routesProvider,
    )
    private val mapActionStrip = MapboxMapActionStrip(this, camera)
    private val navigationInfo = CarNavigationInfoProvider().invalidateOnChange(this)
    private val mapObservers: List<MapboxCarMapObserver> = listOf(
        camera,
        CarRouteLineRenderer(
            options = CarRouteLineRendererOptions.Builder().build(),
            carRoutesProvider = routesProvider,
            onRouteClick = {},
        ),
        CarActiveGuidanceMarkers(routesProvider),
        navigationInfo,
    )

    init {
        lifecycle.addObserver(object : DefaultLifecycleObserver {
            override fun onResume(owner: LifecycleOwner) {
                mapObservers.forEach(mapboxCarMap::registerObserver)
                mapboxCarMap.setGestureHandler(camera.gestureHandler)
            }

            override fun onPause(owner: LifecycleOwner) {
                mapboxCarMap.setGestureHandler(null)
                mapObservers.forEach(mapboxCarMap::unregisterObserver)
            }
        })
    }

    // Call on the main thread after your app fetches a route, before guidance starts.
    fun showPreview(route: NavigationRoute) {
        routes.value = listOf(route)
        mode = Mode.PREVIEW
        camera.updateCameraMode(CarCameraMode.OVERVIEW)
        invalidate()
    }

    private fun startGuidance() {
        mapboxNavigation.setNavigationRoutes(routes.value)
        mode = Mode.ACTIVE_GUIDANCE
        camera.updateCameraMode(CarCameraMode.FOLLOWING)
        invalidate()
    }

    private fun returnToFreeDrive() {
        mapboxNavigation.setNavigationRoutes(emptyList())
        routes.value = emptyList()
        mode = Mode.FREE_DRIVE
        camera.updateCameraMode(CarCameraMode.FOLLOWING)
        invalidate()
    }

    override fun onGetTemplate(): Template = when (mode) {
        Mode.FREE_DRIVE -> CarNavigationTemplates.freeDrive(
            actions("Follow") { camera.updateCameraMode(CarCameraMode.FOLLOWING) },
            mapActionStrip.build(),
        )
        Mode.PREVIEW -> previewTemplate()
        Mode.ACTIVE_GUIDANCE -> CarNavigationTemplates.activeGuidance(
            navigationInfo.carNavigationInfo.value,
            actions("Stop", ::returnToFreeDrive),
            mapActionStrip.build(),
        )
    }

    private fun previewTemplate(): Template {
        val route = routes.value.first().directionsRoute
        val row = Row.Builder()
            .setTitle("Start navigation")
            .addText("${(route.duration() / 60).toInt()} min")
            .setOnClickListener { startGuidance() }
            .build()
        val content = ListTemplate.Builder()
            .setHeader(Header.Builder().setTitle("Route preview").build())
            .setSingleList(ItemList.Builder().addItem(row).build())
            .build()

        return MapWithContentTemplate.Builder()
            .setContentTemplate(content)
            .setActionStrip(actions("Cancel", ::returnToFreeDrive))
            .setMapController(
                MapController.Builder().setMapActionStrip(mapActionStrip.build()).build()
            )
            .build()
    }

    private fun actions(title: String, onClick: () -> Unit): ActionStrip =
        ActionStrip.Builder()
            .addAction(Action.Builder().setTitle(title).setOnClickListener { onClick() }.build())
            .build()
}
```

`showPreview()` changes only the routes displayed on the map. Selecting **Start navigation** passes the route to `MapboxNavigation` and switches to active guidance. `CarNavigationInfoProvider` supplies guidance information and invalidates the screen when it changes. **Cancel** and **Stop** clear the route and return to free drive without stopping location updates.

## Register the screen

In your session, register a `MapboxScreenFactory` after calling `prepareScreens()`. Replace `NAVIGATION` on car systems that support Car API Level 7 or higher, and leave the default screens in place on older car systems. Pass the same map and navigation instances used by your session:

```kotlin
import com.mapbox.maps.extension.androidauto.MapboxCarMap
import com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI
import com.mapbox.navigation.core.lifecycle.MapboxNavigationApp
import com.mapbox.navigation.ui.androidauto.MapboxCarContext
import com.mapbox.navigation.ui.androidauto.screenmanager.MapboxScreen
import com.mapbox.navigation.ui.androidauto.screenmanager.MapboxScreenFactory
import com.mapbox.navigation.ui.androidauto.screenmanager.prepareScreens

// Properties inside your Session class.
private val mapboxCarMap = MapboxCarMap()
private var customNavigationScreen: CustomNavigationScreen? = null

@OptIn(ExperimentalPreviewMapboxNavigationAPI::class)
private val mapboxCarContext = MapboxCarContext(lifecycle, mapboxCarMap)
    .prepareScreens()
    .apply {
        mapboxScreenManager[MapboxScreen.NAVIGATION] = MapboxScreenFactory { carContext ->
            check(carContext.supportsUnifiedNavigation())
            CustomNavigationScreen(
                carContext,
                mapboxCarMap,
                checkNotNull(MapboxNavigationApp.current()),
            ).also { customNavigationScreen = it }
        }
    }
```

Keep your session's existing map setup, navigation lifecycle management, and location permission checks. Initialize `MapboxNavigationApp`, attach the session, and start a trip session before using guidance. This screen does not initialize the map or start a trip session. See [Get Started](https://docs.mapbox.com/android/ja/navigation/guides/androidauto/get-started) for session setup.

After location permission is granted, select `NAVIGATION` when `carContext.supportsUnifiedNavigation()` is `true`, or `FREE_DRIVE` otherwise, as shown in the [Navigation guide](https://docs.mapbox.com/android/ja/navigation/guides/androidauto/navigation-screen).

When your app receives a route, call `customNavigationScreen?.showPreview(route)` on the main thread. This requires the screen to have been created first. Search, route requests, and delivery of their results are app responsibilities.

For brevity, this example omits alternative routes, arrival handling, voice guidance controls, and synchronization with navigation started elsewhere in your app. In a production app, also keep the displayed routes synchronized with reroutes and handle Android Auto's template-type restrictions when returning from other screens.