client: Add lengthier explanation of Models

This commit is contained in:
Andras Slemmer 2016-09-06 11:36:04 +01:00
parent 6b6c51b8d4
commit 2869889769

View File

@ -19,12 +19,51 @@ import kotlin.reflect.KProperty
* This allows decoupling of UI logic from stream initialisation and provides us with a central place to inspect data * This allows decoupling of UI logic from stream initialisation and provides us with a central place to inspect data
* flows. It also allows detecting of looping logic by constructing a stream dependency graph TODO do this. * flows. It also allows detecting of looping logic by constructing a stream dependency graph TODO do this.
* *
* General rule of thumb: A stream/observable should be a model if it may be reused several times.
*
* Usage: * Usage:
* // Inject service -> client event stream * // Inject service -> client event stream
* private val serviceToClient: EventStream<ServiceToClientEvent> by eventStream(WalletMonitorModel::serviceToClient) * private val serviceToClient: EventStream<ServiceToClientEvent> by eventStream(WalletMonitorModel::serviceToClient)
* *
* Each Screen code should have a code layout like this:
*
* class Screen {
* val root = (..)
*
* [ inject UI elements using fxid()/inject() ]
*
* [ inject observable dependencies using observable()/eventSink() etc]
*
* [ define screen-specific observables ]
*
* init {
* [ wire up UI elements ]
* }
* }
*
* For example if I wanted to display a list of all USD cash states:
* class USDCashStatesScreen {
* val root: Pane by fxml()
*
* val usdCashStatesListView: ListView<Cash.State> by fxid("USDCashStatesListView")
*
* val cashStates: ObservableList<Cash.State> by observableList(ContractStateModel::cashStates)
*
* val usdCashStates = cashStates.filter { it.(..).currency == USD }
*
* init {
* Bindings.bindContent(usdCashStatesListView.items, usdCashStates)
* usdCashStatesListView.setCellValueFactory(somethingsomething)
* }
* }
*
* The UI code can just assume that the cash state list comes from somewhere outside. The initialisation of that
* observable is decoupled, it may be mocked or be streamed from the network etc.
*
* Later on we may even want to move all screen-specific observables to a separate Model as well (like usdCashStates) - this
* would allow moving all of the aggregation logic to e.g. a different machine, all the UI will do is inject these and wire
* them up with the UI elements.
*
* Another advantage of this separation is that once we start adding a lot of screens we can still track data dependencies
* in a central place as opposed to ad-hoc wiring up the observables.
*/ */
inline fun <reified M : Any, T> observable(noinline observableProperty: (M) -> Observable<T>) = inline fun <reified M : Any, T> observable(noinline observableProperty: (M) -> Observable<T>) =
@ -56,6 +95,10 @@ inline fun <reified M : Any, T> observableListReadOnly(noinline observableListPr
object Models { object Models {
private val modelStore = HashMap<KClass<*>, Any>() private val modelStore = HashMap<KClass<*>, Any>()
/**
* Holds a class->dependencies map that tracks what screens are depending on what model.
*/
private val dependencyGraph = HashMap<KClass<*>, MutableSet<KClass<*>>>() private val dependencyGraph = HashMap<KClass<*>, MutableSet<KClass<*>>>()
fun <M : Any> initModel(klass: KClass<M>) = modelStore.getOrPut(klass) { klass.java.newInstance() } fun <M : Any> initModel(klass: KClass<M>) = modelStore.getOrPut(klass) { klass.java.newInstance() }