Compose (UI) beyond the UI (Part I): big changes

Jordi Saumell
ProAndroidDev
Published in
7 min readJan 7, 2021

--

Jetpack Compose is the new Android toolkit that will change how we build the UI of our mobile applications. Since it was built as a UI framework, there are a lot of posts and talks that focus on how it works internally, and on how to use it to build UIs.
However, in building an application there is much more than the UI, and Jetpack Compose can bring a lot of change there too. For that reason, in this series I will focus on several changes that might happen to how we build apps in the future, and for the better.
I am not trying to convince you to use it. Remember that ̶i̶t̶ ̶i̶s̶ ̶i̶n̶ ̶a̶l̶p̶h̶a̶ ̶a̶n̶d̶ ̶n̶o̶t̶ ̶c̶o̶n̶s̶i̶d̶e̶r̶e̶d̶ ̶r̶e̶a̶d̶y̶ ̶f̶o̶r̶ ̶p̶r̶o̶d̶u̶c̶t̶i̶o̶n̶ (UPD: since July 2021 it is stable!). However, I believe that it will bring several benefits and that it will be widely adopted for many other reasons besides the benefits it may bring. Therefore, I think it is useful to discuss how it may change the way we build apps, and find what best suits our needs.

Note: all mentions to Compose in this series refer to Compose UI, as there is more than just UI in Compose.

The future of Android development is bright!

What is wrong with Android development today?

There are several aspects of Android development that are complex. Some of these are a source of bugs and something that inexperienced developers (and even experienced ones) generally have trouble with.

Fragment and activity lifecycle

The lifecycle is one of the most complex aspects of developing applications for this platform: it has always been a source of bugs, and difficult to understand for developers.

And it is especially the case with fragments (as you can see ), which have two separate lifecycles -one for the view and one for the fragment itself- and a complex backstack. For example, if you observe data while the fragment is alive and try to update the view you may find that the view has been destroyed; or you may retain (memory leak) a destroyed view while a new one has been created.
With improved documentation, the addition of recommendations, and the introduction of tools like Lifecycle aware components, dealing with the lifecycle has become less problematic. But it is still a source of issues.

Configuration changes

Another way in which the Android lifecycle complicates development is configuration changes. Google advises us to let the system handle configuration changes, and almost all applications follow this advice (UPD: note that this advice was for the old View system, with compose it no longer applies). Letting the system handle configuration changes means that when, for example, the orientation of the device changes, the system recreates the activity (and fragments and views). This has many implications: ongoing tasks like data operations or audio recordings are stopped and the data that we have in memory is lost. Therefore, we need to take care of not losing this state and tasks, and the user has to wait.
There are several solutions to this problem: first, we can ignore the recommendation and handle configuration changes ourselves. As the activity and fragments are not recreated the orientation change will probably run faster, but we need to make sure we reload resources and UI appropriately. This is probably the way to go, at least, for apps that need more control and speed in orientation changes -e.g. media players and camera apps.
Another solution is to have classes that are retained -i.e. not destroyed. This used to be done with headless fragments, and now it is generally implemented by using the ViewModel. The ViewModel has nothing to do with MVVM, and it is simply a class that is retained when fragments and activities are recreated after a configuration change.
Using the ViewModel is not as straightforward as it may seem at first, though. First, the ViewModel is instantiated by the system, so we need factories if we want to have constructor arguments (and we almost always want that). And second, in the ViewModel we cannot easily have access to objects that are bound to the Fragment or activity and have a shorter lifecycle: resources, the locale, the fragment itself… To have access to these objects, we need complex logic to avoid crashes and memory leaks.

Theming and material components

Although this is more or less within UI, and less related to architecture, theming and material components are a source of desperation and wasted hours. Some components from the material library are well documented, and as long as you want to do something that is supported you are covered. But if you want to do something not supported, or you happen to need a widget that is not properly documented (and there are many in this group) you can spend a significant amount of hours simply looking for information. And you are also likely to end up spending several hours building a custom component, afterward.

What does Compose have to do with this?

Although Compose UI is a UI toolkit, how it works enables us to change other aspects of our architecture, besides the UI. These changes can help us find better solutions to the problems stated before, thus making Android development easier and more enjoyable.

Bye-bye Fragments, you will not be missed

