Skip to content

Commit

Permalink
Merge pull request #11 from Kotlin/compose-nav
Browse files Browse the repository at this point in the history
Use Compose navigation library
  • Loading branch information
zsmb13 authored Sep 19, 2024
2 parents f2f39f2 + b41a5ab commit 013fba6
Show file tree
Hide file tree
Showing 9 changed files with 70 additions and 60 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ The data displayed by the app is from [The Metropolitan Museum of Art Collection
The app uses the following multiplatform dependencies in its implementation:

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

> 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.
4 changes: 2 additions & 2 deletions composeApp/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@ kotlin {

implementation(libs.kamel)
implementation(libs.koin.core)
implementation(libs.voyager.navigator)
implementation(libs.voyager.koin)
implementation(libs.koin.compose.viewmodel)
implementation(libs.navigation.compose)
}
}
}
Expand Down
20 changes: 18 additions & 2 deletions composeApp/src/commonMain/kotlin/com/jetbrains/kmpapp/App.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@ import androidx.compose.material.Surface
import androidx.compose.material.darkColors
import androidx.compose.material.lightColors
import androidx.compose.runtime.Composable
import cafe.adriel.voyager.navigator.Navigator
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import com.jetbrains.kmpapp.screens.detail.DetailScreen
import com.jetbrains.kmpapp.screens.list.ListScreen

@Composable
Expand All @@ -15,7 +19,19 @@ fun App() {
colors = if (isSystemInDarkTheme()) darkColors() else lightColors()
) {
Surface {
Navigator(ListScreen)
val navController: NavHostController = rememberNavController()
NavHost(
navController,
startDestination = "list"
) {
composable("list") {
ListScreen(navController)
}
composable("detail/{objectId}") { backStackEntry ->
val objectId = backStackEntry.arguments?.getString("objectId")?.toInt()
DetailScreen(navController, objectId!!)
}
}
}
}
}
13 changes: 7 additions & 6 deletions composeApp/src/commonMain/kotlin/com/jetbrains/kmpapp/di/Koin.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@ import com.jetbrains.kmpapp.data.KtorMuseumApi
import com.jetbrains.kmpapp.data.MuseumApi
import com.jetbrains.kmpapp.data.MuseumRepository
import com.jetbrains.kmpapp.data.MuseumStorage
import com.jetbrains.kmpapp.screens.detail.DetailScreenModel
import com.jetbrains.kmpapp.screens.list.ListScreenModel
import com.jetbrains.kmpapp.screens.detail.DetailViewModel
import com.jetbrains.kmpapp.screens.list.ListViewModel
import io.ktor.client.HttpClient
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
import io.ktor.http.ContentType
import io.ktor.serialization.kotlinx.json.json
import kotlinx.serialization.json.Json
import org.koin.compose.viewmodel.dsl.viewModel
import org.koin.core.context.startKoin
import org.koin.core.module.dsl.factoryOf
import org.koin.dsl.module
Expand All @@ -36,16 +37,16 @@ val dataModule = module {
}
}

