Skip to content

Commit

Permalink
SNAPSHOT dmvapp sample presentation layer
Browse files Browse the repository at this point in the history
  • Loading branch information
carltonwhitehead committed Jun 18, 2024
1 parent e01d017 commit 75139f4
Show file tree
Hide file tree
Showing 18 changed files with 212 additions and 132 deletions.
5 changes: 5 additions & 0 deletions toolkit/presentation/presentation/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@
<artifactId>toolkit-konstraints</artifactId>
<version>0.1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>tech.coner.trailer</groupId>
<artifactId>toolkit-validation</artifactId>
<version>0.1.0-SNAPSHOT</version>
</dependency>

</dependencies>
</project>
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import tech.coner.trailer.toolkit.presentation.model.ItemModel
abstract class LoadableItemAdapter<ARGUMENT, ITEM, ARGUMENT_MODEL, ITEM_MODEL>
where ITEM_MODEL : ItemModel<ITEM> {

abstract val argumentToModelAdapter: ((ARGUMENT) -> ARGUMENT_MODEL)?
abstract val argumentToModelAdapter: ((ARGUMENT) -> ARGUMENT_MODEL)
abstract val itemToModelAdapter: (ITEM) -> ITEM_MODEL
abstract val modelToItemAdapter: (ITEM_MODEL) -> ITEM
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,76 +1,65 @@
package tech.coner.trailer.toolkit.presentation.model

import kotlin.reflect.KProperty1
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.update
import tech.coner.trailer.toolkit.konstraints.CompositeConstraint
import tech.coner.trailer.toolkit.konstraints.ConstraintViolationException
import tech.coner.trailer.toolkit.validation.Feedback
import tech.coner.trailer.toolkit.validation.ValidationResult
import tech.coner.trailer.toolkit.validation.Validator

abstract class BaseItemModel<I, C : CompositeConstraint<I>> : ItemModel<I> {
abstract val constraints: C
abstract class BaseItemModel<ITEM, VALIDATOR_CONTEXT, VALIDATOR_FEEDBACK>
: ItemModel<ITEM, VALIDATOR_FEEDBACK>
where VALIDATOR_FEEDBACK : Feedback {

abstract val initialItem: I
abstract val validator: Validator<VALIDATOR_CONTEXT, ITEM, VALIDATOR_FEEDBACK>
abstract val validatorContext: VALIDATOR_CONTEXT

abstract val initialItem: ITEM
private val _itemFlow by lazy { MutableStateFlow(initialItem) }
final override val itemFlow by lazy { _itemFlow.asStateFlow() }
final override val item: I
final override val item: ITEM
get() = _itemFlow.value

private val _pendingItemFlow by lazy { MutableStateFlow(initialItem) }
final override val pendingItemFlow by lazy { _pendingItemFlow.asStateFlow() }
final override var pendingItem: I
final override var pendingItem: ITEM
get() = pendingItemFlow.value
set(value) = mutatePendingItem { value }
private val _pendingItemValidationFlow by lazy { MutableStateFlow(emptyList<ValidationContent>()) }

private val _pendingItemValidationFlow by lazy {
MutableStateFlow<ValidationResult<ITEM, VALIDATOR_FEEDBACK>>(
ValidationResult(emptyMap())
)
}
final override val pendingItemValidationFlow by lazy { _pendingItemValidationFlow.asStateFlow() }
final override val pendingItemValidation get() = _pendingItemValidationFlow.value

final override fun mutatePendingItem(forceValidate: Boolean?, mutatePendingItemFn: (I) -> I) {
final override fun mutatePendingItem(forceValidate: Boolean?, mutatePendingItemFn: (ITEM) -> ITEM) {
_pendingItemFlow.update { pending -> mutatePendingItemFn(pending) }
if (forceValidate == true) {
validate()
}
}

override fun <P> validatedPropertyFlow(property: KProperty1<I, *>, fn: (I) -> P): Flow<Validated<P>> {
val constraints = constraints.propertyConstraints[property]
?: throw Exception("propertyConstraints does not contain any constraints for property: $property")
return pendingItemFlow.map { item ->
Validated(
fn(item),
constraints
.mapNotNull { constraint -> constraint(item).exceptionOrNull() as ConstraintViolationException? }
.map { Violation(it) }
)
}
}

final override val isPendingItemValid
get() = _pendingItemValidationFlow.value.isValid()
get() = _pendingItemValidationFlow.value.isValid

final override val isPendingItemDirty
get() = item != pendingItem

final override fun validate(): List<ValidationContent> {
return constraints.all
.mapNotNull { it.invoke(pendingItem).exceptionOrNull() as? ConstraintViolationException }
.map { ValidationContent.Error(it.message ?: "Invalid" /* TODO not hard-code english string */) }
final override fun validate(): ValidationResult<ITEM, VALIDATOR_FEEDBACK> {
return validator(validatorContext, pendingItem)
.also { _pendingItemValidationFlow.value = it }
}

override fun commit(forceValidate: Boolean): Result<I> {
if (forceValidate) {
validate()
.also {
if (!it.isValid()) {
return Result.failure(ModelNotReadyToCommitException())
}
}
override fun commit(requireValid: Boolean, successFn: (ITEM) -> Unit) {
if (requireValid) {
if (!validate().isValid) {
return
}
}
return pendingItem
pendingItem
.also { _itemFlow.value = it }
.let { Result.success(it) }
.also { successFn(it) }
}
}
Original file line number Diff line number Diff line change
@@ -1,23 +1,27 @@
package tech.coner.trailer.toolkit.presentation.model

import kotlin.reflect.KProperty1
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
import tech.coner.trailer.toolkit.validation.Feedback
import tech.coner.trailer.toolkit.validation.ValidationResult

interface ItemModel<ITEM, FEEDBACK : Feedback> : Model {
val itemFlow: StateFlow<ITEM>
val item: ITEM

val pendingItemFlow: StateFlow<ITEM>
var pendingItem: ITEM

interface ItemModel<I> : Model {
val itemFlow: StateFlow<I>
val item: I
val pendingItemFlow: StateFlow<I>
var pendingItem: I
val pendingItemValidationFlow: Flow<List<ValidationContent>>
val pendingItemValidation: List<ValidationContent>
val isPendingItemValid: Boolean
val isPendingItemDirty: Boolean

fun mutatePendingItem(forceValidate: Boolean? = null, mutatePendingItemFn: (I) -> I)
fun <P> validatedPropertyFlow(property: KProperty1<I, *>, fn: (I) -> P): Flow<Validated<P>>
val pendingItemValidationFlow: Flow<ValidationResult<ITEM, FEEDBACK>>
val pendingItemValidation: ValidationResult<ITEM, FEEDBACK>

val isPendingItemValid: Boolean

fun mutatePendingItem(forceValidate: Boolean? = null, mutatePendingItemFn: (ITEM) -> ITEM)

fun validate(): List<ValidationContent>
fun validate(): ValidationResult<ITEM, FEEDBACK>

fun commit(forceValidate: Boolean = true): Result<I>
fun commit(requireValid: Boolean = true, successFn: (ITEM) -> Unit)
}

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,42 +1,37 @@
package tech.coner.trailer.toolkit.presentation.presenter

import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.first
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.toolkit.presentation.adapter.LoadableItemAdapter
import tech.coner.trailer.toolkit.presentation.model.ItemModel
import tech.coner.trailer.toolkit.presentation.model.LoadableModel
import tech.coner.trailer.toolkit.presentation.model.Model
import tech.coner.trailer.toolkit.presentation.state.LoadableItemState

abstract class LoadableItemPresenter<
ARGUMENT,
ITEM,
ARGUMENT_MODEL,
ITEM_MODEL
>
: CoroutineScope
where ITEM_MODEL : ItemModel<ITEM> {
>(
override val initialState: LoadableItemState<ITEM, ITEM_MODEL> = LoadableItemState<ITEM, ITEM_MODEL>(LoadableModel.Empty())
)
: SecondDraftPresenter<
ARGUMENT,
ARGUMENT_MODEL,
LoadableItemState<ITEM, ITEM_MODEL>
>(), CoroutineScope
where ARGUMENT_MODEL : Model,
ITEM_MODEL : ItemModel<ITEM> {

protected abstract val adapter: LoadableItemAdapter<ARGUMENT, ITEM, ARGUMENT_MODEL, ITEM_MODEL>

protected abstract val argument: ARGUMENT?
val argumentModel: ARGUMENT_MODEL? by lazy {
argument?.let { adapter.argumentToModelAdapter?.invoke(it) }
abstract override val argument: ARGUMENT
override val argumentModel: ARGUMENT_MODEL by lazy {
argument.let { adapter.argumentToModelAdapter.invoke(it) }
}

private val initialState = LoadableItemState<ITEM, ITEM_MODEL>(LoadableModel.Empty())
private val _stateMutex = Mutex()
private val _stateFlow: MutableStateFlow<LoadableItemState<ITEM, ITEM_MODEL>> by lazy { MutableStateFlow(initialState) }
val stateFlow: StateFlow<LoadableItemState<ITEM, ITEM_MODEL>> by lazy { _stateFlow.asStateFlow() }

suspend fun load() {
update { old -> old.copy(LoadableModel.Loading()) }
performLoad()
Expand Down Expand Up @@ -68,12 +63,4 @@ abstract class LoadableItemPresenter<
}
} as LoadableModel.Loaded<ITEM, ITEM_MODEL>
}

protected suspend fun update(reduceFn: (old: LoadableItemState<ITEM, ITEM_MODEL>) -> LoadableItemState<ITEM, ITEM_MODEL>) {
_stateMutex.withLock {
withTimeout(10.milliseconds) {
_stateFlow.update(reduceFn)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package tech.coner.trailer.toolkit.presentation.presenter

import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import tech.coner.trailer.toolkit.presentation.model.Model
import tech.coner.trailer.toolkit.presentation.state.State

abstract class SecondDraftPresenter<STATE>
where STATE : State {

abstract val initialState: STATE
private val _stateMutex = Mutex()
private val _stateFlow: MutableStateFlow<STATE> by lazy { MutableStateFlow(initialState) }
val stateFlow: StateFlow<STATE> by lazy { _stateFlow.asStateFlow() }

protected suspend fun update(reduceFn: (old: STATE) -> STATE) {
_stateMutex.withLock {
_stateFlow.update(reduceFn)
}
}

interface WithArgument<ARGUMENT, ARGUMENT_MODEL : Model> {

val argument: ARGUMENT
val argumentModel: ARGUMENT_MODEL

}
}
8 changes: 8 additions & 0 deletions toolkit/samples/common/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,18 @@
<artifactId>toolkit-validation</artifactId>
<version>0.1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>tech.coner.trailer</groupId>
<artifactId>toolkit-presentation</artifactId>
<version>0.1.0-SNAPSHOT</version>
</dependency>


<dependency>
<groupId>tech.coner.trailer</groupId>
<artifactId>toolkit-validation-testsupport</artifactId>
<version>0.1.0-SNAPSHOT</version>
<scope>test</scope>
</dependency>
</dependencies>

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package tech.coner.trailer.toolkit.sample.dmvapp.presentation

import tech.coner.trailer.toolkit.presentation.presenter.SecondDraftPresenter
import tech.coner.trailer.toolkit.sample.dmvapp.presentation.model.DriversLicenseApplicationItemModel
import tech.coner.trailer.toolkit.sample.dmvapp.presentation.model.DriversLicenseApplicationModel
import tech.coner.trailer.toolkit.sample.dmvapp.presentation.state.DriversLicenseApplicationState

class DriversLicenseApplicationPresenter(
initialState: DriversLicenseApplicationState? = null
) : SecondDraftPresenter<DriversLicenseApplicationState>() {

override val initialState = initialState
?: DriversLicenseApplicationState(
model = DriversLicenseApplicationItemModel(

)
)
}
Loading

0 comments on commit 75139f4

Please sign in to comment.