Android Views as a Function of State with ViewBinding Case Study 1: The Live Game Stream
Tags: Functional Programming, Views, Android, Jetpack, Animation

The requirements above specify highly dynamic events that are best represented with some sort of state, especially for being able to represent smooth animations for things like panning the court, and animating the basketball. The final state declaration looks like:

The state machine that drives the production of this state is two way bound as some state parameters are dependent on the dimensions of the phone screen, and the dimension input in turns drives the production of the bitmaps used as the background of the experience.

Next, the static definition of the components that make up this experience:

The above by itself is nothing special, it is just a static XML file. The true power comes when it is combined with ViewBinding to create terse, readable two way bindings.

Feeding the display size to the state machine

Next up is binding the output of state to the View . Since the intent is to describe the purely as a function of state, the following are pure functional expressions and use method references where possible to communicate the surrounding context of the the class body isn’t leaking into the binding context. The ViewModel exposes state through the state val which is a LiveData<State> , and mapDistinct is syntactic sugar around LiveData Transformations.map() and Transformations.distinctUntilChanged() . Unfortunately there’s some tautology in the syntax of observing difference slices of the state to the methods that consume them, but a DSL can always be written to make it more terse.

Mapping distinct slices of state to the functions that drive them: v= f(s)

This is practically the entirety of the Fragment that powers the basketball game stream. It is also worth taking a closer look at the implementation of line 28:

Both the large and small banner are themselves ViewBinding types of ViewholderHoopsLargeGamestreamCardBinding and ViewholderHoopsSmallGamestreamCardBinding respectively. Their state is the Banner data class, which is a slice of the larger State class; just like Jetpack Compose would have you aggregate smaller Composables to “compose” them into larger ones, all with strongly typed, statically defined layouts.

Importantly, the following all hold true:

There are no reentrant side effects that attempt to mutate the View after its declaration and binding. The View is declared statically, separating the declaration of the appearance of the View from the logic of driving the View behavior. There is a 1:1 relationship between the ViewBinding and State types, FragmentHoopsLiveGamestreamBinding can be reused anywhere the State is available; subclassing a View to create a custom view for business logic is not necessary. The View is extremely simple; all the complexity is purely in the business layer as the state machine is responsible for driving each animation frame.

The last piece is how the ViewModel is able to drive these animations. I have waxed lyrical and extolled at length the virtues of the Android Dynamic Animation Library, key of which is its ability to be retarget ongoing animations. The above experience is powered by SpringAnimation instances, and specifying the final value the following properties should arrive at:

Alpha TranslationX TranslationY

The emissions of the spring can then be piped into a reactive stream, and finally converted to LiveData for consumption by the View .

Production of State in the ViewModel

With the above each play or action in the game stream creates a Mutation , which in turn modifies a slice of the State . Notice the statelessness of the Mutations ; each just overrides a value in the state, and each Mutation creates a brand new SpringAnimation instance. I just need the initial value to animate from, which is read from the state, and specify the final value.

This statelessness also carries on to the View layer, with each UI element is bound once, and only once to the slice of State it cares about. The above illustrates:

Immutability / Statelessness is extremely powerful while being incredibly simple. Complex UI absolutely can be represented with static View declarations. A follow up case study will cover the case where nodes in the UI tree need to be removed as part of mutations in state, and how static View declarations are not an obstacle to this. Using ViewBinding as a bridge, it becomes easy to aggregate a collection of Views into typed aggregates, and compose behaviors unto them without having to subclass the View class.

Feel free to try out the game stream yourself! It’s free to create and score a game on your device, you will need another device to see the animated game stream in real time however: