DEV Community

loading...
Cover image for Fun with Compose: Bad UI in a Great Framework
Touchlab

Fun with Compose: Bad UI in a Great Framework

Michael Friend
・3 min read

Intro

To celebrate Jetpack Compose finally hitting 1.0, I wanted to take a bit of a deep dive to see what the framework has to offer. Instead of more of the same code labs with useful UI, I decided to bring back an old reddit trend of creating the absolute worst volume control UI possible (highlights here). You can take a look at the code on my Github.

The Results

Microtransactions Radio Buttons Catapult (WIP)
Digits Of PI Seven Segment

Takeaways

As someone who’s never really been a fan of UI work, building UI in Compose has been extremely pleasant and straightforward. Things like custom animations that I haven't done in the View system were surprisingly simple and concise. With Compose I no longer need to context switch between writing Kotlin code and XML layouts, the flow between writing backend code and UI code is much more seamless. The best part was the often lauded LazyColumn in compose that turns 3 files worth of boilerplate RecyclerView code into a couple lines. While it took some effort to change my approach for the Compose mental model, this is a huge leap in the right direction towards making UI work quicker for experienced devs and easier for new devs to learn.

Lessons Learned

A vital concept when learning Compose is how the runtime manages state and recomposition via (Mutable)State objects. Most of the "magic" of recomposition relies on the getValue and setValue methods of these classes. and if you mistakenly go around these methods to read and update your apps state, you can actually prevent the UI from updating as you intended.

I learned this the hard way while implementing the editable seven segment display by using a MutableState<MutableList> and updating the underlying MutableList values on user interaction (i.e. state.value[index] = newValue). Desugaring that statement to state.getValue()[index] = newValue we see that we're never calling MutableState.setValue, so the Compose runtime has no indication of the underlying value change, never triggering a recomposition. On top of that, even if something else triggered a recomposition, the UI wouldn't be updated since we mutated the list without changing the reference, so the underlying equality check used to determine if our composable should run again never detects a change.

Luckily,Compose gives us a mutableStateListOf(...) and List.toMutableStateList() that return a SnapshotStateList, which is a subclass of MutableList (and equivalents for Maps). That means we can just treat it as a MutableList and the state updates will be handled for us when we do things like snapshotStateList[index] = newValue. The main rule of thumb to learn from this is that in Compose you should avoid mutating objects you intend to use as a state and instead emit a brand new value to your LiveData/Flow/State using the copy method on data classes. You can force this practice on yourself by avoiding vars entirely in classes used for UI state.

For a demonstration of this issue, here’s a snippet showing my initial MutableList approach, a functional workaround I made using a list of mutable states, and the proper solution using mutableStateListOf().

Conclusion

Now that Compose is officially at 1.0, you should give it a shot ASAP. Wherever you update the state of your UI, keep an eye out for spots that mutate the value in the State instead of the State itself. Above all, don't be afraid to get creative and have fun when playing with compose, you might be surprised by the things you're able to make easily compared to the old View system.

Discussion (0)