🎯

mapbox-android-patterns

🎯Skill

from mapbox/mapbox-agent-skills

VibeIndex|
What it does

Provides robust, lifecycle-aware Mapbox Maps SDK integration patterns for Android using Kotlin and Jetpack Compose.

πŸ“¦

Part of

mapbox/mapbox-agent-skills(9 items)

mapbox-android-patterns

Installation

Quick InstallInstall with npx
npx add-skill mapbox/mapbox-agent-skills
Quick InstallInstall with npx
npx add-skill mapbox/mapbox-agent-skills --skill mapbox-web-performance-patterns
Quick InstallInstall with npx
npx add-skill mapbox/mapbox-agent-skills --list
Quick InstallInstall with npx
npx add-skill mapbox/mapbox-agent-skills -a cursor
Quick InstallInstall with npx
npx add-skill mapbox/mapbox-agent-skills -a vscode

+ 2 more commands

πŸ“– Extracted from docs: mapbox/mapbox-agent-skills
8Installs
-
AddedFeb 4, 2026

Skill Details

SKILL.md

Integration patterns for Mapbox Maps SDK on Android with Kotlin, Jetpack Compose, lifecycle management, and mobile optimization best practices.

Overview

# Mapbox Android Integration Patterns

Official integration patterns for Mapbox Maps SDK on Android. Covers Kotlin, Jetpack Compose, View system, proper lifecycle management, token handling, offline maps, and mobile-specific optimizations.

Use this skill when:

  • Setting up Mapbox Maps SDK for Android in a new or existing project
  • Integrating maps with Jetpack Compose or View system
  • Implementing proper lifecycle management and cleanup
  • Managing tokens securely in Android apps
  • Working with offline maps and caching
  • Integrating Navigation SDK
  • Optimizing for battery life and memory usage
  • Debugging crashes, memory leaks, or performance issues

---

Core Integration Patterns

Jetpack Compose Pattern (Modern)

Modern approach using Jetpack Compose and Kotlin

```kotlin

import androidx.compose.runtime.*

import androidx.compose.ui.Modifier

import androidx.compose.ui.viewinterop.AndroidView

import com.mapbox.maps.MapView

import com.mapbox.maps.Style

import com.mapbox.maps.plugin.animation.camera

import com.mapbox.geojson.Point

@Composable

fun MapboxMap(

modifier: Modifier = Modifier,

center: Point,

zoom: Double,

onMapReady: (MapView) -> Unit = {}

) {

val mapView = rememberMapViewWithLifecycle()

AndroidView(

modifier = modifier,

factory = { mapView },

update = { view ->

// Update camera when state changes

view.getMapboxMap().apply {

setCamera(

CameraOptions.Builder()

.center(center)

.zoom(zoom)

.build()

)

}

}

)

LaunchedEffect(mapView) {

mapView.getMapboxMap().loadStyleUri(Style.MAPBOX_STREETS) {

onMapReady(mapView)

}

}

}

@Composable

fun rememberMapViewWithLifecycle(): MapView {

val context = LocalContext.current

val mapView = remember {

MapView(context).apply {

id = View.generateViewId()

}

}

// Lifecycle-aware cleanup

DisposableEffect(mapView) {

onDispose {

mapView.onDestroy()

}

}

return mapView

}

// Usage in Composable

@Composable

fun MapScreen() {

var center by remember { mutableStateOf(Point.fromLngLat(-122.4194, 37.7749)) }

var zoom by remember { mutableStateOf(12.0) }

MapboxMap(

modifier = Modifier.fillMaxSize(),

center = center,

zoom = zoom,

onMapReady = { mapView ->

// Add sources and layers

}

)

}

```

Key points:

  • Use AndroidView to integrate MapView in Compose
  • Use remember to preserve MapView across recompositions
  • Use DisposableEffect for proper lifecycle cleanup
  • Handle state updates in update block

View System Pattern (Classic)

Traditional Android View system with proper lifecycle

```kotlin

import android.os.Bundle

import androidx.appcompat.app.AppCompatActivity

import com.mapbox.maps.MapView

import com.mapbox.maps.Style

import com.mapbox.maps.plugin.gestures.addOnMapClickListener

import com.mapbox.geojson.Point

class MapActivity : AppCompatActivity() {

private lateinit var mapView: MapView

override fun onCreate(savedInstanceState: Bundle?) {

super.onCreate(savedInstanceState)

setContentView(R.layout.activity_map)

mapView = findViewById(R.id.mapView)

mapView.getMapboxMap().loadStyleUri(Style.MAPBOX_STREETS) { style ->

// Map loaded, add sources and layers

setupMap(style)

}

// Add click listener

mapView.getMapboxMap().addOnMapClickListener { point ->

handleMapClick(point)

true

}

}

private fun setupMap(style: Style) {

// Add your custom sources and layers

}

private fun handleMapClick(point: Point) {

// Handle map clicks

}

// CRITICAL: Lifecycle methods for proper cleanup

override fun onStart() {

super.onStart()

mapView.onStart()

}

override fun onStop() {

super.onStop()

mapView.onStop()

}

override fun onDestroy() {

super.onDestroy()

mapView.onDestroy()

}

override fun onLowMemory() {

super.onLowMemory()

mapView.onLowMemory()

}

}

```

XML layout (activity_map.xml):

```xml

xmlns:android="http://schemas.android.com/apk/res/android"

android:layout_width="match_parent"

android:layout_height="match_parent">

android:id="@+id/mapView"

android:layout_width="match_parent"

android:layout_height="match_parent" />

```

Key points:

  • Call mapView.onStart(), onStop(), onDestroy(), onLowMemory() in corresponding Activity methods
  • Wait for style to load before adding layers
  • Store MapView reference as lateinit var (will be initialized in onCreate)

Fragment Pattern

```kotlin

import android.os.Bundle

import android.view.LayoutInflater

import android.view.View

import android.view.ViewGroup

import androidx.fragment.app.Fragment

import com.mapbox.maps.MapView

import com.mapbox.maps.Style

class MapFragment : Fragment() {

private var mapView: MapView? = null

override fun onCreateView(

inflater: LayoutInflater,

container: ViewGroup?,

savedInstanceState: Bundle?

): View {

val view = inflater.inflate(R.layout.fragment_map, container, false)

mapView = view.findViewById(R.id.mapView)

mapView?.getMapboxMap()?.loadStyleUri(Style.MAPBOX_STREETS) { style ->

setupMap(style)

}

return view

}

private fun setupMap(style: Style) {

// Add sources and layers

}

override fun onStart() {

super.onStart()

mapView?.onStart()

}

override fun onStop() {

super.onStop()

mapView?.onStop()

}

override fun onDestroyView() {

super.onDestroyView()

mapView?.onDestroy()

mapView = null

}

override fun onLowMemory() {

super.onLowMemory()

mapView?.onLowMemory()

}

}

```

Key points:

  • Set mapView to null in onDestroyView() to prevent leaks
  • Use nullable mapView? for safety
  • Call lifecycle methods appropriately

---

Token Management

βœ… Recommended: String Resources with BuildConfig

1. Add to local.properties (DO NOT commit):

```properties

# local.properties (add to .gitignore)

MAPBOX_ACCESS_TOKEN=pk.your_token_here

```

2. Configure in build.gradle.kts (Module):

```kotlin

android {

defaultConfig {

// Read from local.properties

val properties = Properties()

properties.load(project.rootProject.file("local.properties").inputStream())

buildConfigField(

"String",

"MAPBOX_ACCESS_TOKEN",

"\"${properties.getProperty("MAPBOX_ACCESS_TOKEN", "")}\""

)

// Also add to resources for SDK

resValue(

"string",

"mapbox_access_token",

properties.getProperty("MAPBOX_ACCESS_TOKEN", "")

)

}

buildFeatures {

buildConfig = true

}

}

```

3. Add to .gitignore:

```gitignore

local.properties

```

4. Usage in code:

```kotlin

import com.yourapp.BuildConfig

// Access token automatically picked up from resources

// No need to set manually if in string resources

// Or access programmatically:

val token = BuildConfig.MAPBOX_ACCESS_TOKEN

```

