Skip to content

Commit d91986c

Browse files
committed
fix: add one-time attribution ID initialization for GoogleMap
1 parent 124090f commit d91986c

File tree

2 files changed

+115
-84
lines changed
  • maps-app/src/main/java/com/google/maps/android/compose
  • maps-compose/src/main/java/com/google/maps/android/compose

2 files changed

+115
-84
lines changed

maps-app/src/main/java/com/google/maps/android/compose/MainActivity.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ package com.google.maps.android.compose
1616

1717
import android.content.Intent
1818
import android.os.Bundle
19+
import android.os.StrictMode
1920
import androidx.activity.ComponentActivity
2021
import androidx.activity.compose.setContent
2122
import androidx.activity.enableEdgeToEdge
@@ -48,6 +49,15 @@ class MainActivity : ComponentActivity() {
4849
override fun onCreate(savedInstanceState: Bundle?) {
4950
super.onCreate(savedInstanceState)
5051
enableEdgeToEdge()
52+
53+
StrictMode.setThreadPolicy(
54+
StrictMode.ThreadPolicy.Builder()
55+
.detectDiskReads()
56+
.penaltyLog()
57+
.penaltyDeath()
58+
.build()
59+
)
60+
5161
setContent {
5262
MapsComposeSampleTheme {
5363
Surface(

maps-compose/src/main/java/com/google/maps/android/compose/GoogleMap.kt

Lines changed: 105 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import androidx.compose.runtime.Composable
2626
import androidx.compose.runtime.Composition
2727
import androidx.compose.runtime.CompositionContext
2828
import androidx.compose.runtime.CompositionLocalProvider
29+
import androidx.compose.runtime.LaunchedEffect
2930
import androidx.compose.runtime.Stable
3031
import androidx.compose.runtime.getValue
3132
import androidx.compose.runtime.mutableStateOf
@@ -35,6 +36,7 @@ import androidx.compose.runtime.rememberCoroutineScope
3536
import androidx.compose.runtime.rememberUpdatedState
3637
import androidx.compose.runtime.setValue
3738
import androidx.compose.ui.Modifier
39+
import androidx.compose.ui.platform.LocalContext
3840
import androidx.compose.ui.platform.LocalInspectionMode
3941
import androidx.compose.ui.viewinterop.AndroidView
4042
import androidx.lifecycle.Lifecycle
@@ -53,9 +55,11 @@ import com.google.maps.android.compose.meta.AttributionId
5355
import com.google.maps.android.ktx.awaitMap
5456
import kotlinx.coroutines.CoroutineScope
5557
import kotlinx.coroutines.CoroutineStart
58+
import kotlinx.coroutines.Dispatchers
5659
import kotlinx.coroutines.Job
5760
import kotlinx.coroutines.awaitCancellation
5861
import kotlinx.coroutines.launch
62+
import kotlinx.coroutines.withContext
5963

6064
/**
6165
* A compose container for a [MapView].
@@ -109,101 +113,118 @@ public fun GoogleMap(
109113
return
110114
}
111115

112-
// rememberUpdatedState and friends are used here to make these values observable to
113-
// the subcomposition without providing a new content function each recomposition
114-
val mapClickListeners = remember { MapClickListeners() }.also {
115-
it.indoorStateChangeListener = indoorStateChangeListener
116-
it.onMapClick = onMapClick
117-
it.onMapLongClick = onMapLongClick
118-
it.onMapLoaded = onMapLoaded
119-
it.onMyLocationButtonClick = onMyLocationButtonClick
120-
it.onMyLocationClick = onMyLocationClick
121-
it.onPOIClick = onPOIClick
122-
}
116+
var isInitialized by remember { mutableStateOf(false) }
117+
val context = LocalContext.current
123118

124-
val mapUpdaterState = remember {
125-
MapUpdaterState(
126-
mergeDescendants,
127-
contentDescription,
128-
cameraPositionState,
129-
contentPadding,
130-
locationSource,
131-
properties,
132-
uiSettings,
133-
mapColorScheme?.value,
134-
)
135-
}.also {
136-
it.mergeDescendants = mergeDescendants
137-
it.contentDescription = contentDescription
138-
it.cameraPositionState = cameraPositionState
139-
it.contentPadding = contentPadding
140-
it.locationSource = locationSource
141-
it.mapProperties = properties
142-
it.mapUiSettings = uiSettings
143-
it.mapColorScheme = mapColorScheme?.value
119+
LaunchedEffect(Unit) {
120+
withContext(Dispatchers.IO) {
121+
MapsApiSettings.addInternalUsageAttributionId(context, AttributionId.VALUE)
122+
}
123+
isInitialized = true
144124
}
145125

146-
val parentComposition = rememberCompositionContext()
147-
val currentContent by rememberUpdatedState(content)
148-
var subcompositionJob by remember { mutableStateOf<Job?>(null) }
149-
val parentCompositionScope = rememberCoroutineScope()
150-
151-
AndroidView(
152-
modifier = modifier,
153-
factory = { context ->
154-
MapView(context, googleMapOptionsFactory()) .also { mapView ->
155-
MapsApiSettings.addInternalUsageAttributionId(context, AttributionId.VALUE )
156-
val componentCallbacks = object : ComponentCallbacks2 {
157-
override fun onConfigurationChanged(newConfig: Configuration) {}
158-
@Deprecated("Deprecated in Java", ReplaceWith("onTrimMemory(level)"))
159-
override fun onLowMemory() { mapView.onLowMemory() }
160-
override fun onTrimMemory(level: Int) { mapView.onLowMemory() }
161-
}
162-
context.registerComponentCallbacks(componentCallbacks)
163-
164-
val lifecycleObserver = MapLifecycleEventObserver(mapView)
126+
if (isInitialized) {
127+
// rememberUpdatedState and friends are used here to make these values observable to
128+
// the subcomposition without providing a new content function each recomposition
129+
val mapClickListeners = remember { MapClickListeners() }.also {
130+
it.indoorStateChangeListener = indoorStateChangeListener
131+
it.onMapClick = onMapClick
132+
it.onMapLongClick = onMapLongClick
133+
it.onMapLoaded = onMapLoaded
134+
it.onMyLocationButtonClick = onMyLocationButtonClick
135+
it.onMyLocationClick = onMyLocationClick
136+
it.onPOIClick = onPOIClick
137+
}
165138

166-
mapView.tag = MapTagData(componentCallbacks, lifecycleObserver)
139+
val mapUpdaterState = remember {
140+
MapUpdaterState(
141+
mergeDescendants,
142+
contentDescription,
143+
cameraPositionState,
144+
contentPadding,
145+
locationSource,
146+
properties,
147+
uiSettings,
148+
mapColorScheme?.value,
149+
)
150+
}.also {
151+
it.mergeDescendants = mergeDescendants
152+
it.contentDescription = contentDescription
153+
it.cameraPositionState = cameraPositionState
154+
it.contentPadding = contentPadding
155+
it.locationSource = locationSource
156+
it.mapProperties = properties
157+
it.mapUiSettings = uiSettings
158+
it.mapColorScheme = mapColorScheme?.value
159+
}
167160

168-
// Only register for [lifecycleOwner]'s lifecycle events while MapView is attached
169-
val onAttachStateListener = object : View.OnAttachStateChangeListener {
170-
private var lifecycle: Lifecycle? = null
161+
val parentComposition = rememberCompositionContext()
162+
val currentContent by rememberUpdatedState(content)
163+
var subcompositionJob by remember { mutableStateOf<Job?>(null) }
164+
val parentCompositionScope = rememberCoroutineScope()
165+
166+
AndroidView(
167+
modifier = modifier,
168+
factory = { context ->
169+
MapView(context, googleMapOptionsFactory()).also { mapView ->
170+
MapsApiSettings.addInternalUsageAttributionId(context, AttributionId.VALUE)
171+
val componentCallbacks = object : ComponentCallbacks2 {
172+
override fun onConfigurationChanged(newConfig: Configuration) {}
173+
174+
@Deprecated("Deprecated in Java", ReplaceWith("onTrimMemory(level)"))
175+
override fun onLowMemory() {
176+
mapView.onLowMemory()
177+
}
171178

172-
override fun onViewAttachedToWindow(mapView: View) {
173-
lifecycle = mapView.findViewTreeLifecycleOwner()!!.lifecycle.also {
174-
it.addObserver(lifecycleObserver)
179+
override fun onTrimMemory(level: Int) {
180+
mapView.onLowMemory()
175181
}
176182
}
183+
context.registerComponentCallbacks(componentCallbacks)
184+
185+
val lifecycleObserver = MapLifecycleEventObserver(mapView)
186+
187+
mapView.tag = MapTagData(componentCallbacks, lifecycleObserver)
188+
189+
// Only register for [lifecycleOwner]'s lifecycle events while MapView is attached
190+
val onAttachStateListener = object : View.OnAttachStateChangeListener {
191+
private var lifecycle: Lifecycle? = null
177192

178-
override fun onViewDetachedFromWindow(v: View) {
179-
lifecycle?.removeObserver(lifecycleObserver)
180-
lifecycle = null
181-
lifecycleObserver.moveToBaseState()
193+
override fun onViewAttachedToWindow(mapView: View) {
194+
lifecycle = mapView.findViewTreeLifecycleOwner()!!.lifecycle.also {
195+
it.addObserver(lifecycleObserver)
196+
}
197+
}
198+
199+
override fun onViewDetachedFromWindow(v: View) {
200+
lifecycle?.removeObserver(lifecycleObserver)
201+
lifecycle = null
202+
lifecycleObserver.moveToBaseState()
203+
}
182204
}
183-
}
184205

185-
mapView.addOnAttachStateChangeListener(onAttachStateListener)
186-
}
187-
},
188-
onReset = { /* View is detached. */ },
189-
onRelease = { mapView ->
190-
val (componentCallbacks, lifecycleObserver) = mapView.tagData
191-
mapView.context.unregisterComponentCallbacks(componentCallbacks)
192-
lifecycleObserver.moveToDestroyedState()
193-
mapView.tag = null
194-
},
195-
update = { mapView ->
196-
if (subcompositionJob == null) {
197-
subcompositionJob = parentCompositionScope.launchSubcomposition(
198-
mapUpdaterState,
199-
parentComposition,
200-
mapView,
201-
mapClickListeners,
202-
currentContent,
203-
)
204-
}
205-
}
206-
)
206+
mapView.addOnAttachStateChangeListener(onAttachStateListener)
207+
}
208+
},
209+
onReset = { /* View is detached. */ },
210+
onRelease = { mapView ->
211+
val (componentCallbacks, lifecycleObserver) = mapView.tagData
212+
mapView.context.unregisterComponentCallbacks(componentCallbacks)
213+
lifecycleObserver.moveToDestroyedState()
214+
mapView.tag = null
215+
},
216+
update = { mapView ->
217+
if (subcompositionJob == null) {
218+
subcompositionJob = parentCompositionScope.launchSubcomposition(
219+
mapUpdaterState,
220+
parentComposition,
221+
mapView,
222+
mapClickListeners,
223+
currentContent,
224+
)
225+
}
226+
})
227+
}
207228
}
208229

209230
/**

0 commit comments

Comments
 (0)