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
=========================

Friday, May 10, 2019

UI Architecture Design Patterns

At Google I/O 19, the Android Jetpack team announced Compose, which basically brings a React style UI toolkit to Android. Awesome.

For those who haven't worked in Javascript land before, I highly recommend you take a look at React and read about its philosophy behind the handling of state and data.

tldr; By enforcing a strict 1-1 relationship between a given component's state and the actual visuals drawn, you can be sure that data is always synchronized -- what ever business logic manipulates is what the screen will draw.

However, as with all nice things, Compose is not ready yet for prime time. In fact, it's not even ready for developer time - as it currently is in a pre-alpha stage. Google does recommend though that you prepare your applications for a world where a one way composable view state becomes the norm - and we don't have to wait at all to start doing that!

-----------------------------

Recently I was inspired by a talk given by Netflix engineers at a Droidcon event back in 2018 where they outlined how Netflix approached UI building via independent components which they composed into a screen. I adapted their architecture into the following couple of basic classes.

UiView.kt

@CheckResult
@IdRes
fun id(): Int

fun inflate(savedInstanceState: Bundle?)

fun saveState(outState: Bundle)

fun teardown()



UiViewModel.kt

fun bind(onRender: (state: T, oldState: T?) -> Unit)

fun unbind()



UiComponent.kt

@CheckResult
@IdRes
fun id(): Int

fun bind(
  owner: LifecycleOwner,
  savedInstanceState: Bundle?
  callback: C
)

fun saveState(outState: Bundle)





The UiComponent class would take any number of related UiViews and UiViewModels and would result in self-contained components that could be dropped into Android Views, Activities, or Fragments. Not bad - but there were many shortcomings with my implementation.

The application state was a two way flow, which would prove to be difficult to manage. Because a UiComponent was meant to be self contained, it was both a View and a Controller. My attempt at separating logic out of Activities and Fragments did not actually fix anything - it just changed the name of the problem to UiComponent. As a result, the relationship between UiView and UiViewModel was only loosely defined - sometimes the view would listen to the model and change how it displayed content on screen, sometimes the model would listen to the view and fire off navigation events using some rather hacky logic.

There also was the issue of creating the UiComponent class for each of these UI widget groupings - it was an extra file that really did not do much on its own except abstract other classes needlessly. It had to be better.

I went back to the drawing board and reasoned about how I could simplify this basic 3 class architecture I had built.

My first win was getting rid of the UiComponent class all together. It was basically a useless extra - it served as a Controller for the UiViews passed to it - but I had already established that in my Android architecture the Activity or Fragment was the controller. There was no need to have a Controller wrap another Controller, so UiComponent was removed.

UiView was a little bit trickier. Because Android's current view system is a far cry from reactive composable state, I had to make do with the current UI toolkit - which means I was required to have an inflate(Bundle?) method as that was the step where Android would inflate layout resources and find views and whatnot. Similarly, I needed to keep a teardown() method to release resources, as well as a saveState(Bundle) method for process death persistence. The id() method helped with laying out my UI widgets inside of ConstraintLayout containers, so it too stayed. Looks like I wasn't able to simplify UiView any amount - but that doesn't mean I was not able to improve it.

The main problem with UiView is that there was no established contract for when a UiView updates. Taking a page from the React toolkit, I added a render(T, T?) method which I would adopt as the only place to make dynamic UI updates in a UiView class.

Next I tackled the UiViewModel. My main issue with this class was that it required an arbitrary lifecycle of bind() and unbind() in order to make it possible to use at all. After working at it for a while, I managed to reduce UiViewModel to just one function, render() which will treat its lifecycle as the lifecycle of the RxJava stream it associates state events with. Meaning, once the RxJava stream opens, the ViewModel is "bound" and once the stream is disposed, the ViewModel is "unbound".

Instead of manually passing a callback to and from ViewModels and UiViews, I used an RxJava bus - one for the UiView as a stream of interaction events, and one for the ViewModel as a stream of presentation events. This way, a View would only emit events to its ViewModel, and the ViewModel would only emit events to the Controller. A uni-directional data flow at last.

Finally, to wrap up things and make my new spicy code play nicely with Android, I built a small stateless function which takes any number of UiViews and a ViewModel which controls them all and performs some boiler plate to set everything up. This createComponent function will inflate all Views, open the ViewModel state stream and UiView event stream, and register to a LifecycleObserver, which will dispose the stream and teardown the views when the lifecycle is destroyed. Much nicer.

I'm not the best speaker or the best writer - and can't really convey these ideas easily over a post. You may just be better off looking at the source code itself to gain a better understanding of what I'm trying to do. The buggy code for the new and hopefully improved UiViewModel can be found here. Here is the new UiView code, and my simple component wrapper.

I'll be updating all apps to follow this new architecture and eagerly await the day that Jetpack Compose releases. This new architecture should lead to fewer bugs for all the existing pyamsoft Android applications, as well as speed up the development process of my new application that is still a work in progress... but that's for another day.

========================
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

See my code on GitHub  
=========================