Why this pattern:

  • Token not in source code or version control
  • Works in local development and CI/CD (via environment variables)
  • Automatically injected at build time
  • No hardcoded secrets

❌ Anti-Pattern: Hardcoded Tokens

```kotlin

// ❌ NEVER DO THIS - Token in source code

MapboxOptions.accessToken = "pk.YOUR_MAPBOX_TOKEN_HERE"

```

---

Memory Management and Lifecycle

βœ… Proper Lifecycle Management

```kotlin

import androidx.lifecycle.DefaultLifecycleObserver

import androidx.lifecycle.LifecycleOwner

import com.mapbox.maps.MapView

class MapLifecycleObserver(

private val mapView: MapView

) : DefaultLifecycleObserver {

override fun onStart(owner: LifecycleOwner) {

mapView.onStart()

}

override fun onStop(owner: LifecycleOwner) {

mapView.onStop()

}

override fun onDestroy(owner: LifecycleOwner) {

mapView.onDestroy()

}

fun onLowMemory() {

mapView.onLowMemory()

}

}

// Usage in Activity/Fragment

class MapActivity : AppCompatActivity() {

private lateinit var mapView: MapView

private lateinit var lifecycleObserver: MapLifecycleObserver

override fun onCreate(savedInstanceState: Bundle?) {

super.onCreate(savedInstanceState)

setContentView(R.layout.activity_map)

mapView = findViewById(R.id.mapView)

lifecycleObserver = MapLifecycleObserver(mapView)

// Automatically handle lifecycle

lifecycle.addObserver(lifecycleObserver)

}

override fun onLowMemory() {

super.onLowMemory()

lifecycleObserver.onLowMemory()

}

}

```

βœ… ViewModel Pattern

```kotlin

import androidx.lifecycle.ViewModel

import androidx.lifecycle.viewModelScope

import com.mapbox.geojson.Point

import kotlinx.coroutines.flow.MutableStateFlow

import kotlinx.coroutines.flow.StateFlow

import kotlinx.coroutines.launch

data class MapState(

val center: Point = Point.fromLngLat(-122.4194, 37.7749),

val zoom: Double = 12.0,

val markers: List = emptyList()

)

class MapViewModel : ViewModel() {

private val _mapState = MutableStateFlow(MapState())

val mapState: StateFlow = _mapState

fun updateCenter(point: Point) {

_mapState.value = _mapState.value.copy(center = point)

}

fun addMarker(point: Point) {

val currentMarkers = _mapState.value.markers

_mapState.value = _mapState.value.copy(

markers = currentMarkers + point

)

}

fun loadData() {

viewModelScope.launch {

// Load data from repository

// Update state when ready

}

}

}

```

Benefits:

  • State survives configuration changes
  • Separates business logic from UI
  • Lifecycle-aware
  • Easy to test

---

Offline Maps

Download Region for Offline Use

```kotlin

import com.mapbox.maps.TileStore

import com.mapbox.maps.TileRegionLoadOptions

import com.mapbox.common.TileRegion

import com.mapbox.geojson.Point

import com.mapbox.bindgen.Expected

class OfflineManager(private val context: Context) {

private val tileStore = TileStore.create()

fun downloadRegion(

regionId: String,

bounds: CoordinateBounds,

minZoom: Int = 0,

maxZoom: Int = 16,

onProgress: (Float) -> Unit,

onComplete: (Result) -> Unit

) {

val tilesetDescriptor = tileStore.createDescriptor(

TilesetDescriptorOptions.Builder()

.styleURI(Style.MAPBOX_STREETS)

.minZoom(minZoom.toByte())

.maxZoom(maxZoom.toByte())

.build()

)

val loadOptions = TileRegionLoadOptions.Builder()

.geometry(bounds.toGeometry())

.descriptors(listOf(tilesetDescriptor))

.acceptExpired(false)

.build()

val cancelable = tileStore.loadTileRegion(

regionId,

loadOptions,

{ progress ->

val percent = (progress.completedResourceCount.toFloat() /

progress.requiredResourceCount.toFloat()) * 100

onProgress(percent)

}

) { expected ->

if (expected.isValue) {

onComplete(Result.success(Unit))

} else {

onComplete(Result.failure(Exception(expected.error?.message)))

}

}

}

fun getTileRegions(callback: (List) -> Unit) {

tileStore.getAllTileRegions { expected ->

if (expected.isValue) {

callback(expected.value ?: emptyList())

} else {

callback(emptyList())

}

}

}

fun removeTileRegion(regionId: String, callback: (Boolean) -> Unit) {

tileStore.removeTileRegion(regionId)

callback(true)

}

fun estimateStorageSize(

bounds: CoordinateBounds,

minZoom: Int,

maxZoom: Int

): Long {

// Rough estimate: 50 KB per tile average

val tileCount = estimateTileCount(bounds, minZoom, maxZoom)

return tileCount * 50_000L // bytes

}

private fun estimateTileCount(

bounds: CoordinateBounds,

minZoom: Int,

maxZoom: Int

): Long {

// Simplified tile count estimation

var count = 0L

for (zoom in minZoom..maxZoom) {

val tilesAtZoom = Math.pow(4.0, zoom.toDouble()).toLong()

count += tilesAtZoom

}

return count

}

}

```

