Closed slikasgiedrius closed 2 weeks ago
It should work just fine:
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
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?
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:
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,
)
}
})
}
}
}
}
})
}
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")
}
}
}
}
There was a problem with my nested NavGraphs. Joining both into one helped. Thanks for your help!
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:
Looking at nested NavHost
case (btw nested graphs don't require multiple NavHost
/NavHostController
s)
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
I have made it a public repo - https://github.com/slikasgiedrius/Tob
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
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.
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
I see some iOS build issues after the PR of the changes
It looks like https://youtrack.jetbrains.com/issue/KT-61205 that should be already fixed. Looking why it's still here
Yeah I have just discovered that it's related to K2 (Which I have enabled today) :D
Everything works like a charm. Only K2 compiler issue left.
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
Everything works perfectly now, THANK YOU SO MUCH for your massive help. I'm loving it! Closing this issue
org.jetbrains.compose.resources.MissingResourceException: Missing resource with path
It's tracked in #4720 What's a chain of unrelated bugs! 🫠
Describe the bug iOS doesn't save state when moving between bottom bar items
Affected platforms
Versions
Video https://github.com/JetBrains/compose-multiplatform/assets/9390550/08ae3ca8-f302-4fe8-8029-bce68ea07696
Some code for reference
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