Basic Concepts
The two basic concepts that Iodine makes use of is that of a Component, and that of a View Model. Roughly speaking, a Component is reusable bundle of behavior and a view (i.e. a @Composable fun
) of that behavior's internal state. Oftentimes, it may be convinient to keep the "pure" UI behavior logic decoupled from the actual way in which such logic is rendered -- for this purpose, we have the concept of a ViewModel
. A ViewModel
is pretty much what it sounds like -- a model of a view, or in our case a Component
.
View models and Components
To start, let's dig into how Iodine defines a ViewModel
:
interface Settable<in A> {
fun setValue(newValue: A) { }
}
interface ViewModel<out I, out E, S, in A>: Settable<A> {
val impl: I
val events: Flow<E>
val state: StateFlow<S>
}
Theoretically, Iodine could have just gotten away with using I
here -- as most view models as used in UI design patterns such as Model-View-ViewModel are just plain old interfaces. However, for convinience, we decided to break up the idea of a view model into three distinct components:
I
: The "core" interface of the ViewModel -- this should be an interface that describes methods that can be used to interact with the view model.E
: The type of events that this view model can emit asynchronously at any time. Again, this could be construed as just another method ofI
-- but as this is such a common feature of view models, we reify the typeE
as a seperate part of the API.S
: The type of internal state used by the view model. Usually, in a traditional view model, this would be aprivate
part of the implementation of the API. However, for an IodineViewModel
, we expose this inner state as a reactiveStateFlow<S>
so this view model can be bound to a rendering function to produce a fullComponent
.
Component
s are just like view models, but together with a function for rendering the state of the ViewModel
. Once a ViewModel
has been bound to a mechanism for rendering it's state, there is no longer a reason to care about the state type S
. Thus, whereas a ComponentImpl
is defined as follows:
interface ComponentImpl<out Ctx, out I, out E, S, in A>: ViewModel<I,E,S,A> {
@Composable fun contents(state: S)
}
most of the time we want to use a Component
, we want to keep the internal state S
encapsulated. This can be accomplished with a star projection, which is how Iodine defines an honest-to-goodness Component
:
typealias Component<Ctx,I,E,A> = ComponentImpl<Ctx,I,E,*,A>