Domic — Reactive Virtual DOM for Android
Domic is an abstraction for Android UI layer that mirrors real Android DOM, but reactively.
It allows you:
- Unify interactions with Android UI layer across codebase
- Unit test UI-related code with in-memory implementation of Virtual DOM
- Efficiently render complex state objects (MVI/Redux)
- Enforce async-only interaction with UI layer across codebase
- Reuse existing Android DOM: Views, Widgets, Layouts
Domic — DOMe like.
Table of contents
- Motivation
- State of the Project
- Examples
- Key Take-Aways
- Implementation Details
- Integrating with Existing Projects
- Alternatives
- Terminology
- Credits
Motivation
Scaling Android app codebase and development is complicated. Part of that complication comes from constant need in interaction between app's business logic and UI.
In recent years, lots of progress has been made by community in adopting, inventing and redesigning patterns that other platforms use to solve similar problems: MVP, MVVM, MVI, Redux, etc.
With help of reactive libraries like RxJava combining streams of data and expressing complicated logic became easier.
It naturally shifted application code into a form of reactive cycle where app state is combined with user input and new state is produced for rendering. It was always a cycle, but it's more explicit now.
However we've found that existing approaches have problems with scaling for such reactive rendering cycles and we think there is something that can enhance them to fix those problems.
We think that Domic, a Reactive Virtual DOM, can be that enhancement layer.
It is important to say that there are existing projects that overlap with Domic's functionality but with different trade-offs. Please refer to Alternatives section for details.
State of the Project
At the moment Domic is an experimental project.
It means that Lyft is not using it in production yet. We do however think that this is a perspective direction that will help to shape, scale and move Android and client-side development further in general.
The project doesn't have a public release yet because we want to gather some feedback from community to make sure we didn't make major design errors as scope and internal complexity of the project are quite high.
We're planning to start shipping 0.1.0
version soon though, please stay tuned!
Right now we expect community members interested in this project to clone it and play with its source code and samples
, submit issues and pull requests to shape the project!
Examples
Bind Real Document Object
Binding Signature
val nameOfDocumentObject: VirtualType = BindingType()
Practical Example
val search: EditText = AndroidEditText(v.findViewById(R.id.search))
Observe State
Property Signature
search.observe.textChanges: Observable
Practical Example
search
.observe
.textChanges
.debounce(300, MILLISECONDS, timeScheduler)
.switchMap { searchService.search(it) }
Change State
Function Signature
search.change.enabled(Observable): Disposable
Practical Example
searchState
.map {
when (it) {
is InProgress -> false
is Finished -> true
}
}
.startWith(true)
.subscribe(search.change::enabled)
// `(Observable) -> Disposable` is an extension function Domic provides.
Testing
Binding Signature
val nameOfDocumentObject: VirtualType = BindingType()
Practical Example
val search: EditText = TestEditText()
Simulating State Change
search.simulate.text("search term")
Asserting State
assertThat(search.check.enabled).isEqualTo(false)
Key Take-Aways
Reusing Existing Android DOM
Domic does not require you to rewrite layouts or adopt a new framework to build UI.
Domic binds to existing Android Framework DOM (UI components: Views, Widgets) so you can continue to use all the tooling and libraries you're used to: Views, Widgets, Layouts, Support Library, Layout Preview, IDE, build system.
Threading
Domic takes care of threading.
You can observe state on non-main thread(s), you can change state from non-main thread(s).
This allows to minimize workload on main thread thus giving it more time for rendering and handling user input.
Tip:
You can run
Presenter
/ViewModel
/Model
/Reductor
on non-main thread(s) and only use main thread for minimal amount of required interactions with real Android DOM.
Diffing
Domic takes care of computing diff between previous state and new one thus only rendering what is different.
Motivation: Android Framework already diffs some state changes, typically, simple ones, like TextView.setEnabled()
. However there are relatively expensive state changes like TextView.setText()
that are not diffed by Android Framework and can require re-rendering, computations, notifying listener(s) and so on.
Tip:
You can drop complex state objects on Domic (like Redux or MVI are designed to work) and let it figure what needs to be rendered.
Observing Shared State
Domic makes sure you can observe state of same property with multiple Observer
s.
Typical example would be observing clicks on Android View
: Android View
can only have one click listener at a time, subsequent Observer
effectively detaches previous one from the View
.
Domic however share()
s the Observable
allowing multiple Observer
s observe same state.
Testing
Domic is an abstraction.
Bindings to real Android DOM is just one implementation of that abstraction.
Domic has separate in-memory implementation of the Virtual DOM that let's you unit test your Presenter
/ViewModel
/Model
/Reductor
.
Domic provides synchronous testing API to ease simulating and checking state changes.
Tip:
You can take Domic further and rendered whole app in memory thus be able to run functional and integration tests in memory on JVM*!*
Type Safety
Domic is type safe.
Observing state and changing state encapsulated into separate types withing each Document Object. ie Button.Observe
and Button.Change
.
This allows one to have read-only or write-only reference to a Document Object, thus pushing type safety even further.
Tip:
You can take Domic further and maybe even create a new MV-design with clear separation of code that
Observe
s andChange
s UI?
Implementation Details
TODO
Integrating with Existing Projects
Integrating with Redux
See samples/redux
Alternatives
Terminology
DOM
DOM stands for Document Object Model. Term itself comes from early days of web development (1998), however it's abstract enough and pretty much reflects how Android UI system works: Layouts, Views, Widgets, XML-based markdown language, memory model.
Domic however as of now doesn't track child-parent relationship between Document Objects, instead Domic binds to real DOM objects thus leaving layouting to the real DOM.
Document Object
Since Domic is a Reactive Virtual DOM for Android, we use Document Object as a name for Widget/View/Component type or instance.
It is however valid to call them Widget, View or Component, but Document Object is preferred, specifically for reasons of possible multiplatform support and for sake of easier discussions with developers working on other platforms.
Diffing
Diffing is a process of computing difference between previous known state and new state. Every property that Domic allows to change for a given Document Object is going through comparison with its previous state, thus eliminating updates of real DOM if they're not required.
Rendering
Rendering is a process of reflecting in-memory state of Virtual DOM on the real DOM.
Credits
As many other things in the tech world: programming languages, libraries, cluster management systems, build tools, etc, Domic is not an invention, but rather a compilation of ideas and experience.
Domic was influenced by:
Special credit