Key considerations:

  • Battery impact: Downloading uses significant battery
  • Storage limits: Monitor available disk space
  • Zoom levels: Higher zoom = more tiles = more storage
  • Network type: WiFi vs cellular

Check Available Storage

```kotlin

import android.os.StatFs

import android.os.Environment

fun getAvailableStorageBytes(): Long {

val stat = StatFs(Environment.getDataDirectory().path)

return stat.availableBlocksLong * stat.blockSizeLong

}

fun hasEnoughStorage(requiredBytes: Long): Boolean {

val available = getAvailableStorageBytes()

return available > requiredBytes * 2 // 2x buffer

}

```

---

Navigation SDK Integration

Basic Navigation Setup

```kotlin

import com.mapbox.navigation.core.MapboxNavigation

import com.mapbox.navigation.core.MapboxNavigationProvider

import com.mapbox.navigation.core.directions.session.RoutesObserver

import com.mapbox.navigation.core.trip.session.RouteProgressObserver

import com.mapbox.navigation.core.trip.session.TripSessionState

import com.mapbox.api.directions.v5.models.DirectionsRoute

import com.mapbox.geojson.Point

class NavigationActivity : AppCompatActivity() {

private lateinit var mapboxNavigation: MapboxNavigation

private lateinit var mapView: MapView

override fun onCreate(savedInstanceState: Bundle?) {

super.onCreate(savedInstanceState)

setContentView(R.layout.activity_navigation)

mapView = findViewById(R.id.mapView)

// Initialize Navigation SDK

mapboxNavigation = MapboxNavigationProvider.create(

NavigationOptions.Builder(this)

.accessToken(getString(R.string.mapbox_access_token))

.build()

)

setupObservers()

}

private fun setupObservers() {

// Observe route updates

mapboxNavigation.registerRoutesObserver(object : RoutesObserver {

override fun onRoutesChanged(result: RoutesUpdatedResult) {

val routes = result.navigationRoutes

if (routes.isNotEmpty()) {

// Show route on map

showRouteOnMap(routes.first())

}

}

})

// Observe navigation progress

mapboxNavigation.registerRouteProgressObserver(object : RouteProgressObserver {

override fun onRouteProgressChanged(routeProgress: RouteProgress) {

// Update UI with progress

val distanceRemaining = routeProgress.distanceRemaining

val durationRemaining = routeProgress.durationRemaining

}

})

}

fun startNavigation(destination: Point) {

// Request route

val origin = mapboxNavigation.navigationOptions.locationEngine

.getLastLocation { location ->

location?.let {

val originPoint = Point.fromLngLat(it.longitude, it.latitude)

requestRoute(originPoint, destination)

}

}

}

private fun requestRoute(origin: Point, destination: Point) {

val routeOptions = RouteOptions.builder()

.applyDefaultNavigationOptions()

.coordinates(listOf(origin, destination))

.build()

mapboxNavigation.requestRoutes(

routeOptions,

object : NavigationRouterCallback {

override fun onRoutesReady(

routes: List,

routerOrigin: RouterOrigin

) {

mapboxNavigation.setNavigationRoutes(routes)

mapboxNavigation.startTripSession()

}

override fun onFailure(

reasons: List,

routeOptions: RouteOptions

) {

// Handle error

}

override fun onCanceled(

routeOptions: RouteOptions,

routerOrigin: RouterOrigin

) {

// Handle cancellation

}

}

)

}

private fun showRouteOnMap(route: NavigationRoute) {

// Draw route on map

}

override fun onDestroy() {

super.onDestroy()

mapboxNavigation.onDestroy()

}

}

```

