Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

When adding multiple Clustering composables: onClusterItemClick callbacks are only done for the last added Clustering #294

Closed
bluevoidnl opened this issue Mar 20, 2023 · 4 comments · Fixed by #396
Assignees
Labels
priority: p1 Important issue which blocks shipping the next release. Will be fixed prior to next release. released type: bug Error or flaw in code with unintended results or allowing sub-optimal usage patterns.

Comments

@bluevoidnl
Copy link

bluevoidnl commented Mar 20, 2023

Environment details

Google maps sdk: android-maps-compose version 2.11.2 on Android emulator API 31
com.google.maps.android:maps-compose:2.11.2

Steps to reproduce

When adding multiple Clustering composables, (to cluster equal markertypes ) the onClusterItemClick is only called for the last added Clustering.
Rendering of markers and clusters for different clusterings all works as expected, only click handeling fails for me, so I can not put in my own selected state for a marker.

To verify the issue outside of my code I altered the MapClusteringActivity example by adding two Clustering composables for 1 map.
See code below. When clicking on a marker of 1 clustering logcat will show the debug message: "Cluster item clicked! $it". When clicking an item of the other clustering, no debug message is shown.

Probable cause

I have tracked the cause of the issue down to the MapApplier findInputCallback() extension method.

What happens is that for each Clustering an InputHandlerNode is added to decorations. When a marker is clicked findInputCallback() keeps searching for InputHandlerNode and always returns the last InputHandlerNode that is added.

Then the call continues in MarkerManage.java.onMarkerClick(Marker) where the marker is not found in the mAllObjects hashMap (as it is the MarkerManager of the last added Clustering) and the flow stops.

Copy of MapApplier findInputCallback() extension method:

private inline fun <reified NodeT : MapNode, reified I, O> Iterable<MapNode>.findInputCallback(
    nodeMatchPredicate: (NodeT) -> Boolean,
    nodeInputCallback: NodeT.() -> ((I) -> O)?,
    inputHandlerCallback: InputHandlerNode.() -> ((I) -> O)?
): ((I) -> O)? {
    var callback: ((I) -> O)? = null
    for (item in this) {
        if (item is NodeT && nodeMatchPredicate(item)) {
            // Found a matching node
            return nodeInputCallback(item)
        } else if (item is InputHandlerNode ) {
            // Found an input handler, but keep looking for matching nodes
            callback = inputHandlerCallback(item)
        }
    }
    return callback
}

Reproduce code:

package com.google.maps.android.compose

import android.os.Bundle
import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.google.android.gms.maps.model.CameraPosition
import com.google.android.gms.maps.model.LatLng
import com.google.maps.android.clustering.ClusterItem
import com.google.maps.android.compose.clustering.Clustering
import kotlin.random.Random

private val TAG = MapClusteringActivity::class.simpleName

class MapClusteringActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            GoogleMapClustering()
        }
    }
}

@Composable
fun GoogleMapClustering() {
    val items = remember { mutableStateListOf<MyItem>() }
    val items2 = remember { mutableStateListOf<MyItem>() }
    LaunchedEffect(Unit) {
        for (i in 1..10) {
            val position = LatLng(
                singapore2.latitude + Random.nextFloat(),
                singapore2.longitude + Random.nextFloat(),
            )
            items.add(MyItem(position, "Marker $i", "Snippet"))
            val position2 = LatLng(
                position.latitude,
                position.longitude + 0.1f,
            )
            items2.add(MyItem(position2, "Marker $i copy", "Snippet 2"))
        }
    }

    GoogleMapClustering(items = items, items2 = items2)
}

@OptIn(MapsComposeExperimentalApi::class)
@Composable
fun GoogleMapClustering(items: List<MyItem>, items2: List<MyItem>) {
    GoogleMap(
        modifier = Modifier.fillMaxSize(),
        cameraPositionState = rememberCameraPositionState {
            position = CameraPosition.fromLatLngZoom(singapore, 10f)
        }
    ) {

        cluster(items)
        cluster(items2)
        MarkerInfoWindow(
            state = rememberMarkerState(position = singapore),
            onClick = {
                Log.d(TAG, "Non-cluster marker clicked! $it")
                true
            }
        )
    }
}

@Composable
@OptIn(MapsComposeExperimentalApi::class)
private fun cluster(items: List<MyItem>) {
    Clustering(
        items = items,
        // Optional: Handle clicks on clusters, cluster items, and cluster item info windows
        onClusterClick = {
            Log.d(TAG, "Cluster clicked! $it")
            false
        },
        onClusterItemClick = {
            Log.d(TAG, "Cluster item clicked! $it")
            false
        },
        onClusterItemInfoWindowClick = {
            Log.d(TAG, "Cluster item info window clicked! $it")
        },
        // Optional: Custom rendering for clusters
        clusterContent = { cluster ->
            Surface(
                Modifier.size(40.dp),
                shape = CircleShape,
                color = Color.Blue,
                contentColor = Color.White,
                border = BorderStroke(1.dp, Color.White)
            ) {
                Box(contentAlignment = Alignment.Center) {
                    Text(
                        "%,d".format(cluster.size),
                        fontSize = 16.sp,
                        fontWeight = FontWeight.Black,
                        textAlign = TextAlign.Center
                    )
                }
            }
        },
        // Optional: Custom rendering for non-clustered items
        clusterItemContent = null
    )
}

data class MyItem(
    val itemPosition: LatLng,
    val itemTitle: String,
    val itemSnippet: String,
) : ClusterItem {
    override fun getPosition(): LatLng =
        itemPosition

    override fun getTitle(): String =
        itemTitle

    override fun getSnippet(): String =
        itemSnippet
}
@bluevoidnl bluevoidnl added triage me I really want to be triaged. type: bug Error or flaw in code with unintended results or allowing sub-optimal usage patterns. labels Mar 20, 2023
@wangela
Copy link
Member

wangela commented Mar 20, 2023

If you would like to upvote the priority of this issue, please comment below or react with 👍 so we can see what is popular when we triage.

@bluevoidnl Thank you for opening this issue. 🙏
Please check out these other resources that might help you get to a resolution in the meantime:

This is an automated message, feel free to ignore.

@DSteve595 DSteve595 added priority: p2 Moderately-important priority. Fix may not be included in next release. and removed triage me I really want to be triaged. labels Mar 20, 2023
@bigmeco
Copy link

bigmeco commented Jul 7, 2023

Yes, the same problem on the com.google.maps.android version:maps-compose:2.11.4

@kikoso kikoso added priority: p1 Important issue which blocks shipping the next release. Will be fixed prior to next release. and removed priority: p2 Moderately-important priority. Fix may not be included in next release. labels Aug 18, 2023
@kikoso
Copy link
Collaborator

kikoso commented Sep 12, 2023

@bigmeco , @bluevoidnl , the following PR should fix this issue:

#396

We will get it discussed and eventually merged soon. Thanks for the patience!

@googlemaps-bot
Copy link
Contributor

🎉 This issue has been resolved in version 2.14.1 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
priority: p1 Important issue which blocks shipping the next release. Will be fixed prior to next release. released type: bug Error or flaw in code with unintended results or allowing sub-optimal usage patterns.
Projects
None yet
6 participants