Skip to content

MapViewHolderInterface

MapViewHolderInterface provides access to the native map SDK instances for advanced use cases where MapConductor’s unified API doesn’t cover specific provider functionality. While MapConductor provides a common interface, it doesn’t completely wrap all native features, and MapViewHolderInterface bridges this gap.

MapConductor aims to provide a unified API across map providers, but complete feature parity is not always possible. MapViewHolderInterface allows developers to access the underlying native map instances when they need provider-specific functionality.

interface MapViewHolderInterface<ActualMapViewType, ActualMapType> {
val mapView: ActualMapViewType
val map: ActualMapType
}

Each map provider’s MapViewStateInterface implementation provides access to its specific MapViewHolderInterface:

// Any provider state exposes getMapViewHolder()
val state = rememberGoogleMapViewState()
// May be null until the map is initialized
val holder = state.getMapViewHolder()
// Use the concrete holder type when you need provider-specific APIs
// (e.g. GoogleMapViewHolder / MapboxMapViewHolder / MapLibreMapViewHolder / ...)
// holder?.googleMap?.uiSettings?.isZoomControlsEnabled = false
@Composable
fun MapViewHolderGoogleMapsExample(modifier: Modifier = Modifier) {
val context = LocalContext.current
// Map camera position
val mapViewState = rememberGoogleMapViewState(
cameraPosition = MapCameraPosition(
position = GeoPoint.fromLatLong(28.53456, 77.192845),
zoom = 12.0
),
)
// Using mutableStateOf, recompose when mapStyle changes
var mapStyle by remember { mutableStateOf<MapStyleOptions?>(null) }
// Get ViewHolder
val googleMapViewHolder = mapViewState.getMapViewHolder()
LaunchedEffect(googleMapViewHolder, mapStyle) {
if (googleMapViewHolder == null) return@LaunchedEffect
googleMapViewHolder.map.setMapStyle(mapStyle)
}
Column(modifier = modifier) {
Row(
modifier = Modifier.fillMaxWidth(),
) {
Spacer(modifier = Modifier.size(20.dp))
// Normal Google Maps
Button(onClick = {
mapStyle = null
}) {
Text(
text = "Normal"
)
}
Spacer(modifier = Modifier.size(20.dp))
// Simplified map design
Button(onClick = {
mapStyle = MapStyleOptions.loadRawResourceStyle(context, R.raw.style_map)
}) {
Text(
text = "Simplified"
)
}
}
GoogleMapView(
state = mapViewState,
modifier = Modifier.fillMaxSize(),
) {}
}
}
class MainActivity : ComponentActivity() {
lateinit var permissionsManager: PermissionsManager
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
if (PermissionsManager.areLocationPermissionsGranted(this)) {
// Permission sensitive logic called here, such as activating the Maps SDK's LocationComponent to show the device's location
} else {
permissionsManager = PermissionsManager(this.permissionsListener)
permissionsManager.requestLocationPermissions(this)
}
setContent {
MapConductorSDKTheme {
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
BasicMapExample(
modifier =
Modifier
.padding(innerPadding)
.fillMaxSize(),
)
}
}
}
}
var permissionsListener: PermissionsListener = object : PermissionsListener {
override fun onExplanationNeeded(permissionsToExplain: List<String>) {
// Display an explanation to obtain the location access permissions
}
override fun onPermissionResult(granted: Boolean) {
if (granted) {
// Permission sensitive logic called here, such as activating the Maps SDK's LocationComponent to show the device's location
} else {
// User denied the permission
}
}
}
}
@Composable
fun BasicMapExample(modifier: Modifier = Modifier) {
val mapViewState = rememberMapboxMapViewState(
cameraPosition = MapCameraPosition(
position = GeoPoint.fromLatLong(35.706400, 139.763635),
zoom = 13.0
),
)
// Get ViewHolder
var mapboxMapViewHolder by remember { mutableStateOf<MapboxMapViewHolder?>(null) }
LaunchedEffect(mapboxMapViewHolder) {
mapboxMapViewHolder?.let { holder ->
with(holder.mapView) {
location.locationPuck = createDefault2DPuck(withBearing = true)
location.enabled = true
location.pulsingEnabled = true
location.puckBearing = PuckBearing.COURSE
location.puckBearingEnabled = true
viewport.transitionTo(
targetState = viewport.makeFollowPuckViewportState(),
transition = viewport.makeImmediateViewportTransition()
)
}
}
}
MapboxMapView(
state = mapViewState,
modifier = modifier,
onMapLoaded = {
mapboxMapViewHolder = mapViewState.getMapViewHolder()
}
) {}
}
typealias HereViewHolder = MapViewHolderInterface<MapView, MapScene>
// Access native HERE SDK APIs
hereHolder?.let { holder ->
val hereMapView: MapView = holder.mapView
val mapScene: MapScene = holder.map
// Use HERE specific features
val searchEngine = SearchEngine()
val textQuery = TextQuery("coffee shops", geoCoordinates)
searchEngine.search(textQuery) { searchError, searchResults ->
// Handle HERE search results
}
// HERE routing
val routingEngine = RoutingEngine()
// Configure and use routing
}
@Composable
fun BasicMapExample(
modifier: Modifier = Modifier
) {
// Map camera position
val mapViewState = rememberArcGISMapViewState(
cameraPosition = MapCameraPosition(
position = GeoPoint.fromLatLong(40.40195, -3.68698),
zoom = 8.0
),
)
// Set up AuthenticatorState
val authenticatorState = remember { AuthenticatorState() }
// Hold ViewHolder
var arcGisMapViewHolder by remember { mutableStateOf<ArcGISMapViewHolder?>(null) }
LaunchedEffect(arcGisMapViewHolder) {
// Display traffic information layer
arcGisMapViewHolder?.let { holder ->
val trafficLayer =
ArcGISMapImageLayer("https://traffic.arcgis.com/arcgis/rest/services/World/Traffic/MapServer")
holder.map.scene!!.operationalLayers.add(trafficLayer)
}
}
ArcGISMapView(
state = mapViewState,
modifier = modifier.fillMaxSize(),
sdkInitialize = { context ->
ArcGISOAuthHybridInitialize(
// AuthenticatorState to display login dialog
authenticatorState = authenticatorState
// ArcGIS portal URL
portalUrl = "https://(your).maps.arcgis.com/",
// OAuth2 application redirect URL
redirectUrl = "(application redirectUrl)",
// OAuth2 application client ID
clientId = "(application clientId)",
// (Optional)
// OAuth2 application client secret
// If omitted, login dialog will be displayed
clientSecret = "(application client secret)",
)
},
onMapLoaded = {
// Get ViewHolder
arcGisMapViewHolder = mapViewState.getMapViewHolder()
}
) {}
// Authentication UI (for user login). Used as fallback for hybrid authentication.
Authenticator(authenticatorState = authenticatorState)
}
@Composable
fun CustomStyledMap() {
val mapViewState = rememberGoogleMapViewState()
LaunchedEffect(mapViewState) {
// Wait for map initialization
delay(1000)
mapViewState.getMapViewHolder()?.let { holder ->
val googleMap = holder.map
// Apply custom map style
val styleJson = loadStyleFromAssets(context, "custom_style.json")
googleMap.setMapStyle(MapStyleOptions(styleJson))
// Configure map UI
googleMap.uiSettings.isMyLocationButtonEnabled = false
googleMap.uiSettings.isCompassEnabled = true
}
}
// Replace MapView with your chosen map provider, such as GoogleMapView, MapboxMapView
GoogleMapView(state = mapViewState) {
// Your MapConductor components
}
}
@Composable
fun AnalyticsIntegration() {
val mapViewState = remember { MapboxViewState() }
LaunchedEffect(mapViewState) {
mapViewState.getMapViewHolder()?.let { holder ->
val mapboxMap = holder.map
// Track custom map events
mapboxMap.addOnMapClickListener { point ->
Analytics.track("map_click", mapOf(
"lat" to point.latitude,
"lng" to point.longitude,
"zoom" to mapboxMap.cameraPosition.zoom
))
true
}
// Track style changes
mapboxMap.addOnStyleLoadedListener {
Analytics.track("style_loaded", mapOf(
"style_id" to mapboxMap.style?.styleURI
))
}
}
}
MapboxMapView(state = mapViewState) {
// Your MapConductor components
}
}
@Composable
fun PerformanceOptimizedMap() {
val mapViewState = remember { HereViewState() }
LaunchedEffect(mapViewState) {
mapViewState.getMapViewHolder()?.let { holder ->
val mapScene = holder.map
// HERE-specific performance settings
mapScene.setLayerVisibility(MapScene.Layers.TRAFFIC_FLOW, VisibilityState.VISIBLE)
// Optimize for specific use case
val mapSettings = mapScene.mapSettings
mapSettings.isTiltGesturesEnabled = false
mapSettings.isRotateGesturesEnabled = false
// Custom level of detail settings
mapScene.setLevelOfDetail(LevelOfDetail.HIGH)
}
}
HereMapView(state = mapViewState) {
// Your MapConductor components
}
}
@Composable
fun ThirdPartyIntegration() {
val mapViewState = rememberArcGISMapViewState()
LaunchedEffect(mapViewState) {
mapViewState.getMapViewHolder()?.let { holder ->
val sceneView = holder.map
// Integrate with ArcGIS Online services
val featureTable = ServiceFeatureTable("https://services.arcgis.com/...")
val featureLayer = FeatureLayer(featureTable)
sceneView.map.basemap.baseLayers.add(featureLayer)
// Query features
val queryParams = QueryParameters().apply {
whereClause = "STATE_NAME = 'California'"
}
featureTable.queryFeaturesAsync(queryParams).addDoneListener {
val results = it.result
// Process feature query results
}
}
}
ArcGISMapView(state = mapViewState) {
// Your MapConductor components
}
}
@Composable
fun LifecycleAwareMap() {
val mapViewState = rememberGoogleMapViewState()
val lifecycleOwner = LocalLifecycleOwner.current
DisposableEffect(lifecycleOwner) {
val observer = LifecycleEventObserver { _, event ->
mapViewState.getMapViewHolder()?.let { holder ->
val mapView = holder.mapView
when (event) {
Lifecycle.Event.ON_RESUME -> mapView.onResume()
Lifecycle.Event.ON_PAUSE -> mapView.onPause()
Lifecycle.Event.ON_START -> mapView.onStart()
Lifecycle.Event.ON_STOP -> mapView.onStop()
Lifecycle.Event.ON_DESTROY -> mapView.onDestroy()
else -> { }
}
}
}
lifecycleOwner.lifecycle.addObserver(observer)
onDispose {
lifecycleOwner.lifecycle.removeObserver(observer)
}
}
GoogleMapView(state = mapViewState) {
// Your MapConductor components
}
}
fun accessNativeMap(mapViewState: GoogleMapViewState) {
val holder = mapViewState.getMapViewHolder()
if (holder != null) {
// Safe to access native APIs
val googleMap = holder.map
val mapView = holder.mapView
// Use native features
googleMap.setOnMarkerClickListener { marker ->
// Handle marker click
true
}
} else {
// Map not yet initialized or unavailable
Log.w("MapAccess", "MapViewHolderInterface not available")
}
}
@Composable
fun SafeNativeAccess() {
val mapViewState = remember { MapboxViewState() }
var isMapReady by remember { mutableStateOf(false) }
LaunchedEffect(mapViewState) {
// Poll until map is ready
while (!isMapReady) {
delay(100)
isMapReady = mapViewState.getMapViewHolder() != null
}
// Now safe to use native APIs
mapViewState.getMapViewHolder()?.let { holder ->
val mapboxMap = holder.map
// Configure native features
}
}
MapboxMapView(state = mapViewState) {
// Your MapConductor components
}
}
  1. Initialization Check: Always check if getMapViewHolder() returns non-null before accessing native APIs
  2. Lifecycle Awareness: Properly handle map lifecycle events when using native APIs
  3. Error Handling: Wrap native API calls in try-catch blocks as provider APIs may throw exceptions
  4. Documentation: Refer to each provider’s official documentation for native API usage
  5. Testing: Test thoroughly across all target map providers when using native APIs
  6. Fallback: Provide fallback behavior when native features are unavailable
  7. Version Compatibility: Ensure native API usage is compatible with the SDK versions you’re targeting
  1. Platform Dependency: Native API usage ties your code to specific map providers
  2. Maintenance Overhead: Provider API changes may require updates to native API usage
  3. Testing Complexity: More complex testing required to cover provider-specific code paths
  4. Feature Parity: Not all providers support equivalent native features
  5. MapConductor Integration: Native changes may not integrate with MapConductor’s state management

MapViewHolderInterface is a powerful escape hatch for advanced use cases, but should be used judiciously to maintain the benefits of MapConductor’s unified API approach.