Skip to content
This repository was archived by the owner on Mar 1, 2023. It is now read-only.

Observables and updatables

Max Cai edited this page Jan 26, 2016 · 6 revisions

As mentioned in the previous page, an observable represents an event source and an updatable observes the events. An updatable is registered onto an observable using Observable.addUpdatable(Updatable), and deregistered using Observable.removeUpdatable(Updatable). An event is dispatched to an updatable in the form of invoking Updatable.update().

Updatable registration and deregistration must be paired. It is illegal to add the same updatable to an observable more than once, or to remove an updatable from an observable when the former is not registered to, or already deregistered from, the latter.

An observable is active or activated, by being or becoming observed (having at least one registered updatable), and is inactive or deactivated, by being or becoming unobserved (having no registered updatables).

Event chain

An observable may observe other observables further “upstream” (in the sense of an event propagation path) and translate their events into its own events. A common example is a repository whose data depends on the data supplied by other repositories. For the correct wiring, such an intermediate observable normally keeps strong references to the upstream observables, but will typically only register its internal updatable to the upstream when it itself is active, and deregister the updatable when it is deactivated. This means that the strong references in the downstream direction only exist for registered updatables. It also means that the most downstream updatables (those not managed by these intermediate observables) ultimately control the activation and deactivation of all observables in an event chain.

TODO: image illustrating this hierarchy, with up-arrows for registration, “pulls data from”, and down-arrow for conditional references and event pushing/data flowing direction, like this:

UI lifecycle

The event chain feature is most useful for building reactive architecture with awareness of the UI lifecycle. Let a UI element be an Activity, a Fragment, or a View therein, and its active lifecycle be defined by any Android lifecycle event pair that is sensible for the specific case, such as from onStart to onStop, from onResume to onPause, from onAttachedToWindow to onDetachedFromWindow and so on. Let this UI element be or own an updatable that updates the UI using the data supplied by a repository. The repository in turn uses other event sources and data sources (not necessarily repositories) to calculate its data.

At the beginning of the UI element’s lifecycle, the aforementioned updatable is registered to, and therefore activates, the repository. This will connect the event chain and activate all relevant data processing flows, keeping the data and the UI up to date.

At the end of the UI element’s lifecycle, the updatable is deregistered from the same repository, which, assuming no other updatables are still keeping any observable active, will lead to a cascading teardown of the event chain. If the UI element is to never become active again (due to e.g. the activity being destroyed), because there are no downstream references when the system is inactive, the UI element is free to be garbage collected, making it easy to prevent activity leaks.

Threading

Agera advocates explicit threading and specifies the following threading contract between an observable and its registered updatables:

  • An updatable must be registered onto an observable from a thread with an active Looper (typically the main thread, or an IntentService worker thread). The observable will use the same Looper thread to dispatch the Updatable.update() calls.
  • An updatable can be deregistered from any thread, but to prevent a race condition where the event is dispatched to the updatable after it is deregistered due to the Looper’s internal handling, it is recommended to deregister the updatable from the same Looper thread from which the registration took place.
  • The coder is responsible for keeping a Looper active as long as there are registered updatables depending on it. Exceptions and memory leaks caused by dead Loopers are the coder’s liability.
Clone this wiki locally