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

Popup content sees previous value of staticCompositionLocal #4558

Closed
Omico opened this issue Mar 29, 2024 · 15 comments · Fixed by JetBrains/compose-multiplatform-core#1233
Closed
Assignees
Labels
bug Something isn't working reproduced

Comments

@Omico
Copy link
Contributor

Omico commented Mar 29, 2024

Compose Multiplatform: 1.6.1

2024-03-29_09-19-19.mp4
@Omico Omico added bug Something isn't working submitted labels Mar 29, 2024
@Omico Omico changed the title AlertDialog doesn't recompose immediately on desktop Material3 AlertDialog doesn't recompose immediately on desktop Mar 29, 2024
@m-sasha
Copy link
Contributor

m-sasha commented Mar 29, 2024

Can you post a short reproducer?

@Omico
Copy link
Contributor Author

Omico commented Mar 30, 2024

Hi @m-sasha, please take a look at https://github.com/Omico/issue-compose-multiplatform-4558. The issue only exists if we change the typography.

@Omico Omico changed the title Material3 AlertDialog doesn't recompose immediately on desktop Material3 AlertDialog doesn't recompose immediately on desktop, if we change the typography Mar 30, 2024
@m-sasha
Copy link
Contributor

m-sasha commented Mar 30, 2024

Can you post a short reproducer?

@Omico
Copy link
Contributor Author

Omico commented Mar 30, 2024

@m-sasha
Copy link
Contributor

m-sasha commented Mar 30, 2024

Reproducer:

val TestLocal = staticCompositionLocalOf { 0 }

fun main() = singleWindowApplication {
    var value by remember { mutableStateOf(1) }
    CompositionLocalProvider(TestLocal provides value) {
        println("Outside Value: ${TestLocal.current}")
        Popup(
            onDismissRequest = { },
        ) {
            println("Inside Value: ${TestLocal.current}")
            Row(verticalAlignment = Alignment.CenterVertically) {
                Button(
                    onClick = {
                        value = 2
                    },
                    content = { Text("2") },
                )
                Spacer(modifier = Modifier.width(8.dp))
                Button(
                    onClick = {
                        value = 1
                    },
                    content = { Text("1") },
                )

                Text(TestLocal.current.toString(), Modifier.padding(8.dp))
            }
        }
    }
}

Clicking the buttons, the value seen inside the Popup is always the previous value of the local.
If we change TestLocal to be a compositionLocalOf instead of staticCompositionLocalOf, the issue isn't seen.

@m-sasha m-sasha self-assigned this Mar 30, 2024
@m-sasha m-sasha changed the title Material3 AlertDialog doesn't recompose immediately on desktop, if we change the typography Popup content sees previous value of staticCompositionLocal Mar 30, 2024
@m-sasha
Copy link
Contributor

m-sasha commented Mar 30, 2024

The problem seems to be with a change introduced in JetBrains/compose-multiplatform-core#1086

In rememberComposeSceneLayer we update the layer's compositionLocalContext in a SideEffect, but this is too late because composition happens before SideEffect is executed.

Also, I don't entirely understand why a layer needs compositionLocalContext. Aren't the locals already visible through the parent composition (passed via compositionContext in scene.createLayer)? In Android, a popup only passes the parent composition when creating a popup. And if I remove this passing of currentCompositionLocalContext, the bug goes away, and composition locals still seem to be correctly passed into Popup.

@igordmn @MatkovIvan

@MatkovIvan
Copy link
Member

I don't entirely understand why a layer needs compositionLocalContext

There is no public (outside of runtime) way to extract it from compositionContext, I wrote about it in mentioned PR

Aren't the locals already visible through the parent composition (passed via compositionContext in scene.createLayer)?

Only if it IS parent composition (MultiLayerComposeScene) - it's not always a case for multiplatform, and cannot be for a general case (due to requirement of different coroutine context for different windows)

And if I remove this passing of currentCompositionLocalContext, the bug goes away, and composition locals still seem to be correctly passed into Popup.

Same as above - it will work ONLY for drawing Popup inslde same canvas (MultiLayerComposeScene)

@m-sasha
Copy link
Contributor

m-sasha commented Mar 30, 2024

Why does composition depend on where the composables are drawn (canvas)?

@MatkovIvan
Copy link
Member

Because in case of single canvas it reuses already existing environment/contexts, but in other cases it cannot do that

@m-sasha
Copy link
Contributor

m-sasha commented Apr 1, 2024

In Android, Popup creates a new (Android) View and in it a new Composition with the parent composition set to rememberCompositionContext(). See ComposeView.ensureCompositionCreated().

We, on the other hand, don't pass the parent composition; the new Composition is created with (a new) Recomposer as its parent. RootNodeOwner.setContent is called with parent set to BaseComposeScene.compositionContext, which is a newly created Recomposer.

That seems to be why the composition locals aren't propagated automatically. I imagine it has other implications, and possibly bugs, too.

@m-sasha
Copy link
Contributor

m-sasha commented Apr 2, 2024

I'd recommend

  • Short term: set the layer properties (layoutDirection, density and compositionLocalContext) immediately when rememberComposeSceneLayer is composed.
  • Long term: correctly set up the composition hierarchy, and get rid of compositionLocalContext.

@igordmn
Copy link
Collaborator

igordmn commented Apr 2, 2024

Long term: correctly set up the composition hierarchy, and get rid of compositionLocalContext.

We can't do that, because popup/dialog can have its recomposition cycle. For example, in a case when they are separate windows showed on another screen with a different refresh rate.

The short term solution seems the right solution. Even if the popup content is recomposed not in sync, it should see the most recent composition locals.

@m-sasha
Copy link
Contributor

m-sasha commented Apr 2, 2024

We can't do that, because popup/dialog can have its recomposition cycle. For example, in a case when they are separate windows showed on another screen with a different refresh rate.

I'm not convinced this necessarily means that we can't have a correct composition hierarchy. Why isn't it possible for each ComposeScene to have its own Recomposer, and the Composition have its parent set to the parent CompositionContext? It may require a few changes in compose.runtime, but I'm not even sure that it will.

@igordmn
Copy link
Collaborator

igordmn commented Apr 2, 2024

It may be possible in theory if we allow somehow compositions to be recomposed at different pace. How we do that - having one or multiple Recomposer's - is implementation details. But that can require a lot of work.

@okushnikov
Copy link

Please check the following ticket on YouTrack for follow-ups to this issue. GitHub issues will be closed in the coming weeks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working reproduced
Projects
None yet
6 participants