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

fix: Fixed scope cancellation that leads to bug when invalidating composable doesn't invalidate cluster #538

Merged
merged 1 commit into from
Apr 23, 2024

Conversation

y9san9
Copy link
Contributor

@y9san9 y9san9 commented Mar 21, 2024

Thank you for opening a Pull Request!


Before submitting your PR, there are a few things you can do to make sure it goes smoothly:

  • Make sure to open a GitHub issue as a bug/feature request before writing your code! That way we can discuss the change, evaluate designs, and agree on the general idea
  • Ensure the tests and linter pass
  • Code coverage does not decrease (if any source code was changed)
  • Appropriate docs were updated (if necessary)

Fixes #537 🦕

… when invalidating composable doesn't invalidate marker/cluster
Copy link

google-cla bot commented Mar 21, 2024

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@y9san9
Copy link
Contributor Author

y9san9 commented Mar 21, 2024

Signed CLA

@pioPirrung
Copy link

@dkhawk any updates here? This also seems to fix an issue related to canceling the image loader of coil. I think multiple people are interested in this fix

Issues: #210 #385 #542

@patrickcapadmi
Copy link

Is there any movement on this PR? We'd quite like to get this fix in to help images load in more reliably.

@y9san9
Copy link
Contributor Author

y9san9 commented Apr 15, 2024

By the way, my case was also image loading :)
But I have some other questions regarding how builtin clustering works, so I just copied the code and made my modifications

@googlemaps googlemaps deleted a comment from huanghaieeee Apr 15, 2024
@kikoso
Copy link
Collaborator

kikoso commented Apr 15, 2024

@dkhawk any updates here? This also seems to fix an issue related to canceling the image loader of coil. I think multiple people are interested in this fix

Issues: #210 #385 #542

@pioPirrung , this does not seem to fix the Coil related stuff. The image is still not being rendered.

@pioPirrung
Copy link

pioPirrung commented Apr 15, 2024

@dkhawk any updates here? This also seems to fix an issue related to canceling the image loader of coil. I think multiple people are interested in this fix
Issues: #210 #385 #542

@pioPirrung , this does not seem to fix the Coil related stuff. The image is still not being rendered.

hey @kikoso, thanks for your quick reply and checking onto this issue. I tested it again locally and for me everything runs fine. The DebugLogger of coil does indeed still print that the request has been canceled for the given urls, but every image is shown instantly and correctly for me with this changes in the PR. I tested it now at least 10 times in a row. 🤷🏻‍♂️ Even with fresh install when the image cache is empty works every time. Without the changes in this PR I can reproduce that the image not getting rendered every time.

I'm using a NonHierarchicalViewBasedAlgorithm for the Clustering as someone in the issues mentioned that this helped him to solve the problem. So maybe this is why its working for me 🤷🏻‍♂️

@kikoso
Copy link
Collaborator

kikoso commented Apr 16, 2024

@pioPirrung, this does not seem to fix the Async loading of a Coil image, see the following code snippet:

GoogleMap(
               modifier = Modifier.fillMaxSize()
           ) {
               MarkerComposable(
                   keys = arrayOf("singapore4"),
                   state = MarkerState(
                       position = LatLng(
                           25.0, 25.0
                       )
                   ),
               ) {
                   Column(
                       horizontalAlignment = Alignment.CenterHorizontally,
                       modifier = Modifier
                           .clip(RoundedCornerShape(8.dp))
                           .background(Color.White),
                   ) {

                       AsyncImage(
                           model = "https://picsum.photos/200/300",
                           contentDescription = null,
                           contentScale = ContentScale.Crop,
                           modifier = Modifier
                               .padding(top = 4.dp, start = 6.dp, end = 6.dp)
                               .size(50.dp)
                               .clip(CircleShape)
                               .border(2.dp, Color.Black, CircleShape)
                       )
                       Text(
                           maxLines = 1,
                           textAlign = TextAlign.Center,
                           text = "Hello",
                           modifier = Modifier
                               .width(60.dp),
                           style = MaterialTheme.typography.body1
                       )
                   }
               }
           }

Under which circumstances is it fixing it for you? Can you provide an snippet, ideally?

@pioPirrung
Copy link

pioPirrung commented Apr 17, 2024

Thanks for your reply @kikoso. I did not tried it with plain MarkerComposable. For me (and I think the PR only targets Clustering) the fix works under the circumstance while using a Cluster. As I mentioned above I'm using a NonHierarchicalViewBasedAlgorithm. I did not tried it with the default one for Clustering.

