From b0bb16d94bf4eb3030dbc95c1f823b74b9eced8f Mon Sep 17 00:00:00 2001 From: bpedryc Date: Fri, 9 Jun 2023 22:13:07 +0200 Subject: [PATCH 1/9] Improve naming and package structure --- .../touchlab/kampkit/android/MainActivity.kt | 10 ++-- .../co/touchlab/kampkit/android/MainApp.kt | 10 ++-- .../ui/{Composables.kt => BreedsScreen.kt} | 41 ++++++++-------- ios/KaMPKitiOS/Breeds/BreedsViewModel.swift | 8 +-- .../kampkit/{ => core}/KoinAndroid.kt | 2 +- .../kampkit/{models => core}/ViewModel.kt | 4 +- .../kotlin/co/touchlab/kampkit/KoinTest.kt | 2 + .../co/touchlab/kampkit/{ => core}/AppInfo.kt | 2 +- .../CoroutinesExtensions.kt | 2 +- .../co/touchlab/kampkit/{ => core}/Koin.kt | 13 ++--- .../kampkit/{models => core}/ViewModel.kt | 4 +- .../co/touchlab/kampkit/data/dog/DogApi.kt | 5 ++ .../kampkit/{ktor => data/dog}/DogApiImpl.kt | 5 +- .../dog/DogDatabaseHelper.kt} | 6 +-- .../dog/DogRepository.kt} | 10 ++-- .../BreedResult.kt => data/dog/DogResult.kt} | 4 +- .../kotlin/co/touchlab/kampkit/ktor/DogApi.kt | 7 --- .../breeds/BreedsViewModel.kt} | 18 ++++--- .../touchlab/kampkit/BreedRepositoryTest.kt | 13 ++--- .../co/touchlab/kampkit/BreedViewModelTest.kt | 49 ++++++++++--------- .../kotlin/co/touchlab/kampkit/DogApiTest.kt | 6 +-- .../co/touchlab/kampkit/SqlDelightTest.kt | 7 +-- .../kotlin/co/touchlab/kampkit/TestAppInfo.kt | 2 + .../co/touchlab/kampkit/mock/DogApiMock.kt | 16 +++--- .../co/touchlab/kampkit/{ => core}/KoinIOS.kt | 8 +-- .../kampkit/{models => core}/ViewModel.kt | 4 +- .../kotlin/co/touchlab/kampkit/KoinTest.kt | 1 + 27 files changed, 133 insertions(+), 126 deletions(-) rename app/src/main/kotlin/co/touchlab/kampkit/android/ui/{Composables.kt => BreedsScreen.kt} (85%) rename shared/src/androidMain/kotlin/co/touchlab/kampkit/{ => core}/KoinAndroid.kt (95%) rename shared/src/androidMain/kotlin/co/touchlab/kampkit/{models => core}/ViewModel.kt (91%) rename shared/src/commonMain/kotlin/co/touchlab/kampkit/{ => core}/AppInfo.kt (57%) rename shared/src/commonMain/kotlin/co/touchlab/kampkit/{sqldelight => core}/CoroutinesExtensions.kt (92%) rename shared/src/commonMain/kotlin/co/touchlab/kampkit/{ => core}/Koin.kt (89%) rename shared/src/commonMain/kotlin/co/touchlab/kampkit/{models => core}/ViewModel.kt (80%) create mode 100644 shared/src/commonMain/kotlin/co/touchlab/kampkit/data/dog/DogApi.kt rename shared/src/commonMain/kotlin/co/touchlab/kampkit/{ktor => data/dog}/DogApiImpl.kt (92%) rename shared/src/commonMain/kotlin/co/touchlab/kampkit/{DatabaseHelper.kt => data/dog/DogDatabaseHelper.kt} (93%) rename shared/src/commonMain/kotlin/co/touchlab/kampkit/{models/BreedRepository.kt => data/dog/DogRepository.kt} (87%) rename shared/src/commonMain/kotlin/co/touchlab/kampkit/{response/BreedResult.kt => data/dog/DogResult.kt} (67%) delete mode 100644 shared/src/commonMain/kotlin/co/touchlab/kampkit/ktor/DogApi.kt rename shared/src/commonMain/kotlin/co/touchlab/kampkit/{models/BreedViewModel.kt => ui/breeds/BreedsViewModel.kt} (86%) rename shared/src/iosMain/kotlin/co/touchlab/kampkit/{ => core}/KoinIOS.kt (84%) rename shared/src/iosMain/kotlin/co/touchlab/kampkit/{models => core}/ViewModel.kt (96%) diff --git a/app/src/main/kotlin/co/touchlab/kampkit/android/MainActivity.kt b/app/src/main/kotlin/co/touchlab/kampkit/android/MainActivity.kt index 08ae0954..ed0f2dff 100644 --- a/app/src/main/kotlin/co/touchlab/kampkit/android/MainActivity.kt +++ b/app/src/main/kotlin/co/touchlab/kampkit/android/MainActivity.kt @@ -3,10 +3,10 @@ package co.touchlab.kampkit.android import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent -import co.touchlab.kampkit.android.ui.MainScreen +import co.touchlab.kampkit.android.ui.BreedsScreen import co.touchlab.kampkit.android.ui.theme.KaMPKitTheme -import co.touchlab.kampkit.injectLogger -import co.touchlab.kampkit.models.BreedViewModel +import co.touchlab.kampkit.core.injectLogger +import co.touchlab.kampkit.ui.breeds.BreedsViewModel import co.touchlab.kermit.Logger import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.core.component.KoinComponent @@ -14,13 +14,13 @@ import org.koin.core.component.KoinComponent class MainActivity : ComponentActivity(), KoinComponent { private val log: Logger by injectLogger("MainActivity") - private val viewModel: BreedViewModel by viewModel() + private val viewModel: BreedsViewModel by viewModel() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { KaMPKitTheme { - MainScreen(viewModel, log) + BreedsScreen(viewModel, log) } } } diff --git a/app/src/main/kotlin/co/touchlab/kampkit/android/MainApp.kt b/app/src/main/kotlin/co/touchlab/kampkit/android/MainApp.kt index d40cc2c9..2b78439c 100644 --- a/app/src/main/kotlin/co/touchlab/kampkit/android/MainApp.kt +++ b/app/src/main/kotlin/co/touchlab/kampkit/android/MainApp.kt @@ -4,9 +4,9 @@ import android.app.Application import android.content.Context import android.content.SharedPreferences import android.util.Log -import co.touchlab.kampkit.AppInfo -import co.touchlab.kampkit.initKoin -import co.touchlab.kampkit.models.BreedViewModel +import co.touchlab.kampkit.core.AppInfo +import co.touchlab.kampkit.core.initKoin +import co.touchlab.kampkit.ui.breeds.BreedsViewModel import org.koin.androidx.viewmodel.dsl.viewModel import org.koin.core.parameter.parametersOf import org.koin.dsl.module @@ -18,9 +18,9 @@ class MainApp : Application() { initKoin( module { single { this@MainApp } - viewModel { BreedViewModel(get(), get { parametersOf("BreedViewModel") }) } + viewModel { BreedsViewModel(get(), get { parametersOf("BreedViewModel") }) } single { - get().getSharedPreferences("KAMPSTARTER_SETTINGS", Context.MODE_PRIVATE) + get().getSharedPreferences("KAMPSTARTER_SETTINGS", MODE_PRIVATE) } single { AndroidAppInfo } single { diff --git a/app/src/main/kotlin/co/touchlab/kampkit/android/ui/Composables.kt b/app/src/main/kotlin/co/touchlab/kampkit/android/ui/BreedsScreen.kt similarity index 85% rename from app/src/main/kotlin/co/touchlab/kampkit/android/ui/Composables.kt rename to app/src/main/kotlin/co/touchlab/kampkit/android/ui/BreedsScreen.kt index b9337244..778a5883 100644 --- a/app/src/main/kotlin/co/touchlab/kampkit/android/ui/Composables.kt +++ b/app/src/main/kotlin/co/touchlab/kampkit/android/ui/BreedsScreen.kt @@ -30,21 +30,21 @@ import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import co.touchlab.kampkit.android.R import co.touchlab.kampkit.db.Breed -import co.touchlab.kampkit.models.BreedViewModel -import co.touchlab.kampkit.models.BreedViewState +import co.touchlab.kampkit.ui.breeds.BreedViewState +import co.touchlab.kampkit.ui.breeds.BreedsViewModel import co.touchlab.kermit.Logger import com.google.accompanist.swiperefresh.SwipeRefresh import com.google.accompanist.swiperefresh.rememberSwipeRefreshState @Composable -fun MainScreen( - viewModel: BreedViewModel, +fun BreedsScreen( + viewModel: BreedsViewModel, log: Logger ) { - val dogsState by viewModel.breedState.collectAsStateWithLifecycle() + val breedsState by viewModel.breedsState.collectAsStateWithLifecycle() - MainScreenContent( - dogsState = dogsState, + BreedsScreenContent( + dogsState = breedsState, onRefresh = { viewModel.refreshBreeds() }, onSuccess = { data -> log.v { "View updating with ${data.size} breeds" } }, onError = { exception -> log.e { "Displaying error: $exception" } }, @@ -53,7 +53,7 @@ fun MainScreen( } @Composable -fun MainScreenContent( +fun BreedsScreenContent( dogsState: BreedViewState, onRefresh: () -> Unit = {}, onSuccess: (List) -> Unit = {}, @@ -68,18 +68,19 @@ fun MainScreenContent( state = rememberSwipeRefreshState(isRefreshing = dogsState.isLoading), onRefresh = onRefresh ) { - if (dogsState.isEmpty) { - Empty() - } - val breeds = dogsState.breeds - if (breeds != null) { - LaunchedEffect(breeds) { - onSuccess(breeds) + if (dogsState.error == null) { + if (dogsState.isEmpty) { + Empty() + } else { + val breeds = dogsState.breeds + LaunchedEffect(breeds) { + onSuccess(breeds) + } + Success(successData = breeds, favoriteBreed = onFavorite) } - Success(successData = breeds, favoriteBreed = onFavorite) } - val error = dogsState.error - if (error != null) { + + dogsState.error?.let { error -> LaunchedEffect(error) { onError(error) } @@ -172,8 +173,8 @@ fun FavoriteIcon(breed: Breed) { @Preview @Composable -fun MainScreenContentPreview_Success() { - MainScreenContent( +fun BreedsScreenContentPreview_Success() { + BreedsScreenContent( dogsState = BreedViewState( breeds = listOf( Breed(0, "appenzeller", false), diff --git a/ios/KaMPKitiOS/Breeds/BreedsViewModel.swift b/ios/KaMPKitiOS/Breeds/BreedsViewModel.swift index c4ea2b2e..b92a8eb9 100644 --- a/ios/KaMPKitiOS/Breeds/BreedsViewModel.swift +++ b/ios/KaMPKitiOS/Breeds/BreedsViewModel.swift @@ -12,18 +12,18 @@ import shared import KMPNativeCoroutinesCombine class BreedsViewModel: ObservableObject { - + @Published var state: BreedViewState = BreedViewState.companion.default() - + private var viewModelDelegate: BreedViewModelDelegate = KotlinDependencies.shared.getBreedViewModel() private var cancellables = [AnyCancellable]() - + deinit { viewModelDelegate.clear() } func subscribeState() { - createPublisher(for: viewModelDelegate.breedStateFlow) + createPublisher(for: viewModelDelegate.breedsStateFlow) .sink { _ in } receiveValue: { [weak self] (breedState: BreedViewState) in self?.state = breedState } diff --git a/shared/src/androidMain/kotlin/co/touchlab/kampkit/KoinAndroid.kt b/shared/src/androidMain/kotlin/co/touchlab/kampkit/core/KoinAndroid.kt similarity index 95% rename from shared/src/androidMain/kotlin/co/touchlab/kampkit/KoinAndroid.kt rename to shared/src/androidMain/kotlin/co/touchlab/kampkit/core/KoinAndroid.kt index ed6bc238..595c3eec 100644 --- a/shared/src/androidMain/kotlin/co/touchlab/kampkit/KoinAndroid.kt +++ b/shared/src/androidMain/kotlin/co/touchlab/kampkit/core/KoinAndroid.kt @@ -1,4 +1,4 @@ -package co.touchlab.kampkit +package co.touchlab.kampkit.core import co.touchlab.kampkit.db.KaMPKitDb import com.russhwolf.settings.Settings diff --git a/shared/src/androidMain/kotlin/co/touchlab/kampkit/models/ViewModel.kt b/shared/src/androidMain/kotlin/co/touchlab/kampkit/core/ViewModel.kt similarity index 91% rename from shared/src/androidMain/kotlin/co/touchlab/kampkit/models/ViewModel.kt rename to shared/src/androidMain/kotlin/co/touchlab/kampkit/core/ViewModel.kt index 9eb0f27e..7467d1ad 100644 --- a/shared/src/androidMain/kotlin/co/touchlab/kampkit/models/ViewModel.kt +++ b/shared/src/androidMain/kotlin/co/touchlab/kampkit/core/ViewModel.kt @@ -1,4 +1,4 @@ -package co.touchlab.kampkit.models +package co.touchlab.kampkit.core import kotlinx.coroutines.CoroutineScope import androidx.lifecycle.ViewModel as AndroidXViewModel @@ -10,4 +10,4 @@ actual abstract class ViewModel actual constructor() : AndroidXViewModel() { actual override fun onCleared() { super.onCleared() } -} +} \ No newline at end of file diff --git a/shared/src/androidUnitTest/kotlin/co/touchlab/kampkit/KoinTest.kt b/shared/src/androidUnitTest/kotlin/co/touchlab/kampkit/KoinTest.kt index 4be2ca84..0171b63e 100644 --- a/shared/src/androidUnitTest/kotlin/co/touchlab/kampkit/KoinTest.kt +++ b/shared/src/androidUnitTest/kotlin/co/touchlab/kampkit/KoinTest.kt @@ -4,6 +4,8 @@ import android.app.Application import android.content.Context import androidx.test.core.app.ApplicationProvider.getApplicationContext import androidx.test.ext.junit.runners.AndroidJUnit4 +import co.touchlab.kampkit.core.AppInfo +import co.touchlab.kampkit.core.initKoin import co.touchlab.kermit.Logger import org.junit.experimental.categories.Category import org.junit.runner.RunWith diff --git a/shared/src/commonMain/kotlin/co/touchlab/kampkit/AppInfo.kt b/shared/src/commonMain/kotlin/co/touchlab/kampkit/core/AppInfo.kt similarity index 57% rename from shared/src/commonMain/kotlin/co/touchlab/kampkit/AppInfo.kt rename to shared/src/commonMain/kotlin/co/touchlab/kampkit/core/AppInfo.kt index f2f339df..a1ff4c11 100644 --- a/shared/src/commonMain/kotlin/co/touchlab/kampkit/AppInfo.kt +++ b/shared/src/commonMain/kotlin/co/touchlab/kampkit/core/AppInfo.kt @@ -1,4 +1,4 @@ -package co.touchlab.kampkit +package co.touchlab.kampkit.core interface AppInfo { val appId: String diff --git a/shared/src/commonMain/kotlin/co/touchlab/kampkit/sqldelight/CoroutinesExtensions.kt b/shared/src/commonMain/kotlin/co/touchlab/kampkit/core/CoroutinesExtensions.kt similarity index 92% rename from shared/src/commonMain/kotlin/co/touchlab/kampkit/sqldelight/CoroutinesExtensions.kt rename to shared/src/commonMain/kotlin/co/touchlab/kampkit/core/CoroutinesExtensions.kt index d8ac0aa5..fcb0faab 100644 --- a/shared/src/commonMain/kotlin/co/touchlab/kampkit/sqldelight/CoroutinesExtensions.kt +++ b/shared/src/commonMain/kotlin/co/touchlab/kampkit/core/CoroutinesExtensions.kt @@ -1,4 +1,4 @@ -package co.touchlab.kampkit.sqldelight +package co.touchlab.kampkit.core import com.squareup.sqldelight.Transacter import com.squareup.sqldelight.TransactionWithoutReturn diff --git a/shared/src/commonMain/kotlin/co/touchlab/kampkit/Koin.kt b/shared/src/commonMain/kotlin/co/touchlab/kampkit/core/Koin.kt similarity index 89% rename from shared/src/commonMain/kotlin/co/touchlab/kampkit/Koin.kt rename to shared/src/commonMain/kotlin/co/touchlab/kampkit/core/Koin.kt index db7c2df1..2660f34a 100644 --- a/shared/src/commonMain/kotlin/co/touchlab/kampkit/Koin.kt +++ b/shared/src/commonMain/kotlin/co/touchlab/kampkit/core/Koin.kt @@ -1,8 +1,9 @@ -package co.touchlab.kampkit +package co.touchlab.kampkit.core -import co.touchlab.kampkit.ktor.DogApi -import co.touchlab.kampkit.ktor.DogApiImpl -import co.touchlab.kampkit.models.BreedRepository +import co.touchlab.kampkit.data.dog.DogApi +import co.touchlab.kampkit.data.dog.DogApiImpl +import co.touchlab.kampkit.data.dog.DogDatabaseHelper +import co.touchlab.kampkit.data.dog.DogRepository import co.touchlab.kermit.Logger import co.touchlab.kermit.StaticConfig import co.touchlab.kermit.platformLogWriter @@ -42,7 +43,7 @@ fun initKoin(appModule: Module): KoinApplication { private val coreModule = module { single { - DatabaseHelper( + DogDatabaseHelper( get(), getWith("DatabaseHelper"), Dispatchers.Default @@ -66,7 +67,7 @@ private val coreModule = module { factory { (tag: String?) -> if (tag != null) baseLogger.withTag(tag) else baseLogger } single { - BreedRepository( + DogRepository( get(), get(), get(), diff --git a/shared/src/commonMain/kotlin/co/touchlab/kampkit/models/ViewModel.kt b/shared/src/commonMain/kotlin/co/touchlab/kampkit/core/ViewModel.kt similarity index 80% rename from shared/src/commonMain/kotlin/co/touchlab/kampkit/models/ViewModel.kt rename to shared/src/commonMain/kotlin/co/touchlab/kampkit/core/ViewModel.kt index d3c42256..d0eb076b 100644 --- a/shared/src/commonMain/kotlin/co/touchlab/kampkit/models/ViewModel.kt +++ b/shared/src/commonMain/kotlin/co/touchlab/kampkit/core/ViewModel.kt @@ -1,8 +1,8 @@ -package co.touchlab.kampkit.models +package co.touchlab.kampkit.core import kotlinx.coroutines.CoroutineScope expect abstract class ViewModel() { val viewModelScope: CoroutineScope protected open fun onCleared() -} +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/co/touchlab/kampkit/data/dog/DogApi.kt b/shared/src/commonMain/kotlin/co/touchlab/kampkit/data/dog/DogApi.kt new file mode 100644 index 00000000..3019c101 --- /dev/null +++ b/shared/src/commonMain/kotlin/co/touchlab/kampkit/data/dog/DogApi.kt @@ -0,0 +1,5 @@ +package co.touchlab.kampkit.data.dog + +interface DogApi { + suspend fun getJsonFromApi(): DogResult +} diff --git a/shared/src/commonMain/kotlin/co/touchlab/kampkit/ktor/DogApiImpl.kt b/shared/src/commonMain/kotlin/co/touchlab/kampkit/data/dog/DogApiImpl.kt similarity index 92% rename from shared/src/commonMain/kotlin/co/touchlab/kampkit/ktor/DogApiImpl.kt rename to shared/src/commonMain/kotlin/co/touchlab/kampkit/data/dog/DogApiImpl.kt index 1e55468b..dfb16667 100644 --- a/shared/src/commonMain/kotlin/co/touchlab/kampkit/ktor/DogApiImpl.kt +++ b/shared/src/commonMain/kotlin/co/touchlab/kampkit/data/dog/DogApiImpl.kt @@ -1,6 +1,5 @@ -package co.touchlab.kampkit.ktor +package co.touchlab.kampkit.data.dog -import co.touchlab.kampkit.response.BreedResult import co.touchlab.stately.ensureNeverFrozen import io.ktor.client.HttpClient import io.ktor.client.call.body @@ -45,7 +44,7 @@ class DogApiImpl(private val log: KermitLogger, engine: HttpClientEngine) : DogA ensureNeverFrozen() } - override suspend fun getJsonFromApi(): BreedResult { + override suspend fun getJsonFromApi(): DogResult { log.d { "Fetching Breeds from network" } return client.get { dogs("api/breeds/list/all") diff --git a/shared/src/commonMain/kotlin/co/touchlab/kampkit/DatabaseHelper.kt b/shared/src/commonMain/kotlin/co/touchlab/kampkit/data/dog/DogDatabaseHelper.kt similarity index 93% rename from shared/src/commonMain/kotlin/co/touchlab/kampkit/DatabaseHelper.kt rename to shared/src/commonMain/kotlin/co/touchlab/kampkit/data/dog/DogDatabaseHelper.kt index f5e82022..3fdd8029 100644 --- a/shared/src/commonMain/kotlin/co/touchlab/kampkit/DatabaseHelper.kt +++ b/shared/src/commonMain/kotlin/co/touchlab/kampkit/data/dog/DogDatabaseHelper.kt @@ -1,8 +1,8 @@ -package co.touchlab.kampkit +package co.touchlab.kampkit.data.dog import co.touchlab.kampkit.db.Breed import co.touchlab.kampkit.db.KaMPKitDb -import co.touchlab.kampkit.sqldelight.transactionWithContext +import co.touchlab.kampkit.core.transactionWithContext import co.touchlab.kermit.Logger import com.squareup.sqldelight.db.SqlDriver import com.squareup.sqldelight.runtime.coroutines.asFlow @@ -12,7 +12,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flowOn -class DatabaseHelper( +class DogDatabaseHelper( sqlDriver: SqlDriver, private val log: Logger, private val backgroundDispatcher: CoroutineDispatcher diff --git a/shared/src/commonMain/kotlin/co/touchlab/kampkit/models/BreedRepository.kt b/shared/src/commonMain/kotlin/co/touchlab/kampkit/data/dog/DogRepository.kt similarity index 87% rename from shared/src/commonMain/kotlin/co/touchlab/kampkit/models/BreedRepository.kt rename to shared/src/commonMain/kotlin/co/touchlab/kampkit/data/dog/DogRepository.kt index 4f8c2b22..6b392537 100644 --- a/shared/src/commonMain/kotlin/co/touchlab/kampkit/models/BreedRepository.kt +++ b/shared/src/commonMain/kotlin/co/touchlab/kampkit/data/dog/DogRepository.kt @@ -1,23 +1,21 @@ -package co.touchlab.kampkit.models +package co.touchlab.kampkit.data.dog -import co.touchlab.kampkit.DatabaseHelper import co.touchlab.kampkit.db.Breed -import co.touchlab.kampkit.ktor.DogApi import co.touchlab.kermit.Logger import co.touchlab.stately.ensureNeverFrozen import com.russhwolf.settings.Settings import kotlinx.coroutines.flow.Flow import kotlinx.datetime.Clock -class BreedRepository( - private val dbHelper: DatabaseHelper, +class DogRepository( + private val dbHelper: DogDatabaseHelper, private val settings: Settings, private val dogApi: DogApi, log: Logger, private val clock: Clock ) { - private val log = log.withTag("BreedModel") + private val log = log.withTag("DogRepository") companion object { internal const val DB_TIMESTAMP_KEY = "DbTimestampKey" diff --git a/shared/src/commonMain/kotlin/co/touchlab/kampkit/response/BreedResult.kt b/shared/src/commonMain/kotlin/co/touchlab/kampkit/data/dog/DogResult.kt similarity index 67% rename from shared/src/commonMain/kotlin/co/touchlab/kampkit/response/BreedResult.kt rename to shared/src/commonMain/kotlin/co/touchlab/kampkit/data/dog/DogResult.kt index 49784591..08d36915 100644 --- a/shared/src/commonMain/kotlin/co/touchlab/kampkit/response/BreedResult.kt +++ b/shared/src/commonMain/kotlin/co/touchlab/kampkit/data/dog/DogResult.kt @@ -1,9 +1,9 @@ -package co.touchlab.kampkit.response +package co.touchlab.kampkit.data.dog import kotlinx.serialization.Serializable @Serializable -data class BreedResult( +data class DogResult( val message: Map>, var status: String ) diff --git a/shared/src/commonMain/kotlin/co/touchlab/kampkit/ktor/DogApi.kt b/shared/src/commonMain/kotlin/co/touchlab/kampkit/ktor/DogApi.kt deleted file mode 100644 index 0e296ee0..00000000 --- a/shared/src/commonMain/kotlin/co/touchlab/kampkit/ktor/DogApi.kt +++ /dev/null @@ -1,7 +0,0 @@ -package co.touchlab.kampkit.ktor - -import co.touchlab.kampkit.response.BreedResult - -interface DogApi { - suspend fun getJsonFromApi(): BreedResult -} diff --git a/shared/src/commonMain/kotlin/co/touchlab/kampkit/models/BreedViewModel.kt b/shared/src/commonMain/kotlin/co/touchlab/kampkit/ui/breeds/BreedsViewModel.kt similarity index 86% rename from shared/src/commonMain/kotlin/co/touchlab/kampkit/models/BreedViewModel.kt rename to shared/src/commonMain/kotlin/co/touchlab/kampkit/ui/breeds/BreedsViewModel.kt index 27567f07..94083df8 100644 --- a/shared/src/commonMain/kotlin/co/touchlab/kampkit/models/BreedViewModel.kt +++ b/shared/src/commonMain/kotlin/co/touchlab/kampkit/ui/breeds/BreedsViewModel.kt @@ -1,6 +1,8 @@ -package co.touchlab.kampkit.models +package co.touchlab.kampkit.ui.breeds +import co.touchlab.kampkit.data.dog.DogRepository import co.touchlab.kampkit.db.Breed +import co.touchlab.kampkit.core.ViewModel import co.touchlab.kermit.Logger import com.rickclephas.kmp.nativecoroutines.NativeCoroutinesState import kotlinx.coroutines.Job @@ -13,8 +15,8 @@ import kotlinx.coroutines.launch import kotlin.native.ObjCName @ObjCName("BreedViewModelDelegate") -class BreedViewModel( - private val breedRepository: BreedRepository, +class BreedsViewModel( + private val dogRepository: DogRepository, log: Logger ) : ViewModel() { private val log = log.withTag("BreedViewModel") @@ -23,7 +25,7 @@ class BreedViewModel( MutableStateFlow(BreedViewState(isLoading = true)) @NativeCoroutinesState - val breedState: StateFlow = mutableBreedState + val breedsState: StateFlow = mutableBreedState init { observeBreeds() @@ -37,7 +39,7 @@ class BreedViewModel( // Refresh breeds, and emit any exception that was thrown so we can handle it downstream val refreshFlow = flow { try { - breedRepository.refreshBreedsIfStale() + dogRepository.refreshBreedsIfStale() emit(null) } catch (exception: Exception) { emit(exception) @@ -45,7 +47,7 @@ class BreedViewModel( } viewModelScope.launch { - combine(refreshFlow, breedRepository.getBreeds()) { throwable, breeds -> throwable to breeds } + combine(refreshFlow, dogRepository.getBreeds()) { throwable, breeds -> throwable to breeds } .collect { (error, breeds) -> mutableBreedState.update { previousState -> val errorMessage = if (error != null) { @@ -70,7 +72,7 @@ class BreedViewModel( return viewModelScope.launch { log.v { "refreshBreeds" } try { - breedRepository.refreshBreeds() + dogRepository.refreshBreeds() } catch (exception: Exception) { handleBreedError(exception) } @@ -79,7 +81,7 @@ class BreedViewModel( fun updateBreedFavorite(breed: Breed): Job { return viewModelScope.launch { - breedRepository.updateBreedFavorite(breed) + dogRepository.updateBreedFavorite(breed) } } diff --git a/shared/src/commonTest/kotlin/co/touchlab/kampkit/BreedRepositoryTest.kt b/shared/src/commonTest/kotlin/co/touchlab/kampkit/BreedRepositoryTest.kt index 9656b8d3..39e96da6 100644 --- a/shared/src/commonTest/kotlin/co/touchlab/kampkit/BreedRepositoryTest.kt +++ b/shared/src/commonTest/kotlin/co/touchlab/kampkit/BreedRepositoryTest.kt @@ -1,10 +1,11 @@ package co.touchlab.kampkit import app.cash.turbine.test +import co.touchlab.kampkit.data.dog.DogDatabaseHelper import co.touchlab.kampkit.db.Breed import co.touchlab.kampkit.mock.ClockMock import co.touchlab.kampkit.mock.DogApiMock -import co.touchlab.kampkit.models.BreedRepository +import co.touchlab.kampkit.data.dog.DogRepository import co.touchlab.kermit.Logger import co.touchlab.kermit.StaticConfig import com.russhwolf.settings.MapSettings @@ -21,7 +22,7 @@ class BreedRepositoryTest { private var kermit = Logger(StaticConfig()) private var testDbConnection = testDbConnection() - private var dbHelper = DatabaseHelper( + private var dbHelper = DogDatabaseHelper( testDbConnection, kermit, Dispatchers.Default @@ -32,7 +33,7 @@ class BreedRepositoryTest { // Need to start at non-zero time because the default value for db timestamp is 0 private val clock = ClockMock(Clock.System.now()) - private val repository: BreedRepository = BreedRepository(dbHelper, settings, ktorApi, kermit, clock) + private val repository: DogRepository = DogRepository(dbHelper, settings, ktorApi, kermit, clock) companion object { private val appenzeller = Breed(1, "appenzeller", false) @@ -78,7 +79,7 @@ class BreedRepositoryTest { @Test fun `Get updated breeds when stale and preserve favorites`() = runTest { - settings.putLong(BreedRepository.DB_TIMESTAMP_KEY, (clock.currentInstant - 2.hours).toEpochMilliseconds()) + settings.putLong(DogRepository.DB_TIMESTAMP_KEY, (clock.currentInstant - 2.hours).toEpochMilliseconds()) val successResult = ktorApi.successResult() val resultWithExtraBreed = successResult.copy(message = successResult.message + ("extra" to emptyList())) @@ -110,7 +111,7 @@ class BreedRepositoryTest { @Test fun `No web call if data is not stale`() = runTest { - settings.putLong(BreedRepository.DB_TIMESTAMP_KEY, clock.currentInstant.toEpochMilliseconds()) + settings.putLong(DogRepository.DB_TIMESTAMP_KEY, clock.currentInstant.toEpochMilliseconds()) ktorApi.prepareResult(ktorApi.successResult()) repository.refreshBreedsIfStale() @@ -132,7 +133,7 @@ class BreedRepositoryTest { @Test fun `Rethrow on API error when stale`() = runTest { - settings.putLong(BreedRepository.DB_TIMESTAMP_KEY, (clock.currentInstant - 2.hours).toEpochMilliseconds()) + settings.putLong(DogRepository.DB_TIMESTAMP_KEY, (clock.currentInstant - 2.hours).toEpochMilliseconds()) ktorApi.throwOnCall(RuntimeException("Test error")) val throwable = assertFails { diff --git a/shared/src/commonTest/kotlin/co/touchlab/kampkit/BreedViewModelTest.kt b/shared/src/commonTest/kotlin/co/touchlab/kampkit/BreedViewModelTest.kt index 38654cad..71540893 100644 --- a/shared/src/commonTest/kotlin/co/touchlab/kampkit/BreedViewModelTest.kt +++ b/shared/src/commonTest/kotlin/co/touchlab/kampkit/BreedViewModelTest.kt @@ -2,13 +2,14 @@ package co.touchlab.kampkit import app.cash.turbine.ReceiveTurbine import app.cash.turbine.test +import co.touchlab.kampkit.data.dog.DogDatabaseHelper +import co.touchlab.kampkit.data.dog.DogRepository +import co.touchlab.kampkit.data.dog.DogResult import co.touchlab.kampkit.db.Breed import co.touchlab.kampkit.mock.ClockMock import co.touchlab.kampkit.mock.DogApiMock -import co.touchlab.kampkit.models.BreedRepository -import co.touchlab.kampkit.models.BreedViewModel -import co.touchlab.kampkit.models.BreedViewState -import co.touchlab.kampkit.response.BreedResult +import co.touchlab.kampkit.ui.breeds.BreedViewState +import co.touchlab.kampkit.ui.breeds.BreedsViewModel import co.touchlab.kermit.Logger import co.touchlab.kermit.StaticConfig import com.russhwolf.settings.MapSettings @@ -26,7 +27,7 @@ import kotlin.time.Duration.Companion.hours class BreedViewModelTest { private var kermit = Logger(StaticConfig()) private var testDbConnection = testDbConnection() - private var dbHelper = DatabaseHelper( + private var dbHelper = DogDatabaseHelper( testDbConnection, kermit, Dispatchers.Default @@ -37,8 +38,8 @@ class BreedViewModelTest { // Need to start at non-zero time because the default value for db timestamp is 0 private val clock = ClockMock(Clock.System.now()) - private val repository: BreedRepository = BreedRepository(dbHelper, settings, ktorApi, kermit, clock) - private val viewModel by lazy { BreedViewModel(repository, kermit) } + private val repository: DogRepository = DogRepository(dbHelper, settings, ktorApi, kermit, clock) + private val viewModel by lazy { BreedsViewModel(repository, kermit) } companion object { private val appenzeller = Breed(1, "appenzeller", false) @@ -68,7 +69,7 @@ class BreedViewModelTest { fun `Get breeds without cache`() = runTest { ktorApi.prepareResult(ktorApi.successResult()) - viewModel.breedState.test { + viewModel.breedsState.test { assertEquals( breedViewStateSuccessNoFavorite, awaitItemPrecededBy(BreedViewState(isLoading = true), BreedViewState(isEmpty = true)) @@ -78,9 +79,9 @@ class BreedViewModelTest { @Test fun `Get breeds empty`() = runTest { - ktorApi.prepareResult(BreedResult(emptyMap(), "success")) + ktorApi.prepareResult(DogResult(emptyMap(), "success")) - viewModel.breedState.test { + viewModel.breedsState.test { assertEquals( BreedViewState(isEmpty = true), awaitItemPrecededBy(BreedViewState(isLoading = true)) @@ -90,7 +91,7 @@ class BreedViewModelTest { @Test fun `Get updated breeds with cache and preserve favorites`() = runTest { - settings.putLong(BreedRepository.DB_TIMESTAMP_KEY, clock.currentInstant.toEpochMilliseconds()) + settings.putLong(DogRepository.DB_TIMESTAMP_KEY, clock.currentInstant.toEpochMilliseconds()) val successResult = ktorApi.successResult() val resultWithExtraBreed = successResult.copy(message = successResult.message + ("extra" to emptyList())) @@ -99,7 +100,7 @@ class BreedViewModelTest { dbHelper.insertBreeds(breedNames) dbHelper.updateFavorite(australianLike.id, true) - viewModel.breedState.test { + viewModel.breedsState.test { assertEquals(breedViewStateSuccessFavorite, awaitItemPrecededBy(BreedViewState(isLoading = true))) expectNoEvents() @@ -114,7 +115,7 @@ class BreedViewModelTest { @Test fun `Get updated breeds when stale and preserve favorites`() = runTest { - settings.putLong(BreedRepository.DB_TIMESTAMP_KEY, (clock.currentInstant - 2.hours).toEpochMilliseconds()) + settings.putLong(DogRepository.DB_TIMESTAMP_KEY, (clock.currentInstant - 2.hours).toEpochMilliseconds()) val successResult = ktorApi.successResult() val resultWithExtraBreed = successResult.copy(message = successResult.message + ("extra" to emptyList())) @@ -123,7 +124,7 @@ class BreedViewModelTest { dbHelper.insertBreeds(breedNames) dbHelper.updateFavorite(australianLike.id, true) - viewModel.breedState.test { + viewModel.breedsState.test { // id is 5 here because it incremented twice when trying to insert duplicate breeds assertEquals( BreedViewState(breedViewStateSuccessFavorite.breeds.plus(Breed(5, "extra", false))), @@ -134,12 +135,12 @@ class BreedViewModelTest { @Test fun `Toggle favorite cached breed`() = runTest { - settings.putLong(BreedRepository.DB_TIMESTAMP_KEY, clock.currentInstant.toEpochMilliseconds()) + settings.putLong(DogRepository.DB_TIMESTAMP_KEY, clock.currentInstant.toEpochMilliseconds()) dbHelper.insertBreeds(breedNames) dbHelper.updateFavorite(australianLike.id, true) - viewModel.breedState.test { + viewModel.breedsState.test { assertEquals(breedViewStateSuccessFavorite, awaitItemPrecededBy(BreedViewState(isLoading = true))) expectNoEvents() @@ -153,11 +154,11 @@ class BreedViewModelTest { @Test fun `No web call if data is not stale`() = runTest { - settings.putLong(BreedRepository.DB_TIMESTAMP_KEY, clock.currentInstant.toEpochMilliseconds()) + settings.putLong(DogRepository.DB_TIMESTAMP_KEY, clock.currentInstant.toEpochMilliseconds()) ktorApi.prepareResult(ktorApi.successResult()) dbHelper.insertBreeds(breedNames) - viewModel.breedState.test { + viewModel.breedsState.test { assertEquals(breedViewStateSuccessNoFavorite, awaitItemPrecededBy(BreedViewState(isLoading = true))) assertEquals(0, ktorApi.calledCount) expectNoEvents() @@ -175,7 +176,7 @@ class BreedViewModelTest { fun `Display API error on first run`() = runTest { ktorApi.throwOnCall(RuntimeException("Test error")) - viewModel.breedState.test { + viewModel.breedsState.test { assertEquals( BreedViewState(error = "Unable to download breed list"), awaitItemPrecededBy(BreedViewState(isLoading = true), BreedViewState(isEmpty = true)) @@ -186,10 +187,10 @@ class BreedViewModelTest { @Test fun `Ignore API error with cache`() = runTest { dbHelper.insertBreeds(breedNames) - settings.putLong(BreedRepository.DB_TIMESTAMP_KEY, (clock.currentInstant - 2.hours).toEpochMilliseconds()) + settings.putLong(DogRepository.DB_TIMESTAMP_KEY, (clock.currentInstant - 2.hours).toEpochMilliseconds()) ktorApi.throwOnCall(RuntimeException("Test error")) - viewModel.breedState.test { + viewModel.breedsState.test { assertEquals( breedViewStateSuccessNoFavorite, awaitItemPrecededBy(BreedViewState(isLoading = true)) @@ -210,7 +211,7 @@ class BreedViewModelTest { fun `Ignore API error on refresh with cache`() = runTest { ktorApi.prepareResult(ktorApi.successResult()) - viewModel.breedState.test { + viewModel.breedsState.test { assertEquals( breedViewStateSuccessNoFavorite, awaitItemPrecededBy(BreedViewState(isLoading = true), BreedViewState(isEmpty = true)) @@ -229,10 +230,10 @@ class BreedViewModelTest { @Test fun `Show API error on refresh without cache`() = runTest { - settings.putLong(BreedRepository.DB_TIMESTAMP_KEY, clock.currentInstant.toEpochMilliseconds()) + settings.putLong(DogRepository.DB_TIMESTAMP_KEY, clock.currentInstant.toEpochMilliseconds()) ktorApi.throwOnCall(RuntimeException("Test error")) - viewModel.breedState.test { + viewModel.breedsState.test { assertEquals(BreedViewState(isEmpty = true), awaitItemPrecededBy(BreedViewState(isLoading = true))) expectNoEvents() diff --git a/shared/src/commonTest/kotlin/co/touchlab/kampkit/DogApiTest.kt b/shared/src/commonTest/kotlin/co/touchlab/kampkit/DogApiTest.kt index 77998e35..b84921ea 100644 --- a/shared/src/commonTest/kotlin/co/touchlab/kampkit/DogApiTest.kt +++ b/shared/src/commonTest/kotlin/co/touchlab/kampkit/DogApiTest.kt @@ -1,7 +1,7 @@ package co.touchlab.kampkit -import co.touchlab.kampkit.ktor.DogApiImpl -import co.touchlab.kampkit.response.BreedResult +import co.touchlab.kampkit.data.dog.DogApiImpl +import co.touchlab.kampkit.data.dog.DogResult import co.touchlab.kermit.LogWriter import co.touchlab.kermit.Logger import co.touchlab.kermit.LoggerConfig @@ -40,7 +40,7 @@ class DogApiTest { val result = dogApi.getJsonFromApi() assertEquals( - BreedResult( + DogResult( mapOf( "affenpinscher" to emptyList(), "african" to listOf("shepherd") diff --git a/shared/src/commonTest/kotlin/co/touchlab/kampkit/SqlDelightTest.kt b/shared/src/commonTest/kotlin/co/touchlab/kampkit/SqlDelightTest.kt index 1bb86861..0b72423c 100644 --- a/shared/src/commonTest/kotlin/co/touchlab/kampkit/SqlDelightTest.kt +++ b/shared/src/commonTest/kotlin/co/touchlab/kampkit/SqlDelightTest.kt @@ -1,5 +1,6 @@ package co.touchlab.kampkit +import co.touchlab.kampkit.data.dog.DogDatabaseHelper import co.touchlab.kermit.Logger import co.touchlab.kermit.StaticConfig import kotlinx.coroutines.Dispatchers @@ -12,15 +13,15 @@ import kotlin.test.assertTrue class SqlDelightTest { - private lateinit var dbHelper: DatabaseHelper + private lateinit var dbHelper: DogDatabaseHelper - private suspend fun DatabaseHelper.insertBreed(name: String) { + private suspend fun DogDatabaseHelper.insertBreed(name: String) { insertBreeds(listOf(name)) } @BeforeTest fun setup() = runTest { - dbHelper = DatabaseHelper( + dbHelper = DogDatabaseHelper( testDbConnection(), Logger(StaticConfig()), Dispatchers.Default diff --git a/shared/src/commonTest/kotlin/co/touchlab/kampkit/TestAppInfo.kt b/shared/src/commonTest/kotlin/co/touchlab/kampkit/TestAppInfo.kt index 3e243553..6cfd8476 100644 --- a/shared/src/commonTest/kotlin/co/touchlab/kampkit/TestAppInfo.kt +++ b/shared/src/commonTest/kotlin/co/touchlab/kampkit/TestAppInfo.kt @@ -1,5 +1,7 @@ package co.touchlab.kampkit +import co.touchlab.kampkit.core.AppInfo + object TestAppInfo : AppInfo { override val appId: String = "Test" } diff --git a/shared/src/commonTest/kotlin/co/touchlab/kampkit/mock/DogApiMock.kt b/shared/src/commonTest/kotlin/co/touchlab/kampkit/mock/DogApiMock.kt index 5460f847..f1ae5d83 100644 --- a/shared/src/commonTest/kotlin/co/touchlab/kampkit/mock/DogApiMock.kt +++ b/shared/src/commonTest/kotlin/co/touchlab/kampkit/mock/DogApiMock.kt @@ -1,30 +1,30 @@ package co.touchlab.kampkit.mock -import co.touchlab.kampkit.ktor.DogApi -import co.touchlab.kampkit.response.BreedResult +import co.touchlab.kampkit.data.dog.DogApi +import co.touchlab.kampkit.data.dog.DogResult // TODO convert this to use Ktor's MockEngine class DogApiMock : DogApi { - private var nextResult: () -> BreedResult = { error("Uninitialized!") } + private var nextResult: () -> DogResult = { error("Uninitialized!") } var calledCount = 0 private set - override suspend fun getJsonFromApi(): BreedResult { + override suspend fun getJsonFromApi(): DogResult { val result = nextResult() calledCount++ return result } - fun successResult(): BreedResult { + fun successResult(): DogResult { val map = HashMap>().apply { put("appenzeller", emptyList()) put("australian", listOf("shepherd")) } - return BreedResult(map, "success") + return DogResult(map, "success") } - fun prepareResult(breedResult: BreedResult) { - nextResult = { breedResult } + fun prepareResult(dogResult: DogResult) { + nextResult = { dogResult } } fun throwOnCall(throwable: Throwable) { diff --git a/shared/src/iosMain/kotlin/co/touchlab/kampkit/KoinIOS.kt b/shared/src/iosMain/kotlin/co/touchlab/kampkit/core/KoinIOS.kt similarity index 84% rename from shared/src/iosMain/kotlin/co/touchlab/kampkit/KoinIOS.kt rename to shared/src/iosMain/kotlin/co/touchlab/kampkit/core/KoinIOS.kt index 82ae4d7c..5beef660 100644 --- a/shared/src/iosMain/kotlin/co/touchlab/kampkit/KoinIOS.kt +++ b/shared/src/iosMain/kotlin/co/touchlab/kampkit/core/KoinIOS.kt @@ -1,7 +1,7 @@ -package co.touchlab.kampkit +package co.touchlab.kampkit.core import co.touchlab.kampkit.db.KaMPKitDb -import co.touchlab.kampkit.models.BreedViewModel +import co.touchlab.kampkit.ui.breeds.BreedsViewModel import co.touchlab.kermit.Logger import com.russhwolf.settings.NSUserDefaultsSettings import com.russhwolf.settings.Settings @@ -32,7 +32,7 @@ actual val platformModule = module { single { Darwin.create() } - single { BreedViewModel(get(), getWith("BreedViewModel")) } + single { BreedsViewModel(get(), getWith("BreedViewModel")) } } // Access from Swift to create a logger @@ -42,5 +42,5 @@ fun Koin.loggerWithTag(tag: String) = @Suppress("unused") // Called from Swift object KotlinDependencies : KoinComponent { - fun getBreedViewModel() = getKoin().get() + fun getBreedViewModel() = getKoin().get() } diff --git a/shared/src/iosMain/kotlin/co/touchlab/kampkit/models/ViewModel.kt b/shared/src/iosMain/kotlin/co/touchlab/kampkit/core/ViewModel.kt similarity index 96% rename from shared/src/iosMain/kotlin/co/touchlab/kampkit/models/ViewModel.kt rename to shared/src/iosMain/kotlin/co/touchlab/kampkit/core/ViewModel.kt index 296b6316..abfa5c9f 100644 --- a/shared/src/iosMain/kotlin/co/touchlab/kampkit/models/ViewModel.kt +++ b/shared/src/iosMain/kotlin/co/touchlab/kampkit/core/ViewModel.kt @@ -1,4 +1,4 @@ -package co.touchlab.kampkit.models +package co.touchlab.kampkit.core import com.rickclephas.kmp.nativecoroutines.NativeCoroutineScope import kotlinx.coroutines.MainScope @@ -29,4 +29,4 @@ actual abstract class ViewModel { onCleared() viewModelScope.cancel() } -} +} \ No newline at end of file diff --git a/shared/src/iosTest/kotlin/co/touchlab/kampkit/KoinTest.kt b/shared/src/iosTest/kotlin/co/touchlab/kampkit/KoinTest.kt index 99cf751d..52631c11 100644 --- a/shared/src/iosTest/kotlin/co/touchlab/kampkit/KoinTest.kt +++ b/shared/src/iosTest/kotlin/co/touchlab/kampkit/KoinTest.kt @@ -1,5 +1,6 @@ package co.touchlab.kampkit +import co.touchlab.kampkit.core.initKoinIos import co.touchlab.kermit.Logger import org.koin.core.context.stopKoin import org.koin.core.parameter.parametersOf From 78f2818ce944be9d7bbea677cac957f2ee5e6622 Mon Sep 17 00:00:00 2001 From: bpedryc Date: Fri, 9 Jun 2023 23:08:05 +0200 Subject: [PATCH 2/9] Add newline at the end of ViewModel classes --- .../androidMain/kotlin/co/touchlab/kampkit/core/ViewModel.kt | 2 +- .../src/commonMain/kotlin/co/touchlab/kampkit/core/ViewModel.kt | 2 +- shared/src/iosMain/kotlin/co/touchlab/kampkit/core/ViewModel.kt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/shared/src/androidMain/kotlin/co/touchlab/kampkit/core/ViewModel.kt b/shared/src/androidMain/kotlin/co/touchlab/kampkit/core/ViewModel.kt index 7467d1ad..a5cb3267 100644 --- a/shared/src/androidMain/kotlin/co/touchlab/kampkit/core/ViewModel.kt +++ b/shared/src/androidMain/kotlin/co/touchlab/kampkit/core/ViewModel.kt @@ -10,4 +10,4 @@ actual abstract class ViewModel actual constructor() : AndroidXViewModel() { actual override fun onCleared() { super.onCleared() } -} \ No newline at end of file +} diff --git a/shared/src/commonMain/kotlin/co/touchlab/kampkit/core/ViewModel.kt b/shared/src/commonMain/kotlin/co/touchlab/kampkit/core/ViewModel.kt index d0eb076b..4e9aab7c 100644 --- a/shared/src/commonMain/kotlin/co/touchlab/kampkit/core/ViewModel.kt +++ b/shared/src/commonMain/kotlin/co/touchlab/kampkit/core/ViewModel.kt @@ -5,4 +5,4 @@ import kotlinx.coroutines.CoroutineScope expect abstract class ViewModel() { val viewModelScope: CoroutineScope protected open fun onCleared() -} \ No newline at end of file +} diff --git a/shared/src/iosMain/kotlin/co/touchlab/kampkit/core/ViewModel.kt b/shared/src/iosMain/kotlin/co/touchlab/kampkit/core/ViewModel.kt index abfa5c9f..ef4ba26e 100644 --- a/shared/src/iosMain/kotlin/co/touchlab/kampkit/core/ViewModel.kt +++ b/shared/src/iosMain/kotlin/co/touchlab/kampkit/core/ViewModel.kt @@ -29,4 +29,4 @@ actual abstract class ViewModel { onCleared() viewModelScope.cancel() } -} \ No newline at end of file +} From 052b8ce65e970717783ae3b7c5544f49dbcb8339 Mon Sep 17 00:00:00 2001 From: bpedryc Date: Thu, 15 Jun 2023 10:08:26 +0200 Subject: [PATCH 3/9] Fixed the imports order in DogDatabaseHelper --- .../kotlin/co/touchlab/kampkit/data/dog/DogDatabaseHelper.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/src/commonMain/kotlin/co/touchlab/kampkit/data/dog/DogDatabaseHelper.kt b/shared/src/commonMain/kotlin/co/touchlab/kampkit/data/dog/DogDatabaseHelper.kt index 3fdd8029..26f1273b 100644 --- a/shared/src/commonMain/kotlin/co/touchlab/kampkit/data/dog/DogDatabaseHelper.kt +++ b/shared/src/commonMain/kotlin/co/touchlab/kampkit/data/dog/DogDatabaseHelper.kt @@ -1,8 +1,8 @@ package co.touchlab.kampkit.data.dog +import co.touchlab.kampkit.core.transactionWithContext import co.touchlab.kampkit.db.Breed import co.touchlab.kampkit.db.KaMPKitDb -import co.touchlab.kampkit.core.transactionWithContext import co.touchlab.kermit.Logger import com.squareup.sqldelight.db.SqlDriver import com.squareup.sqldelight.runtime.coroutines.asFlow From 44a90f9aa01cfb97c974e016a4071fa14f8051f4 Mon Sep 17 00:00:00 2001 From: bpedryc Date: Thu, 15 Jun 2023 11:15:38 +0200 Subject: [PATCH 4/9] Fixed the imports order in BreedsViewModel --- .../kotlin/co/touchlab/kampkit/ui/breeds/BreedsViewModel.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/src/commonMain/kotlin/co/touchlab/kampkit/ui/breeds/BreedsViewModel.kt b/shared/src/commonMain/kotlin/co/touchlab/kampkit/ui/breeds/BreedsViewModel.kt index 94083df8..b208bbd9 100644 --- a/shared/src/commonMain/kotlin/co/touchlab/kampkit/ui/breeds/BreedsViewModel.kt +++ b/shared/src/commonMain/kotlin/co/touchlab/kampkit/ui/breeds/BreedsViewModel.kt @@ -1,8 +1,8 @@ package co.touchlab.kampkit.ui.breeds +import co.touchlab.kampkit.core.ViewModel import co.touchlab.kampkit.data.dog.DogRepository import co.touchlab.kampkit.db.Breed -import co.touchlab.kampkit.core.ViewModel import co.touchlab.kermit.Logger import com.rickclephas.kmp.nativecoroutines.NativeCoroutinesState import kotlinx.coroutines.Job From 1d7f8e060e1d531fd0006bac811c672480cfcc22 Mon Sep 17 00:00:00 2001 From: bpedryc Date: Thu, 15 Jun 2023 11:29:56 +0200 Subject: [PATCH 5/9] Fixed the imports order in BreedRepositoryTest --- .../kotlin/co/touchlab/kampkit/BreedRepositoryTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/src/commonTest/kotlin/co/touchlab/kampkit/BreedRepositoryTest.kt b/shared/src/commonTest/kotlin/co/touchlab/kampkit/BreedRepositoryTest.kt index 39e96da6..60d30ba5 100644 --- a/shared/src/commonTest/kotlin/co/touchlab/kampkit/BreedRepositoryTest.kt +++ b/shared/src/commonTest/kotlin/co/touchlab/kampkit/BreedRepositoryTest.kt @@ -2,10 +2,10 @@ package co.touchlab.kampkit import app.cash.turbine.test import co.touchlab.kampkit.data.dog.DogDatabaseHelper +import co.touchlab.kampkit.data.dog.DogRepository import co.touchlab.kampkit.db.Breed import co.touchlab.kampkit.mock.ClockMock import co.touchlab.kampkit.mock.DogApiMock -import co.touchlab.kampkit.data.dog.DogRepository import co.touchlab.kermit.Logger import co.touchlab.kermit.StaticConfig import com.russhwolf.settings.MapSettings From 6dd3e506fd3eee1ed58fce745e8151abe66b6c3b Mon Sep 17 00:00:00 2001 From: bpedryc Date: Thu, 15 Jun 2023 12:23:37 +0200 Subject: [PATCH 6/9] Change the BreedXYZ names to BreedsXYZ --- .../co/touchlab/kampkit/android/MainApp.kt | 2 +- .../kampkit/android/ui/BreedsScreen.kt | 6 +- ios/KaMPKitiOS/Breeds/BreedsScreen.swift | 4 +- ios/KaMPKitiOS/Breeds/BreedsViewModel.swift | 6 +- .../kampkit/ui/breeds/BreedsViewModel.kt | 31 ++------- .../kampkit/ui/breeds/BreedsViewState.kt | 16 +++++ ...iewModelTest.kt => BreedsViewModelTest.kt} | 68 +++++++++---------- .../co/touchlab/kampkit/core/KoinIOS.kt | 4 +- 8 files changed, 68 insertions(+), 69 deletions(-) create mode 100644 shared/src/commonMain/kotlin/co/touchlab/kampkit/ui/breeds/BreedsViewState.kt rename shared/src/commonTest/kotlin/co/touchlab/kampkit/{BreedViewModelTest.kt => BreedsViewModelTest.kt} (71%) diff --git a/app/src/main/kotlin/co/touchlab/kampkit/android/MainApp.kt b/app/src/main/kotlin/co/touchlab/kampkit/android/MainApp.kt index 2b78439c..b6c202ca 100644 --- a/app/src/main/kotlin/co/touchlab/kampkit/android/MainApp.kt +++ b/app/src/main/kotlin/co/touchlab/kampkit/android/MainApp.kt @@ -18,7 +18,7 @@ class MainApp : Application() { initKoin( module { single { this@MainApp } - viewModel { BreedsViewModel(get(), get { parametersOf("BreedViewModel") }) } + viewModel { BreedsViewModel(get(), get { parametersOf("BreedsViewModel") }) } single { get().getSharedPreferences("KAMPSTARTER_SETTINGS", MODE_PRIVATE) } diff --git a/app/src/main/kotlin/co/touchlab/kampkit/android/ui/BreedsScreen.kt b/app/src/main/kotlin/co/touchlab/kampkit/android/ui/BreedsScreen.kt index 778a5883..d242d7ff 100644 --- a/app/src/main/kotlin/co/touchlab/kampkit/android/ui/BreedsScreen.kt +++ b/app/src/main/kotlin/co/touchlab/kampkit/android/ui/BreedsScreen.kt @@ -30,7 +30,7 @@ import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import co.touchlab.kampkit.android.R import co.touchlab.kampkit.db.Breed -import co.touchlab.kampkit.ui.breeds.BreedViewState +import co.touchlab.kampkit.ui.breeds.BreedsViewState import co.touchlab.kampkit.ui.breeds.BreedsViewModel import co.touchlab.kermit.Logger import com.google.accompanist.swiperefresh.SwipeRefresh @@ -54,7 +54,7 @@ fun BreedsScreen( @Composable fun BreedsScreenContent( - dogsState: BreedViewState, + dogsState: BreedsViewState, onRefresh: () -> Unit = {}, onSuccess: (List) -> Unit = {}, onError: (String) -> Unit = {}, @@ -175,7 +175,7 @@ fun FavoriteIcon(breed: Breed) { @Composable fun BreedsScreenContentPreview_Success() { BreedsScreenContent( - dogsState = BreedViewState( + dogsState = BreedsViewState( breeds = listOf( Breed(0, "appenzeller", false), Breed(1, "australian", true) diff --git a/ios/KaMPKitiOS/Breeds/BreedsScreen.swift b/ios/KaMPKitiOS/Breeds/BreedsScreen.swift index 1b0c8e33..cf959b0e 100644 --- a/ios/KaMPKitiOS/Breeds/BreedsScreen.swift +++ b/ios/KaMPKitiOS/Breeds/BreedsScreen.swift @@ -29,7 +29,7 @@ struct BreedsScreen: View { } struct BreedsContent: View { - var state: BreedViewState + var state: BreedsViewState var onBreedFavorite: (Breed) -> Void var refresh: () -> Void @@ -74,7 +74,7 @@ struct BreedRowView: View { struct BreedsScreen_Previews: PreviewProvider { static var previews: some View { BreedsContent( - state: BreedViewState( + state: BreedsViewState( breeds: [ Breed(id: 0, name: "appenzeller", favorite: false), Breed(id: 1, name: "australian", favorite: true) diff --git a/ios/KaMPKitiOS/Breeds/BreedsViewModel.swift b/ios/KaMPKitiOS/Breeds/BreedsViewModel.swift index b92a8eb9..635da4bb 100644 --- a/ios/KaMPKitiOS/Breeds/BreedsViewModel.swift +++ b/ios/KaMPKitiOS/Breeds/BreedsViewModel.swift @@ -13,9 +13,9 @@ import KMPNativeCoroutinesCombine class BreedsViewModel: ObservableObject { - @Published var state: BreedViewState = BreedViewState.companion.default() + @Published var state: BreedsViewState = BreedsViewState.companion.default() - private var viewModelDelegate: BreedViewModelDelegate = KotlinDependencies.shared.getBreedViewModel() + private var viewModelDelegate: BreedsViewModelDelegate = KotlinDependencies.shared.getBreedsViewModel() private var cancellables = [AnyCancellable]() deinit { @@ -24,7 +24,7 @@ class BreedsViewModel: ObservableObject { func subscribeState() { createPublisher(for: viewModelDelegate.breedsStateFlow) - .sink { _ in } receiveValue: { [weak self] (breedState: BreedViewState) in + .sink { _ in } receiveValue: { [weak self] (breedState: BreedsViewState) in self?.state = breedState } .store(in: &cancellables) diff --git a/shared/src/commonMain/kotlin/co/touchlab/kampkit/ui/breeds/BreedsViewModel.kt b/shared/src/commonMain/kotlin/co/touchlab/kampkit/ui/breeds/BreedsViewModel.kt index b208bbd9..77ff7ad0 100644 --- a/shared/src/commonMain/kotlin/co/touchlab/kampkit/ui/breeds/BreedsViewModel.kt +++ b/shared/src/commonMain/kotlin/co/touchlab/kampkit/ui/breeds/BreedsViewModel.kt @@ -14,27 +14,23 @@ import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import kotlin.native.ObjCName -@ObjCName("BreedViewModelDelegate") +@ObjCName("BreedsViewModelDelegate") class BreedsViewModel( private val dogRepository: DogRepository, log: Logger ) : ViewModel() { - private val log = log.withTag("BreedViewModel") + private val log = log.withTag("BreedsViewModel") - private val mutableBreedState: MutableStateFlow = - MutableStateFlow(BreedViewState(isLoading = true)) + private val mutableBreedState: MutableStateFlow = + MutableStateFlow(BreedsViewState(isLoading = true)) @NativeCoroutinesState - val breedsState: StateFlow = mutableBreedState + val breedsState: StateFlow = mutableBreedState init { observeBreeds() } - override fun onCleared() { - log.v("Clearing BreedViewModel") - } - private fun observeBreeds() { // Refresh breeds, and emit any exception that was thrown so we can handle it downstream val refreshFlow = flow { @@ -55,7 +51,7 @@ class BreedsViewModel( } else { previousState.error } - BreedViewState( + BreedsViewState( isLoading = false, breeds = breeds, error = errorMessage.takeIf { breeds.isEmpty() }, @@ -89,7 +85,7 @@ class BreedsViewModel( log.e(throwable) { "Error downloading breed list" } mutableBreedState.update { if (it.breeds.isEmpty()) { - BreedViewState(error = "Unable to refresh breed list") + BreedsViewState(error = "Unable to refresh breed list") } else { // Just let it fail silently if we have a cache it.copy(isLoading = false) @@ -97,16 +93,3 @@ class BreedsViewModel( } } } - -data class BreedViewState( - val breeds: List = emptyList(), - val error: String? = null, - val isLoading: Boolean = false, - val isEmpty: Boolean = false -) { - companion object { - // This method lets you use the default constructor values in Swift. When accessing the - // constructor directly, they will not work there and would need to be provided explicitly. - fun default() = BreedViewState() - } -} diff --git a/shared/src/commonMain/kotlin/co/touchlab/kampkit/ui/breeds/BreedsViewState.kt b/shared/src/commonMain/kotlin/co/touchlab/kampkit/ui/breeds/BreedsViewState.kt new file mode 100644 index 00000000..fb2e61ee --- /dev/null +++ b/shared/src/commonMain/kotlin/co/touchlab/kampkit/ui/breeds/BreedsViewState.kt @@ -0,0 +1,16 @@ +package co.touchlab.kampkit.ui.breeds + +import co.touchlab.kampkit.db.Breed + +data class BreedsViewState( + val breeds: List = emptyList(), + val error: String? = null, + val isLoading: Boolean = false, + val isEmpty: Boolean = false +) { + companion object { + // This method lets you use the default constructor values in Swift. When accessing the + // constructor directly, they will not work there and would need to be provided explicitly. + fun default() = BreedsViewState() + } +} diff --git a/shared/src/commonTest/kotlin/co/touchlab/kampkit/BreedViewModelTest.kt b/shared/src/commonTest/kotlin/co/touchlab/kampkit/BreedsViewModelTest.kt similarity index 71% rename from shared/src/commonTest/kotlin/co/touchlab/kampkit/BreedViewModelTest.kt rename to shared/src/commonTest/kotlin/co/touchlab/kampkit/BreedsViewModelTest.kt index 71540893..bd7ef1d7 100644 --- a/shared/src/commonTest/kotlin/co/touchlab/kampkit/BreedViewModelTest.kt +++ b/shared/src/commonTest/kotlin/co/touchlab/kampkit/BreedsViewModelTest.kt @@ -8,7 +8,7 @@ import co.touchlab.kampkit.data.dog.DogResult import co.touchlab.kampkit.db.Breed import co.touchlab.kampkit.mock.ClockMock import co.touchlab.kampkit.mock.DogApiMock -import co.touchlab.kampkit.ui.breeds.BreedViewState +import co.touchlab.kampkit.ui.breeds.BreedsViewState import co.touchlab.kampkit.ui.breeds.BreedsViewModel import co.touchlab.kermit.Logger import co.touchlab.kermit.StaticConfig @@ -24,7 +24,7 @@ import kotlin.test.Test import kotlin.test.assertEquals import kotlin.time.Duration.Companion.hours -class BreedViewModelTest { +class BreedsViewModelTest { private var kermit = Logger(StaticConfig()) private var testDbConnection = testDbConnection() private var dbHelper = DogDatabaseHelper( @@ -45,13 +45,13 @@ class BreedViewModelTest { private val appenzeller = Breed(1, "appenzeller", false) private val australianNoLike = Breed(2, "australian", false) private val australianLike = Breed(2, "australian", true) - private val breedViewStateSuccessNoFavorite = BreedViewState( + private val breedsViewStateSuccessNoFavorite = BreedsViewState( breeds = listOf(appenzeller, australianNoLike) ) - private val breedViewStateSuccessFavorite = BreedViewState( + private val breedsViewStateSuccessFavorite = BreedsViewState( breeds = listOf(appenzeller, australianLike) ) - private val breedNames = breedViewStateSuccessNoFavorite.breeds.map { it.name } + private val breedNames = breedsViewStateSuccessNoFavorite.breeds.map { it.name } } @BeforeTest @@ -71,8 +71,8 @@ class BreedViewModelTest { viewModel.breedsState.test { assertEquals( - breedViewStateSuccessNoFavorite, - awaitItemPrecededBy(BreedViewState(isLoading = true), BreedViewState(isEmpty = true)) + breedsViewStateSuccessNoFavorite, + awaitItemPrecededBy(BreedsViewState(isLoading = true), BreedsViewState(isEmpty = true)) ) } } @@ -83,8 +83,8 @@ class BreedViewModelTest { viewModel.breedsState.test { assertEquals( - BreedViewState(isEmpty = true), - awaitItemPrecededBy(BreedViewState(isLoading = true)) + BreedsViewState(isEmpty = true), + awaitItemPrecededBy(BreedsViewState(isLoading = true)) ) } } @@ -101,14 +101,14 @@ class BreedViewModelTest { dbHelper.updateFavorite(australianLike.id, true) viewModel.breedsState.test { - assertEquals(breedViewStateSuccessFavorite, awaitItemPrecededBy(BreedViewState(isLoading = true))) + assertEquals(breedsViewStateSuccessFavorite, awaitItemPrecededBy(BreedsViewState(isLoading = true))) expectNoEvents() viewModel.refreshBreeds().join() // id is 5 here because it incremented twice when trying to insert duplicate breeds assertEquals( - BreedViewState(breedViewStateSuccessFavorite.breeds.plus(Breed(5, "extra", false))), - awaitItemPrecededBy(breedViewStateSuccessFavorite.copy(isLoading = true)) + BreedsViewState(breedsViewStateSuccessFavorite.breeds.plus(Breed(5, "extra", false))), + awaitItemPrecededBy(breedsViewStateSuccessFavorite.copy(isLoading = true)) ) } } @@ -127,8 +127,8 @@ class BreedViewModelTest { viewModel.breedsState.test { // id is 5 here because it incremented twice when trying to insert duplicate breeds assertEquals( - BreedViewState(breedViewStateSuccessFavorite.breeds.plus(Breed(5, "extra", false))), - awaitItemPrecededBy(BreedViewState(isLoading = true), breedViewStateSuccessFavorite) + BreedsViewState(breedsViewStateSuccessFavorite.breeds.plus(Breed(5, "extra", false))), + awaitItemPrecededBy(BreedsViewState(isLoading = true), breedsViewStateSuccessFavorite) ) } } @@ -141,13 +141,13 @@ class BreedViewModelTest { dbHelper.updateFavorite(australianLike.id, true) viewModel.breedsState.test { - assertEquals(breedViewStateSuccessFavorite, awaitItemPrecededBy(BreedViewState(isLoading = true))) + assertEquals(breedsViewStateSuccessFavorite, awaitItemPrecededBy(BreedsViewState(isLoading = true))) expectNoEvents() viewModel.updateBreedFavorite(australianLike).join() assertEquals( - breedViewStateSuccessNoFavorite, - awaitItemPrecededBy(breedViewStateSuccessFavorite.copy(isLoading = true)) + breedsViewStateSuccessNoFavorite, + awaitItemPrecededBy(breedsViewStateSuccessFavorite.copy(isLoading = true)) ) } } @@ -159,14 +159,14 @@ class BreedViewModelTest { dbHelper.insertBreeds(breedNames) viewModel.breedsState.test { - assertEquals(breedViewStateSuccessNoFavorite, awaitItemPrecededBy(BreedViewState(isLoading = true))) + assertEquals(breedsViewStateSuccessNoFavorite, awaitItemPrecededBy(BreedsViewState(isLoading = true))) assertEquals(0, ktorApi.calledCount) expectNoEvents() viewModel.refreshBreeds().join() assertEquals( - breedViewStateSuccessNoFavorite, - awaitItemPrecededBy(breedViewStateSuccessNoFavorite.copy(isLoading = true)) + breedsViewStateSuccessNoFavorite, + awaitItemPrecededBy(breedsViewStateSuccessNoFavorite.copy(isLoading = true)) ) assertEquals(1, ktorApi.calledCount) } @@ -178,8 +178,8 @@ class BreedViewModelTest { viewModel.breedsState.test { assertEquals( - BreedViewState(error = "Unable to download breed list"), - awaitItemPrecededBy(BreedViewState(isLoading = true), BreedViewState(isEmpty = true)) + BreedsViewState(error = "Unable to download breed list"), + awaitItemPrecededBy(BreedsViewState(isLoading = true), BreedsViewState(isEmpty = true)) ) } } @@ -192,8 +192,8 @@ class BreedViewModelTest { viewModel.breedsState.test { assertEquals( - breedViewStateSuccessNoFavorite, - awaitItemPrecededBy(BreedViewState(isLoading = true)) + breedsViewStateSuccessNoFavorite, + awaitItemPrecededBy(BreedsViewState(isLoading = true)) ) expectNoEvents() @@ -201,8 +201,8 @@ class BreedViewModelTest { viewModel.refreshBreeds().join() assertEquals( - breedViewStateSuccessNoFavorite, - awaitItemPrecededBy(breedViewStateSuccessNoFavorite.copy(isLoading = true)) + breedsViewStateSuccessNoFavorite, + awaitItemPrecededBy(breedsViewStateSuccessNoFavorite.copy(isLoading = true)) ) } } @@ -213,8 +213,8 @@ class BreedViewModelTest { viewModel.breedsState.test { assertEquals( - breedViewStateSuccessNoFavorite, - awaitItemPrecededBy(BreedViewState(isLoading = true), BreedViewState(isEmpty = true)) + breedsViewStateSuccessNoFavorite, + awaitItemPrecededBy(BreedsViewState(isLoading = true), BreedsViewState(isEmpty = true)) ) expectNoEvents() @@ -222,8 +222,8 @@ class BreedViewModelTest { viewModel.refreshBreeds().join() assertEquals( - breedViewStateSuccessNoFavorite, - awaitItemPrecededBy(breedViewStateSuccessNoFavorite.copy(isLoading = true)) + breedsViewStateSuccessNoFavorite, + awaitItemPrecededBy(breedsViewStateSuccessNoFavorite.copy(isLoading = true)) ) } } @@ -234,13 +234,13 @@ class BreedViewModelTest { ktorApi.throwOnCall(RuntimeException("Test error")) viewModel.breedsState.test { - assertEquals(BreedViewState(isEmpty = true), awaitItemPrecededBy(BreedViewState(isLoading = true))) + assertEquals(BreedsViewState(isEmpty = true), awaitItemPrecededBy(BreedsViewState(isLoading = true))) expectNoEvents() viewModel.refreshBreeds().join() assertEquals( - BreedViewState(error = "Unable to refresh breed list"), - awaitItemPrecededBy(BreedViewState(isEmpty = true, isLoading = true)) + BreedsViewState(error = "Unable to refresh breed list"), + awaitItemPrecededBy(BreedsViewState(isEmpty = true, isLoading = true)) ) } } @@ -248,7 +248,7 @@ class BreedViewModelTest { // There's a race condition where intermediate states can get missed if the next state comes too fast. // This function addresses that by awaiting an item that may or may not be preceded by the specified other items -private suspend fun ReceiveTurbine.awaitItemPrecededBy(vararg items: BreedViewState): BreedViewState { +private suspend fun ReceiveTurbine.awaitItemPrecededBy(vararg items: BreedsViewState): BreedsViewState { var nextItem = awaitItem() for (item in items) { if (item == nextItem) { diff --git a/shared/src/iosMain/kotlin/co/touchlab/kampkit/core/KoinIOS.kt b/shared/src/iosMain/kotlin/co/touchlab/kampkit/core/KoinIOS.kt index 5beef660..74e8c0c0 100644 --- a/shared/src/iosMain/kotlin/co/touchlab/kampkit/core/KoinIOS.kt +++ b/shared/src/iosMain/kotlin/co/touchlab/kampkit/core/KoinIOS.kt @@ -32,7 +32,7 @@ actual val platformModule = module { single { Darwin.create() } - single { BreedsViewModel(get(), getWith("BreedViewModel")) } + single { BreedsViewModel(get(), getWith("BreedsViewModel")) } } // Access from Swift to create a logger @@ -42,5 +42,5 @@ fun Koin.loggerWithTag(tag: String) = @Suppress("unused") // Called from Swift object KotlinDependencies : KoinComponent { - fun getBreedViewModel() = getKoin().get() + fun getBreedsViewModel() = getKoin().get() } From c2dddac14701d9173b4449b173644fb79f03b43d Mon Sep 17 00:00:00 2001 From: bpedryc Date: Thu, 15 Jun 2023 14:30:02 +0200 Subject: [PATCH 7/9] Fix the import order in BreedsScreen and BreedsViewModelTest --- .../main/kotlin/co/touchlab/kampkit/android/ui/BreedsScreen.kt | 2 +- .../kotlin/co/touchlab/kampkit/BreedsViewModelTest.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/kotlin/co/touchlab/kampkit/android/ui/BreedsScreen.kt b/app/src/main/kotlin/co/touchlab/kampkit/android/ui/BreedsScreen.kt index d242d7ff..35d2b5de 100644 --- a/app/src/main/kotlin/co/touchlab/kampkit/android/ui/BreedsScreen.kt +++ b/app/src/main/kotlin/co/touchlab/kampkit/android/ui/BreedsScreen.kt @@ -30,8 +30,8 @@ import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import co.touchlab.kampkit.android.R import co.touchlab.kampkit.db.Breed -import co.touchlab.kampkit.ui.breeds.BreedsViewState import co.touchlab.kampkit.ui.breeds.BreedsViewModel +import co.touchlab.kampkit.ui.breeds.BreedsViewState import co.touchlab.kermit.Logger import com.google.accompanist.swiperefresh.SwipeRefresh import com.google.accompanist.swiperefresh.rememberSwipeRefreshState diff --git a/shared/src/commonTest/kotlin/co/touchlab/kampkit/BreedsViewModelTest.kt b/shared/src/commonTest/kotlin/co/touchlab/kampkit/BreedsViewModelTest.kt index bd7ef1d7..77dfe338 100644 --- a/shared/src/commonTest/kotlin/co/touchlab/kampkit/BreedsViewModelTest.kt +++ b/shared/src/commonTest/kotlin/co/touchlab/kampkit/BreedsViewModelTest.kt @@ -8,8 +8,8 @@ import co.touchlab.kampkit.data.dog.DogResult import co.touchlab.kampkit.db.Breed import co.touchlab.kampkit.mock.ClockMock import co.touchlab.kampkit.mock.DogApiMock -import co.touchlab.kampkit.ui.breeds.BreedsViewState import co.touchlab.kampkit.ui.breeds.BreedsViewModel +import co.touchlab.kampkit.ui.breeds.BreedsViewState import co.touchlab.kermit.Logger import co.touchlab.kermit.StaticConfig import com.russhwolf.settings.MapSettings From 07311be46f2afe515495159cbbe2ce2d37fe185b Mon Sep 17 00:00:00 2001 From: bpedryc Date: Mon, 3 Jul 2023 12:30:16 +0200 Subject: [PATCH 8/9] Add the domain layer --- .../kampkit/android/ui/BreedsScreen.kt | 4 +-- ios/KaMPKitiOS/Breeds/BreedsScreen.swift | 2 +- .../kotlin/co/touchlab/kampkit/core/Koin.kt | 7 +++-- .../kampkit/data/dog/DogDatabaseHelper.kt | 6 ++-- ...epository.kt => NetworkBreedRepository.kt} | 28 +++++++++++++------ .../co/touchlab/kampkit/domain/breed/Breed.kt | 7 +++++ .../kampkit/domain/breed/BreedRepository.kt | 10 +++++++ .../kampkit/ui/breeds/BreedsViewModel.kt | 15 +++++----- .../kampkit/ui/breeds/BreedsViewState.kt | 2 +- .../co/touchlab/kampkit/db/Table.sq | 14 +++++----- .../touchlab/kampkit/BreedsViewModelTest.kt | 21 +++++++------- ...oryTest.kt => NetworkDogRepositoryTest.kt} | 16 +++++------ 12 files changed, 81 insertions(+), 51 deletions(-) rename shared/src/commonMain/kotlin/co/touchlab/kampkit/data/dog/{DogRepository.kt => NetworkBreedRepository.kt} (63%) create mode 100644 shared/src/commonMain/kotlin/co/touchlab/kampkit/domain/breed/Breed.kt create mode 100644 shared/src/commonMain/kotlin/co/touchlab/kampkit/domain/breed/BreedRepository.kt rename shared/src/commonTest/kotlin/co/touchlab/kampkit/{BreedRepositoryTest.kt => NetworkDogRepositoryTest.kt} (87%) diff --git a/app/src/main/kotlin/co/touchlab/kampkit/android/ui/BreedsScreen.kt b/app/src/main/kotlin/co/touchlab/kampkit/android/ui/BreedsScreen.kt index 35d2b5de..1f5d73ea 100644 --- a/app/src/main/kotlin/co/touchlab/kampkit/android/ui/BreedsScreen.kt +++ b/app/src/main/kotlin/co/touchlab/kampkit/android/ui/BreedsScreen.kt @@ -29,7 +29,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import co.touchlab.kampkit.android.R -import co.touchlab.kampkit.db.Breed +import co.touchlab.kampkit.domain.breed.Breed import co.touchlab.kampkit.ui.breeds.BreedsViewModel import co.touchlab.kampkit.ui.breeds.BreedsViewState import co.touchlab.kermit.Logger @@ -48,7 +48,7 @@ fun BreedsScreen( onRefresh = { viewModel.refreshBreeds() }, onSuccess = { data -> log.v { "View updating with ${data.size} breeds" } }, onError = { exception -> log.e { "Displaying error: $exception" } }, - onFavorite = { viewModel.updateBreedFavorite(it) } + onFavorite = { viewModel.updateBreedFavorite(it.id) } ) } diff --git a/ios/KaMPKitiOS/Breeds/BreedsScreen.swift b/ios/KaMPKitiOS/Breeds/BreedsScreen.swift index cf959b0e..12e7b11d 100644 --- a/ios/KaMPKitiOS/Breeds/BreedsScreen.swift +++ b/ios/KaMPKitiOS/Breeds/BreedsScreen.swift @@ -16,7 +16,7 @@ struct BreedsScreen: View { var body: some View { BreedsContent( state: viewModel.state, - onBreedFavorite: { viewModel.onBreedFavorite($0) }, + onBreedFavorite: { viewModel.onBreedFavorite(breed: $0) }, refresh: { viewModel.refresh() } ) .onAppear(perform: { diff --git a/shared/src/commonMain/kotlin/co/touchlab/kampkit/core/Koin.kt b/shared/src/commonMain/kotlin/co/touchlab/kampkit/core/Koin.kt index 2660f34a..5bd590b6 100644 --- a/shared/src/commonMain/kotlin/co/touchlab/kampkit/core/Koin.kt +++ b/shared/src/commonMain/kotlin/co/touchlab/kampkit/core/Koin.kt @@ -3,7 +3,8 @@ package co.touchlab.kampkit.core import co.touchlab.kampkit.data.dog.DogApi import co.touchlab.kampkit.data.dog.DogApiImpl import co.touchlab.kampkit.data.dog.DogDatabaseHelper -import co.touchlab.kampkit.data.dog.DogRepository +import co.touchlab.kampkit.data.dog.NetworkBreedRepository +import co.touchlab.kampkit.domain.breed.BreedRepository import co.touchlab.kermit.Logger import co.touchlab.kermit.StaticConfig import co.touchlab.kermit.platformLogWriter @@ -66,8 +67,8 @@ private val coreModule = module { val baseLogger = Logger(config = StaticConfig(logWriterList = listOf(platformLogWriter())), "KampKit") factory { (tag: String?) -> if (tag != null) baseLogger.withTag(tag) else baseLogger } - single { - DogRepository( + single { + NetworkBreedRepository( get(), get(), get(), diff --git a/shared/src/commonMain/kotlin/co/touchlab/kampkit/data/dog/DogDatabaseHelper.kt b/shared/src/commonMain/kotlin/co/touchlab/kampkit/data/dog/DogDatabaseHelper.kt index 26f1273b..730af8ac 100644 --- a/shared/src/commonMain/kotlin/co/touchlab/kampkit/data/dog/DogDatabaseHelper.kt +++ b/shared/src/commonMain/kotlin/co/touchlab/kampkit/data/dog/DogDatabaseHelper.kt @@ -1,7 +1,7 @@ package co.touchlab.kampkit.data.dog import co.touchlab.kampkit.core.transactionWithContext -import co.touchlab.kampkit.db.Breed +import co.touchlab.kampkit.db.DbBreed import co.touchlab.kampkit.db.KaMPKitDb import co.touchlab.kermit.Logger import com.squareup.sqldelight.db.SqlDriver @@ -19,7 +19,7 @@ class DogDatabaseHelper( ) { private val dbRef: KaMPKitDb = KaMPKitDb(sqlDriver) - fun selectAllItems(): Flow> = + fun selectAllItems(): Flow> = dbRef.tableQueries .selectAll() .asFlow() @@ -35,7 +35,7 @@ class DogDatabaseHelper( } } - fun selectById(id: Long): Flow> = + fun selectById(id: Long): Flow> = dbRef.tableQueries .selectById(id) .asFlow() diff --git a/shared/src/commonMain/kotlin/co/touchlab/kampkit/data/dog/DogRepository.kt b/shared/src/commonMain/kotlin/co/touchlab/kampkit/data/dog/NetworkBreedRepository.kt similarity index 63% rename from shared/src/commonMain/kotlin/co/touchlab/kampkit/data/dog/DogRepository.kt rename to shared/src/commonMain/kotlin/co/touchlab/kampkit/data/dog/NetworkBreedRepository.kt index 6b392537..df74d05f 100644 --- a/shared/src/commonMain/kotlin/co/touchlab/kampkit/data/dog/DogRepository.kt +++ b/shared/src/commonMain/kotlin/co/touchlab/kampkit/data/dog/NetworkBreedRepository.kt @@ -1,19 +1,22 @@ package co.touchlab.kampkit.data.dog -import co.touchlab.kampkit.db.Breed +import co.touchlab.kampkit.domain.breed.Breed +import co.touchlab.kampkit.domain.breed.BreedRepository import co.touchlab.kermit.Logger import co.touchlab.stately.ensureNeverFrozen import com.russhwolf.settings.Settings import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.map import kotlinx.datetime.Clock -class DogRepository( +class NetworkBreedRepository( private val dbHelper: DogDatabaseHelper, private val settings: Settings, private val dogApi: DogApi, log: Logger, private val clock: Clock -) { +) : BreedRepository { private val log = log.withTag("DogRepository") @@ -25,15 +28,19 @@ class DogRepository( ensureNeverFrozen() } - fun getBreeds(): Flow> = dbHelper.selectAllItems() + override fun getBreeds(): Flow> { + return dbHelper.selectAllItems().map { list -> + list.map { dbBreed -> dbBreed.toDomain() } + } + } - suspend fun refreshBreedsIfStale() { + override suspend fun refreshBreedsIfStale() { if (isBreedListStale()) { refreshBreeds() } } - suspend fun refreshBreeds() { + override suspend fun refreshBreeds() { val breedResult = dogApi.getJsonFromApi() log.v { "Breed network result: ${breedResult.status}" } val breedList = breedResult.message.keys.sorted().toList() @@ -45,8 +52,11 @@ class DogRepository( } } - suspend fun updateBreedFavorite(breed: Breed) { - dbHelper.updateFavorite(breed.id, !breed.favorite) + override suspend fun updateBreedFavorite(breedId: Long) { + val foundBreedsWithId = dbHelper.selectById(breedId).first() + foundBreedsWithId.firstOrNull()?.let { breed -> + dbHelper.updateFavorite(breed.id, !breed.favorite) + } } private fun isBreedListStale(): Boolean { @@ -58,4 +68,6 @@ class DogRepository( } return stale } + + private fun co.touchlab.kampkit.db.DbBreed.toDomain() = Breed(id, name, favorite) } diff --git a/shared/src/commonMain/kotlin/co/touchlab/kampkit/domain/breed/Breed.kt b/shared/src/commonMain/kotlin/co/touchlab/kampkit/domain/breed/Breed.kt new file mode 100644 index 00000000..27c7787e --- /dev/null +++ b/shared/src/commonMain/kotlin/co/touchlab/kampkit/domain/breed/Breed.kt @@ -0,0 +1,7 @@ +package co.touchlab.kampkit.domain.breed + +data class Breed( + val id: Long, + val name: String, + val favorite: Boolean +) diff --git a/shared/src/commonMain/kotlin/co/touchlab/kampkit/domain/breed/BreedRepository.kt b/shared/src/commonMain/kotlin/co/touchlab/kampkit/domain/breed/BreedRepository.kt new file mode 100644 index 00000000..b5cbb2e2 --- /dev/null +++ b/shared/src/commonMain/kotlin/co/touchlab/kampkit/domain/breed/BreedRepository.kt @@ -0,0 +1,10 @@ +package co.touchlab.kampkit.domain.breed + +import kotlinx.coroutines.flow.Flow + +interface BreedRepository { + fun getBreeds(): Flow> + suspend fun refreshBreedsIfStale() + suspend fun refreshBreeds() + suspend fun updateBreedFavorite(breedId: Long) +} diff --git a/shared/src/commonMain/kotlin/co/touchlab/kampkit/ui/breeds/BreedsViewModel.kt b/shared/src/commonMain/kotlin/co/touchlab/kampkit/ui/breeds/BreedsViewModel.kt index 77ff7ad0..33897c9b 100644 --- a/shared/src/commonMain/kotlin/co/touchlab/kampkit/ui/breeds/BreedsViewModel.kt +++ b/shared/src/commonMain/kotlin/co/touchlab/kampkit/ui/breeds/BreedsViewModel.kt @@ -1,8 +1,7 @@ package co.touchlab.kampkit.ui.breeds import co.touchlab.kampkit.core.ViewModel -import co.touchlab.kampkit.data.dog.DogRepository -import co.touchlab.kampkit.db.Breed +import co.touchlab.kampkit.domain.breed.BreedRepository import co.touchlab.kermit.Logger import com.rickclephas.kmp.nativecoroutines.NativeCoroutinesState import kotlinx.coroutines.Job @@ -16,7 +15,7 @@ import kotlin.native.ObjCName @ObjCName("BreedsViewModelDelegate") class BreedsViewModel( - private val dogRepository: DogRepository, + private val breedRepository: BreedRepository, log: Logger ) : ViewModel() { private val log = log.withTag("BreedsViewModel") @@ -35,7 +34,7 @@ class BreedsViewModel( // Refresh breeds, and emit any exception that was thrown so we can handle it downstream val refreshFlow = flow { try { - dogRepository.refreshBreedsIfStale() + breedRepository.refreshBreedsIfStale() emit(null) } catch (exception: Exception) { emit(exception) @@ -43,7 +42,7 @@ class BreedsViewModel( } viewModelScope.launch { - combine(refreshFlow, dogRepository.getBreeds()) { throwable, breeds -> throwable to breeds } + combine(refreshFlow, breedRepository.getBreeds()) { throwable, breeds -> throwable to breeds } .collect { (error, breeds) -> mutableBreedState.update { previousState -> val errorMessage = if (error != null) { @@ -68,16 +67,16 @@ class BreedsViewModel( return viewModelScope.launch { log.v { "refreshBreeds" } try { - dogRepository.refreshBreeds() + breedRepository.refreshBreeds() } catch (exception: Exception) { handleBreedError(exception) } } } - fun updateBreedFavorite(breed: Breed): Job { + fun updateBreedFavorite(breedId: Long): Job { return viewModelScope.launch { - dogRepository.updateBreedFavorite(breed) + breedRepository.updateBreedFavorite(breedId) } } diff --git a/shared/src/commonMain/kotlin/co/touchlab/kampkit/ui/breeds/BreedsViewState.kt b/shared/src/commonMain/kotlin/co/touchlab/kampkit/ui/breeds/BreedsViewState.kt index fb2e61ee..0a908bad 100644 --- a/shared/src/commonMain/kotlin/co/touchlab/kampkit/ui/breeds/BreedsViewState.kt +++ b/shared/src/commonMain/kotlin/co/touchlab/kampkit/ui/breeds/BreedsViewState.kt @@ -1,6 +1,6 @@ package co.touchlab.kampkit.ui.breeds -import co.touchlab.kampkit.db.Breed +import co.touchlab.kampkit.domain.breed.Breed data class BreedsViewState( val breeds: List = emptyList(), diff --git a/shared/src/commonMain/sqldelight/co/touchlab/kampkit/db/Table.sq b/shared/src/commonMain/sqldelight/co/touchlab/kampkit/db/Table.sq index a8b51359..7c4af76a 100644 --- a/shared/src/commonMain/sqldelight/co/touchlab/kampkit/db/Table.sq +++ b/shared/src/commonMain/sqldelight/co/touchlab/kampkit/db/Table.sq @@ -1,26 +1,26 @@ import kotlin.Boolean; -CREATE TABLE Breed ( +CREATE TABLE DbBreed ( id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL UNIQUE, favorite INTEGER AS Boolean NOT NULL DEFAULT 0 ); selectAll: -SELECT * FROM Breed; +SELECT * FROM DbBreed; selectById: -SELECT * FROM Breed WHERE id = ?; +SELECT * FROM DbBreed WHERE id = ?; selectByName: -SELECT * FROM Breed WHERE name = ?; +SELECT * FROM DbBreed WHERE name = ?; insertBreed: -INSERT OR IGNORE INTO Breed(name) +INSERT OR IGNORE INTO DbBreed(name) VALUES (?); deleteAll: -DELETE FROM Breed; +DELETE FROM DbBreed; updateFavorite: -UPDATE Breed SET favorite = ? WHERE id = ?; +UPDATE DbBreed SET favorite = ? WHERE id = ?; diff --git a/shared/src/commonTest/kotlin/co/touchlab/kampkit/BreedsViewModelTest.kt b/shared/src/commonTest/kotlin/co/touchlab/kampkit/BreedsViewModelTest.kt index 77dfe338..36f83861 100644 --- a/shared/src/commonTest/kotlin/co/touchlab/kampkit/BreedsViewModelTest.kt +++ b/shared/src/commonTest/kotlin/co/touchlab/kampkit/BreedsViewModelTest.kt @@ -3,9 +3,10 @@ package co.touchlab.kampkit import app.cash.turbine.ReceiveTurbine import app.cash.turbine.test import co.touchlab.kampkit.data.dog.DogDatabaseHelper -import co.touchlab.kampkit.data.dog.DogRepository import co.touchlab.kampkit.data.dog.DogResult -import co.touchlab.kampkit.db.Breed +import co.touchlab.kampkit.data.dog.NetworkBreedRepository +import co.touchlab.kampkit.domain.breed.Breed +import co.touchlab.kampkit.domain.breed.BreedRepository import co.touchlab.kampkit.mock.ClockMock import co.touchlab.kampkit.mock.DogApiMock import co.touchlab.kampkit.ui.breeds.BreedsViewModel @@ -38,7 +39,7 @@ class BreedsViewModelTest { // Need to start at non-zero time because the default value for db timestamp is 0 private val clock = ClockMock(Clock.System.now()) - private val repository: DogRepository = DogRepository(dbHelper, settings, ktorApi, kermit, clock) + private val repository: BreedRepository = NetworkBreedRepository(dbHelper, settings, ktorApi, kermit, clock) private val viewModel by lazy { BreedsViewModel(repository, kermit) } companion object { @@ -91,7 +92,7 @@ class BreedsViewModelTest { @Test fun `Get updated breeds with cache and preserve favorites`() = runTest { - settings.putLong(DogRepository.DB_TIMESTAMP_KEY, clock.currentInstant.toEpochMilliseconds()) + settings.putLong(NetworkBreedRepository.DB_TIMESTAMP_KEY, clock.currentInstant.toEpochMilliseconds()) val successResult = ktorApi.successResult() val resultWithExtraBreed = successResult.copy(message = successResult.message + ("extra" to emptyList())) @@ -115,7 +116,7 @@ class BreedsViewModelTest { @Test fun `Get updated breeds when stale and preserve favorites`() = runTest { - settings.putLong(DogRepository.DB_TIMESTAMP_KEY, (clock.currentInstant - 2.hours).toEpochMilliseconds()) + settings.putLong(NetworkBreedRepository.DB_TIMESTAMP_KEY, (clock.currentInstant - 2.hours).toEpochMilliseconds()) val successResult = ktorApi.successResult() val resultWithExtraBreed = successResult.copy(message = successResult.message + ("extra" to emptyList())) @@ -135,7 +136,7 @@ class BreedsViewModelTest { @Test fun `Toggle favorite cached breed`() = runTest { - settings.putLong(DogRepository.DB_TIMESTAMP_KEY, clock.currentInstant.toEpochMilliseconds()) + settings.putLong(NetworkBreedRepository.DB_TIMESTAMP_KEY, clock.currentInstant.toEpochMilliseconds()) dbHelper.insertBreeds(breedNames) dbHelper.updateFavorite(australianLike.id, true) @@ -144,7 +145,7 @@ class BreedsViewModelTest { assertEquals(breedsViewStateSuccessFavorite, awaitItemPrecededBy(BreedsViewState(isLoading = true))) expectNoEvents() - viewModel.updateBreedFavorite(australianLike).join() + viewModel.updateBreedFavorite(australianLike.id).join() assertEquals( breedsViewStateSuccessNoFavorite, awaitItemPrecededBy(breedsViewStateSuccessFavorite.copy(isLoading = true)) @@ -154,7 +155,7 @@ class BreedsViewModelTest { @Test fun `No web call if data is not stale`() = runTest { - settings.putLong(DogRepository.DB_TIMESTAMP_KEY, clock.currentInstant.toEpochMilliseconds()) + settings.putLong(NetworkBreedRepository.DB_TIMESTAMP_KEY, clock.currentInstant.toEpochMilliseconds()) ktorApi.prepareResult(ktorApi.successResult()) dbHelper.insertBreeds(breedNames) @@ -187,7 +188,7 @@ class BreedsViewModelTest { @Test fun `Ignore API error with cache`() = runTest { dbHelper.insertBreeds(breedNames) - settings.putLong(DogRepository.DB_TIMESTAMP_KEY, (clock.currentInstant - 2.hours).toEpochMilliseconds()) + settings.putLong(NetworkBreedRepository.DB_TIMESTAMP_KEY, (clock.currentInstant - 2.hours).toEpochMilliseconds()) ktorApi.throwOnCall(RuntimeException("Test error")) viewModel.breedsState.test { @@ -230,7 +231,7 @@ class BreedsViewModelTest { @Test fun `Show API error on refresh without cache`() = runTest { - settings.putLong(DogRepository.DB_TIMESTAMP_KEY, clock.currentInstant.toEpochMilliseconds()) + settings.putLong(NetworkBreedRepository.DB_TIMESTAMP_KEY, clock.currentInstant.toEpochMilliseconds()) ktorApi.throwOnCall(RuntimeException("Test error")) viewModel.breedsState.test { diff --git a/shared/src/commonTest/kotlin/co/touchlab/kampkit/BreedRepositoryTest.kt b/shared/src/commonTest/kotlin/co/touchlab/kampkit/NetworkDogRepositoryTest.kt similarity index 87% rename from shared/src/commonTest/kotlin/co/touchlab/kampkit/BreedRepositoryTest.kt rename to shared/src/commonTest/kotlin/co/touchlab/kampkit/NetworkDogRepositoryTest.kt index 60d30ba5..e7850239 100644 --- a/shared/src/commonTest/kotlin/co/touchlab/kampkit/BreedRepositoryTest.kt +++ b/shared/src/commonTest/kotlin/co/touchlab/kampkit/NetworkDogRepositoryTest.kt @@ -2,8 +2,8 @@ package co.touchlab.kampkit import app.cash.turbine.test import co.touchlab.kampkit.data.dog.DogDatabaseHelper -import co.touchlab.kampkit.data.dog.DogRepository -import co.touchlab.kampkit.db.Breed +import co.touchlab.kampkit.data.dog.NetworkBreedRepository +import co.touchlab.kampkit.domain.breed.Breed import co.touchlab.kampkit.mock.ClockMock import co.touchlab.kampkit.mock.DogApiMock import co.touchlab.kermit.Logger @@ -18,7 +18,7 @@ import kotlin.test.assertEquals import kotlin.test.assertFails import kotlin.time.Duration.Companion.hours -class BreedRepositoryTest { +class NetworkDogRepositoryTest { private var kermit = Logger(StaticConfig()) private var testDbConnection = testDbConnection() @@ -33,7 +33,7 @@ class BreedRepositoryTest { // Need to start at non-zero time because the default value for db timestamp is 0 private val clock = ClockMock(Clock.System.now()) - private val repository: DogRepository = DogRepository(dbHelper, settings, ktorApi, kermit, clock) + private val repository = NetworkBreedRepository(dbHelper, settings, ktorApi, kermit, clock) companion object { private val appenzeller = Breed(1, "appenzeller", false) @@ -79,7 +79,7 @@ class BreedRepositoryTest { @Test fun `Get updated breeds when stale and preserve favorites`() = runTest { - settings.putLong(DogRepository.DB_TIMESTAMP_KEY, (clock.currentInstant - 2.hours).toEpochMilliseconds()) + settings.putLong(NetworkBreedRepository.DB_TIMESTAMP_KEY, (clock.currentInstant - 2.hours).toEpochMilliseconds()) val successResult = ktorApi.successResult() val resultWithExtraBreed = successResult.copy(message = successResult.message + ("extra" to emptyList())) @@ -104,14 +104,14 @@ class BreedRepositoryTest { assertEquals(breedsFavorite, awaitItem()) expectNoEvents() - repository.updateBreedFavorite(australianLike) + repository.updateBreedFavorite(australianLike.id) assertEquals(breedsNoFavorite, awaitItem()) } } @Test fun `No web call if data is not stale`() = runTest { - settings.putLong(DogRepository.DB_TIMESTAMP_KEY, clock.currentInstant.toEpochMilliseconds()) + settings.putLong(NetworkBreedRepository.DB_TIMESTAMP_KEY, clock.currentInstant.toEpochMilliseconds()) ktorApi.prepareResult(ktorApi.successResult()) repository.refreshBreedsIfStale() @@ -133,7 +133,7 @@ class BreedRepositoryTest { @Test fun `Rethrow on API error when stale`() = runTest { - settings.putLong(DogRepository.DB_TIMESTAMP_KEY, (clock.currentInstant - 2.hours).toEpochMilliseconds()) + settings.putLong(NetworkBreedRepository.DB_TIMESTAMP_KEY, (clock.currentInstant - 2.hours).toEpochMilliseconds()) ktorApi.throwOnCall(RuntimeException("Test error")) val throwable = assertFails { From bdae045299c998090a97ac2fec29be19b59f7263 Mon Sep 17 00:00:00 2001 From: bpedryc Date: Mon, 3 Jul 2023 13:13:15 +0200 Subject: [PATCH 9/9] Fix iOS methods for favoriting a breed --- ios/KaMPKitiOS/Breeds/BreedsScreen.swift | 6 +++--- ios/KaMPKitiOS/Breeds/BreedsViewModel.swift | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ios/KaMPKitiOS/Breeds/BreedsScreen.swift b/ios/KaMPKitiOS/Breeds/BreedsScreen.swift index 12e7b11d..c82259b8 100644 --- a/ios/KaMPKitiOS/Breeds/BreedsScreen.swift +++ b/ios/KaMPKitiOS/Breeds/BreedsScreen.swift @@ -16,7 +16,7 @@ struct BreedsScreen: View { var body: some View { BreedsContent( state: viewModel.state, - onBreedFavorite: { viewModel.onBreedFavorite(breed: $0) }, + onBreedFavorite: { viewModel.onBreedFavorite($0) }, refresh: { viewModel.refresh() } ) .onAppear(perform: { @@ -30,7 +30,7 @@ struct BreedsScreen: View { struct BreedsContent: View { var state: BreedsViewState - var onBreedFavorite: (Breed) -> Void + var onBreedFavorite: (Int64) -> Void var refresh: () -> Void var body: some View { @@ -38,7 +38,7 @@ struct BreedsContent: View { VStack { List(state.breeds, id: \.id) { breed in BreedRowView(breed: breed) { - onBreedFavorite(breed) + onBreedFavorite(breed.id) } } if let error = state.error { diff --git a/ios/KaMPKitiOS/Breeds/BreedsViewModel.swift b/ios/KaMPKitiOS/Breeds/BreedsViewModel.swift index 635da4bb..0d19c64a 100644 --- a/ios/KaMPKitiOS/Breeds/BreedsViewModel.swift +++ b/ios/KaMPKitiOS/Breeds/BreedsViewModel.swift @@ -35,8 +35,8 @@ class BreedsViewModel: ObservableObject { cancellables.removeAll() } - func onBreedFavorite(_ breed: Breed) { - viewModelDelegate.updateBreedFavorite(breed: breed) + func onBreedFavorite(_ breedId: Int64) { + viewModelDelegate.updateBreedFavorite(breedId: breedId) } func refresh() {