First, with Compose we do not need to use Fragments. If we want to migrate an existing application it may be appropriate to have fragments wrapping composables until we complete the migration process; but for new projects, we can forget about fragments. Compose gives us access to Context and the Android framework, and composable functions may contain parts of the UI as big as the whole screen and be used with navigation frameworks. So, there is no reason for having fragments in a 100% Compose application: they would provide no value and they would require handling their lifecycles.

An end to destruction by configuration changes

Where it gets interesting, though, is if we consider handling configuration changes ourselves.

We will discuss how in the following post, but with Compose it is much easier to do than with the current view system. This means that we do not need to worry about the view outliving the controller (ViewModel, Presenter, etc), or about ongoing tasks being stopped (for tasks that are not bound to a screen we still need to worry, but that is another topic). It also means we can have more control over how views are adjusted and we can build faster user experiences because we avoid recreating heavyweight components like the Activity.

The ViewModel is not needed

Another consequence of handling the configuration changes ourselves is that the ViewModel should no longer be necessary, as its only purpose is to hold data and tasks that need to survive configuration changes. Using our own view controller that does not extend ViewModel would be beneficial because it could be more lightweight, not require a factory, and be more platform agnostic. However, as we will see in a future post, it is not straightforward.

Theming and material components for human beings

Finally, building custom widgets in Compose is much easier than in the current UI toolkit. Therefore, if our design is not supported by the material library we can easily build it.

But there is even more! We can look at the implementation of any material component and easily see how it works, what theming attributes it uses, and what parameters it accepts. So, theming and working with material components will be much easier, as we can see what is supported right from the code, and implement ourselves what is missing.

Other changes Compose can bring

Last nail on Databinding’s coffin

Following the current trend, architectures are more and more reactive each day, and one key step we need to do in reactive programming is to bind the data to the UI. We do not necessarily have a problem here, but that does not mean it cannot be improved.
With the current toolkit we can use the databinding plugin to reduce boilerplate in binding the data to the UI. However, it is not too popular because it slows down builds and when it fails it is quite hard to debug (although it has improved significantly).
In Compose we do not need to worry about boilerplate when binding data, as composables are functions of our data. So data and UI are automatically bound thanks to how Compose works.
Also, there is LiveData. It is a useful observable that is generally used to hold the data that the UI will observe, and it helps us easily manage the android lifecycle by observing only when appropriate. With Kotlin it does not work so well, though, as we need to handle nullable types even if we declare variables as non-nullable.
As the databinding plugin only works with LiveData, if you use it you cannot substitute LiveData with Flow (UPD: since Android Studio ArticFox we can use databinding with StateFlow). This will no longer be the case with Compose. With Flow, besides not having to deal with nullable types when we do not want to have a nullable, we also get many more operators, and the ability to do expensive work off of the main thread.

Welcome multiplatform

Finally, we cannot ignore multiplatform. The push for Kotlin Multiplatform is significant, which could be a game-changer in the industry. And Compose is built to support multiplatform: Jetbrains has already built Compose for Desktop! (UPD: and for web!)
The changes mentioned before help make our code less Android-specific. This will ease having more shared code when supporting multiple platforms.

Conclusion

I am not trying to sell you Jetpack Compose. It requires learning from scratch how to build UIs, it will have bugs and gotchas, i̶t̶ ̶i̶s̶ ̶i̶n̶ ̶A̶l̶p̶h̶a̶ ̶a̶n̶d̶ ̶i̶t̶ ̶i̶s̶ ̶n̶o̶t̶ ̶t̶o̶o̶ ̶p̶e̶r̶f̶o̶r̶m̶a̶n̶t̶ ̶a̶s̶ ̶o̶f̶ ̶n̶o̶w̶ (UPD Compose 1.0: it is stable now, and performance is much better). But I am hopeful because I believe it can significantly improve our experience developing apps, and the quality or UX of the apps we build.
As we have seen, in the near future we are likely to build apps without fragments, without Android ViewModels, with a one-to-one mapping between the lifecycle of the view and its controller, and several other changes that can make the development easier and more enjoyable.
So, in conclusion, a lot can change soon and there will be a lot to learn and experiment with.
In the next episodes, we will see ways to implement some of the changes mentioned in this article. You can follow me to get notified.

Here you can find the next one: Compose (UI) beyond the UI (Part II): applying changes

--

--

Self-learner Android passionate. Android tech-lead in progress at Basetis