BlocStateBuilder
A BlocState
can be defined using a BlocStateBuilder
blocState<CountState, CountState> {
initialState = CountState(1)
accept { proposal, state ->
// make a decision whether to accept or reject the proposal
// return Null to reject the proposal
// return the proposal to accept it (and update the state)
proposal
}
}
initialState
is obviously the initial state of theBlocState
(and this of theBloc
)accept
is the function that accepts/rejects a proposal and updates the state if it's accepted
initialState
and accept
are both mandatory parameters(unfortunately there's no compile time check for this).
Since State
and Proposal
are identical in above example, it can be simplified to (no more accept
function either):
blocState(CountState(1))
These are relatively simple / default BlocState
implementations.
To create your own BlocState
, extend the BlocStateBase
class which implements the Sink
and the StateStream
(which is good enough to implement the BlocState
interface).
Typically you'd want to change:
- how
Proposals
are accepted/rejected - how/where
State
is stored - how/from where
State
is retrieved
An example of the first case is the DefaultBlocState which is used by the BlocBuilder
itself:
internal open class DefaultBlocState<State : Any, Proposal : Any>(
initialState: State,
private val accept: Acceptor<Proposal, State>,
) : BlocStateBase<State, Proposal>(initialState) {
override fun send(proposal: Proposal) {
accept(proposal, value)?.also { state.send(it) }
}
}
All DefaultBlocState
does is adding the accept/reject functionality.
An example of the second and third cases are the PersistingToDoState and the ReduxBlocState.
The former stores state in a local database and retrieves if from the same db:
// inject the database
private val dao = getKoinInstance<ToDoDao>()
// retrieve the db content and send it to the StateStream
init {
coroutineScope.launch(Dispatchers.Default) {
dao.getFlow().collect { state.send(it) }
}
}
// send state to the database
override fun send(proposal: List<ToDo>) {
proposal.forEach { newTodo ->
val oldTodo = value.firstOrNull { it.uuid == newTodo.uuid }
if (newTodo != oldTodo) {
dao.upsert(newTodo.uuid, newTodo.description, newTodo.completed)
}
}
}
The latter stores state in a Redux store:
// subscribe to sub state from the Redux store and send it to the StateStream
init {
// selectScoped will unsubscribe from the store automatically when the Bloc is destroyed
// select is a memoized selector to subscribe to the store's sub state
store.selectScoped(disposableScope = this, select = select) { model ->
state.send(map(model))
}
}
// send state to the Redux store
override fun send(proposal: Proposal) {
store.dispatch(proposal)
}