inline fun <reified M : Any, T> observable(noinline observableProperty: (M) -> <ERROR CLASS><T>): <ERROR CLASS>
This file defines a global Models store and delegates to inject event streams/sinks. Note that all streams here are global.
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.
Usage: // Inject service -> client event stream private val serviceToClient: EventStream 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/eventSinketc
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.