compose-multiplatform icon indicating copy to clipboard operation
compose-multiplatform copied to clipboard

Support saving state for nested `NavHostController`

Open slikasgiedrius opened this issue 1 year ago • 22 comments

Describe the bug iOS doesn't save state when moving between bottom bar items

Affected platforms

  • iOS

Versions

  • Kotlin version*: 1.9.23
  • Compose Multiplatform version*: 1.6.10-dev1593

Video https://github.com/JetBrains/compose-multiplatform/assets/9390550/08ae3ca8-f302-4fe8-8029-bce68ea07696

Some code for reference

Scaffold(
    bottomBar = {
        BottomNavigation {
            BottomNavigationItem(
                icon = {
                    Icon(
                        Icons.Default.Home,
                        contentDescription = BottomNavigationItems.HOME.name
                    )
                },
                label = { Text(BottomNavigationItems.HOME.name) },
                selected = BottomNavigationItems.HOME == selectedScreen,
                onClick = {
                    selectedScreen = BottomNavigationItems.HOME
                    navController.navigate(
                        route = BottomNavigationItems.HOME.name
                    ) {
                        navController.graph.startDestinationRoute?.let {
                            popUpTo(it) {
                                saveState = true
                            }
                            launchSingleTop = true
                            restoreState = true
                        }
                    }
                },
                modifier = Modifier.padding(0.dp)
            )
            BottomNavigationItem(
                icon = {
                    Icon(
                        Icons.Default.AccountCircle,
                        contentDescription = BottomNavigationItems.PROFILE.name
                    )
                },
                label = { Text(BottomNavigationItems.PROFILE.name) },
                selected = BottomNavigationItems.PROFILE == selectedScreen,
                onClick = {
                    selectedScreen = BottomNavigationItems.PROFILE
                    navController.navigate(
                        route = BottomNavigationItems.PROFILE.name
                    ) {
                        navController.graph.startDestinationRoute?.let {
                            popUpTo(it) {
                                saveState = true
                            }
                            launchSingleTop = true
                            restoreState = true
                        }
                    }
                },
                modifier = Modifier.padding(0.dp)
            )
        }
    },
    content = {
        NavHost(
            navController = navController,
            startDestination = BottomNavigationItems.HOME.name
        ) {
            composable(BottomNavigationItems.HOME.name) {
                Column(
                    modifier = Modifier.fillMaxSize(),
                    verticalArrangement = Arrangement.Center,
                    horizontalAlignment = Alignment.CenterHorizontally
                ) {
                    ClickNavigation()
                }
            }
            composable(BottomNavigationItems.PROFILE.name) {
                Column(
                    modifier = Modifier.fillMaxSize(),
                    verticalArrangement = Arrangement.Center,
                    horizontalAlignment = Alignment.CenterHorizontally
                ) {
                    Text("PROFILE")
                }
            }
        }
    }
)

Additional context I am quite new with Multiplatform, but I am trying to do everything in the common code without ever touching the platform based implementations

slikasgiedrius avatar Apr 30 '24 07:04 slikasgiedrius

It should work just fine:

https://github.com/JetBrains/compose-multiplatform/assets/1836384/a279376c-2edf-4ad4-9487-566d2fc1fc88

Tested with: Compose 1.6.10-beta03, Navigation 2.7.0-alpha03 I don't see a version of the navigation library in the description, so the recommendation is quite common: use the latest version instead of early dev. There were a few related changes, but I'm not entirely sure which one exactly fixed this case.

Closing as it works on the latest version

MatkovIvan avatar May 01 '24 23:05 MatkovIvan

I am using

implementation("org.jetbrains.androidx.navigation:navigation-compose:2.8.0-alpha02") and compose = "1.6.10-beta03"

But I get the same result. @MatkovIvan Can you share your codebase for this working example so I can take a look?

slikasgiedrius avatar May 02 '24 07:05 slikasgiedrius

