Android Views as a Function of State with ViewBinding Case Study 1: The Live Gam...
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
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:
- The basketball game stream should be able to pan between both sides of the court to represent where the action is currently happening.
- The basketball game stream should be able to animate the basketball according to the play being reflected.
- In situations like free throws, the backdrop should transition between images of the basketball court and the basketball hoop.
- After each play, a banner (small or large) should offer a description of the play that just ended.
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.
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.
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
andState
types,FragmentHoopsLiveGamestreamBinding
can be reused anywhere theState
is available; subclassing aView
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
.
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 ofViews
into typed aggregates, and compose behaviors unto them without having to subclass theView
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:
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK