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 20, 2024
1 parent e01d017 commit b060c7f
Show file tree
Hide file tree
Showing 28 changed files with 504 additions and 293 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
@@ -1,11 +1,11 @@
package tech.coner.trailer.toolkit.presentation.adapter

import tech.coner.trailer.toolkit.presentation.model.ItemModel
import tech.coner.trailer.toolkit.validation.Feedback

abstract class LoadableItemAdapter<ARGUMENT, ITEM, ARGUMENT_MODEL, ITEM_MODEL>
where ITEM_MODEL : ItemModel<ITEM> {
abstract class LoadableItemAdapter<ITEM, ITEM_MODEL, FEEDBACK : Feedback>
where ITEM_MODEL : ItemModel<ITEM, FEEDBACK> {

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)
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
package tech.coner.trailer.toolkit.presentation.model

sealed class LoadableModel<ITEM, ITEM_MODEL : ItemModel<ITEM>>
import tech.coner.trailer.toolkit.validation.Feedback

sealed class LoadableModel<ITEM, ITEM_MODEL : ItemModel<ITEM, FEEDBACK>, FEEDBACK : Feedback>
: Model {

/**
* Initial model corresponding to the presenter having initial state,
* prior to starting to load anything, or if it was fully reset.
*/
class Empty<ITEM, ITEM_MODEL : ItemModel<ITEM>> : LoadableModel<ITEM, ITEM_MODEL>() {
class Empty<ITEM, ITEM_MODEL : ItemModel<ITEM, FEEDBACK>, FEEDBACK : Feedback> : LoadableModel<ITEM, ITEM_MODEL, FEEDBACK>() {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
Expand All @@ -28,26 +30,26 @@ sealed class LoadableModel<ITEM, ITEM_MODEL : ItemModel<ITEM>>
* It may be helpful to implement the partial model with a different
* type than the loaded item.
*/
data class Loading<ITEM, ITEM_MODEL : ItemModel<ITEM>>(
data class Loading<ITEM, ITEM_MODEL : ItemModel<ITEM, FEEDBACK>, FEEDBACK : Feedback>(
val partial: ITEM_MODEL? = null
) : LoadableModel<ITEM, ITEM_MODEL>()
) : LoadableModel<ITEM, ITEM_MODEL, FEEDBACK>()

/**
* Model indicates the item has loaded.
*
* @property item the item resulting from the load operation.
*/
data class Loaded<ITEM, ITEM_MODEL : ItemModel<ITEM>>(
data class Loaded<ITEM, ITEM_MODEL : ItemModel<ITEM, FEEDBACK>, FEEDBACK : Feedback>(
val item: ITEM_MODEL
) : LoadableModel<ITEM, ITEM_MODEL>()
) : LoadableModel<ITEM, ITEM_MODEL, FEEDBACK>()

/**
* Model indicates the load operation failed.
*
* @property cause the cause of the failure, if known
*/
data class LoadFailed<ITEM, ITEM_MODEL : ItemModel<ITEM>>(
data class LoadFailed<ITEM, ITEM_MODEL : ItemModel<ITEM, FEEDBACK>, FEEDBACK : Feedback>(
val cause: Throwable
) : LoadableModel<ITEM, ITEM_MODEL>()
) : LoadableModel<ITEM, ITEM_MODEL, FEEDBACK>()

}

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

Loading

0 comments on commit b060c7f

Please sign in to comment.