Navigation SDK features:

  • Turn-by-turn guidance
  • Voice instructions
  • Route progress tracking
  • Rerouting
  • Traffic-aware routing
  • Offline navigation (with offline regions)

---

Mobile Performance Optimization

Battery Optimization

```kotlin

import android.content.Context

import android.os.PowerManager

class BatteryAwareMapActivity : AppCompatActivity() {

private lateinit var mapView: MapView

private lateinit var powerManager: PowerManager

override fun onCreate(savedInstanceState: Bundle?) {

super.onCreate(savedInstanceState)

setContentView(R.layout.activity_map)

mapView = findViewById(R.id.mapView)

powerManager = getSystemService(Context.POWER_SERVICE) as PowerManager

observeBatteryState()

}

private fun observeBatteryState() {

if (powerManager.isPowerSaveMode) {

enableLowPowerMode()

}

// Register broadcast receiver for power save mode changes

registerReceiver(

object : BroadcastReceiver() {

override fun onReceive(context: Context?, intent: Intent?) {

if (powerManager.isPowerSaveMode) {

enableLowPowerMode()

} else {

enableNormalMode()

}

}

},

IntentFilter(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED)

)

}

private fun enableLowPowerMode() {

// Reduce frame rate

mapView.getMapboxMap().setMaximumFps(30)

// Disable 3D features

// Reduce tile quality

}

private fun enableNormalMode() {

mapView.getMapboxMap().setMaximumFps(60)

}

}

```

Memory Optimization

```kotlin

override fun onLowMemory() {

super.onLowMemory()

mapView.onLowMemory()

// Clear map cache

mapView.getMapboxMap().clearData { result ->

if (result.isValue) {

Log.d("Map", "Cache cleared")

}

}

}

override fun onTrimMemory(level: Int) {

super.onTrimMemory(level)

when (level) {

ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW,

ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL -> {

// Clear non-essential data

mapView.getMapboxMap().clearData { }

}

}

}

```

Network Optimization

```kotlin

import android.net.ConnectivityManager

import android.net.NetworkCapabilities

class NetworkAwareMapActivity : AppCompatActivity() {

private lateinit var connectivityManager: ConnectivityManager

override fun onCreate(savedInstanceState: Bundle?) {

super.onCreate(savedInstanceState)

connectivityManager = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager

observeNetworkState()

}

private fun observeNetworkState() {

val networkCallback = object : ConnectivityManager.NetworkCallback() {

override fun onCapabilitiesChanged(

network: Network,

capabilities: NetworkCapabilities

) {

when {

capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> {

// WiFi - use full quality

enableHighQuality()

}

capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> {

// Cellular - reduce data usage

enableLowDataMode()

}

}

}

}

connectivityManager.registerDefaultNetworkCallback(networkCallback)

}

private fun enableHighQuality() {

// Use full resolution tiles

}

private fun enableLowDataMode() {

// Reduce tile resolution

// Limit prefetching

}

}

```

---

Common Mistakes and Solutions

❌ Mistake 1: Not Calling Lifecycle Methods

```kotlin

// ❌ BAD: MapView lifecycle not managed

class MapActivity : AppCompatActivity() {

private lateinit var mapView: MapView

override fun onCreate(savedInstanceState: Bundle?) {

super.onCreate(savedInstanceState)

mapView = findViewById(R.id.mapView)

// No lifecycle methods called!

}

}

// βœ… GOOD: Proper lifecycle management

class MapActivity : AppCompatActivity() {

private lateinit var mapView: MapView

override fun onCreate(savedInstanceState: Bundle?) {

super.onCreate(savedInstanceState)

mapView = findViewById(R.id.mapView)

}

override fun onStart() {

super.onStart()

mapView.onStart()

}

override fun onStop() {

super.onStop()

mapView.onStop()

}

override fun onDestroy() {

super.onDestroy()

mapView.onDestroy()

}

override fun onLowMemory() {

super.onLowMemory()

mapView.onLowMemory()

}

}

```

