// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.example.GMC
import android.os.Bundle
import android.util.Log
import android.view.MotionEvent
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.animation.EnterTransition
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.Button
import androidx.compose.material.CircularProgressIndicator
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.input.pointer.pointerInteropFilter
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.unit.dp
import com.google.android.gms.maps.model.CameraPosition
import com.google.android.gms.maps.model.LatLng
import com.google.android.gms.maps.model.Marker
import com.google.android.gms.maps.model.StrokeStyle
import com.google.android.gms.maps.model.StyleSpan
import com.google.maps.android.compose.CameraPositionState
import com.google.maps.android.compose.GoogleMap
import com.google.maps.android.compose.MapProperties
import com.google.maps.android.compose.MapType
import com.google.maps.android.compose.MapUiSettings
import com.google.maps.android.compose.MarkerInfoWindowContent
import com.google.maps.android.compose.rememberCameraPositionState
import com.google.maps.android.compose.rememberMarkerState
private const val TAG = "ScrollingMapActivity"
val singapore = LatLng(1.3588227, 103.8742114)
val singapore2 = LatLng(1.40, 103.77)
val singapore3 = LatLng(1.45, 103.77)
val singapore4 = LatLng(1.50, 103.77)
val singapore5 = LatLng(1.3418, 103.8461)
val singapore6 = LatLng(1.3430, 103.8844)
val singapore7 = LatLng(1.3430, 103.9116)
val singapore8 = LatLng(1.3300, 103.8624)
val singapore9 = LatLng(1.3200, 103.8541)
val singapore10 = LatLng(1.3200, 103.8765)
val defaultCameraPosition = CameraPosition.fromLatLngZoom(singapore, 11f)
val styleSpan = StyleSpan(
StrokeStyle.gradientBuilder(
Color.Red.toArgb(),
Color.Green.toArgb(),
).build(),
)
class MapInColumnActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
// Observing and controlling the camera's state can be done with a CameraPositionState
val cameraPositionState = rememberCameraPositionState {
position = defaultCameraPosition
}
var columnScrollingEnabled by remember { mutableStateOf(true) }
// Use a LaunchedEffect keyed on the camera moving state to enable column scrolling when the camera stops moving
LaunchedEffect(cameraPositionState.isMoving) {
if (!cameraPositionState.isMoving) {
columnScrollingEnabled = true
Log.d(TAG, "Map camera stopped moving - Enabling column scrolling...")
}
}
MapInColumn(
modifier = Modifier.fillMaxSize(),
cameraPositionState,
columnScrollingEnabled = columnScrollingEnabled,
onMapTouched = {
columnScrollingEnabled = false
Log.d(
TAG,
"User touched map - Disabling column scrolling after user touched this Box..."
)
},
onMapLoaded = { }
)
}
}
}
@OptIn(ExperimentalComposeUiApi::class)
@Composable
fun MapInColumn(
modifier: Modifier = Modifier,
cameraPositionState: CameraPositionState,
columnScrollingEnabled: Boolean,
onMapTouched: () -> Unit,
onMapLoaded: () -> Unit,
) {
Surface(
modifier = modifier,
color = MaterialTheme.colors.background
) {
var isMapLoaded by remember { mutableStateOf(false) }
var showMap by remember { mutableStateOf(true) }
Column(
Modifier
.fillMaxSize()
.verticalScroll(
rememberScrollState(),
columnScrollingEnabled
),
horizontalAlignment = Alignment.Start
) {
Spacer(modifier = Modifier.padding(10.dp))
Button(onClick = { showMap = !showMap }) {
Text(text = "Show/Hide map")
}
for (i in 1..20) {
Text(
text = "Item $i",
modifier = Modifier
.padding(start = 10.dp)
.testTag("Item $i")
)
}
Spacer(modifier = Modifier.padding(10.dp))
Box(
Modifier
.fillMaxWidth()
.height(200.dp)
) {
if (showMap) GoogleMapViewInColumn(
modifier = Modifier
.fillMaxSize()
.testTag("Map")
.pointerInteropFilter(
onTouchEvent = {
when (it.action) {
MotionEvent.ACTION_DOWN -> {
onMapTouched()
false
}
else -> {
Log.d(
TAG,
"MotionEvent ${it.action} - this never triggers."
)
true
}
}
}
),
cameraPositionState = cameraPositionState,
onMapLoaded = {
isMapLoaded = true
onMapLoaded()
},
)
if (!isMapLoaded) {
androidx.compose.animation.AnimatedVisibility(
modifier = Modifier
.fillMaxSize(),
visible = !isMapLoaded,
enter = EnterTransition.None,
exit = fadeOut()
) {
CircularProgressIndicator(
modifier = Modifier
.background(MaterialTheme.colors.background)
.wrapContentSize()
)
}
}
}
Spacer(modifier = Modifier.padding(10.dp))
for (i in 21..40) {
Text(
text = "Item $i",
modifier = Modifier
.padding(start = 10.dp)
.testTag("Item $i")
)
}
Spacer(modifier = Modifier.padding(10.dp))
}
}
}
@Composable
private fun GoogleMapViewInColumn(
modifier: Modifier,
cameraPositionState: CameraPositionState,
onMapLoaded: () -> Unit,
) {
val singaporeState = rememberMarkerState(position = singapore)
var uiSettings by remember { mutableStateOf(MapUiSettings(compassEnabled = false)) }
var mapProperties by remember {
mutableStateOf(MapProperties(mapType = MapType.NORMAL))
}
GoogleMap(
modifier = modifier,
cameraPositionState = cameraPositionState,
properties = mapProperties,
uiSettings = uiSettings,
onMapLoaded = onMapLoaded
) {
// Drawing on the map is accomplished with a child-based API
val markerClick: (Marker) -> Boolean = {
Log.d(TAG, "${it.title} was clicked")
cameraPositionState.projection?.let { projection ->
Log.d(TAG, "The current projection is: $projection")
}
false
}
MarkerInfoWindowContent(
state = singaporeState,
title = "Singapore",
onClick = markerClick,
draggable = true,
) {
Text(it.title ?: "Title", color = Color.Red)
}
}
}
java.lang.OutOfMemoryError: Failed to allocate a 118720 byte allocation with 106056 free bytes and 103KB until OOM, target footprint 50331648, growth limit 50331648
at m.fdu.d(:com.google.android.gms.policy_maps_core_dynamite@241610205@241610202042.636179997.636179997:22)
at m.fea.c(:com.google.android.gms.policy_maps_core_dynamite@241610205@241610202042.636179997.636179997:13)
at m.enc.k(:com.google.android.gms.policy_maps_core_dynamite@241610205@241610202042.636179997.636179997:8)
at m.etl.a(:com.google.android.gms.policy_maps_core_dynamite@241610205@241610202042.636179997.636179997:13)
at m.eum.d(:com.google.android.gms.policy_maps_core_dynamite@241610205@241610202042.636179997.636179997:116)
at m.erb.run(:com.google.android.gms.policy_maps_core_dynamite@241610205@241610202042.636179997.636179997:164)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:462)
at m.cab.run(:com.google.android.gms.policy_maps_core_dynamite@241610205@241610202042.636179997.636179997:12)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
at m.car.run(:com.google.android.gms.policy_maps_core_dynamite@241610205@241610202042.636179997.636179997:40)
Environment details
Specify the API at the beginning of the title (for example, "Places: ...")
Google Maps Compose Android
OS type and version
Mac OS Sonoma 14.5
Android API 29 emulator
Library version and other environment information
gms-maps-compose = "com.google.maps.android:maps-compose:5.0.3"
gms-maps-compose-utils = "com.google.maps.android:maps-compose-utils:5.0.3"
Steps to reproduce
Code example
Stack trace
Note:
I checked in Memory Profiler that memory increases from around 350 MB to 450MB and then crashes.
It is easy to reproduce, need some older/low end device (tested on emulators).
It blocks us of releasing feature to the client.
I used your sample code from GoogleMaps repo:https://github.com/googlemaps/android-maps-compose
just modifying by adding Button to change visibility state of Google maps in column.
Could you fix it, please? Or at least give us some temporary quick fix solution?
Thank you in advance.