Skip to content

Commit

Permalink
Add the domain layer
Browse files Browse the repository at this point in the history
  • Loading branch information
bpedryc committed Jul 3, 2023
1 parent c2dddac commit 07311be
Show file tree
Hide file tree
Showing 12 changed files with 81 additions and 51 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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) }
)
}

Expand Down
2 changes: 1 addition & 1 deletion ios/KaMPKitiOS/Breeds/BreedsScreen.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
7 changes: 4 additions & 3 deletions shared/src/commonMain/kotlin/co/touchlab/kampkit/core/Koin.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<BreedRepository> {
NetworkBreedRepository(
get(),
get(),
get(),
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -19,7 +19,7 @@ class DogDatabaseHelper(
) {
private val dbRef: KaMPKitDb = KaMPKitDb(sqlDriver)

fun selectAllItems(): Flow<List<Breed>> =
fun selectAllItems(): Flow<List<DbBreed>> =
dbRef.tableQueries
.selectAll()
.asFlow()
Expand All @@ -35,7 +35,7 @@ class DogDatabaseHelper(
}
}

fun selectById(id: Long): Flow<List<Breed>> =
fun selectById(id: Long): Flow<List<DbBreed>> =
dbRef.tableQueries
.selectById(id)
.asFlow()
Expand Down
Original file line number Diff line number Diff line change
@@ -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")

Expand All @@ -25,15 +28,19 @@ class DogRepository(
ensureNeverFrozen()
}

fun getBreeds(): Flow<List<Breed>> = dbHelper.selectAllItems()
override fun getBreeds(): Flow<List<Breed>> {
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()
Expand All @@ -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 {
Expand All @@ -58,4 +68,6 @@ class DogRepository(
}
return stale
}

private fun co.touchlab.kampkit.db.DbBreed.toDomain() = Breed(id, name, favorite)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package co.touchlab.kampkit.domain.breed

data class Breed(
val id: Long,
val name: String,
val favorite: Boolean
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package co.touchlab.kampkit.domain.breed

import kotlinx.coroutines.flow.Flow

interface BreedRepository {
fun getBreeds(): Flow<List<Breed>>
suspend fun refreshBreedsIfStale()
suspend fun refreshBreeds()
suspend fun updateBreedFavorite(breedId: Long)
}
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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")
Expand All @@ -35,15 +34,15 @@ class BreedsViewModel(
// Refresh breeds, and emit any exception that was thrown so we can handle it downstream
val refreshFlow = flow<Throwable?> {
try {
dogRepository.refreshBreedsIfStale()
breedRepository.refreshBreedsIfStale()
emit(null)
} catch (exception: Exception) {
emit(exception)
}
}

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) {
Expand All @@ -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)
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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<Breed> = emptyList(),
Expand Down
14 changes: 7 additions & 7 deletions shared/src/commonMain/sqldelight/co/touchlab/kampkit/db/Table.sq
Original file line number Diff line number Diff line change
@@ -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 = ?;
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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()))
Expand All @@ -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()))
Expand All @@ -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)
Expand All @@ -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))
Expand All @@ -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)

Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down
Loading

0 comments on commit 07311be

Please sign in to comment.