3

Android Views as a Function of State with ViewBinding Case Study 1: The Live Gam...

 3 years ago
source link: https://proandroiddev.com/android-views-as-a-function-of-state-with-viewbinding-case-study-1-the-live-game-stream-c8367ac13ace
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Android Views as a Function of State with ViewBinding Case Study 1: The Live Game Stream

Image for post
Image for post
Bugdroid ❤s single line tags

This post is part of a series that shows how Views in android can be represented purely as a function of some state with no side effects helping to build a robust, easy to scale and maintain app with the help of ViewBinding.

This post is also a follow up to Building the right View Abstraction. In that post, I stated it wasn’t XML that was holding the Android View System back, but rather state dichotomy between views and the business layer. I went on further to say that having stateless views would go a long way to improving the View writing process whether programmatically a la Jetpack Compose, or from static declarations via XML.

This post will expand on the latter part; while addressing the sentiment that static view declarations cannot easily represent dynamic content. To do this, I shall be using the basketball game stream feature we just launched at GameChanger as a case study. First let’s go over the high level feature requirements:

  1. The basketball game stream should be able to pan between both sides of the court to represent where the action is currently happening.
  2. The basketball game stream should be able to animate the basketball according to the play being reflected.
  3. In situations like free throws, the backdrop should transition between images of the basketball court and the basketball hoop.
  4. After each play, a banner (small or large) should offer a description of the play that just ended.
Image for post
Image for post
Image for post
Image for post
The basketball game stream experience

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:

  1. There are no reentrant side effects that attempt to mutate the View after its declaration and binding.
  2. The View is declared statically, separating the declaration of the appearance of the View from the logic of driving the View behavior.
  3. 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.
  4. 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:

  1. Alpha
  2. TranslationX
  3. 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:

  1. Immutability / Statelessness is extremely powerful while being incredibly simple.
  2. 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.
  3. 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:

The full implementation of the ViewModel that drives the experience and its animation mapping follows below. It’s fairly heavy on rx, but not unfamiliar:


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK