Skip to content

Commit 013fba6

Browse files
authored
Merge pull request #11 from Kotlin/compose-nav
Use Compose navigation library
2 parents f2f39f2 + b41a5ab commit 013fba6

File tree

9 files changed

+70
-60
lines changed

9 files changed

+70
-60
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,10 @@ The data displayed by the app is from [The Metropolitan Museum of Art Collection
1818
The app uses the following multiplatform dependencies in its implementation:
1919

2020
- [Compose Multiplatform](https://jb.gg/compose) for UI
21+
- [Compose Navigation](https://www.jetbrains.com/help/kotlin-multiplatform-dev/compose-navigation-routing.html)
2122
- [Ktor](https://ktor.io/) for networking
2223
- [kotlinx.serialization](https://github.com/Kotlin/kotlinx.serialization) for JSON handling
2324
- [Kamel](https://github.com/Kamel-Media/Kamel) for image loading
2425
- [Koin](https://github.com/InsertKoinIO/koin) for dependency injection
25-
- [Voyager](https://github.com/adrielcafe/voyager) for navigation and screen models
2626

2727
> These are just some of the possible libraries to use for these tasks with Kotlin Multiplatform, and their usage here isn't a strong recommendation for these specific libraries over the available alternatives. You can find a wide variety of curated multiplatform libraries in the [kmp-awesome](https://github.com/terrakok/kmp-awesome) repository.

composeApp/build.gradle.kts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,8 @@ kotlin {
5151

5252
implementation(libs.kamel)
5353
implementation(libs.koin.core)
54-
implementation(libs.voyager.navigator)
55-
implementation(libs.voyager.koin)
54+
implementation(libs.koin.compose.viewmodel)
55+
implementation(libs.navigation.compose)
5656
}
5757
}
5858
}

composeApp/src/commonMain/kotlin/com/jetbrains/kmpapp/App.kt

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@ import androidx.compose.material.Surface
66
import androidx.compose.material.darkColors
77
import androidx.compose.material.lightColors
88
import androidx.compose.runtime.Composable
9-
import cafe.adriel.voyager.navigator.Navigator
9+
import androidx.navigation.NavHostController
10+
import androidx.navigation.compose.NavHost
11+
import androidx.navigation.compose.composable
12+
import androidx.navigation.compose.rememberNavController
13+
import com.jetbrains.kmpapp.screens.detail.DetailScreen
1014
import com.jetbrains.kmpapp.screens.list.ListScreen
1115

1216
@Composable
@@ -15,7 +19,19 @@ fun App() {
1519
colors = if (isSystemInDarkTheme()) darkColors() else lightColors()
1620
) {
1721
Surface {
18-
Navigator(ListScreen)
22+
val navController: NavHostController = rememberNavController()
23+
NavHost(
24+
navController,
25+
startDestination = "list"
26+
) {
27+
composable("list") {
28+
ListScreen(navController)
29+
}
30+
composable("detail/{objectId}") { backStackEntry ->
31+
val objectId = backStackEntry.arguments?.getString("objectId")?.toInt()
32+
DetailScreen(navController, objectId!!)
33+
}
34+
}
1935
}
2036
}
2137
}

composeApp/src/commonMain/kotlin/com/jetbrains/kmpapp/di/Koin.kt

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,14 @@ import com.jetbrains.kmpapp.data.KtorMuseumApi
55
import com.jetbrains.kmpapp.data.MuseumApi
66
import com.jetbrains.kmpapp.data.MuseumRepository
77
import com.jetbrains.kmpapp.data.MuseumStorage
8-
import com.jetbrains.kmpapp.screens.detail.DetailScreenModel
9-
import com.jetbrains.kmpapp.screens.list.ListScreenModel
8+
import com.jetbrains.kmpapp.screens.detail.DetailViewModel
9+
import com.jetbrains.kmpapp.screens.list.ListViewModel
1010
import io.ktor.client.HttpClient
1111
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
1212
import io.ktor.http.ContentType
1313
import io.ktor.serialization.kotlinx.json.json
1414
import kotlinx.serialization.json.Json
15+
import org.koin.compose.viewmodel.dsl.viewModel
1516
import org.koin.core.context.startKoin
1617
import org.koin.core.module.dsl.factoryOf
1718
import org.koin.dsl.module
@@ -36,16 +37,16 @@ val dataModule = module {
3637
}
3738
}
3839

39-
val screenModelsModule = module {
40-
factoryOf(::ListScreenModel)
41-
factoryOf(::DetailScreenModel)
40+
val viewModelModule = module {
41+
factoryOf(::ListViewModel)
42+
factoryOf(::DetailViewModel)
4243
}
4344

4445
fun initKoin() {
4546
startKoin {
4647
modules(
4748
dataModule,
48-
screenModelsModule,
49+
viewModelModule,
4950
)
5051
}
5152
}

composeApp/src/commonMain/kotlin/com/jetbrains/kmpapp/screens/detail/DetailScreen.kt

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,7 @@ import androidx.compose.ui.text.buildAnnotatedString
3333
import androidx.compose.ui.text.font.FontWeight
3434
import androidx.compose.ui.text.withStyle
3535
import androidx.compose.ui.unit.dp
36-
import cafe.adriel.voyager.core.screen.Screen
37-
import cafe.adriel.voyager.koin.getScreenModel
38-
import cafe.adriel.voyager.navigator.LocalNavigator
39-
import cafe.adriel.voyager.navigator.currentOrThrow
36+
import androidx.navigation.NavController
4037
import com.jetbrains.kmpapp.data.MuseumObject
4138
import com.jetbrains.kmpapp.screens.EmptyScreenContent
4239
import io.kamel.image.KamelImage
@@ -52,20 +49,21 @@ import kmp_app_template.composeapp.generated.resources.label_medium
5249
import kmp_app_template.composeapp.generated.resources.label_repository
5350
import kmp_app_template.composeapp.generated.resources.label_title
5451
import org.jetbrains.compose.resources.stringResource
52+
import org.koin.compose.viewmodel.koinViewModel
5553

56-
data class DetailScreen(val objectId: Int) : Screen {
57-
@Composable
58-
override fun Content() {
59-
val navigator = LocalNavigator.currentOrThrow
60-
val screenModel: DetailScreenModel = getScreenModel()
54+
@Composable
55+
fun DetailScreen(
56+
navController: NavController,
57+
objectId: Int,
58+
) {
59+
val viewModel = koinViewModel<DetailViewModel>()
6160

62-
val obj by screenModel.getObject(objectId).collectAsState(initial = null)
63-
AnimatedContent(obj != null) { objectAvailable ->
64-
if (objectAvailable) {
65-
ObjectDetails(obj!!, onBackClick = { navigator.pop() })
66-
} else {
67-
EmptyScreenContent(Modifier.fillMaxSize())
68-
}
61+
val obj by viewModel.getObject(objectId).collectAsState(initial = null)
62+
AnimatedContent(obj != null) { objectAvailable ->
63+
if (objectAvailable) {
64+
ObjectDetails(obj!!, onBackClick = { navController.navigateUp() })
65+
} else {
66+
EmptyScreenContent(Modifier.fillMaxSize())
6967
}
7068
}
7169
}
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
package com.jetbrains.kmpapp.screens.detail
22

3-
import cafe.adriel.voyager.core.model.ScreenModel
3+
import androidx.lifecycle.ViewModel
44
import com.jetbrains.kmpapp.data.MuseumObject
55
import com.jetbrains.kmpapp.data.MuseumRepository
66
import kotlinx.coroutines.flow.Flow
77

8-
class DetailScreenModel(private val museumRepository: MuseumRepository) : ScreenModel {
8+
class DetailViewModel(private val museumRepository: MuseumRepository) : ViewModel() {
99
fun getObject(objectId: Int): Flow<MuseumObject?> =
1010
museumRepository.getObjectById(objectId)
1111
}

composeApp/src/commonMain/kotlin/com/jetbrains/kmpapp/screens/list/ListScreen.kt

Lines changed: 18 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -28,35 +28,30 @@ import androidx.compose.ui.graphics.Color
2828
import androidx.compose.ui.layout.ContentScale
2929
import androidx.compose.ui.text.font.FontWeight
3030
import androidx.compose.ui.unit.dp
31-
import cafe.adriel.voyager.core.screen.Screen
32-
import cafe.adriel.voyager.koin.getScreenModel
33-
import cafe.adriel.voyager.navigator.LocalNavigator
34-
import cafe.adriel.voyager.navigator.currentOrThrow
31+
import androidx.navigation.NavController
3532
import com.jetbrains.kmpapp.data.MuseumObject
3633
import com.jetbrains.kmpapp.screens.EmptyScreenContent
37-
import com.jetbrains.kmpapp.screens.detail.DetailScreen
3834
import io.kamel.image.KamelImage
3935
import io.kamel.image.asyncPainterResource
36+
import org.koin.compose.viewmodel.koinViewModel
4037

41-
data object ListScreen : Screen {
42-
@Composable
43-
override fun Content() {
44-
val navigator = LocalNavigator.currentOrThrow
45-
val screenModel: ListScreenModel = getScreenModel()
46-
47-
val objects by screenModel.objects.collectAsState()
38+
@Composable
39+
fun ListScreen(
40+
navController: NavController,
41+
) {
42+
val viewModel = koinViewModel<ListViewModel>()
43+
val objects by viewModel.objects.collectAsState()
4844

49-
AnimatedContent(objects.isNotEmpty()) { objectsAvailable ->
50-
if (objectsAvailable) {
51-
ObjectGrid(
52-
objects = objects,
53-
onObjectClick = { objectId ->
54-
navigator.push(DetailScreen(objectId))
55-
}
56-
)
57-
} else {
58-
EmptyScreenContent(Modifier.fillMaxSize())
59-
}
45+
AnimatedContent(objects.isNotEmpty()) { objectsAvailable ->
46+
if (objectsAvailable) {
47+
ObjectGrid(
48+
objects = objects,
49+
onObjectClick = { objectId ->
50+
navController.navigate("detail/$objectId")
51+
}
52+
)
53+
} else {
54+
EmptyScreenContent(Modifier.fillMaxSize())
6055
}
6156
}
6257
}
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
package com.jetbrains.kmpapp.screens.list
22

3-
import cafe.adriel.voyager.core.model.ScreenModel
4-
import cafe.adriel.voyager.core.model.screenModelScope
3+
import androidx.lifecycle.ViewModel
4+
import androidx.lifecycle.viewModelScope
55
import com.jetbrains.kmpapp.data.MuseumObject
66
import com.jetbrains.kmpapp.data.MuseumRepository
77
import kotlinx.coroutines.flow.SharingStarted
88
import kotlinx.coroutines.flow.StateFlow
99
import kotlinx.coroutines.flow.stateIn
1010

11-
class ListScreenModel(museumRepository: MuseumRepository) : ScreenModel {
11+
class ListViewModel(museumRepository: MuseumRepository) : ViewModel() {
1212
val objects: StateFlow<List<MuseumObject>> =
1313
museumRepository.getObjects()
14-
.stateIn(screenModelScope, SharingStarted.WhileSubscribed(5000), emptyList())
14+
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), emptyList())
1515
}

gradle/libs.versions.toml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,24 @@ androidx-activityCompose = "1.9.2"
44
androidx-ui-tooling = "1.7.0"
55
compose-multiplatform = "1.6.11"
66
kamel = "0.9.5"
7-
koin = "3.5.6"
7+
koin = "4.0.0"
88
kotlin = "2.0.20"
99
ktor = "2.3.12"
10-
voyager = "1.0.0"
10+
navigationCompose = "2.7.0-alpha07"
1111

1212
[libraries]
1313
androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activityCompose" }
1414
androidx-compose-ui-tooling = { module = "androidx.compose.ui:ui-tooling", version.ref = "androidx-ui-tooling" }
1515
androidx-compose-ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview", version.ref = "androidx-ui-tooling" }
1616
kamel = { module = "media.kamel:kamel-image", version.ref = "kamel" }
1717
koin-core = { module = "io.insert-koin:koin-core", version.ref = "koin" }
18+
koin-compose-viewmodel = { module = "io.insert-koin:koin-compose-viewmodel", version.ref = "koin" }
1819
ktor-client-content-negotiation = { module = "io.ktor:ktor-client-content-negotiation", version.ref = "ktor" }
1920
ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
2021
ktor-client-darwin = { module = "io.ktor:ktor-client-darwin", version.ref = "ktor" }
2122
ktor-client-okhttp = { module = "io.ktor:ktor-client-okhttp", version.ref = "ktor" }
2223
ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" }
23-
voyager-koin = { module = "cafe.adriel.voyager:voyager-koin", version.ref = "voyager" }
24-
voyager-navigator = { module = "cafe.adriel.voyager:voyager-navigator", version.ref = "voyager" }
24+
navigation-compose = { module = "org.jetbrains.androidx.navigation:navigation-compose", version.ref = "navigationCompose" }
2525

2626
[plugins]
2727
androidApplication = { id = "com.android.application", version.ref = "agp" }

0 commit comments

Comments
 (0)