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 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 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):
< androidx.constraintlayout.widget.ConstraintLayout xmlns: android = " http://schemas.android.com/apk/res/android " android: layout_width = " match_parent " android: layout_height = " match_parent "
com.mapbox.maps.MapView android: id = " @+id/mapView " android: layout_width = " match_parent " android: layout_height = " match_parent " / </ androidx.constraintlayout.widget.ConstraintLayout
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 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):
local.properties (add to .gitignore)
MAPBOX_ACCESS_TOKEN
- pk.your_token_here
- 2. Configure in
- build.gradle.kts
- (Module):
- 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
- :
- local.properties
- 4. Usage in code:
- 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
- // ❌ NEVER DO THIS - Token in source code
- MapboxOptions
- .
- accessToken
- =
- "pk.YOUR_MAPBOX_TOKEN_HERE"
- Memory Management and Lifecycle
- ✅ Proper Lifecycle Management
- 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
- 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
- <
- Point
- >
- =
- emptyList
- (
- )
- )
- class
- MapViewModel
- :
- ViewModel
- (
- )
- {
- private
- val
- _mapState
- =
- MutableStateFlow
- (
- MapState
- (
- )
- )
- val
- mapState
- :
- StateFlow
- <
- MapState
- >
- =
- _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
- 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
- >
- )
- ->
- 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
- <
- TileRegion
- >
- )
- ->
- 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
- 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
- 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
- <
- NavigationRoute
- >
- ,
- routerOrigin
- :
- RouterOrigin
- )
- {
- mapboxNavigation
- .
- setNavigationRoutes
- (
- routes
- )
- mapboxNavigation
- .
- startTripSession
- (
- )
- }
- override
- fun
- onFailure
- (
- reasons
- :
- List
- <
- RouterFailure
- >
- ,
- 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
- 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
- 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
- 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
- // ❌ 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
- // ❌ 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
- // ❌ 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:
- <
- uses-permission
- android:
- name
- =
- "
- android.permission.ACCESS_FINE_LOCATION
- "
- />
- <
- uses-permission
- android:
- name
- =
- "
- android.permission.ACCESS_COARSE_LOCATION
- "
- />
- ❌ Mistake 4: Adding Layers Before Map Loads
- // ❌ 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
- 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
- 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
- <
- MapView
- >
- (
- R
- .
- id
- .
- mapView
- )
- assertNotNull
- (
- mapView
- )
- }
- }
- }
- Troubleshooting
- Map Not Displaying
- Checklist:
- ✅ Token configured in string resources?
- ✅ Correct package name in token restrictions?
- ✅ MapboxMaps dependency added to build.gradle?
- ✅ MapView lifecycle methods called?
- ✅ Internet permission in AndroidManifest.xml?
- <
- uses-permission
- android:
- name
- =
- "
- android.permission.INTERNET
- "
- />
- Memory Leaks
- Use Android Studio Profiler:
- Run → Profile 'app' → Memory
- Look for MapView instances not being garbage collected
- Ensure
- mapView.onDestroy()
- is called
- 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
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
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 API Reference Examples Navigation SDK Gradle Installation Migration Guides