From 1fcf9ff9b5683e51dc5ea3847bd2c774ee1d9da9 Mon Sep 17 00:00:00 2001 From: Carlton Whitehead Date: Sat, 1 Jun 2024 22:33:13 -0400 Subject: [PATCH] SNAPSHOT: second draft presentation --- .../presentation-library-test/pom.xml | 14 ++-- .../fooapp/data/service/FooService.kt | 2 + .../fooapp/domain/constraint/FooConstraint.kt | 6 +- .../testsupport/fooapp/domain/entity/Foo.kt | 0 .../exception/AlreadyExistsException.kt | 3 + .../domain/exception/NotFoundException.kt | 0 .../domain/service/TestableFooService.kt | 48 +++++++++++ .../fooapp/presentation/adapter/FooAdapter.kt | 2 - .../fooapp/presentation/model/FooModel.kt | 6 +- .../presenter/FooDetailPresenter.kt} | 18 ++-- .../presenter/FooDetailPresenterTest.kt | 83 +++++++++++++++++++ .../presenter/LoadableItemPresenterTest.kt | 32 ------- .../domain/service/TestableFooService.kt | 29 ------- .../fooapp/presentation/state/FooState.kt | 7 -- .../library/adapter/LoadableItemAdapter.kt | 9 +- .../library/model/LoadableModel.kt | 19 +++-- .../presenter/LoadableItemPresenter.kt | 51 ++++++++++-- .../library/state/LoadableItemState.kt | 4 +- 18 files changed, 222 insertions(+), 111 deletions(-) rename presentation/{presentation-library-testsupport => presentation-library-test}/src/main/kotlin/tech/coner/trailer/presentation/library/testsupport/fooapp/data/service/FooService.kt (74%) rename presentation/{presentation-library-testsupport => presentation-library-test}/src/main/kotlin/tech/coner/trailer/presentation/library/testsupport/fooapp/domain/constraint/FooConstraint.kt (81%) rename presentation/{presentation-library-testsupport => presentation-library-test}/src/main/kotlin/tech/coner/trailer/presentation/library/testsupport/fooapp/domain/entity/Foo.kt (100%) create mode 100644 presentation/presentation-library-test/src/main/kotlin/tech/coner/trailer/presentation/library/testsupport/fooapp/domain/exception/AlreadyExistsException.kt rename presentation/{presentation-library-testsupport => presentation-library-test}/src/main/kotlin/tech/coner/trailer/presentation/library/testsupport/fooapp/domain/exception/NotFoundException.kt (100%) create mode 100644 presentation/presentation-library-test/src/main/kotlin/tech/coner/trailer/presentation/library/testsupport/fooapp/domain/service/TestableFooService.kt rename presentation/{presentation-library-testsupport => presentation-library-test}/src/main/kotlin/tech/coner/trailer/presentation/library/testsupport/fooapp/presentation/adapter/FooAdapter.kt (84%) rename presentation/{presentation-library-testsupport => presentation-library-test}/src/main/kotlin/tech/coner/trailer/presentation/library/testsupport/fooapp/presentation/model/FooModel.kt (89%) rename presentation/{presentation-library-testsupport/src/main/kotlin/tech/coner/trailer/presentation/library/testsupport/fooapp/presentation/presenter/FooPresenter.kt => presentation-library-test/src/main/kotlin/tech/coner/trailer/presentation/library/testsupport/fooapp/presentation/presenter/FooDetailPresenter.kt} (64%) create mode 100644 presentation/presentation-library-test/src/test/kotlin/tech/coner/trailer/presentation/library/presenter/FooDetailPresenterTest.kt delete mode 100644 presentation/presentation-library-test/src/test/kotlin/tech/coner/trailer/presentation/library/presenter/LoadableItemPresenterTest.kt delete mode 100644 presentation/presentation-library-testsupport/src/main/kotlin/tech/coner/trailer/presentation/library/testsupport/fooapp/domain/service/TestableFooService.kt delete mode 100644 presentation/presentation-library-testsupport/src/main/kotlin/tech/coner/trailer/presentation/library/testsupport/fooapp/presentation/state/FooState.kt diff --git a/presentation/presentation-library-test/pom.xml b/presentation/presentation-library-test/pom.xml index 5ad65ea8..ff68be78 100644 --- a/presentation/presentation-library-test/pom.xml +++ b/presentation/presentation-library-test/pom.xml @@ -21,25 +21,19 @@ org.jetbrains.kotlinx kotlinx-coroutines-core - tech.coner.trailer presentation-library 0.1.0-SNAPSHOT - test + compile + tech.coner.trailer presentation-library-testsupport 0.1.0-SNAPSHOT test - - tech.coner.trailer - io - 0.1.0-SNAPSHOT - test - org.junit.jupiter junit-jupiter @@ -53,6 +47,10 @@ org.jetbrains.kotlinx kotlinx-coroutines-test-jvm + + app.cash.turbine + turbine-jvm + diff --git a/presentation/presentation-library-testsupport/src/main/kotlin/tech/coner/trailer/presentation/library/testsupport/fooapp/data/service/FooService.kt b/presentation/presentation-library-test/src/main/kotlin/tech/coner/trailer/presentation/library/testsupport/fooapp/data/service/FooService.kt similarity index 74% rename from presentation/presentation-library-testsupport/src/main/kotlin/tech/coner/trailer/presentation/library/testsupport/fooapp/data/service/FooService.kt rename to presentation/presentation-library-test/src/main/kotlin/tech/coner/trailer/presentation/library/testsupport/fooapp/data/service/FooService.kt index 09a4d2d9..c411d3a7 100644 --- a/presentation/presentation-library-testsupport/src/main/kotlin/tech/coner/trailer/presentation/library/testsupport/fooapp/data/service/FooService.kt +++ b/presentation/presentation-library-test/src/main/kotlin/tech/coner/trailer/presentation/library/testsupport/fooapp/data/service/FooService.kt @@ -4,6 +4,8 @@ import tech.coner.trailer.presentation.library.testsupport.fooapp.domain.entity. interface FooService { + suspend fun create(create: Foo): Result suspend fun findById(id: Foo.Id): Result suspend fun update(update: Foo): Result + suspend fun deleteById(id: Foo.Id): Result } \ No newline at end of file diff --git a/presentation/presentation-library-testsupport/src/main/kotlin/tech/coner/trailer/presentation/library/testsupport/fooapp/domain/constraint/FooConstraint.kt b/presentation/presentation-library-test/src/main/kotlin/tech/coner/trailer/presentation/library/testsupport/fooapp/domain/constraint/FooConstraint.kt similarity index 81% rename from presentation/presentation-library-testsupport/src/main/kotlin/tech/coner/trailer/presentation/library/testsupport/fooapp/domain/constraint/FooConstraint.kt rename to presentation/presentation-library-test/src/main/kotlin/tech/coner/trailer/presentation/library/testsupport/fooapp/domain/constraint/FooConstraint.kt index 5b132050..b494cb79 100644 --- a/presentation/presentation-library-testsupport/src/main/kotlin/tech/coner/trailer/presentation/library/testsupport/fooapp/domain/constraint/FooConstraint.kt +++ b/presentation/presentation-library-test/src/main/kotlin/tech/coner/trailer/presentation/library/testsupport/fooapp/domain/constraint/FooConstraint.kt @@ -6,10 +6,10 @@ import tech.coner.trailer.toolkit.konstraints.CompositeConstraint class FooConstraint : CompositeConstraint() { val valueIsInRange = propertyConstraint( - property = Foo::value, + property = Foo::id, assessFn = { - when (it.value) { - in Foo.values().indices -> true + when (it.id.value) { + in 0..4 -> true else -> false } }, diff --git a/presentation/presentation-library-testsupport/src/main/kotlin/tech/coner/trailer/presentation/library/testsupport/fooapp/domain/entity/Foo.kt b/presentation/presentation-library-test/src/main/kotlin/tech/coner/trailer/presentation/library/testsupport/fooapp/domain/entity/Foo.kt similarity index 100% rename from presentation/presentation-library-testsupport/src/main/kotlin/tech/coner/trailer/presentation/library/testsupport/fooapp/domain/entity/Foo.kt rename to presentation/presentation-library-test/src/main/kotlin/tech/coner/trailer/presentation/library/testsupport/fooapp/domain/entity/Foo.kt diff --git a/presentation/presentation-library-test/src/main/kotlin/tech/coner/trailer/presentation/library/testsupport/fooapp/domain/exception/AlreadyExistsException.kt b/presentation/presentation-library-test/src/main/kotlin/tech/coner/trailer/presentation/library/testsupport/fooapp/domain/exception/AlreadyExistsException.kt new file mode 100644 index 00000000..eaf3492e --- /dev/null +++ b/presentation/presentation-library-test/src/main/kotlin/tech/coner/trailer/presentation/library/testsupport/fooapp/domain/exception/AlreadyExistsException.kt @@ -0,0 +1,3 @@ +package tech.coner.trailer.presentation.library.testsupport.fooapp.domain.exception + +class AlreadyExistsException(message: String? = null, cause: Throwable? = null) : Exception(message, cause) diff --git a/presentation/presentation-library-testsupport/src/main/kotlin/tech/coner/trailer/presentation/library/testsupport/fooapp/domain/exception/NotFoundException.kt b/presentation/presentation-library-test/src/main/kotlin/tech/coner/trailer/presentation/library/testsupport/fooapp/domain/exception/NotFoundException.kt similarity index 100% rename from presentation/presentation-library-testsupport/src/main/kotlin/tech/coner/trailer/presentation/library/testsupport/fooapp/domain/exception/NotFoundException.kt rename to presentation/presentation-library-test/src/main/kotlin/tech/coner/trailer/presentation/library/testsupport/fooapp/domain/exception/NotFoundException.kt diff --git a/presentation/presentation-library-test/src/main/kotlin/tech/coner/trailer/presentation/library/testsupport/fooapp/domain/service/TestableFooService.kt b/presentation/presentation-library-test/src/main/kotlin/tech/coner/trailer/presentation/library/testsupport/fooapp/domain/service/TestableFooService.kt new file mode 100644 index 00000000..e9ded3e4 --- /dev/null +++ b/presentation/presentation-library-test/src/main/kotlin/tech/coner/trailer/presentation/library/testsupport/fooapp/domain/service/TestableFooService.kt @@ -0,0 +1,48 @@ +package tech.coner.trailer.presentation.library.testsupport.fooapp.domain.service + +import tech.coner.trailer.presentation.library.testsupport.fooapp.data.service.FooService +import tech.coner.trailer.presentation.library.testsupport.fooapp.domain.entity.FOO_ID_BAR +import tech.coner.trailer.presentation.library.testsupport.fooapp.domain.entity.FOO_ID_BAT +import tech.coner.trailer.presentation.library.testsupport.fooapp.domain.entity.FOO_ID_BAZ +import tech.coner.trailer.presentation.library.testsupport.fooapp.domain.entity.FOO_ID_FOO +import tech.coner.trailer.presentation.library.testsupport.fooapp.domain.entity.Foo +import tech.coner.trailer.presentation.library.testsupport.fooapp.domain.exception.AlreadyExistsException +import tech.coner.trailer.presentation.library.testsupport.fooapp.domain.exception.NotFoundException +import tech.coner.trailer.toolkit.konstraints.Constraint + +class TestableFooService(private val constraint: Constraint) : FooService { + + private val map: MutableMap = arrayOf( + FOO_ID_FOO to "foo", + FOO_ID_BAR to "bar", + FOO_ID_BAZ to "baz", + FOO_ID_BAT to "bat", + ) + .associate { (idValue, name) -> Foo.Id(idValue).let { it to Foo(it, name) } } + .toMutableMap() + + override suspend fun create(create: Foo): Result { + if (map.containsKey(create.id)) { + return Result.failure(AlreadyExistsException()) + } + return constraint(create) + .map { map[create.id] = create; create } + } + + override suspend fun findById(id: Foo.Id): Result { + return map[id]?.let { Result.success(it) } + ?: Result.failure(NotFoundException()) + } + + override suspend fun update(update: Foo): Result { + return findById(update.id) + .mapCatching { constraint(update).getOrThrow() } + .map { map[update.id] = update; update } + } + + override suspend fun deleteById(id: Foo.Id): Result { + return map.remove(id) + ?.let { Result.success(it) } + ?: Result.failure(NotFoundException()) + } +} diff --git a/presentation/presentation-library-testsupport/src/main/kotlin/tech/coner/trailer/presentation/library/testsupport/fooapp/presentation/adapter/FooAdapter.kt b/presentation/presentation-library-test/src/main/kotlin/tech/coner/trailer/presentation/library/testsupport/fooapp/presentation/adapter/FooAdapter.kt similarity index 84% rename from presentation/presentation-library-testsupport/src/main/kotlin/tech/coner/trailer/presentation/library/testsupport/fooapp/presentation/adapter/FooAdapter.kt rename to presentation/presentation-library-test/src/main/kotlin/tech/coner/trailer/presentation/library/testsupport/fooapp/presentation/adapter/FooAdapter.kt index 2046ccbd..49deeaad 100644 --- a/presentation/presentation-library-testsupport/src/main/kotlin/tech/coner/trailer/presentation/library/testsupport/fooapp/presentation/adapter/FooAdapter.kt +++ b/presentation/presentation-library-test/src/main/kotlin/tech/coner/trailer/presentation/library/testsupport/fooapp/presentation/adapter/FooAdapter.kt @@ -3,11 +3,9 @@ package tech.coner.trailer.presentation.library.testsupport.fooapp.presentation. import tech.coner.trailer.presentation.library.adapter.LoadableItemAdapter import tech.coner.trailer.presentation.library.testsupport.fooapp.domain.entity.Foo import tech.coner.trailer.presentation.library.testsupport.fooapp.presentation.model.FooModel -import tech.coner.trailer.presentation.library.testsupport.fooapp.presentation.state.FooState class FooAdapter : LoadableItemAdapter< Foo.Id, - FooState, Foo, Unit, FooModel diff --git a/presentation/presentation-library-testsupport/src/main/kotlin/tech/coner/trailer/presentation/library/testsupport/fooapp/presentation/model/FooModel.kt b/presentation/presentation-library-test/src/main/kotlin/tech/coner/trailer/presentation/library/testsupport/fooapp/presentation/model/FooModel.kt similarity index 89% rename from presentation/presentation-library-testsupport/src/main/kotlin/tech/coner/trailer/presentation/library/testsupport/fooapp/presentation/model/FooModel.kt rename to presentation/presentation-library-test/src/main/kotlin/tech/coner/trailer/presentation/library/testsupport/fooapp/presentation/model/FooModel.kt index f5ef4b5d..4219f912 100644 --- a/presentation/presentation-library-testsupport/src/main/kotlin/tech/coner/trailer/presentation/library/testsupport/fooapp/presentation/model/FooModel.kt +++ b/presentation/presentation-library-test/src/main/kotlin/tech/coner/trailer/presentation/library/testsupport/fooapp/presentation/model/FooModel.kt @@ -4,7 +4,7 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map import tech.coner.trailer.presentation.library.model.BaseItemModel import tech.coner.trailer.presentation.library.testsupport.fooapp.domain.constraint.FooConstraint -import tech.coner.trailer.presentation.library.testsupport.fooapp.domain.entity.* +import tech.coner.trailer.presentation.library.testsupport.fooapp.domain.entity.Foo class FooModel(override val original: Foo) : BaseItemModel() { override val constraints = FooConstraint() @@ -13,6 +13,10 @@ class FooModel(override val original: Foo) : BaseItemModel() it.name.capitalizeFirstChar() } + fun setName(name: String) { + updateItem { it.copy(name = name) } + } + private fun String.capitalizeFirstChar(): String { return when (length) { 0 -> this diff --git a/presentation/presentation-library-testsupport/src/main/kotlin/tech/coner/trailer/presentation/library/testsupport/fooapp/presentation/presenter/FooPresenter.kt b/presentation/presentation-library-test/src/main/kotlin/tech/coner/trailer/presentation/library/testsupport/fooapp/presentation/presenter/FooDetailPresenter.kt similarity index 64% rename from presentation/presentation-library-testsupport/src/main/kotlin/tech/coner/trailer/presentation/library/testsupport/fooapp/presentation/presenter/FooPresenter.kt rename to presentation/presentation-library-test/src/main/kotlin/tech/coner/trailer/presentation/library/testsupport/fooapp/presentation/presenter/FooDetailPresenter.kt index c7f7be49..71231915 100644 --- a/presentation/presentation-library-testsupport/src/main/kotlin/tech/coner/trailer/presentation/library/testsupport/fooapp/presentation/presenter/FooPresenter.kt +++ b/presentation/presentation-library-test/src/main/kotlin/tech/coner/trailer/presentation/library/testsupport/fooapp/presentation/presenter/FooDetailPresenter.kt @@ -1,19 +1,25 @@ package tech.coner.trailer.presentation.library.testsupport.fooapp.presentation.presenter +import kotlin.coroutines.CoroutineContext import tech.coner.trailer.presentation.library.presenter.LoadableItemPresenter -import tech.coner.trailer.presentation.library.state.LoadableItem -import tech.coner.trailer.presentation.library.testsupport.fooapp.presentation.model.FooModel -import tech.coner.trailer.presentation.library.testsupport.fooapp.presentation.state.FooState +import tech.coner.trailer.presentation.library.testsupport.fooapp.data.service.FooService import tech.coner.trailer.presentation.library.testsupport.fooapp.domain.entity.Foo import tech.coner.trailer.presentation.library.testsupport.fooapp.presentation.adapter.FooAdapter +import tech.coner.trailer.presentation.library.testsupport.fooapp.presentation.model.FooModel -class FooPresenter(override val argument: Foo.Id) : LoadableItemPresenter< +class FooDetailPresenter( + override val argument: Foo.Id, + private val service: FooService, + override val coroutineContext: CoroutineContext +) : LoadableItemPresenter< Foo.Id, - FooState, Foo, Unit, FooModel >() { - override val initialState = FooState(LoadableItem.Empty()) override val adapter = FooAdapter() + + override suspend fun performLoad(): Result { + return service.findById(argument) + } } \ No newline at end of file diff --git a/presentation/presentation-library-test/src/test/kotlin/tech/coner/trailer/presentation/library/presenter/FooDetailPresenterTest.kt b/presentation/presentation-library-test/src/test/kotlin/tech/coner/trailer/presentation/library/presenter/FooDetailPresenterTest.kt new file mode 100644 index 00000000..579d34a4 --- /dev/null +++ b/presentation/presentation-library-test/src/test/kotlin/tech/coner/trailer/presentation/library/presenter/FooDetailPresenterTest.kt @@ -0,0 +1,83 @@ +package tech.coner.trailer.presentation.library.presenter + +import app.cash.turbine.test +import assertk.assertThat +import assertk.assertions.isEqualTo +import assertk.assertions.isInstanceOf +import assertk.assertions.isNotNull +import assertk.assertions.prop +import kotlinx.coroutines.CoroutineName +import kotlinx.coroutines.Job +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.Test +import tech.coner.trailer.presentation.library.model.LoadableModel +import tech.coner.trailer.presentation.library.testsupport.fooapp.domain.constraint.FooConstraint +import tech.coner.trailer.presentation.library.testsupport.fooapp.domain.entity.FOO_ID_FOO +import tech.coner.trailer.presentation.library.testsupport.fooapp.domain.entity.Foo +import tech.coner.trailer.presentation.library.testsupport.fooapp.domain.exception.NotFoundException +import tech.coner.trailer.presentation.library.testsupport.fooapp.domain.service.TestableFooService +import tech.coner.trailer.presentation.library.testsupport.fooapp.presentation.model.FooModel +import tech.coner.trailer.presentation.library.testsupport.fooapp.presentation.presenter.FooDetailPresenter + +class FooDetailPresenterTest { + + @Test + fun itsModelFlowShouldBeAdaptedFromInitialState() = runTest { + val id = Foo.Id(FOO_ID_FOO) + val presenter = createPresenter(id) + + presenter.modelFlow.test { + assertThat(expectMostRecentItem()) + .isEqualTo(LoadableModel.Empty(null)) + } + } + + @Test + fun itsModelFlowShouldEmitWhenLoadingAndLoaded() = runTest { + val id = Foo.Id(FOO_ID_FOO) + val presenter = createPresenter(id) + + presenter.modelFlow.test { + skipItems(1) + + presenter.load() + + assertThat(awaitItem()) + .isInstanceOf>() + assertThat(awaitItem()) + .isInstanceOf>() + } + } + + @Test + fun itsModelFlowShouldEmitWhenLoadingAndLoadFailed() = runTest { + val id = Foo.Id(Int.MAX_VALUE) + val presenter = createPresenter(id) + + presenter.modelFlow.test { + skipItems(1) + + presenter.load() + + assertThat(awaitItem()) + .isInstanceOf>() + assertThat(awaitItem()) + .isInstanceOf>() + .prop(LoadableModel.LoadFailed::cause) + .isNotNull() + .isInstanceOf() + } + } + +} + +private fun TestScope.createPresenter(argument: Foo.Id): FooDetailPresenter { + return FooDetailPresenter( + argument = argument, + service = TestableFooService( + constraint = FooConstraint() + ), + coroutineContext = coroutineContext + Job() + CoroutineName("FooDetailPresenter") + ) +} diff --git a/presentation/presentation-library-test/src/test/kotlin/tech/coner/trailer/presentation/library/presenter/LoadableItemPresenterTest.kt b/presentation/presentation-library-test/src/test/kotlin/tech/coner/trailer/presentation/library/presenter/LoadableItemPresenterTest.kt deleted file mode 100644 index 9a79650f..00000000 --- a/presentation/presentation-library-test/src/test/kotlin/tech/coner/trailer/presentation/library/presenter/LoadableItemPresenterTest.kt +++ /dev/null @@ -1,32 +0,0 @@ -package tech.coner.trailer.presentation.library.presenter - -import assertk.assertThat -import assertk.assertions.isEqualTo -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.test.runTest -import org.junit.jupiter.api.Test -import tech.coner.trailer.presentation.library.model.LoadableModel -import tech.coner.trailer.presentation.library.testsupport.fooapp.presentation.presenter.FooPresenter - -class LoadableItemPresenterTest { - - - @Test - fun itsModelFlowShouldBeAdaptedFromInitialState() = runTest { - val presenter = FooPresenter() - - val actual = presenter.modelFlow.first() - - assertThat(actual).isEqualTo(LoadableModel.Empty(null)) - } - - @Test - fun itsModelFlowShouldChangeToLoadingWhenLoadingBegins() = runTest { - val presenter = FooPresenter() - } - - @Test - fun itsModelFlowShouldChangeToLoadedWhenLoadingFinishes() = runTest { - TODO() - } -} \ No newline at end of file diff --git a/presentation/presentation-library-testsupport/src/main/kotlin/tech/coner/trailer/presentation/library/testsupport/fooapp/domain/service/TestableFooService.kt b/presentation/presentation-library-testsupport/src/main/kotlin/tech/coner/trailer/presentation/library/testsupport/fooapp/domain/service/TestableFooService.kt deleted file mode 100644 index 67770230..00000000 --- a/presentation/presentation-library-testsupport/src/main/kotlin/tech/coner/trailer/presentation/library/testsupport/fooapp/domain/service/TestableFooService.kt +++ /dev/null @@ -1,29 +0,0 @@ -package tech.coner.trailer.presentation.library.testsupport.fooapp.domain.service - -import tech.coner.trailer.presentation.library.testsupport.fooapp.data.service.FooService -import tech.coner.trailer.presentation.library.testsupport.fooapp.domain.entity.* -import tech.coner.trailer.presentation.library.testsupport.fooapp.domain.exception.NotFoundException -import tech.coner.trailer.toolkit.konstraints.Constraint - -class TestableFooService(private val constraint: Constraint) : FooService { - - private val table: MutableMap = arrayOf( - FOO_ID_FOO to "foo", - FOO_ID_BAR to "bar", - FOO_ID_BAZ to "baz", - FOO_ID_BAT to "bat", - ) - .associate { (idValue, name) -> Foo.Id(idValue).let { it to Foo(it, name) } } - .toMutableMap() - - override suspend fun findById(id: Foo.Id): Result { - return table[id]?.let { Result.success(it) } - ?: Result.failure(NotFoundException()) - } - - override suspend fun update(update: Foo): Result { - return findById(update.id) - .mapCatching { constraint(update).getOrThrow() } - .map { table[update.id] = update; update } - } -} diff --git a/presentation/presentation-library-testsupport/src/main/kotlin/tech/coner/trailer/presentation/library/testsupport/fooapp/presentation/state/FooState.kt b/presentation/presentation-library-testsupport/src/main/kotlin/tech/coner/trailer/presentation/library/testsupport/fooapp/presentation/state/FooState.kt deleted file mode 100644 index 83afa6fd..00000000 --- a/presentation/presentation-library-testsupport/src/main/kotlin/tech/coner/trailer/presentation/library/testsupport/fooapp/presentation/state/FooState.kt +++ /dev/null @@ -1,7 +0,0 @@ -package tech.coner.trailer.presentation.library.testsupport.fooapp.presentation.state - -import tech.coner.trailer.presentation.library.state.LoadableItem -import tech.coner.trailer.presentation.library.state.LoadableItemState -import tech.coner.trailer.presentation.library.testsupport.fooapp.domain.entity.Foo - -data class FooState(override val loadable: LoadableItem) : LoadableItemState \ No newline at end of file diff --git a/presentation/presentation-library/src/main/kotlin/tech/coner/trailer/presentation/library/adapter/LoadableItemAdapter.kt b/presentation/presentation-library/src/main/kotlin/tech/coner/trailer/presentation/library/adapter/LoadableItemAdapter.kt index eaeeae5d..43aa224c 100644 --- a/presentation/presentation-library/src/main/kotlin/tech/coner/trailer/presentation/library/adapter/LoadableItemAdapter.kt +++ b/presentation/presentation-library/src/main/kotlin/tech/coner/trailer/presentation/library/adapter/LoadableItemAdapter.kt @@ -5,15 +5,14 @@ import tech.coner.trailer.presentation.library.model.LoadableModel import tech.coner.trailer.presentation.library.state.LoadableItem import tech.coner.trailer.presentation.library.state.LoadableItemState -abstract class LoadableItemAdapter - where STATE : LoadableItemState, - ITEM_MODEL : ItemModel { +abstract class LoadableItemAdapter + where ITEM_MODEL : ItemModel { protected abstract val argumentModelAdapter: ((ARGUMENT) -> ARGUMENT_MODEL)? - protected abstract val partialItemAdapter: ((ARGUMENT, STATE) -> ITEM_MODEL)? + protected abstract val partialItemAdapter: ((ARGUMENT, LoadableItemState) -> ITEM_MODEL)? protected abstract val itemAdapter: (ITEM) -> ITEM_MODEL - operator fun invoke(argument: ARGUMENT, state: STATE): LoadableModel { + operator fun invoke(argument: ARGUMENT, state: LoadableItemState): LoadableModel { val argumentModel: ARGUMENT_MODEL? = argumentModelAdapter?.invoke(argument) return when (val loadable = state.loadable) { is LoadableItem.Empty -> LoadableModel.Empty( diff --git a/presentation/presentation-library/src/main/kotlin/tech/coner/trailer/presentation/library/model/LoadableModel.kt b/presentation/presentation-library/src/main/kotlin/tech/coner/trailer/presentation/library/model/LoadableModel.kt index f8fe1881..462d9317 100644 --- a/presentation/presentation-library/src/main/kotlin/tech/coner/trailer/presentation/library/model/LoadableModel.kt +++ b/presentation/presentation-library/src/main/kotlin/tech/coner/trailer/presentation/library/model/LoadableModel.kt @@ -1,6 +1,7 @@ package tech.coner.trailer.presentation.library.model -sealed class LoadableModel : Model { +sealed class LoadableModel> + : Model { /** * Model represents presenter arguments @@ -11,9 +12,9 @@ sealed class LoadableModel : Model { * Initial model corresponding to the presenter having initial state, * prior to starting to load anything, or if it was fully reset. */ - data class Empty( + data class Empty>( override val argument: ARGUMENT_MODEL? - ) : LoadableModel() + ) : LoadableModel() /** * Model indicates the item is loading. @@ -24,29 +25,29 @@ sealed class LoadableModel : Model { * It may be helpful to implement the partial model with a different * type than the loaded item. */ - data class Loading( + data class Loading>( override val argument: ARGUMENT_MODEL?, val partial: ITEM_MODEL? = null - ) : LoadableModel() + ) : LoadableModel() /** * Model indicates the item has loaded. * * @property item the item resulting from the load operation. */ - data class Loaded( + data class Loaded>( override val argument: ARGUMENT_MODEL?, val item: ITEM_MODEL - ) : LoadableModel() + ) : LoadableModel() /** * Model indicates the load operation failed. * * @property cause the cause of the failure, if known */ - data class LoadFailed( + data class LoadFailed>( override val argument: ARGUMENT_MODEL?, val cause: Throwable? - ) : LoadableModel() + ) : LoadableModel() } \ No newline at end of file diff --git a/presentation/presentation-library/src/main/kotlin/tech/coner/trailer/presentation/library/presenter/LoadableItemPresenter.kt b/presentation/presentation-library/src/main/kotlin/tech/coner/trailer/presentation/library/presenter/LoadableItemPresenter.kt index 8d31a5bf..e75f9b0b 100644 --- a/presentation/presentation-library/src/main/kotlin/tech/coner/trailer/presentation/library/presenter/LoadableItemPresenter.kt +++ b/presentation/presentation-library/src/main/kotlin/tech/coner/trailer/presentation/library/presenter/LoadableItemPresenter.kt @@ -1,35 +1,72 @@ package tech.coner.trailer.presentation.library.presenter +import kotlin.time.Duration.Companion.milliseconds +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import kotlinx.coroutines.withTimeout import tech.coner.trailer.presentation.library.adapter.LoadableItemAdapter import tech.coner.trailer.presentation.library.model.ItemModel import tech.coner.trailer.presentation.library.model.LoadableModel +import tech.coner.trailer.presentation.library.state.LoadableItem import tech.coner.trailer.presentation.library.state.LoadableItemState abstract class LoadableItemPresenter< ARGUMENT, - STATE, ITEM, ARGUMENT_MODEL, ITEM_MODEL > - where STATE : LoadableItemState, - ITEM_MODEL : ItemModel { + : CoroutineScope + where ITEM_MODEL : ItemModel { protected abstract val argument: ARGUMENT - protected abstract val initialState: STATE - protected abstract val adapter: LoadableItemAdapter + protected val initialState = LoadableItemState(LoadableItem.Empty()) - private val _stateFlow: MutableStateFlow by lazy { MutableStateFlow(initialState) } - val stateFlow: StateFlow by lazy { _stateFlow.asStateFlow() } + protected abstract val adapter: LoadableItemAdapter + + private val _stateMutex = Mutex() + private val _stateFlow: MutableStateFlow> by lazy { MutableStateFlow(initialState) } + val stateFlow: StateFlow> by lazy { _stateFlow.asStateFlow() } val modelFlow: Flow> by lazy { stateFlow .map { state -> adapter(argument, state) } } + + suspend fun load() { + update { old -> old.copy(LoadableItem.Loading()) } + performLoad() + .onSuccess { update { old -> old.copy(LoadableItem.Loaded(it)) } } + .onFailure { update { old -> old.copy(LoadableItem.LoadFailed(it)) } } + } + + protected abstract suspend fun performLoad(): Result + + protected suspend fun update(reduceFn: (old: LoadableItemState) -> LoadableItemState) { + _stateMutex.withLock { + withTimeout(10.milliseconds) { + _stateFlow.update(reduceFn) + } + } + } + + suspend fun commit(): Result { + _stateMutex.withLock { + withTimeout(10.milliseconds) { + _stateFlow.update { state -> + when (val loadable = state.loadable) { + + } + } + } + } + } } \ No newline at end of file diff --git a/presentation/presentation-library/src/main/kotlin/tech/coner/trailer/presentation/library/state/LoadableItemState.kt b/presentation/presentation-library/src/main/kotlin/tech/coner/trailer/presentation/library/state/LoadableItemState.kt index e4fc519c..e2646373 100644 --- a/presentation/presentation-library/src/main/kotlin/tech/coner/trailer/presentation/library/state/LoadableItemState.kt +++ b/presentation/presentation-library/src/main/kotlin/tech/coner/trailer/presentation/library/state/LoadableItemState.kt @@ -1,5 +1,5 @@ package tech.coner.trailer.presentation.library.state -interface LoadableItemState : State { +data class LoadableItemState( val loadable: LoadableItem -} \ No newline at end of file +) : State