adrielcafe / voyager

🛸 A pragmatic navigation library for Jetpack Compose
https://voyager.adriel.cafe
MIT License
2.59k stars 137 forks source link

push Screen Crash with Double BottomSheetNavigator and Transition #403

Open RavenLiao opened 6 months ago

RavenLiao commented 6 months ago

version

v1.0.0

example code

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            Navigator(StartScreen) { navigator ->
                GlobalNavigatorWrapper(navigator) {
                    SlideTransition(navigator) {
                        it.Content()
                    }
                }
            }
        }
    }
}

val LocalGlobalNavigator: ProvidableCompositionLocal<Navigator> =
    staticCompositionLocalOf { error("LocalGlobalNavigator not initialized") }

@Composable
fun GlobalNavigatorWrapper(navigator: Navigator, content: @Composable () -> Unit) {
    CompositionLocalProvider(LocalGlobalNavigator provides navigator, content)
}

object StartScreen : Screen {
    private fun readResolve(): Any = StartScreen

    @Composable
    override fun Content() {
        BottomSheetNavigator {
            TabNavigator(TestTab) {
                CurrentScreen()
            }
        }
    }
}

object TestTab : Tab {
    private fun readResolve(): Any = TestTab
    override val options: TabOptions
        @Composable get() = TabOptions(0U, "test")

    @Composable
    override fun Content() {
        val navigator = LocalGlobalNavigator.current

        Column {
            Button(onClick = {
                navigator.push(JumpScreen)
            }) {
                Text(text = "jump with BottomSheetNavigator")
            }
            Button(onClick = {
                navigator.push(Jump2Screen)
            }) {
                Text(text = "jump no BottomSheetNavigator")
            }
        }
    }

}

object JumpScreen : Screen {
    private fun readResolve(): Any = JumpScreen

    @Composable
    override fun Content() {
        BottomSheetNavigator {
            Text(text = "with BottomSheetNavigator")
        }
    }
}

object Jump2Screen : Screen {
    private fun readResolve(): Any = Jump2Screen

    @Composable
    override fun Content() {
        Text(text = "no BottomSheetNavigator")
    }
}

crash log

after clik "jump with BottomSheetNavigator" button will crash:

 java.lang.IllegalArgumentException: Key cafe.adriel.voyager.navigator.bottomSheet.HiddenBottomSheetScreen:currentScreen was used multiple times 
                                                        at androidx.compose.runtime.saveable.SaveableStateHolderImpl$SaveableStateProvider$1$1.invoke(SaveableStateHolder.kt:89)
                                                        at androidx.compose.runtime.saveable.SaveableStateHolderImpl$SaveableStateProvider$1$1.invoke(SaveableStateHolder.kt:88)
                                                        at androidx.compose.runtime.DisposableEffectImpl.onRemembered(Effects.kt:83)
                                                        at androidx.compose.runtime.CompositionImpl$RememberEventDispatcher.dispatchRememberObservers(Composition.kt:1295)
                                                        at androidx.compose.runtime.CompositionImpl.applyChangesInLocked(Composition.kt:984)
                                                        at androidx.compose.runtime.CompositionImpl.applyChanges(Composition.kt:1005)
                                                        at androidx.compose.runtime.Recomposer.composeInitial$runtime_release(Recomposer.kt:1099)
                                                        at androidx.compose.runtime.ComposerImpl$CompositionContextImpl.composeInitial$runtime_release(Composer.kt:3599)
                                                        at androidx.compose.runtime.CompositionImpl.composeInitial(Composition.kt:633)
                                                        at androidx.compose.runtime.CompositionImpl.setContent(Composition.kt:619)
                                                        at androidx.compose.ui.layout.LayoutNodeSubcompositionsState.subcomposeInto(SubcomposeLayout.kt:500)
                                                        at androidx.compose.ui.layout.LayoutNodeSubcompositionsState.subcompose(SubcomposeLayout.kt:472)
                                                        at androidx.compose.ui.layout.LayoutNodeSubcompositionsState.subcompose(SubcomposeLayout.kt:463)
                                                        at androidx.compose.ui.layout.LayoutNodeSubcompositionsState.subcompose(SubcomposeLayout.kt:447)
                                                        at androidx.compose.ui.layout.LayoutNodeSubcompositionsState$Scope.subcompose(SubcomposeLayout.kt:872)
                                                        at androidx.compose.foundation.layout.BoxWithConstraintsKt$BoxWithConstraints$1$1.invoke-0kLqBqw(BoxWithConstraints.kt:69)
                                                        at androidx.compose.foundation.layout.BoxWithConstraintsKt$BoxWithConstraints$1$1.invoke(BoxWithConstraints.kt:67)
                                                        at androidx.compose.ui.layout.LayoutNodeSubcompositionsState$createMeasurePolicy$1.measure-3p2s80s(SubcomposeLayout.kt:709)
                                                        at androidx.compose.ui.node.InnerNodeCoordinator.measure-BRTryo0(InnerNodeCoordinator.kt:126)
                                                        at androidx.compose.ui.node.LayoutNodeLayoutDelegate$performMeasureBlock$1.invoke(LayoutNodeLayoutDelegate.kt:252)
                                                        at androidx.compose.ui.node.LayoutNodeLayoutDelegate$performMeasureBlock$1.invoke(LayoutNodeLayoutDelegate.kt:251)
                                                        at androidx.compose.runtime.snapshots.Snapshot$Companion.observe(Snapshot.kt:2303)
                                                        at androidx.compose.runtime.snapshots.SnapshotStateObserver$ObservedScopeMap.observe(SnapshotStateObserver.kt:500)
                                                        at androidx.compose.runtime.snapshots.SnapshotStateObserver.observeReads(SnapshotStateObserver.kt:256)
                                                        at androidx.compose.ui.node.OwnerSnapshotObserver.observeReads$ui_release(OwnerSnapshotObserver.kt:133)
                                                        at androidx.compose.ui.node.OwnerSnapshotObserver.observeMeasureSnapshotReads$ui_release(OwnerSnapshotObserver.kt:113)
                                                        at androidx.compose.ui.node.LayoutNodeLayoutDelegate.performMeasure-BRTryo0(LayoutNodeLayoutDelegate.kt:1617)
                                                        at androidx.compose.ui.node.LayoutNodeLayoutDelegate.access$performMeasure-BRTryo0(LayoutNodeLayoutDelegate.kt:36)
                                                        at androidx.compose.ui.node.LayoutNodeLayoutDelegate$MeasurePassDelegate.remeasure-BRTryo0(LayoutNodeLayoutDelegate.kt:620)
                                                        at androidx.compose.ui.node.LayoutNodeLayoutDelegate$MeasurePassDelegate.measure-BRTryo0(LayoutNodeLayoutDelegate.kt:596)
                                                        at androidx.compose.animation.AnimatedEnterExitMeasurePolicy.measure-3p2s80s(AnimatedVisibility.kt:851)
                                                        at androidx.compose.ui.node.InnerNodeCoordinator.measure-BRTryo0(InnerNodeCoordinator.kt:126)
                                                        at androidx.compose.animation.EnterExitTransitionModifierNode.measure-3p2s80s(EnterExitTransition.kt:1156)
                                                        at androidx.compose.ui.node.LayoutModifierNodeCoordinator.measure-BRTryo0(LayoutModifierNodeCoordinator.kt:116)
                                                        at androidx.compose.ui.graphics.SimpleGraphicsLayerModifier.measure-3p2s80s(GraphicsLayerModifier.kt:646)
                                                        at androidx.compose.ui.node.LayoutModifierNodeCoordinator.measure-BRTryo0(LayoutModifierNodeCoordinator.kt:116)