val screenModelsModule = module {
factoryOf(::ListScreenModel)
factoryOf(::DetailScreenModel)
val viewModelModule = module {
factoryOf(::ListViewModel)
factoryOf(::DetailViewModel)
}

fun initKoin() {
startKoin {
modules(
dataModule,
screenModelsModule,
viewModelModule,
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,7 @@ import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.unit.dp
import cafe.adriel.voyager.core.screen.Screen
import cafe.adriel.voyager.koin.getScreenModel
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow
import androidx.navigation.NavController
import com.jetbrains.kmpapp.data.MuseumObject
import com.jetbrains.kmpapp.screens.EmptyScreenContent
import io.kamel.image.KamelImage
Expand All @@ -52,20 +49,21 @@ import kmp_app_template.composeapp.generated.resources.label_medium
import kmp_app_template.composeapp.generated.resources.label_repository
import kmp_app_template.composeapp.generated.resources.label_title
import org.jetbrains.compose.resources.stringResource
import org.koin.compose.viewmodel.koinViewModel

data class DetailScreen(val objectId: Int) : Screen {
@Composable
override fun Content() {
val navigator = LocalNavigator.currentOrThrow
val screenModel: DetailScreenModel = getScreenModel()
@Composable
fun DetailScreen(
navController: NavController,
objectId: Int,
) {
val viewModel = koinViewModel<DetailViewModel>()

val obj by screenModel.getObject(objectId).collectAsState(initial = null)
AnimatedContent(obj != null) { objectAvailable ->
if (objectAvailable) {
ObjectDetails(obj!!, onBackClick = { navigator.pop() })
} else {
EmptyScreenContent(Modifier.fillMaxSize())
}
val obj by viewModel.getObject(objectId).collectAsState(initial = null)
AnimatedContent(obj != null) { objectAvailable ->
if (objectAvailable) {
ObjectDetails(obj!!, onBackClick = { navController.navigateUp() })
} else {
EmptyScreenContent(Modifier.fillMaxSize())
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package com.jetbrains.kmpapp.screens.detail

import cafe.adriel.voyager.core.model.ScreenModel
import androidx.lifecycle.ViewModel
import com.jetbrains.kmpapp.data.MuseumObject
import com.jetbrains.kmpapp.data.MuseumRepository
import kotlinx.coroutines.flow.Flow

class DetailScreenModel(private val museumRepository: MuseumRepository) : ScreenModel {
class DetailViewModel(private val museumRepository: MuseumRepository) : ViewModel() {
fun getObject(objectId: Int): Flow<MuseumObject?> =
museumRepository.getObjectById(objectId)
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,35 +28,30 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import cafe.adriel.voyager.core.screen.Screen
import cafe.adriel.voyager.koin.getScreenModel
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow
import androidx.navigation.NavController
import com.jetbrains.kmpapp.data.MuseumObject
import com.jetbrains.kmpapp.screens.EmptyScreenContent
import com.jetbrains.kmpapp.screens.detail.DetailScreen
import io.kamel.image.KamelImage
import io.kamel.image.asyncPainterResource
import org.koin.compose.viewmodel.koinViewModel

data object ListScreen : Screen {
@Composable
override fun Content() {
val navigator = LocalNavigator.currentOrThrow
val screenModel: ListScreenModel = getScreenModel()

val objects by screenModel.objects.collectAsState()
@Composable
fun ListScreen(
navController: NavController,
) {
val viewModel = koinViewModel<ListViewModel>()
val objects by viewModel.objects.collectAsState()

AnimatedContent(objects.isNotEmpty()) { objectsAvailable ->
if (objectsAvailable) {
ObjectGrid(
objects = objects,
onObjectClick = { objectId ->
navigator.push(DetailScreen(objectId))
}
)
} else {
EmptyScreenContent(Modifier.fillMaxSize())
}
AnimatedContent(objects.isNotEmpty()) { objectsAvailable ->
if (objectsAvailable) {
ObjectGrid(
objects = objects,
onObjectClick = { objectId ->
navController.navigate("detail/$objectId")
}
)
} else {
EmptyScreenContent(Modifier.fillMaxSize())
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
package com.jetbrains.kmpapp.screens.list

import cafe.adriel.voyager.core.model.ScreenModel
import cafe.adriel.voyager.core.model.screenModelScope
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.jetbrains.kmpapp.data.MuseumObject
import com.jetbrains.kmpapp.data.MuseumRepository
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.stateIn

class ListScreenModel(museumRepository: MuseumRepository) : ScreenModel {
class ListViewModel(museumRepository: MuseumRepository) : ViewModel() {
val objects: StateFlow<List<MuseumObject>> =
museumRepository.getObjects()
.stateIn(screenModelScope, SharingStarted.WhileSubscribed(5000), emptyList())
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), emptyList())
}
8 changes: 4 additions & 4 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,24 @@ androidx-activityCompose = "1.9.2"
androidx-ui-tooling = "1.7.0"
compose-multiplatform = "1.6.11"
kamel = "0.9.5"
koin = "3.5.6"
koin = "4.0.0"
kotlin = "2.0.20"
ktor = "2.3.12"
voyager = "1.0.0"
navigationCompose = "2.7.0-alpha07"

[libraries]
androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activityCompose" }
androidx-compose-ui-tooling = { module = "androidx.compose.ui:ui-tooling", version.ref = "androidx-ui-tooling" }
androidx-compose-ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview", version.ref = "androidx-ui-tooling" }
kamel = { module = "media.kamel:kamel-image", version.ref = "kamel" }
koin-core = { module = "io.insert-koin:koin-core", version.ref = "koin" }
koin-compose-viewmodel = { module = "io.insert-koin:koin-compose-viewmodel", version.ref = "koin" }
ktor-client-content-negotiation = { module = "io.ktor:ktor-client-content-negotiation", version.ref = "ktor" }
ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
ktor-client-darwin = { module = "io.ktor:ktor-client-darwin", version.ref = "ktor" }
ktor-client-okhttp = { module = "io.ktor:ktor-client-okhttp", version.ref = "ktor" }
ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" }
voyager-koin = { module = "cafe.adriel.voyager:voyager-koin", version.ref = "voyager" }
voyager-navigator = { module = "cafe.adriel.voyager:voyager-navigator", version.ref = "voyager" }
navigation-compose = { module = "org.jetbrains.androidx.navigation:navigation-compose", version.ref = "navigationCompose" }

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

0 comments on commit 013fba6

Please sign in to comment.