docs/epoxy.md
Epoxy is an open source library by Airbnb that makes it easier to build complex layouts. It builds on top of RecyclerView, and allows you to work with multiple view types in a declarative, reactive way.
Epoxy uses immutable "models" to describe the interface between data and view. An EpoxyModel class exists for each unique view in your layout, and defines how the view is created, styled, and bound to data. Epoxy provides a powerful annotation processor to generate these models for you based on custom views or databinding layouts.
To create the layout of a page, EpoxyModels are declared in the order that views should appear, with the data that should be bound to them. This is called "building models". Since the models are immutable they must be rebuilt whenever the data changes to provide a new snapshot that describes the view.
This looks like this (adopted from the Mavericks sample app):
fun invalidate() {
recyclerView.requestModelBuild()
}
fun buildModels() = withState(viewModel) { state ->
header {
id("header")
title("Hello World")
}
state.items.forEach { item ->
itemCard {
id(item.id)
title(item.title)
clickListener { _ ->
navigateToItemDetails(item.id)
}
}
}
}
The model building pattern of Epoxy enforces a one way data flow. When a state change occurs, models are rebuilt asynchronously and Epoxy runs a background diff to figure out what changed. Changes are then dispatched on the main thread to the RecyclerView. This fits in perfectly with the reactive, state based approach of Mavericks.
A pattern we use at Airbnb, and that we share in the sample app, is that each fragment contains an EpoxyController that defines how models are built. Mavericks automatically manages this, and rebuilds the models whenever the state changes and the view is invalidated. This can be built into a base fragment, so that individual feature fragments have very little overhead in declaring a new page.
An abridged version looks like this: (The complete code can be found in the sample app)
abstract class BaseEpoxyFragment : Fragment(R.layout.base_epoxy_fragment) {
abstract val epoxyController: EpoxyController
override fun onViewCreated() {
recyclerView.setController(epoxyController)
}
override fun invalidate() {
recyclerView.requestModelBuild()
}
}
We can then create a new page very easily be extending our base fragment:
class HelloWorldEpoxyFragment : BaseFragment() {
private val viewModel: HelloWorldViewModel by fragmentViewModel()
override fun epoxyController() = simpleController(viewModel) { state ->
marquee {
id("marquee")
title(state.title)
}
}
}
While Epoxy gives us a pretty syntax for declaring UI, it has a lot of other things going on under the hood that improve its usefulness with Mavericks.
itemCard, are Kotlin extension functions generated by Epoxy. Epoxy generates almost all the boilerplate for you, so you can simply create an xml layout with databinding and use it immediately in a Mavericks fragment.