Code Snippet
@OptIn(MapsComposeExperimentalApi::class)
@Composable
fun TestMapScreen() {
    val cameraPositionState = rememberCameraPositionState {
        position = CameraPosition.fromLatLngZoom(LatLng(49.897288, 10.8783964), 7f)
    }

    Box(
        modifier = Modifier.fillMaxSize()
    ) {
        GoogleMap(
            modifier = Modifier
                .fillMaxSize(),
            onMapLoaded = { },
            cameraPositionState = cameraPositionState,
            uiSettings = MapUiSettings(
                myLocationButtonEnabled = false,
                mapToolbarEnabled = false,
                zoomControlsEnabled = false
            )
        ) {
            val configuration = LocalConfiguration.current
            val screenHeight = configuration.screenHeightDp.dp
            val screenWidth = configuration.screenWidthDp.dp
            val clusterManager = rememberClusterManager<MyItem>()
            clusterManager?.setAlgorithm(
                NonHierarchicalViewBasedAlgorithm(
                    screenWidth.value.toInt(),
                    screenHeight.value.toInt()
                )
            )
            val renderer = rememberClusterRenderer(
                clusterContent = { cluster ->
                    MyItemClusterContent(
                        modifier = Modifier
                            .size(30.dp, 38.dp)
                            .clip(MarkerShape())
                            .background(
                                Color(
                                    red = 255,
                                    green = 165,
                                    blue = 0
                                )
                            ),
                        cluster = cluster
                    )
                },
                clusterItemContent = { item ->
                    MyItemMarker(item)
                },
                clusterManager = clusterManager,
            )
            SideEffect {
                clusterManager ?: return@SideEffect
                clusterManager.setOnClusterItemClickListener {
                    true
                }
            }
            SideEffect {
                if (clusterManager?.renderer != renderer && renderer != null) {
                    (renderer as DefaultClusterRenderer).minClusterSize = 2
                    clusterManager?.renderer = renderer
                }
            }

            if (clusterManager != null) {
                Clustering(
                    items = previewData,
                    clusterManager = clusterManager,
                )
            }
        }
    }
}

@Composable
fun MyItemMarker(item: MyItem) {
    val context = LocalContext.current
    Column(
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Box(
            modifier = Modifier
                .size(30.dp, 38.dp)
                .clip(MarkerShape())
                .background(Color.Green),
        ) {
            AsyncImage(
                modifier = Modifier
                    .size(30.dp)
                    .padding(5.dp),
                model = ImageRequest.Builder(context)
                    .data(item.itemUrl)
                    .allowHardware(false)
                    .crossfade(false)
                    .error(R.drawable.placeholder)
                    .diskCacheKey(item.id)
                    .memoryCacheKey(item.id)
                    .fallback(R.drawable.placeholder)
                    .placeholder(R.drawable.placeholder)
                    .build(),
                contentDescription = null
            )
        }
        Column(
            modifier = Modifier.width(150.dp),
        ) {
            Text(
                modifier = Modifier.fillMaxWidth(),
                text = item.itemTitle,
                style = MaterialTheme.typography.labelSmall,
                textAlign = TextAlign.Center
            )
        }
    }
}

@Composable
fun MyItemClusterContent(
    modifier: Modifier = Modifier,
    cluster: Cluster<MyItem>
) {
    Column(
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Box(
            modifier = modifier
        ) {
            Text(
                modifier = Modifier
                    .padding(5.dp)
                    .fillMaxWidth(),
                text = "%,d".format(cluster.size),
                fontSize = 16.sp,
                fontWeight = FontWeight.Black,
                textAlign = TextAlign.Center
            )
        }
        Column(
            modifier = Modifier.width(130.dp),
        ) {
            Text(
                modifier = Modifier.fillMaxWidth(),
                text = cluster.items.stream().findFirst().get().title,
                style = MaterialTheme.typography.bodyMedium,
                textAlign = TextAlign.Center
            )
            Text(
                modifier = Modifier.fillMaxWidth(),
                text = "+${cluster.size - 1} more",
                style = MaterialTheme.typography.labelSmall,
                textAlign = TextAlign.Center
            )
        }
    }
}

data class MyItem(
    val id: String = UUID.randomUUID().toString(),
    val itemPosition: LatLng,
    val itemTitle: String,
    val itemUrl: String = "https://picsum.photos/200/300",
) : ClusterItem {
    override fun getPosition(): LatLng =
        itemPosition

    override fun getTitle(): String =
        itemTitle

    override fun getSnippet(): String? = null

    override fun getZIndex(): Float? = null
}

