android-maps-compose: When used in a LazyColumn, GoogleMap cannot be scrolled vertically

Thanks for your work on this one, it’s really great to finally have an official Compose wrapper for Google Maps!

In our use case, we have a custom GoogleMaps wrapper using AndroidView that sits inside a LazyColumn. It is able to be scrolled vertically and horizontally without scrolling the LazyColumn by using a transparent view and calling .requestDisallowInterceptTouchEvent(true) on the parent.

We are hoping to switch to this, but we noticed it still suffers from the original issue we had to work around. Is usage in a LazyColumn a supported use case and is there a more Compose-friendly way to get nested scrolling working properly with this wrapper?

Thanks!

Steps to reproduce

  1. Include GoogleMap in a LazyColumn
  2. Vertically drag on the map to attempt to scroll it
  3. Observe the LazyColumn scrolls instead of the map

Thanks!

About this issue

  • Original URL
  • State: open
  • Created 2 years ago
  • Reactions: 1
  • Comments: 16 (6 by maintainers)

Most upvoted comments

I successfully solved it with cameraPositionState: Column ( .verticalScroll(rememberScrollState(), enabled = !cameraPositionState.isMoving) )

I wasn’t able to get it working exactly like this, but the experimental motionEventSpy worked like a charm:

var columnScrollingEnabled: Boolean by remember { mutableStateOf(true) }

...

LazyColumn(userScrollEnabled = columnScrollingEnabled)

...

item {
    GoogleMap(Modifier.motionEventSpy {
        when (it.action) {
            MotionEvent.ACTION_DOWN -> {
                columnScrollingEnabled = false
            }
            MotionEvent.ACTION_UP -> {
                columnScrollingEnabled = true
            }
        }
    },
...

By @ptornhult , This worked for me

Sorry for the very late reply here, I’ve finally had some time to circle back to this library.

In #78 a solution was added to allow for scrolling working inside a normal Column. The solution doesn’t work for LazyColumn, but I’ve found it can easily be adapted to work. Just swap out the MapInColumn with the code below in the test app to see it in action. Seems to work well!

If others can verify this works for them, I’d like to see the docs just updated to show something like this as an example, and then we can again close this issue. Thanks all!

@OptIn(ExperimentalComposeUiApi::class)
@Composable
fun MapInLazyColumn(modifier: Modifier = Modifier,
                        cameraPositionState: CameraPositionState,
                        columnScrollingEnabled: Boolean,
                        onMapTouched: () -> Unit,
                        onMapLoaded: () -> Unit,) {
    var isMapLoaded by remember { mutableStateOf(false) }
    LazyColumn(modifier, userScrollEnabled = columnScrollingEnabled) {
        item {
            Box(
                Modifier
                    .fillMaxWidth()
                    .height(200.dp)
            ) {
                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) {
                    AnimatedVisibility(
                        modifier = Modifier
                            .fillMaxSize(),
                        visible = !isMapLoaded,
                        enter = EnterTransition.None,
                        exit = fadeOut()
                    ) {
                        CircularProgressIndicator(
                            modifier = Modifier
                                .background(MaterialTheme.colors.background)
                                .wrapContentSize()
                        )
                    }
                }
            }
        }

        items(100) { item ->
            Text("$item", modifier = Modifier
                .padding(start = 10.dp, bottom = 10.dp)
                .testTag("Item $item"))
        }
    }
}

I wasn’t able to get it working exactly like this, but the experimental motionEventSpy worked like a charm:

var columnScrollingEnabled: Boolean by remember { mutableStateOf(true) }

...

LazyColumn(userScrollEnabled = columnScrollingEnabled)

...

item {
    GoogleMap(Modifier.motionEventSpy {
        when (it.action) {
            MotionEvent.ACTION_DOWN -> {
                columnScrollingEnabled = false
            }
            MotionEvent.ACTION_UP -> {
                columnScrollingEnabled = true
            }
        }
    },
...

I’d like to request for this issue to be reopened, as it has not been adequately addressed. There is a documented workaround available now thanks to @barbeau, but that’s all it is, a workaround, and it’s fairly messy. It’s also unclear what parts of the elaborate sample code are needed to actually make it work and what can be left out. (I figured it out via my own minimal sample project.)

A Compose component that is draggable by nature should expose draggable behavior out of the box even in the context of nested scrolling, without additional configuration needed.

A custom wrapper using requestDisallowInterceptTouchEvent()as described in the OP may be a good approach, and as @arriolac suggested it can be controlled by a flag for the time being to gather feedback. This is the approach that I’ve used for years, but maps-compose has taken it away for now.

Another, preferable, avenue that should be fully explored is the standard Compose nested scrolling support that has been worked on here: https://issuetracker.google.com/issues/174348612. The work is ongoing and I have pointed out its current inapplicability to MapView, so this would be the time to connect with the people behind it and see if it can be done.

the experimental motionEventSpy worked like a charm:

var columnScrollingEnabled: Boolean by remember { mutableStateOf(true) }

...

LazyColumn(userScrollEnabled = columnScrollingEnabled)

...

item {
    GoogleMap(Modifier.motionEventSpy {
        when (it.action) {
            MotionEvent.ACTION_DOWN -> {
                columnScrollingEnabled = false
            }
            MotionEvent.ACTION_UP -> {
                columnScrollingEnabled = true
            }
        }
    },
...

Indeed it works fine. Tip for those who have a map nested in multiple composable layers, but don’t want to pass state parameter through each. Use CompositionLocalProvider to incapsulate logic of accessing/providing current pressed state in a custom class.