Following the way of migration to Jetpack Compose

Recently I completed the migration of my pet project to Jetpack Compose. I’d like to tell you how this process went. And also I want to mention some particular interesting cases I faced. So let’s start from the beginning.
What is the Jetpack Compose?
Compose is an alternative way of producing the user interface in Android. Its main idea is to describe how a particular app state should be presented to the user. If the app state is changed, the UI is generated again. This is why Compose is called a declarative framework. In contrast the View system is rather focussed on mutating the UI to match the updated app state. By not mutating the UI Compose skips the whole class of potential bugs related to View’s state management.
Compose isn’t the only declarative framework for Android. Litho and Flutter, for example, have very similar concepts.
By the way, Android isn’t the only supported platform. Although the support is quite initial, you can use it in Web and Desktop apps. I also expect the official iOS support in the future.
I encourage you to visit the official documentation where the core concepts of Compose are explained in more detailed fashion.
In what state Compose currently is?
At the time of writing of this article the Compose is in beta state (beta08). Beta state means the list of features is decent enough and it can be used in production, but may contain bugs. Google promises to polish it and deliver the release state in July 2021. Currently Google is focussed on making it stable rather than on extending of the features list, which is a very robust approach. This also means that sometimes you may not find some subtle functionality you got used to with Views.
However, not everything is in beta actually. Some integrations with other Jetpack libraries are still in alpha state, meaning a lot can change towads their release.
Sometimes new beta releases introduce breaking changes, so it is crucial to read the release notes every time.
For my personal case the beta08 introduces a relatively big bug. And up until beta07 everything worked fine. According to the issue tracker it is already fixed and I have to wait for the beta09. And even then I still will have to recheck, well, everythings that is implemented with Compose.
I’m glad creating an issue in the issue tracker helps solving the problem. Problems may occure in any software at any point actually, but for something that is still in beta such cases are more likely, I would say.
A huge work was done since the first developer preview of Compose. Extrapolating this effort to the future, I believe the Compose will be a very good solution, making working with Views completely unnecessary, unless some strong reasons exist.
What does a migration to Compose presume?
Jetpack Compose is a UI framework with its own API. But Activities can only display Views. The ComposeView class while being a View is the entry point to the Compose code.
So, Activities are still a thing. What about Fragments? If you heavily depend on them, like for example have some custom navigation solution, then it is fine to keep them. You may use the ComposeView as the only view of your fragments. However, with Compose you can actually move away from Fragments completely and even remove androidx.appcompat and androidx.fragment dependencies. Compose has an integration with Jetpack Navigation, which makes Fragments even less relevant.
What about ViewModels or Presenters? If the View layer is properly decoupled from the presentation logic, then no changes need to be done. And here lays the boundary of Compose, as it is only a UI framework.
While Presenters themselves can be left untouched, the MVP pattern is more focussed on partial mutating of the View layer by calling setters on it. This doesn’t convenitenly work with Compose. You may end up creating an intermediary layer here to expose the observable state to the actual View layer, which now resembles… the MVVM pattern. Yes, MVVM and MVI works naturally with Compose, as they are focussed on exposing the state.
Of course, the migration presumes time investments in learning and actual coding. While its API not being 100% stable even in beta state, in rare cases you may solve certain things more than one time.
APK size will be impacted and the build time too. And you may be surprised how exactly. This post by Chris Banes has a very good comparison.
Why might you consider the migration?
The complete migration positively impacts the build speed and the binary size. But even if the complete migration isn’t possible in big projects with tones of legacy code, you can still benefit from introducing the Compose in your project.
Android theming and styling is quite complicated. It is hard and time consuming to traverse the styles chain in order to find a place where a certain attribute is defined. Or understand what attribute to set and where to place it in order to set the appearance or the behaviour of certain standard UI components. In Compose it is much easier to understand what color, text style or other value is used in a UI element just by looking at its source code (most of the time just looking at a function definition). Overriding a value is as simple as just passing an argument to a composable function.
Material Design is engraved into Android platform. To have really consistent Material UI across all possible devices we use the MDC library. But sometimes the Material Design specification can be a limitation: in cases when your design system isn’t truly based on Material, but you are still forced to use many Material components. And with Compose it is possible and really quite simple to define your own design system or to customize the Compose Material. The Jetsnack sample app is a good example of such a custom design system. Jetpack Compose is higly and easily customizable.
If not migrating old UI, you can implement new UI with Compose. You may benefit from implementing certain complex custom views with Compose. The AbstractComposeView class is a good start for that. An example of this idea is the Schedule Calendar.

