Firefox Lockwise for Android Architecture

RxKotlin

Firefox Lockwise for Android makes extensive use of RxKotlin, an implementation of the Observable pattern from ReactiveX. More information and many marble diagrams can be found in the ReactiveX documentation. The rest of this document relies on a basic understanding of the reader of the ReactiveX-style Observer implementation. Their intro document is a good starting point.

Flux

Architecture Pattern

In short, Flux architecture design maintains a unidirectional data flow, in which a global Dispatcher receives Actions & dispatches them to appropriate Stores. The Stores, in turn, process data & provide the source of truth for the Views. As users interact with the Views, any updates are made via a dispatched Action and the cycle begins again. See this flux architecture writeup for more details on the original Flux architecture scheme.

Lockwise implements a modified version of the described architecture (LockFlux), keeping in mind that the original implementation ignores asynchronous work. In this implementation, all asynchronous work is handled by the Stores as they reduce actions and state updates to their observable state.

Memory Management

The five major components of this architecture (View, Presenter, Store, Dispatcher, and Action) have distinct lifecycle management based on their functions.

View/Presenter pairs are allocated and de-allocated as views get displayed or hidden in turn.

Stores and the Dispatcher are global singleton objects, meaning that they get lazy-loaded by the application as their shared members get accessed by the Presenters for view configuration or dispatching.

Actions get deallocated as soon as they reach the end observer for their intended function.

View/Presenter

All views are bound to a presenter. In this separation, the presenter is responsible for all business logic, and the view is abstracted to a simple interface. The view is responsible for UI-specific configuration and passing user input to its presenter for handling. This allows any complex view-related configuration to be abstracted when dealing with business logic changes, and vice versa.

In the current implementation of LockFlux on Android, all Views are composed of Fragments, not Activities. Situations requiring an Activity to be added to the application will be reviewed on a case-to-case basis.

Actions

Actions are tiny enums or sealed classes that contain declarative language about either the triggering user action or the update request for a given Store. Actions can also be used to pass objects (item IDs, string resources, Telemetry events) between fragments.

Dispatcher

The dispatcher class is the simplest in the application; it provides an Action-accepting method as a wrapper for the PublishSubject<Action> that publishes all dispatched actions to interested Stores:

class Dispatcher {
    companion object {
        val shared = Dispatcher()
    }

    private val actionSubject = PublishSubject.create<Action>()

    val register: Observable<Action> = this.actionSubject

    fun dispatch(action: Action) {
        this.actionSubject.onNext(action)
    }
}

Store

Stores provide an opaque wrapper around system storage or simple Replay- /Publish- Subjects for the purposes of data access and view configuration. They selectively register with the Dispatcher for the Actions that they care about for updating state.

Stores also perform any asynchronous tasks that relate to the updating of their local state. The Actions dispatched by Presenters are completely decoupled from observed Store state, removing the need for callback configurations.

It's important to note that there is no concept of ordering; all Actions will be delivered to Stores in realtime as they are dispatched.

View Routing

The special case in this scenario is view routing. To handle the view-changing component of the architecture, there is a RouteStore observed by a RoutePresenter that rides along on the back of a RootActivity. This “containing” activity is not displayed to the user; rather, it performs the role of listening for navigation-specific Actions & performing the necessary top-level fragment swapping and back stack manipulation. Routing logic lives entirely separately from individual view configuration logic, allowing for modular view manipulation and easy testing.

Benefits of Flux

Close readers will note that this document borrows heavily from a similar one in our iOS application.

The shared Flux pattern and reactive libraries allow us to "borrow" view-presentation logic from iOS as we move forward on Android.

Additionally, the separation of high-level view manipulation from view allows us to iterate quickly on implementation details and design feedback without implications for unrelated parts of the app.