❌ Mistake 2: Memory Leaks in Fragments

```kotlin

// ❌ BAD: MapView not cleaned up in Fragment

class MapFragment : Fragment() {

private lateinit var mapView: MapView

override fun onCreateView(

inflater: LayoutInflater,

container: ViewGroup?,

savedInstanceState: Bundle?

): View {

val view = inflater.inflate(R.layout.fragment_map, container, false)

mapView = view.findViewById(R.id.mapView)

return view

}

// No cleanup!

}

// βœ… GOOD: Proper cleanup

class MapFragment : Fragment() {

private var mapView: MapView? = null

override fun onCreateView(

inflater: LayoutInflater,

container: ViewGroup?,

savedInstanceState: Bundle?

): View {

val view = inflater.inflate(R.layout.fragment_map, container, false)

mapView = view.findViewById(R.id.mapView)

return view

}

override fun onDestroyView() {

super.onDestroyView()

mapView?.onDestroy()

mapView = null // Prevent leaks

}

}

```

❌ Mistake 3: Ignoring Location Permissions

```kotlin

// ❌ BAD: Enabling location without checking permissions

mapView.location.enabled = true

// βœ… GOOD: Request and check permissions

import androidx.activity.result.contract.ActivityResultContracts

class MapActivity : AppCompatActivity() {

private val locationPermissionRequest = registerForActivityResult(

ActivityResultContracts.RequestMultiplePermissions()

) { permissions ->

when {

permissions[Manifest.permission.ACCESS_FINE_LOCATION] == true -> {

enableLocationTracking()

}

permissions[Manifest.permission.ACCESS_COARSE_LOCATION] == true -> {

enableLocationTracking()

}

else -> {

// Handle denied

}

}

}

override fun onCreate(savedInstanceState: Bundle?) {

super.onCreate(savedInstanceState)

requestLocationPermissions()

}

private fun requestLocationPermissions() {

when {

ContextCompat.checkSelfPermission(

this,

Manifest.permission.ACCESS_FINE_LOCATION

) == PackageManager.PERMISSION_GRANTED -> {

enableLocationTracking()

}

else -> {

locationPermissionRequest.launch(

arrayOf(

Manifest.permission.ACCESS_FINE_LOCATION,

Manifest.permission.ACCESS_COARSE_LOCATION

)

)

}

}

}

private fun enableLocationTracking() {

mapView.location.enabled = true

}

}

```

Add to AndroidManifest.xml:

```xml

```

❌ Mistake 4: Adding Layers Before Map Loads

```kotlin

// ❌ BAD: Adding layers immediately

override fun onCreate(savedInstanceState: Bundle?) {

super.onCreate(savedInstanceState)

mapView = findViewById(R.id.mapView)

addCustomLayers() // Map not loaded yet!

}

// βœ… GOOD: Wait for style to load

override fun onCreate(savedInstanceState: Bundle?) {

super.onCreate(savedInstanceState)

mapView = findViewById(R.id.mapView)

mapView.getMapboxMap().loadStyleUri(Style.MAPBOX_STREETS) { style ->

addCustomLayers(style)

}

}

```

---

Testing Patterns

Unit Testing Map Logic

```kotlin

import org.junit.Test

import org.junit.Assert.*

import com.mapbox.geojson.Point

class MapLogicTest {

@Test

fun testCoordinateConversion() {

val point = Point.fromLngLat(-122.4194, 37.7749)

// Test your map logic without creating actual MapView

val converted = MapLogic.convert(point)

assertEquals(-122.4194, converted.longitude(), 0.001)

assertEquals(37.7749, converted.latitude(), 0.001)

}

}

```

Instrumented Testing with Maps