avoid crash

  1. remove BottomSheetNavigator in StartScreen or JumpScreen
  2. remove Transition
diegoberaldin commented 5 months ago

Hi! I happened to have the same issue (just on one bottom sheet and just in release builds, not in debug ones). Do I have to both remove the SlideTransition and remove the BottomSheetNavigator altogether?

I don't know if it helps, but in my case I have the BottomSheetNavigator as parent and the root navigator as a child, the crash happens just when I call show(screen: Screen) on the former.

EDIT: I realized I am using a third Navigator inside the bottom sheet content, and that is the cause of the issue, this explains why it crashes only with that bottom sheet and not the other plain ones.

RavenLiao commented 5 months ago

Hi! I happened to have the same issue (just on one bottom sheet and just in release builds, not in debug ones). Do I have to both remove the SlideTransition and remove the BottomSheetNavigator altogether?

I don't know if it helps, but in my case I have the BottomSheetNavigator as parent and the root navigator as a child, the crash happens just when I call show(screen: Screen) on the former.

EDIT: I realized I am using a third Navigator inside the bottom sheet content, and that is the cause of the issue, this explains why it crashes only with that bottom sheet and not the other plain ones.

Now I use the following code to use BottomSheetNavigator and Transition simultaneously . I hope it will help you.

                Navigator(MainScreen) { navigator ->
                    GlobalNavigatorWrapper(navigator) {
                        MyBottomSheetNavigator {
                            ParcelableNavigatorWrapper {
                                MyCustomTransition(navigator) { screen ->
                                    screen.Content()
                                }
                            }
                        }
                    }
                }

@OptIn(ExperimentalVoyagerApi::class)
@Composable
fun ParcelableNavigatorWrapper(content: @Composable () -> Unit) {
    CompositionLocalProvider(
        LocalNavigatorSaver provides parcelableNavigatorSaver(),
        content = content
    )
}

val LocalGlobalNavigator: ProvidableCompositionLocal<Navigator> =
    staticCompositionLocalOf { error("LocalGlobalNavigator not initialized") }

@Composable
fun GlobalNavigatorWrapper(navigator: Navigator, content: @Composable () -> Unit) {
    CompositionLocalProvider(LocalGlobalNavigator provides navigator, content)
}
diegoberaldin commented 5 months ago

I am removing the nested navigator inside the bottom sheet, I do not actually need it and can achieve a similar behaviour with Crossfade to have a transition.