You may also be surprised how the absence of the necessary context switching between XML layouts and Java/Kotlin code helps being focussed.
First videos by Google about Jetpack Compose emphisized the simplicity of common use cases in Android development like using very simple LazyColumn() function instead of RecyclerView/Adapter/ViewHolder trio. Even if you use live templates for this, the approach of Compose is still faster to implement. I think this is an example of considering the previous 10 years while developing a new UI framework. I personally find the conciseness of Jetpack Compose code one of its stronges features.
And of course a migration from Views to Compose for the sake of the migration itself is worthless and harmful. Never do that. It is reasonable only if it solves your daily problems and improves the future development.
Strategies for migrations
Compose comes with useful interoperability features that help you migrate an existed codebase in a way you prefer. Specifically I mean:
- The ComposeView class. It allows rending of Compose UI while being a child of a ViewGroup. You can start replacing leaf nodes of your View tree, step by step moving towards the root, merging sibling View hierarchies into a single ComposeView.
This is the strategy I used, as it felt like the best suitable one. - The AndroidView() composable function. That one allows using Views inside the Compose UI. When there is no way for reimplementing a View you may still keep using it. This function allows the migration in the opposite direction - from the root to leaf nodes.
Jetpack Compose is a brand new tool that considers a decade of Android Views evolving and that makes daily work easier and faster.
Now let’s talk about certain specific cases that I faced during the migration that I find worth mentioning.
Case 1: Material Theme duplication avoiding
Compose Material supports the second version of Meterial Design specification. Specifically you can setup colors, font styles and shapes according to the predefined semantics. But wait, most probably you already have these definitions in your resource files. Should you duplicate them in Compose manually? Theme adapters come to the rescue.
The MDC-Android Compose Theme Adapter apart from having such a long name deprives you from the necessity of redefining all these theme components twice.
If you still use the AppCompat theme (aka Material Design 1.0), then you need to check the AppCompat Compose Theme Adapter.
This library is a part of Accompanist - a set of utility libraries for Compose, which I strongly advise you to check out.
While you are migrating or if you have to preserve Views in your project, you will have to live with a theme adapter, as it is better to have a single source of truth of core theme values.
Case 2: Preferences and MenuItem APIs
Many of Jetpack Compose UI components are extensible with Slot APIs. This approach presumes incapsulating an overall structure of a UI element while allowing to extend certain its parts. One of the overloads of TopAppBar() function, for example, accepts the actions: @Composable RowScope.() -> Unit
argument for menu items section and you can pass a regular composable function here, which would draw whatever you want. But the thing is that you not really can, but actually have to pass an implementation of the whole menu in case you need it. As of beta08 there is no default counterpart of the MenuItem API. And you have to manually implement displaying icons, shrinking actions into a More element, displaying dialogs for submenus, Activity and Fragments menus merging together and all other stuff MenuItem API provides. Of course you can stick to the bare minimum accourding to your needs, but if you heavily use menus in multiple screens, then perhaps you will end up recreating MenuItem API counterpart yourself.
Another example of the same situation is the lack of androidx.preference library analogue. This library contains even more functionality that may be needed to be backported to Compose manually.
Currently, while being quite extensible the Compose sometimes lacks the old known things that are very concise on their own. Manual reimplementation of such things may be a reason not to make a migration of certain parts of your app. However, the community may focus the developers of Compose to fill these gaps after 1.0 release.
Case 3: Implicit content styling
Sometimes a simple Text("Well, text")
results in a text with a particular style and of a particular color (even not the one from the theme). It may seem odd why a text suddenly gets a particular look. This is how I met CompositionLocals (ex Ambients).
CompositionLocal is a way of defining something like a global value for a composition subtree. The whole subtree may access it. There are many predefined CompositionLocals like LocalTextStyle and LocalContentAlpha. A Text()
function actually checkes these values and applies them and that is why the it can look differently. Many UI components while encapsulating a structure and providing the Slots API can also set CompositionLocals to deprive the client code of manual styling of whatever comes to a slot. For example the AlertDialog() already sets text styles for its title and message texts. You can create your own CompositionLocals and they may hold data other than just styling information.
The good thing about Compose is that its source code is available and you can easily trace such a beviour if you didn’t know it exists. You can also simply debug the execution of composable functions. It is actually much easier than tracking a lot of lifecycle callbacks of Views and theme attributes resolving. Just looking at the source code of composable functions answers most of the questions that arise about their usage.
Case 4: Shared element transition
Currently the Jetpack Compose lacks the native transition animations between different screens. However, with the interoperability with Views you can still use some of them. Shared element transitions between Activities are possible with usage of AndroidView()
function and setting the transitionName
property of necessary views. The Activity that is started may want to postpone the enter transition for the time the Views are actually attached. It works even if the calling Activity also uses the Compose with AndroidView()
.

Despite my pet project doesn’t have any shared element transitions, this question bothered me all the time. Sad that currently Compose doesn’t support it out of the box, but gladly I found this workaround.
Recommended resources
If you are interested in Compose then you may find useful these links.
- Official documentation - beware, that this set of documents gets updated frequently. With time you may find new articles and even sections here. So keep checking this one out.
- Jetpack Compose Pathway - for more hands-on experience for learning of Compose. Also gets new sections over time.
- Compose Material Package summary - these summaries are really helpful to get the idea of what is actually available to you. They have code samples and even images of the result look. Check out summaries of other packages of Compose too.
- Compose Sample apps - very good example apps of how the Compose API can be applied.
One more thing I want to mention. Some articles and answers on Stack Overflow may contain references to the developer preview or alpha state of Compose. Pay attention to what version was used, as it may be obsolete by now and the mentioned API may not exist anymore.
Conclusion
Jetpack Compose forces you to rethink how to implement something what you already know how to do. Knowing an alternative way of creating the UI allows you to understand good and bad things about both Views and Compose and for sure it will push you to select the right choice for your next feature or project.
Thanks for reading. Cheers!