```kotlin

import androidx.test.ext.junit.rules.ActivityScenarioRule

import androidx.test.ext.junit.runners.AndroidJUnit4

import org.junit.Rule

import org.junit.Test

import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)

class MapActivityTest {

@get:Rule

val activityRule = ActivityScenarioRule(MapActivity::class.java)

@Test

fun testMapLoads() {

activityRule.scenario.onActivity { activity ->

val mapView = activity.findViewById(R.id.mapView)

assertNotNull(mapView)

}

}

}

```

---

Troubleshooting

Map Not Displaying

Checklist:

  1. βœ… Token configured in string resources?
  2. βœ… Correct package name in token restrictions?
  3. βœ… MapboxMaps dependency added to build.gradle?
  4. βœ… MapView lifecycle methods called?
  5. βœ… Internet permission in AndroidManifest.xml?

```xml

```

Memory Leaks

Use Android Studio Profiler:

  1. Run β†’ Profile 'app' β†’ Memory
  2. Look for MapView instances not being garbage collected
  3. Ensure mapView.onDestroy() is called
  4. Set mapView = null in Fragments after destroy

Slow Performance

Common causes:

  • Too many markers (use clustering or symbols)
  • Large GeoJSON sources (use vector tiles)
  • Not handling lifecycle properly
  • Not calling onLowMemory()
  • Running on emulator (use device for accurate testing)

---

Platform-Specific Considerations

Android Version Support

  • Android 6.0+ (API 23+): Minimum supported version
  • Android 12+ (API 31+): New permission handling
  • Android 13+ (API 33+): Runtime notification permissions

Device Optimization

```kotlin

import android.app.ActivityManager

import android.content.Context

fun isLowRamDevice(): Boolean {

val activityManager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager

return activityManager.isLowRamDevice

}

// Adjust map quality based on device

if (isLowRamDevice()) {

// Reduce detail, limit features

}

```

Screen Density

```kotlin

val density = resources.displayMetrics.density

when {

density >= 4.0 -> {

// xxxhdpi displays

// Use highest quality

}

density >= 3.0 -> {

// xxhdpi displays

// High quality

}

density >= 2.0 -> {

// xhdpi displays

// Standard quality

}

}

```

---

Reference

  • [Mapbox Maps SDK for Android](https://docs.mapbox.com/android/maps/guides/)
  • [API Reference](https://docs.mapbox.com/android/maps/api-reference/)
  • [Examples](https://docs.mapbox.com/android/maps/examples/)
  • [Navigation SDK](https://docs.mapbox.com/android/navigation/guides/)
  • [Gradle Installation](https://docs.mapbox.com/android/maps/guides/install/)
  • [Migration Guides](https://docs.mapbox.com/android/maps/guides/migrate-to-v10/)

More from this repository8

🎯
mapbox-web-performance-patterns🎯Skill

Optimizes Mapbox web applications by eliminating initialization waterfalls, reducing load times, and improving rendering performance across data loading and map rendering.

🎯
mapbox-web-integration-patterns🎯Skill

Provides official, production-ready integration patterns for embedding Mapbox GL JS across web frameworks with best practices and setup guidance.

🎯
mapbox-style-patterns🎯Skill

Provides curated Mapbox style patterns and layer configurations for implementing common mapping use cases like restaurant finders, real estate, and data visualization.

🎯
mapbox-style-quality🎯Skill

Validates and optimizes Mapbox styles by checking expressions, accessibility, data integrity, and performance before production deployment.

🎯
mapbox-token-security🎯Skill

Secures Mapbox access tokens by implementing granular scope management, URL restrictions, and token rotation strategies across different environments.

🎯
mapbox-cartography🎯Skill

Guides map designers in creating visually compelling and accessible Mapbox maps using expert cartographic principles, color theory, and typography best practices.

🎯
mapbox-ios-patterns🎯Skill

Enables seamless Mapbox Maps SDK integration in iOS apps using Swift, SwiftUI, and UIKit with best practices for lifecycle management and performance.

🎯
mapbox-google-maps-migration🎯Skill

Helps developers seamlessly migrate from Google Maps Platform to Mapbox GL JS by providing comprehensive API translation, styling patterns, and migration strategies.