val markerMain = LatLng(49.89, 10.87)
val previewData = listOf(
    MyItem(
        itemPosition = LatLng(
            markerMain.latitude + Random.nextFloat(),
            markerMain.longitude + Random.nextFloat(),
        ),
        itemTitle = "Hello"
    ),
    MyItem(
        itemPosition = LatLng(
            markerMain.latitude + Random.nextFloat(),
            markerMain.longitude + Random.nextFloat(),
        ),
        itemTitle = "Hello"
    ),
    MyItem(
        itemPosition = LatLng(
            markerMain.latitude + Random.nextFloat(),
            markerMain.longitude + Random.nextFloat(),
        ),
        itemTitle = "Hello"
    ),
    MyItem(
        itemPosition = LatLng(
            markerMain.latitude + Random.nextFloat(),
            markerMain.longitude + Random.nextFloat(),
        ),
        itemTitle = "Hello"
    ),
    MyItem(
        itemPosition = LatLng(
            markerMain.latitude + Random.nextFloat(),
            markerMain.longitude + Random.nextFloat(),
        ),
        itemTitle = "Hello"
    ),
    MyItem(
        itemPosition = LatLng(
            markerMain.latitude + Random.nextFloat(),
            markerMain.longitude + Random.nextFloat(),
        ),
        itemTitle = "Hello"
    ),
    MyItem(
        itemPosition = LatLng(
            markerMain.latitude + Random.nextFloat(),
            markerMain.longitude + Random.nextFloat(),
        ),
        itemTitle = "Hello"
    ),
    MyItem(
        itemPosition = LatLng(
            markerMain.latitude + Random.nextFloat(),
            markerMain.longitude + Random.nextFloat(),
        ),
        itemTitle = "Hello"
    ),
    MyItem(
        itemPosition = LatLng(
            markerMain.latitude + Random.nextFloat(),
            markerMain.longitude + Random.nextFloat(),
        ),
        itemTitle = "Hello"
    ),
    MyItem(
        itemPosition = LatLng(
            markerMain.latitude + Random.nextFloat(),
            markerMain.longitude + Random.nextFloat(),
        ),
        itemTitle = "Hello"
    ),
    MyItem(
        itemPosition = LatLng(
            markerMain.latitude + Random.nextFloat(),
            markerMain.longitude + Random.nextFloat(),
        ),
        itemTitle = "Hello"
    ),
    MyItem(
        itemPosition = LatLng(
            markerMain.latitude + Random.nextFloat(),
            markerMain.longitude + Random.nextFloat(),
        ),
        itemTitle = "Hello"
    ),
    MyItem(
        itemPosition = LatLng(
            markerMain.latitude + Random.nextFloat(),
            markerMain.longitude + Random.nextFloat(),
        ),
        itemTitle = "Hello"
    ),
    MyItem(
        itemPosition = LatLng(
            markerMain.latitude + Random.nextFloat(),
            markerMain.longitude + Random.nextFloat(),
        ),
        itemTitle = "Hello"
    ),
    MyItem(
        itemPosition = LatLng(
            markerMain.latitude + Random.nextFloat(),
            markerMain.longitude + Random.nextFloat(),
        ),
        itemTitle = "Hello"
    ),
)

And here is also a video of proof.

Screen_recording_20240417_182257.mp4

@kikoso
Copy link
Collaborator

kikoso commented Apr 18, 2024

Hi @pioPirrung . I was a bit confused after reading this comment, since they are specifically targeting MarkerComposable.

The fix does indeed seem to prevent the Coil Coroutine from being canceled and let the image load from either network or cache (you can activate the debugger on Coil and search for RealImageLoader). It does not fix the related Coil issues (#210 #385 #542) on the MarkerComposable, but that can be tackled on a different PR.

Verifying that this does not have any non-intended behaviors.

@pioPirrung
Copy link

Hi @kikoso, thanks again for your really quick reply. Ah now I see, sorry for the confusion. Initially I thought that this PR would fix it for MarkerComposable and for Clustering, but this isn't the case, that's correct. So sorry for that!

@danprado
Copy link

danprado commented Apr 19, 2024

Confirming this fix worked for me when trying to load an AsyncImage with Coil.
Note: This fixes it for Clustering only. MarkerComposable is still broken.

@kikoso kikoso changed the title fix(close #537): Fixed scope cancellation that leads to bug when invalidating composable doesn't invalidate marker/cluster fix: Fixed scope cancellation that leads to bug when invalidating composable doesn't invalidate cluster Apr 23, 2024
@dkhawk dkhawk merged commit 5496365 into googlemaps:main Apr 23, 2024
8 of 9 checks passed
googlemaps-bot pushed a commit that referenced this pull request Apr 23, 2024
## [4.4.1](v4.4.0...v4.4.1) (2024-04-23)

### Bug Fixes

* **close #537:** fixed scope cancellation that leads to bug when invalidating composable doesn't invalidate marker/cluster ([#538](#538)) ([5496365](5496365)), closes [#537](#537)
@googlemaps-bot
Copy link
Contributor

🎉 This PR is included in version 4.4.1 🎉

The release is available on:

Your semantic-release bot 📦🚀

@jialbanc
Copy link

This issue is still unfixed when using only MarkerComposable

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Marker is not updating when it's getting recomposed
8 participants