I've noticed another issue which is probably related. Opening a screen of the first tab and then switching tabs results to screen state and the whole screen stays the same while iOS is being set to the default screen and state:

https://github.com/JetBrains/compose-multiplatform/assets/9390550/7f7a07e7-7549-4d68-bdc5-65d033e0b88b

commonMain.dependencies {
            implementation(compose.runtime)
            implementation(compose.foundation)
            implementation(compose.material)
            implementation(compose.ui)
            implementation(compose.components.resources)
            implementation(compose.components.uiToolingPreview)

            //Moko mvvm
            api("dev.icerock.moko:mvvm-core:0.16.1")
            api("dev.icerock.moko:mvvm-compose:0.16.1")

            //Kamel
            implementation("media.kamel:kamel-image:0.9.4")

            //Ktor
            implementation("io.ktor:ktor-client-core:2.3.10")
            implementation("io.ktor:ktor-client-content-negotiation:2.3.10")
            implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.10")

            //Kotlinx serialization
            implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3")

            //Koin
            implementation("io.insert-koin:koin-core:3.5.6")
            implementation("io.insert-koin:koin-compose:1.1.5")

            //Navigation
            implementation("org.jetbrains.androidx.navigation:navigation-compose:2.8.0-alpha02")
        }


[versions]
agp = "8.2.0"
android-compileSdk = "34"
android-minSdk = "24"
android-targetSdk = "34"
androidx-activityCompose = "1.9.0"
androidx-appcompat = "1.6.1"
androidx-constraintlayout = "2.1.4"
androidx-core-ktx = "1.13.0"
androidx-espresso-core = "3.5.1"
androidx-material = "1.11.0"
androidx-test-junit = "1.1.5"
compose = "1.6.10-beta03"
compose-plugin = "1.6.2"
junit = "4.13.2"
kotlin = "1.9.23"
kotlinxDatetime = "0.5.0"


@Composable
fun App() {
    TobTheme {
        BottomNavigation()
    }
}


private enum class BottomNavigationItems {
    HOME,
    PROFILE,
}

@Composable
fun BottomNavigation() {
    val navController = rememberNavController()
    var selectedScreen by remember { mutableStateOf(BottomNavigationItems.HOME) }

    Scaffold(
        bottomBar = {
            BottomNavigation {
                BottomNavigationItem(
                    icon = {
                        Icon(
                            Icons.Default.Home,
                            contentDescription = BottomNavigationItems.HOME.name
                        )
                    },
                    label = { Text(BottomNavigationItems.HOME.name) },
                    selected = BottomNavigationItems.HOME == selectedScreen,
                    onClick = {
                        selectedScreen = BottomNavigationItems.HOME
                        navController.navigate(
                            route = BottomNavigationItems.HOME.name
                        ) {
                            navController.graph.startDestinationRoute?.let {
                                popUpTo(it) {
                                    saveState = true
                                }
                                launchSingleTop = true
                                restoreState = true
                            }
                        }
                    },
                    modifier = Modifier.padding(0.dp)
                )
                BottomNavigationItem(
                    icon = {
                        Icon(
                            Icons.Default.AccountCircle,
                            contentDescription = BottomNavigationItems.PROFILE.name
                        )
                    },
                    label = { Text(BottomNavigationItems.PROFILE.name) },
                    selected = BottomNavigationItems.PROFILE == selectedScreen,
                    onClick = {
                        selectedScreen = BottomNavigationItems.PROFILE
                        navController.navigate(
                            route = BottomNavigationItems.PROFILE.name
                        ) {
                            navController.graph.startDestinationRoute?.let {
                                popUpTo(it) {
                                    saveState = true
                                }
                                launchSingleTop = true
                                restoreState = true
                            }
                        }
                    },
                    modifier = Modifier.padding(0.dp)
                )
            }
        },
        content = {
            NavHost(
                navController = navController,
                startDestination = BottomNavigationItems.HOME.name
            ) {
                composable(BottomNavigationItems.HOME.name) {
                    Column(
                        modifier = Modifier.fillMaxSize(),
                        verticalArrangement = Arrangement.Center,
                        horizontalAlignment = Alignment.CenterHorizontally
                    ) {
                        ClickNavigation()
                    }
                }
                composable(BottomNavigationItems.PROFILE.name) {
                    Column(
                        modifier = Modifier.fillMaxSize(),
                        verticalArrangement = Arrangement.Center,
                        horizontalAlignment = Alignment.CenterHorizontally
                    ) {
                        Text("PROFILE")
                    }
                }
            }
        }
    )
}


