-
Notifications
You must be signed in to change notification settings - Fork 500
MvRx at Airbnb
Non-Airbnb engineers: this page serves as a reference for what MvRx looks like us. Feel free to pick and choose parts that are applicable for you.
Airbnb employees: this page contains documentation for the Airbnb-specific APIs added on top of MvRx.
You don't need to create your own Activity
with MvRx. When you register your Fragment in MvRxFragments
(detailed below), the factory it returns includes a newInstance
, newIntent
, and startActivity
function.
Add your Fragment to MvRxFragments
like this:
object YourFeature : Fragments("com.airbnb.android.yourfeature") {
fun yourFragment() = create(".YourFragment")
fun yourFragmentWithArgs() = create<YourArgs>(".YourArgsFragment")
}
The create function returns a factory with a newInstance()
, newIntent
, and startActivity
helpers. If create
has a generic type, each of the helper functions will take type-safe args.
MvRx will concatenate the prefix passed to Fragments
with the suffix passed to create()
and will create it with reflection. This enables you launch features from any module without having to add a hard dependency on it.
MvRxFragment includes a default layout with:
- An
AirRecyclerView
hooked up to a defaultEpoxyController
. - An
AirToolbar
. - A footer
- A CoordinatorLayout configured for PopTarts/Snackbars.
These can be accessed in a child fragment with the
recyclerView
,toolbar
andfooterContainer
properties.
Override buildModels()
in your Fragment. When you use the MvRx ViewModel delegates (like fragmentViewModel
), it will automatically subscribe to any changes and request model build any time it changes.
override buildFooter()
and return a single epoxy model like this:
override fun EpoxyController.buildFooter() = fixedActionFooter {
val (state) = withState(viewModel1)
buttonText(state.title)
}
With MvRx, you never have to manually set your arguments bundle or create arguments keys. Instead, you should create a single @Parcelize
Kotlin data class to hold the args for your Fragment.
Then:
- Use the generic
create<A>(...)
function inMvRxFragments
. When you use the generic for, thenewInstance
,newIntent
, andstartActivity
functions will require you to pass in your args in a type-safe manner. - Give any state class used by a ViewModel in your fragment a secondary constructor that takes your args class. It will automatically be used to create the initial state for your ViewModel.
- If possible, your args should only be used for creating the initial state (see #2). However, if you do need to access them in your Fragment, you may do so with the
arg
delegate:
private val args by arg<YourArgs>()
If you want to use a custom layout, override the layout()
function and return your own. If you use R.id.toolbar
, R.id.recycler_view
, and R.id.footer_container
, MvRxFragment will wire them up as if you used the default layout so it can save you that overhead.
The properties mentioned above will also be wired up. There are optional versions of the properties prefixed with optional
.
Override toolbarMenuRes
with your menu resource. Then you just have to override onOptionsItemSelected
.
Override lightForegroundToolbar
and return true.
Call showFragment
to have a fragment slide in from the right or showModal
to have it slide up from the bottom. If you use showModal
You shouldn't need to use any actual Fragment lifecycles. However, if you need to do any initialization, override initView
. At that point, the view will have been created and the Fragment
will be attached to an Activity
.
To have a retry-able and swipe-able PopTart
appear for the failure of a network request, add this to initView
:
registerFailurePoptart(viewModel1, AnnotatedDemoState::listing, viewModel1::fetchListing())
MvRx includes a simple way to track TTI on a page. Override isInteractive
and return the appropriate TTIState
for the current state. A PerformanceLogger
will automatically start in onCreate
and will log appropriately the first time isInteractive
returns INTERACTIVE
, FAILED
, or CANCELLED
.
To make logging easier, there is a helper function that takes a vararg Async
state properties and returns the appropriate TTI state.
Use it like this:
override fun isInteractive(): TTIState {
val (state) = withState(viewModel)
return mergeAsyncProperties(state.asyncProp1, state.asyncProp2)
}
Handling the back button normally requires setting a listener on AirActivity
. As a convenience, MvRx does this automatically. You should think twice before using this but it is there if you need it.
Swipe down from the middle of the toolbar and it will toggle the MvRx debug view. It will flash and increment every time the view is invalidated as well as show the TTI time if it was overridden.
The Airbnb specific BaseMvRxViewModel
:
- Adds
execute
extension functions forAirRequest
. - Uses a default
SingleFireRequestExecutor
from theBaseGraph
with which all requests can be executed.
If you annotate create()
functions in MvRxFragments
with @Launchable
, then it will be accessible via the MvRxLauncher which can be accessed by long pressing the app icon and launching its shortcut.
If your Fragment has arguments, annotate functions in its companion object
with @MockArgs
and it will use those in the launcher instead. You can create multiple @MockArgs
and give them names. Their function name will show up in the launcher.