Tuesday, May 14, 2019

Who Owns State on Android

This is sort of a follow up to my last post.

The idea of state in an Android application is a confusing one. What is state? Why is it so important, and how do we attempt to get it "right"?

state in an Android application is the idea behind "who dictates what is currently being shown and being done on my device at this very moment". For example, for an app which opens a screen that changes colors between red, blue, and green, the "state" of that application could be which color is currently being displayed on screen.

Android has traditionally been very very bad at state management - I would argue it makes Android the most difficult platform to develop on simply because the job of state management is left up to the developer to manually implement in almost every single use case. Contrast this with something like web development, where HTML and scripts are cached by default unless you opt out, and strict frameworks like Angular's enforcing of DI principles or React's enforcing of one way MVI state help web developers keep their focus on the experience of using an application instead of worrying about how that tool works under the hood.

Don't get me wrong, I have many complaints with modern development and how it de-emphasizes knowing the importance of how the building blocks work - but I digress. Ultimately, at the end of the day we all want to build cool useful tools, not argue about the proper way to send a stream of bytes over the wire.

I personally believe one of the reasons why Android has traditionally been so poor at state management is because unlike these Web frameworks - or even iOS development which enforces a strict separation between Views and their ViewControllers, Android has historically never enforced any kind of recommendation or best practice - leading to many developers to learn with no idea of what even good practice may be. Android addressed the most general use case of state management, providing us with the simple onSaveInstanceState(Bundle?). Unfortunately, a general purpose tool that tries to be a little of something for everyone often ends up being nothing for anyone.

The reason why Scoop, and MvRx, Mosby, and Mobius need to exist is because of the failing of Android as a base platform to provide its developer community with a sane foundation to build on. The reason why I spent the last 2 months noodling on the idea of my own MVI framework (not saying it is any better than any of the other solutions - it was just more fun) was because these shortcomings of the platform had slowed me down for the last time. Even Google seems to acknowledge the mistake now that they are coming out with JetPack Compose which will one day be the future of Android UI.

UI is the one most difficult things in almost any application. Android applications are usually not very complex - they don't do much heavy lifting and generally just talk to servers. Their job is usually nothing more than to look pretty and feel cool when you click and tap and swipe around. Of course, not all applications are like this - pyamsoft applications are purely offline experiences using local databases or other methods of storage, so some heavy lifting is done in the applications, but nothing huge. For many cases though, the most rewarding payoff for time spent in development is to show a cool new page or animation to delight users and to provide a fun environment for working developers, or to build a cool new useful application that we use and enjoy day to day.

And Android is fun - its some of the most fun programming you'll ever have. It's so rewarding to finally get something to work and see it all come together before your very eyes. But some of that reward can be due - in part - to simply overcoming the large amount of useless ceremony that the platform puts in your way in order for you to get to your eureka moment.

Activity result callbacks, permission callbacks, fragment visibility callbacks, Activity window state changes, Window inset consumption, configuration change handling, service foreground state handling, notification channel management, receiver registration, sensor management, view related memory leaks - all of these are side effects of the platform that are left to the developer to deal with on a case by case basis. I love it - I love the freedom I get to build each application unique from the last, and I hate it when I just need to get things done.

And the crux of all these problems, the Achilles heel of UI development, is state and the ownership of state. On Android, who owns the state of a particular visual on screen?

Activities and Fragments are monstrous classes with many different potential roles. To save ourselves from going insane too quickly we always try to delegate much responsibility away from these classes as possible. There are only at max two jobs these classes should be required to do, handle system lifecycle events and handle navigation events. Everything else should be delegated away if possible. Do yourself a favor and save yourself in the future - your Android system classes like Activity, and Fragment - and to a slight degree Service - should be the ViewController, not the view. The only state these controllers would hold, is "which views are displaying on screen." This is complimented by things like the FragmentManager, which are only accessible from these classes, that handles its own internal state of "which fragment is displaying on screen." You should avoid View related state entirely if possible in your Activity and Fragment classes. UI state and the ownership of what is displayed on screen should be handled by the UiViewModel instead.

The View is a two part system - the UiViewModel and the UiView itself. The UiView should generally be a stateless class that knows how to color and shape and position itself to look pretty. The UiViewModel is a simple data class which holds a representation of the current look of the View in code. So for example, a UiViewModel like

data class MyUiViewModel(val isRed: Boolean, val displayName: String)


Would convey a model that controls the displayName rendered inside of it's UiView's TextView and whether or not the background color is red

By keeping the UiView stateless, any scenario can be rendered on demand. "Hello World" with a red background, "Hello Foo" without a red background, a blank name and a red background, and so on.

As we've seen in frameworks like React, UiViews are only updated via a single render(T) method which is given a snapshot of UiViewModel state as its only T parameter (some implementations also pass the previous state T as an additional parameter). This way, we can be guaranteed that any UI changes are all happening at the same time, and that the UI will always consistently display itself as a function of state.

By letting UiViewModels be the single source of truth and own the UiView state entirely, we can avoid Android lifecycle related bugs and achieve more consistent and predictable UIs. Of course, this can come at the cost of more typing - but the trade off of less cognitive load during debugging is its own reward.

Its not perfect - and it never will be. Modeling state in an asynchronous system like Android means bugs - and bugs are eternal. Problems still exist outside of the realm of state though, like what is the scope of each model of state? What, if anything, in Android owns the global "top down" view of application scope - and how can we synchronize the various bits of UI state between multiple views?

Perhaps another day.

For more - certainly better - reading on the architecture around Android and state, see this and this to understand why I am harping on state so much.

Stay tuned.

========================
Follow pyamsoft around the Web for updates and announcements about the newest applications!
Like what I do?

Send me an email at: pyam.soft@gmail.com
Or find me online at: https://pyamsoft.blogspot.com

Follow my Facebook Page
Check out my code on GitHub
=========================