@Composable
fun ClickNavigation() {
    val navController = rememberNavController()
    NavHost(
        navController = navController,
        startDestination = MainNavHostDestinations.Routes.HOME
    ) {
        composable(
            route = MainNavHostDestinations.Routes.HOME
        ) {
            HomeScreen(
                onArticleClicked = {
                    navController.openArticle(articleTitle = it)
                }
            )
        }
        composable(
            route = MainNavHostDestinations.Routes.ARTICLE,
            arguments = listOf(
                navArgument(name = MainNavHostDestinations.ArticleArgs.TITLE) {
                    type = NavType.StringType
                }
            )
        ) {
            DetailedArticle(
                title = it.arguments?.getString(MainNavHostDestinations.ArticleArgs.TITLE),
                onNavigateBack = {
                    navController.navigateBack()
                }
            )
        }
    }
}

@OptIn(ExperimentalResourceApi::class, ExperimentalMaterialApi::class)
@Composable
fun HomeScreen(
    viewModel: HomeViewModel = koinInject(),
    onArticleClicked: (String) -> Unit,
) {
    val uiState by viewModel.uiState.collectAsState()

    LazyColumn(content = {
        items(uiState.articles) {
            Card(backgroundColor = Color.LightGray,
                modifier = Modifier.padding(all = 4.dp).fillMaxWidth().height(300.dp),
                onClick = {
                    onArticleClicked(it.title)
                }) {
                Column(
                    horizontalAlignment = Alignment.CenterHorizontally
                ) {
                    Text(it.title)
                    if (it.urlToImage.isNullOrEmpty()) {
                        Image(
                            painter = painterResource(Res.drawable.compose_multiplatform),
                            null,
                        )
                    } else {
                        KamelImage(resource = asyncPainterResource(it.urlToImage),
                            contentDescription = "",
                            contentScale = ContentScale.FillWidth,
                            onLoading = { CircularProgressIndicator(it) },
                            onFailure = {
                                Column {
                                    Text(
                                        text = "Failed to load",
                                        fontWeight = FontWeight.Bold,
                                    )
                                }
                            })
                    }
                }
            }
        }
    })
}

slikasgiedrius avatar May 02 '24 07:05 slikasgiedrius

2.8.0-alpha02

It was explicitly reverted to 2.7 branch to avoid compatibility issues - we're using the original Google's binary on Android and 2.8 introduces dependency on Compose 1.7. It causes switching to Compose 1.7 alpha on Android and mismatching with common code. Please use 2.7 until Compose Multiplatform 1.7. It's not "older"

My testing code is based on yours:

private enum class BottomNavigationItems {
    HOME,
    PROFILE,
}

@Composable
fun App() {
    var selectedScreen by remember { mutableStateOf(BottomNavigationItems.HOME) }
    val navController = rememberNavController()
    Scaffold(
        bottomBar = {
            BottomNavigation {
                BottomNavigationItem(
                    icon = {
                        Icon(
                            Icons.Default.Home,
                            contentDescription = BottomNavigationItems.HOME.name
                        )
                    },
                    label = { Text(BottomNavigationItems.HOME.name) },
                    selected = BottomNavigationItems.HOME == selectedScreen,
                    onClick = {
                        selectedScreen = BottomNavigationItems.HOME
                        navController.navigate(
                            route = BottomNavigationItems.HOME.name
                        ) {
                            navController.graph.startDestinationRoute?.let {
                                popUpTo(it) {
                                    saveState = true
                                }
                                launchSingleTop = true
                                restoreState = true
                            }
                        }
                    },
                    modifier = Modifier.padding(0.dp)
                )
                BottomNavigationItem(
                    icon = {
                        Icon(
                            Icons.Default.AccountCircle,
                            contentDescription = BottomNavigationItems.PROFILE.name
                        )
                    },
                    label = { Text(BottomNavigationItems.PROFILE.name) },
                    selected = BottomNavigationItems.PROFILE == selectedScreen,
                    onClick = {
                        selectedScreen = BottomNavigationItems.PROFILE
                        navController.navigate(
                            route = BottomNavigationItems.PROFILE.name
                        ) {
                            navController.graph.startDestinationRoute?.let {
                                popUpTo(it) {
                                    saveState = true
                                }
                                launchSingleTop = true
                                restoreState = true
                            }
                        }
                    },
                    modifier = Modifier.padding(0.dp)
                )
            }
        },
        content = {
            NavHost(
                navController = navController,
                startDestination = BottomNavigationItems.HOME.name
            ) {
                composable(BottomNavigationItems.HOME.name) {
                    Box(
                        modifier = Modifier.fillMaxSize(),
                        contentAlignment = Alignment.Center,
                    ) {
                        ClickNavigation()
                    }
                }
                composable(BottomNavigationItems.PROFILE.name) {
                    Box(
                        modifier = Modifier.fillMaxSize(),
                        contentAlignment = Alignment.Center,
                    ) {
                        Text("PROFILE")
                    }
                }
            }
        }
    )
}

@Composable
private fun ClickNavigation() {
        val lazyListState = rememberLazyListState()
        LazyColumn(state = lazyListState) {
            items(99) {
                val color = when (it % 7) {
                    0 -> Color.Red
                    1 -> Color.Blue
                    2 -> Color.Green
                    3 -> Color.Yellow
                    4 -> Color.Magenta
                    5 -> Color.Gray
                    6 -> Color.Cyan
                    else -> Color.Transparent
                }
                Row(
                    modifier = Modifier
                        .fillMaxWidth()
                        .padding(12.dp),
                    verticalAlignment = Alignment.CenterVertically,
                ) {
                    Box(modifier = Modifier.size(40.dp).background(color))
                    Spacer(modifier = Modifier.width(12.dp))
                    BasicText("Item number $it")
                }
            }
        }
}

MatkovIvan avatar May 02 '24 08:05 MatkovIvan

There was a problem with my nested NavGraphs. Joining both into one helped. Thanks for your help!

slikasgiedrius avatar May 02 '24 08:05 slikasgiedrius

I am receiving errors when running android on Navigation 2.7.0-alpha03 :

Caused by: org.gradle.api.internal.artifacts.ivyservice.DefaultLenientConfiguration$ArtifactResolveException: Could not resolve all files for configuration ':composeApp:debugRuntimeClasspath'.

This issue still persists though:

https://github.com/JetBrains/compose-multiplatform/assets/9390550/979f386c-e6ad-4f23-a094-c3599bcc3600

slikasgiedrius avatar May 02 '24 09:05 slikasgiedrius

Looking at nested NavHost case (btw nested graphs don't require multiple NavHost/NavHostControllers)

Caused by: org.gradle.api.internal.artifacts.ivyservice.DefaultLenientConfiguration$ArtifactResolveException: Could not resolve all files for configuration ':composeApp:debugRuntimeClasspath'.

Cannot say anything without reproduction, but it doesn't look related to navigation

MatkovIvan avatar May 02 '24 09:05 MatkovIvan

I have made it a public repo - https://github.com/slikasgiedrius/Tob

slikasgiedrius avatar May 02 '24 09:05 slikasgiedrius

Investigation regarding state: save/restoring state works, but keys that are based on composition-key-hash are different. For now, I'm not sure that there is a guarantee that it will be the same even on Android. Trying to find why they are changed

MatkovIvan avatar May 02 '24 10:05 MatkovIvan

Ok, It's about restoring the state of nested NavHostControllers - it's the limitation for these first alpha releases. Sorry, I didn't match the case with that unimplemented TODO. It's under TODO for future versions because it requires serialization of structures based on Android's Parcelable that isn't ported to multiplatform yet.

Keeping this issue open to track this

Workaround 1: Use single NavHostController and define nested graphs via NavGraphBuilder.navigation() function. See documentation

Workaround 2: Move second rememberNavController() call out of wiped composition.

MatkovIvan avatar May 02 '24 11:05 MatkovIvan

Regarding versions: I've prepared the fix https://github.com/slikasgiedrius/Tob/pull/1

It was a misusage of the version constants. The issue for renaming these constants in the template is tracked here: https://youtrack.jetbrains.com/issue/KT-66613

MatkovIvan avatar May 02 '24 11:05 MatkovIvan

I see some iOS build issues after the PR of the changes

Screenshot 2024-05-02 at 14 39 31

slikasgiedrius avatar May 02 '24 11:05 slikasgiedrius

It looks like https://youtrack.jetbrains.com/issue/KT-61205 that should be already fixed. Looking why it's still here

MatkovIvan avatar May 02 '24 11:05 MatkovIvan

Yeah I have just discovered that it's related to K2 (Which I have enabled today) :D

slikasgiedrius avatar May 02 '24 11:05 slikasgiedrius

Everything works like a charm. Only K2 compiler issue left.

slikasgiedrius avatar May 02 '24 11:05 slikasgiedrius

Ok, it was because K2 experimental mode in Kotlin 1.9. It's already fixed, I've updated Kotlin to 2.0.0-RC2 in your project - https://github.com/slikasgiedrius/Tob/pull/2

MatkovIvan avatar May 02 '24 12:05 MatkovIvan

Everything works perfectly now, THANK YOU SO MUCH for your massive help. I'm loving it! Closing this issue

slikasgiedrius avatar May 02 '24 12:05 slikasgiedrius

org.jetbrains.compose.resources.MissingResourceException: Missing resource with path

It's tracked in #4720 What's a chain of unrelated bugs! 🫠

MatkovIvan avatar May 02 '24 12:05 MatkovIvan

Hey! Sorry for commenting on a closed thread, I have the same situation with slikasgiedrius, from the second part (the one where you renamed the thread to "support saving state for nested NavController")

On Android it works perfectly, but on IOS it does not. I tried using the workarounds you provided, but I still can't get it to work.

Stancescu-Andrei avatar Jun 14 '24 06:06 Stancescu-Andrei

here is my code: navigation.kt

object BaseRoutes {
    const val home = "home"
    const val callCenter = "callcenter"
    const val account = "account"
}

object HomeRoutes {
    const val homeRoot = "${BaseRoutes.home}/root"
    const val home_2 = "${BaseRoutes.home}/home_2"
    const val home_3 = "${BaseRoutes.home}/home_3"
}

object CallCenterRoutes {
    const val callCenterRoot = "${BaseRoutes.callCenter}/root"
    const val callcenter_02 = "${BaseRoutes.callCenter}/callcenter_2"
    const val callcenter_03 = "${BaseRoutes.callCenter}/callcenter_3"
}

object AccountRoutes {
    const val accountRoot = "${BaseRoutes.account}/root"
}

fun NavGraphBuilder.buildNavGraph(
    navController: NavController,
    accountViewModel: AccountScreenViewModel
) {
    home(navController = navController)
    account(accountViewModel)
    callCenter(navController = navController)
}

fun NavGraphBuilder.home(navController: NavController) {
    navigation(
        route = BaseRoutes.home,
        startDestination = HomeRoutes.homeRoot
    ) {
        composable(route = HomeRoutes.homeRoot) {
            ShowHomeUI(navController = navController)
        }
        composable(route = HomeRoutes.home_2) {
            GenericScreen(route = HomeRoutes.home_3, navController = navController, text = "home 2")
        }
        composable(route = HomeRoutes.home_3) {
            GenericScreen(route = route ?: "", navController = navController, text = "home 3")
        }
    }
}



// Tab 2
fun NavGraphBuilder.callCenter(navController: NavController) {
    navigation(
        route = BaseRoutes.callCenter,
        startDestination = CallCenterRoutes.callCenterRoot
    ) {
        composable(route = CallCenterRoutes.callCenterRoot) {
            ShowCallCenterUI(navController = navController)
        }
        composable(route = CallCenterRoutes.callcenter_02) {
            GenericScreen(
                route = CallCenterRoutes.callcenter_03,
                navController = navController,
                text = "Callcenter 2"
            )
        }
        composable(route = CallCenterRoutes.callcenter_03) {
            GenericScreen(route = route ?: "", navController = navController, text = "Callcenter 3")
        }
    }
}

// Tab 3
fun NavGraphBuilder.account(accountViewModel: AccountScreenViewModel) {
    navigation(
        route = BaseRoutes.account,
        startDestination = AccountRoutes.accountRoot
    ) {
        composable(route = AccountRoutes.accountRoot) {
            AccountScreen(
                viewModel = accountViewModel,
                modifier = Modifier.Companion
            )
        }
    }
}

App.kt (only the relevant part)

`           Scaffold(
                bottomBar = {
                    BottomNavigationBar(rootNavController)
                },
            ) {
                NavHost(
                    modifier = Modifier.padding(it),
                    navController = rootNavController,
                    startDestination = BaseRoutes.home
                ) {
                    buildNavGraph(navController = rootNavController, viewModelAccountScreen)
                }
            }
            

BottomNavBar.kt

sealed class BottomNavigationItem(val name: String, val icon: DrawableResource, val route: String) {
    data object Home : BottomNavigationItem(
        name = "home",
        icon = Res.drawable.ic_home,
        route = BaseRoutes.home
    )

    data object CallCenter : BottomNavigationItem(
        name = "callcenter",
        icon = Res.drawable.ic_callcenter,
        route = BaseRoutes.callCenter
    )

    data object Account : BottomNavigationItem(
        name = "account",
        icon = Res.drawable.ic_person,
        route = BaseRoutes.account
    )

}


@Composable
fun BottomNavigationBar(navController: NavController) {
    val screens = listOf(
        BottomNavigationItem.Home,
        BottomNavigationItem.CallCenter,
        BottomNavigationItem.Account
    )

    BottomNavigation(
// TODO check content height (above tabbar)
        backgroundColor = Color(0xFF, 0x00, 0x00, 0x66),
        windowInsets = BottomNavigationDefaults.windowInsets
    ) {
        val navBackStackEntry by navController.currentBackStackEntryAsState()
        val currentRoute = navBackStackEntry?.destination?.route
        screens.forEach { screen ->
            val selected = currentRoute?.split("/")?.first() == screen.route
            BottomNavigationItem(
                modifier = Modifier
                    .background(md_theme_dark_onSecondaryContainer),
                alwaysShowLabel = false,
                icon = {
                    Icon(
                        modifier = Modifier
                            .size(25.dp, 25.dp),
                        tint = Color.Black,
                        painter = painterResource(screen.icon),
                        contentDescription = null
                    )
                },
                label = {
                    Text(
                        color = Color.Black,
                        text = screen.name
                    )
                },
                selected = selected,
                onClick = {
                    navController.navigate(screen.route) {
                        popUpTo(navController.graph.findStartDestination().route.toString()) {
                            saveState = true
                        }
                        launchSingleTop = true
                        restoreState = true
                    }
                }
            )
        }
    }
}

Stancescu-Andrei avatar Jun 14 '24 06:06 Stancescu-Andrei

@Stancescu-Andrei It's not super clear from your message what exactly doesn't work for you, I hope I've got it right.

On Android it works perfectly, but on IOS it does not

Saving state for nested NavHostController is still not supported, I'll reopen this issue to track it. However nested graphs that I suggested as workaround works the same as on Android as far as I see.

Regarding your sample with switching tabs. The code should be adopted for the change from nested host to nested graphs a bit. This should work in your case:

val currentGraphRoute = navBackStackEntry?.destination?.parent?.route
navController.navigate(screen.route) {
    popUpTo(currentGraphRoute!!) {
        inclusive = true
        saveState = true
    }
    launchSingleTop = true
    restoreState = true
}

MatkovIvan avatar Jun 14 '24 08:06 MatkovIvan

Thanks! Indeed, this is what I was missing, now everything works great, thanks again!

Stancescu-Andrei avatar Jun 14 '24 10:06 Stancescu-Andrei

@MatkovIvan Hello! Could you please tell when you are going to support saving state for nested NavHosts on iOS?

Too-Many-Bytes avatar Jul 23 '24 18:07 Too-Many-Bytes

@Too-Many-Bytes in a TODO list, but no ETA because there is an ongoing discussion with Google that can affect the it. Also, I want to emphasize that I'm not aware of scenarios where the nested graphs approach doesn't work (see https://github.com/JetBrains/compose-multiplatform/issues/4735#issuecomment-2090235055), so it shouldn't be a blocker for anyone.

MatkovIvan avatar Jul 23 '24 19:07 MatkovIvan

@MatkovIvan I've studied the documentation, but I'm not sure if this approach is good in my case. If possible, please tell me how you would solve the problem with a nested graph.

My case: I have two top-level screens: Registration and Main, so there is one root fullscreen NavHost (1) which holds them. Also, inside MainScreen there is a bottom navigation bar and the second NavHost (2) which is not fullscreen (because the navigation bar with tabs takes some place). Between tabs in MainScreen saving state is working fine. But the problem occurs in such scenario: One of the tabs is the messenger with chats, so it is in MainScreen NavHost (2). However, a particular chat i want to open in fullscreen without bottom navigation bar. So I need to navigate to the particular chat screen within the root level NavHost (1). As a result, MainScreen goes into the backstack when i navigate to the particular chat and after returning to the messenger in MainScreen I get no restored state in MainScreen NavHost (2).

If you can give me advice on how to solve the problem effectively, I would appreciate it

Too-Many-Bytes avatar Jul 23 '24 20:07 Too-Many-Bytes

Thanks for explaining the use case, indeed for non-fullscreen sub navigation multiple NavHosts is the best fit.

I beleive that second mentioned workaround is easy to implement:

Workaround 2: Move second rememberNavController() call out of wiped composition.

It will work because the state of controller matters here, not NavHost itself.

PS I'll try to resolve this without changes in the dependencies (that currently blocked), but still no promises regarding ETA.

MatkovIvan avatar Jul 23 '24 20:07 MatkovIvan

Workaround helped without serious efforts. Thanks a lot!

Too-Many-Bytes avatar Jul 24 '24 05:07 Too-Many-Bytes

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

okushnikov avatar Aug 26 '24 13:08 okushnikov

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

okushnikov avatar Aug 26 '24 13:08 okushnikov

Hi everyone. Someone can help me in this problem? iOS save and restore state not working, android working fine, I tested all versions of compose-plugin and compose-multiplatform navigation. Please help me. Thanks

https://youtrack.jetbrains.com/issue/CMP-6691/Compose-navigation-restore-state-not-working

saggeldi avatar Sep 20 '24 05:09 saggeldi