Skip to content

Commit

Permalink
use KMMViewModel in Compose for Desktop client
Browse files Browse the repository at this point in the history
  • Loading branch information
joreilly committed Jul 31, 2023
1 parent 232090c commit 2689d84
Show file tree
Hide file tree
Showing 20 changed files with 96 additions and 141 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
@file:OptIn(ExperimentalMaterial3Api::class)

package dev.johnoreilly.bikeshare

import android.os.Bundle
Expand All @@ -13,7 +11,6 @@ import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeDrawing
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable
Expand Down Expand Up @@ -47,9 +44,9 @@ class MainActivity : ComponentActivity() {
}

sealed class Screen(val title: String) {
object CountryListScreen : Screen("CountryList")
object NetworkListScreen : Screen("NetworkList")
object StationsScreen : Screen("Stations")
data object CountryListScreen : Screen("CountryList")
data object NetworkListScreen : Screen("NetworkList")
data object StationsScreen : Screen("Stations")
}


Expand Down Expand Up @@ -80,16 +77,16 @@ fun BikeShareApp() {
NavHost(navController, startDestination = Screen.CountryListScreen.title) {
composable(Screen.CountryListScreen.title) {
CountryListScreen {
navController.navigate(Screen.NetworkListScreen.title + "/$it")
navController.navigate(Screen.NetworkListScreen.title + "/${it.code}")
}
}
composable(Screen.NetworkListScreen.title + "/{countryCode}") { backStackEntry ->
NetworkListScreen(backStackEntry.arguments?.get("countryCode") as String,
NetworkListScreen(backStackEntry.arguments?.getString("countryCode") as String,
networkSelected = { navController.navigate(Screen.StationsScreen.title + "/$it") },
popBack = { navController.popBackStack() })
}
composable(Screen.StationsScreen.title + "/{networkId}") { backStackEntry ->
StationsScreen(backStackEntry.arguments?.get("networkId") as String,
StationsScreen(backStackEntry.arguments?.getString("networkId") as String,
popBack = { navController.popBackStack() })
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

package dev.johnoreilly.bikeshare.ui

import android.annotation.SuppressLint
import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
Expand All @@ -24,13 +25,14 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import dev.johnoreilly.common.viewmodel.CountriesViewModelShared
import dev.johnoreilly.common.viewmodel.Country
import kotlinx.coroutines.launch
import org.koin.androidx.compose.getViewModel
import java.util.*


@Composable
fun CountryListScreen(countrySelected: (countryCode: String) -> Unit) {
fun CountryListScreen(countrySelected: (country: Country) -> Unit) {
val viewModel = getViewModel<CountriesViewModelShared>()
val countryList by viewModel.countryList.collectAsState()

Expand All @@ -41,8 +43,8 @@ fun CountryListScreen(countrySelected: (countryCode: String) -> Unit) {
val listState = rememberLazyListState()

LazyColumn(state = listState) {
items(countryList.sortedBy { getCountryName(it) }) { countryCode ->
CountryView(countryCode, countrySelected)
items(countryList) { country ->
CountryView(country, countrySelected)
}
}

Expand Down Expand Up @@ -71,30 +73,26 @@ fun CountryListScreen(countrySelected: (countryCode: String) -> Unit) {
}
}

@SuppressLint("DiscouragedApi")
@Composable
fun CountryView(countryCode: String, countrySelected: (countryCode: String) -> Unit) {
fun CountryView(country: Country, countrySelected: (country: Country) -> Unit) {
val context = LocalContext.current

Row(
modifier = Modifier
.fillMaxWidth()
.clickable(onClick = { countrySelected(countryCode) })
.clickable(onClick = { countrySelected(country) })
.padding(start = 16.dp, top = 8.dp, end = 16.dp, bottom = 8.dp),
verticalAlignment = Alignment.CenterVertically
) {

val countryName = getCountryName(countryCode)
val flagResourceId = context.resources.getIdentifier("flag_${countryCode.lowercase(Locale.getDefault())}", "drawable", context.getPackageName())
val flagResourceId = context.resources.getIdentifier("flag_${country.code.lowercase(Locale.getDefault())}", "drawable", context.packageName)
if (flagResourceId != 0) {
Image(painterResource(flagResourceId), modifier = Modifier.size(32.dp), contentDescription = countryName)
Image(painterResource(flagResourceId), modifier = Modifier.size(32.dp), contentDescription = country.displayName)
}

Spacer(modifier = Modifier.size(16.dp))
Text(text = countryName, style = MaterialTheme.typography.bodyLarge)
Text(text = country.displayName, style = MaterialTheme.typography.bodyLarge)
}
}

fun getCountryName(countryCode: String): String {
val locale = Locale("", countryCode)
return locale.displayCountry
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import dev.johnoreilly.common.getCountryName
import dev.johnoreilly.common.model.Network
import dev.johnoreilly.common.viewmodel.NetworksViewModelShared
import org.koin.androidx.compose.getViewModel
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,13 @@ import org.koin.androidx.compose.getViewModel
@Composable
fun StationsScreen(networkId: String, popBack: (() -> Unit)?) {
val viewModel = getViewModel<StationsViewModelShared>()
val stationsState = viewModel.stations.collectAsState()
val stations by viewModel.stations.collectAsState()

LaunchedEffect(networkId) {
viewModel.setNetwork(networkId)
}

var navigationIcon: @Composable() (() -> Unit)? = null
var navigationIcon: @Composable (() -> Unit)? = null
if (popBack != null) { navigationIcon = {
IconButton(onClick = { popBack() }) { Icon(Icons.Filled.ArrowBack, contentDescription = "Back") } }
}
Expand All @@ -50,7 +50,7 @@ fun StationsScreen(networkId: String, popBack: (() -> Unit)?) {
TopAppBar(title = { Text(networkId) }, navigationIcon = navigationIcon!!)
}) { paddingValues ->
LazyColumn(Modifier.padding(paddingValues)) {
items(stationsState.value) { station ->
items(stations) { station ->
StationView(station)
}
}
Expand Down
15 changes: 5 additions & 10 deletions common/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ kotlin {
api(core)
api(test)
}

api("com.rickclephas.kmm:kmm-viewmodel-core:${Versions.kmmViewModel}")
}
}

Expand All @@ -95,18 +97,11 @@ kotlin {
val iOSTest by getting {
}

val mobileMain by creating {
dependsOn(commonMain)
androidMain.dependsOn(this)
iOSMain.dependsOn(this)
dependencies {
implementation("com.rickclephas.kmm:kmm-viewmodel-core:${Versions.kmmViewModel}")
}
}


val jvmMain by getting {
dependencies {
// hack to allow use of MainScope() in shared code used by JVM console app
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-swing:${Versions.kotlinCoroutines}")

implementation(Deps.Ktor.clientJava)
implementation(Deps.Ktor.slf4j)
}
Expand Down
39 changes: 0 additions & 39 deletions common/common.podspec

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,14 @@ package dev.johnoreilly.common

import io.ktor.client.engine.android.*
import org.koin.dsl.module
import java.util.Locale

actual fun platformModule() = module {
single { Android.create() }
}

actual fun getCountryName(countryCode: String): String {
val locale = Locale("", countryCode)
return locale.displayCountry
}

2 changes: 2 additions & 0 deletions common/src/commonMain/kotlin/dev/johnoreilly/common/expect.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ package dev.johnoreilly.common
import org.koin.core.module.Module

expect fun platformModule(): Module

expect fun getCountryName(countryCode: String): String
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,12 @@ class CityBikesRepository: KoinComponent {
private val cityBikesApi: CityBikesApi by inject()
private val realm: Realm by inject()

val mainScope: CoroutineScope = MainScope()
private val mainScope: CoroutineScope = MainScope()

private val _groupedNetworkList = MutableStateFlow<Map<String,List<Network>>>(emptyMap())
val groupedNetworkList: StateFlow<Map<String,List<Network>>> = _groupedNetworkList

private val _networkList = MutableStateFlow<List<Network>>(emptyList())
val networkList: StateFlow<List<Network>> = _networkList

init {
mainScope.launch {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,21 @@ package dev.johnoreilly.common.viewmodel
import com.rickclephas.kmm.viewmodel.KMMViewModel
import com.rickclephas.kmm.viewmodel.*
import com.rickclephas.kmp.nativecoroutines.NativeCoroutinesState
import dev.johnoreilly.common.getCountryName
import dev.johnoreilly.common.repository.CityBikesRepository
import kotlinx.coroutines.flow.*
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject

data class Country(val code: String, val displayName: String)

open class CountriesViewModelShared : KMMViewModel(), KoinComponent {
private val cityBikesRepository: CityBikesRepository by inject()

@NativeCoroutinesState
val countryList: StateFlow<List<String>> = cityBikesRepository.groupedNetworkList.map {
it.keys.toList()
val countryList: StateFlow<List<Country>> = cityBikesRepository.groupedNetworkList.map {
it.keys.toList().map { countryCode ->
Country(countryCode, getCountryName(countryCode))
}.sortedBy { it.displayName }
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), emptyList())
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@ import kotlinx.coroutines.flow.*
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject


open class StationsViewModelShared : KMMViewModel(), KoinComponent {
private val cityBikesRepository: CityBikesRepository by inject()

private val network = MutableStateFlow<String?>(null)

@NativeCoroutinesState
val stations = network.filterNotNull().flatMapLatest { cityBikesRepository.pollNetworkUpdates(it) }
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), emptyList())
val stations = network.filterNotNull().flatMapLatest {
cityBikesRepository.pollNetworkUpdates(it)
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), emptyList())

fun setNetwork(networkId: String) {
network.value = networkId
Expand Down
12 changes: 9 additions & 3 deletions common/src/iOSMain/kotlin/dev/johnoreilly/common/actual.kt
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
package dev.johnoreilly.common

import io.ktor.client.engine.ios.*
import io.ktor.client.engine.darwin.Darwin
import org.koin.dsl.module
import platform.Foundation.NSLocale
import platform.Foundation.NSLocaleCountryCode
import platform.Foundation.NSLocaleKey
import platform.Foundation.currentLocale


actual fun platformModule() = module {
single { Ios.create() }
single { Darwin.create() }
}



actual fun getCountryName(countryCode: String): String {
return NSLocale.currentLocale.displayNameForKey(NSLocaleCountryCode, countryCode) ?: countryCode
}
5 changes: 5 additions & 0 deletions common/src/jvmMain/kotlin/dev/johnoreilly/common/actual.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,14 @@ package dev.johnoreilly.common

import io.ktor.client.engine.java.*
import org.koin.dsl.module
import java.util.Locale


actual fun platformModule() = module {
single { Java.create() }
}

actual fun getCountryName(countryCode: String): String {
val locale = Locale("", countryCode)
return locale.displayCountry
}
12 changes: 0 additions & 12 deletions common/src/macOSMain/kotlin/dev/johnoreilly/common/actual.kt

This file was deleted.

Loading

0 comments on commit 2689d84

Please sign in to comment.