Inspiration
The architecture was inspired by the following design patterns and UI frameworks (among others):
Design patterns
- MVI (Model-View-Intent)
- MVVM (Model-View-ViewModel)
- SAM
- Flutter BloC
Frameworks
Learnings
The essential ideas we take from them are:
- data flowing between model and view is immutable
- data flows unidirectional from the view to the model and back
- data flows are reactive
- every concern is mapped to a component or a function to have clear separation
- functions are first-class citizens
Goals
The architectural goals of the Kotlin Bloc
framework are:
- Be platform-agnostic: it’s a KMM framework so this is obvious.
- Be reactive: reactive UIs have become the standard in recent years for good reasons.
- Be composable: ability to decompose the ui into small components and combine them into larger components again.
- Be as un-opinionated as possible: support different technologies, programming styles, app complexities and team sizes.
- Be minimalistic and lightweight: some existing frameworks are very comprehensive but are heavyweight and require to write lots of boilerplate code (e.g. Decompose). Strict contracts between components (leading to boilerplate code) are crucial in larger teams but they can bog down the team’s productivity when speed is crucial (and in smaller teams). A more strict approach should be supported but not enforced.
- Be predictable: the order of execution (synchronous and asynchronous) and the concurrency model must be clearly specified and lead to predictable and repeatable outcome.
Design
The framework has two main components:
- The Bloc (Business Logic Component) encapsulates your application's business logic. It receives Action(s) from the view, processes those actions and outputs Proposals and optionally SideEffect(s).
- The BlocState holds the component's State. It's separate from the actual Bloc to support different scenarios like:
- share state between business logic components
- persist state (database, network)
- use a global state container like Redux
- others...
The View is obviously an important component too but technically not part of the framework itself (although there are extensions that support/simplify the implementation for different target platforms).
tip
Reducers in Kotlin Bloc
return a Proposal
instead of State
. While in many cases they have the same type, thanks to Proposals
:
- a
BlocState]
has the "power" to enforce domain specific rules like address validation or enrichment - it's easy to connect a Redux store to a
Bloc
(as itsBlocState
), in which caseProposals
become Redux actions (see Redux) - use
Blocs
asBlocsState
(see Bloc isA BlocState)
The idea of Proposals
was inspired by the SAM pattern.
Not surprisingly the Flutter Bloc nomenclature is used for some of the other components / sub-components of this architecture as well:
- A Sink is a destination for arbitrary data and used to send data from one to the next component. The processing of the data is either synchronous or asynchronous depending on the receiver.
- A Stream is a source of asynchronous data. Stream are always "hot" meaning data is emitted regardless whether a component is listening (or subscribed or collecting -> different names for the same thing). The streams for
State
andSideEffect
s have important differences that will be explained in detail in Bloc and in Bloc State.