Author
Andres De La GranaAndres de la Grana is a Mobile Developer at Moove It. With 5+ years of experience, Andres is passionate about Android development. He has worked on various scalable applications with lots of users, and believes in creating clean code and following best practices.
Jetpack Compose is an Android UI toolkit introduced by Google. It improves the way the UI is built in most current Android applications, simplifying the process and speeding it up. One of the best things about it is that it uses Kotlin.
Jetpack Compose, as its name suggests, makes use of the Composite pattern and declarative programming. So, before checking how it works, let’s explain some concepts.
Concepts
Before talking about composables, we need to know some useful concepts. So, I encourage you to read more about the following topics:
- Composite pattern:
https://refactoring.guru/design-patterns/composite
- Imperative programming:
https://en.wikipedia.org/wiki/Imperative_programming
- Declarative programming:
https://en.wikipedia.org/wiki/Declarative_programming
- Key differences between Imperative and Declarative programming:
https://www.educative.io/blog/declarative-vs-imperative-programming
Android UI systems comparison
XML UI system
The XML UI system is based on inheritance.
All views inherit from View and all its child views, like a TextView, inherit all the View’s “knowledge”, for example, paint the background. You can think of a TextView as a better version of a View because it adds new functionality.
The same happens with EditText. It inherits from TextView. The “problem” here is that you can only get the knowledge from one specific parent.
Note that an EditText is a TextView and a TextView is a View.
Compose UI system
The Compose UI system is based on composition.
A composable can be considered as a View but the relationship is not parent-child like in inheritance. Here we have a composable that can have as many composable references as needed. Each composable can be thought of as a task. If you need a more specialized task, you can nest another composable inside another.
Note that Composable has another composable that has another composable and so on.
XML vs Compose code comparison
Let’s see some code.
Just to compare XML vs Compose let’s build a very simple layout:
XML Version
Same layout with Compose:
In compose, functions are used to implement component inheritance. A component function can only be called in another composable function. More atomic components mean a more flexible components structure (but more components to maintain).
In the composable code, you might notice some strange “state” code. Let’s talk a little bit about it.
State management
First of all, what is the state in an app? The state in an app is any value that can change over time. The State determines what is shown in the UI at a particular time.
If we create a composable like this:
We are creating a static composable that prints “Hello world” and we have no chance of updating it.
So, how can we change a composable state? The answer is: events.
Events in compose
Events are inputs generated from outside or inside our application. For example, clicking on a button in the app or receiving a response from a network request.
In all Android apps, there is an UI update loop. This is how it looks:
At first, an initial state is displayed on the screen. If an event happens, the event handler changes the state and now the UI displays the new state on the screen, and so on.
If we modify our current Composable to be something like this:
After pressing the button you will notice that the UI is not updating. So why? Now the answer is recomposition.
Recomposition
Basically, when the state in a Composable has changed, it has to be re-run to show the new state on the screen.
Compose is able to track the Composable state and schedule recompositions when the state has changed. It’s important to say that only the affected composables will be re-rendered and not the whole UI. To know more about recomposition please refer to the following documentation: Lifecycle of composables
So, we have to use the Compose API to achieve this behavior. Let’s modify our current composable:
Here we can see some particular Compose stuff:
- MutableState: Wraps a state of type T (generic) inside a value parameter that is observed by Compose. So to access the state, you have to call this value inside the wrapper. It is initialized using mutableStateOf with the initial value.
- remember: inline Composable function. A value calculated inside remember is stored in the composition and the stored value is kept across the recompositions. If we want to remember the state between configuration changes we should replace this with rememberSaveable (it persists the state in a Bundle).
Usually MutableState and remember are used together in composable functions.
Using delegates could improve code readability and make it much simpler:
State hoisting
Before talking about state hoisting, let’s mention what stateful and stateless composables are.
Stateful composable: is a composable that holds its own state
Stateless composable: is a composable that doesn’t hold any state
With that in mind, let’s talk about state hoisting.
State hoisting in Compose, is a pattern of moving state to a composable’s caller to make a composable stateless. It has some important benefits:
- Single source of truth
- It can be shared with multiple composables
- It is interceptable by callers that can decide to ignore or modify the state
- It decouple the state from the composable itself
To see it more clearly, let’s refactor our previous composable into a stateful and stateless widget:
Working with lists
Building a list with XML
To show items in a list with XML, we need a few files and/or classes:
- A RecyclerView or a ListView in the parent ViewGroup
- A Recycler adapter
- A ViewHolder
- An item xml layout
It is more complicated than it should be. A lot of code to show a simple items list.
Building a list with Compose
We only need a LazyColumn. Just that. In the item/items scope we can specify the item composable. Look at the example:
There are a lot of things to take into account when building an items list like recomposition, keys, paging, etc. But since this is an introductory blog post I recommend visiting the following link: Lists and grids
Navigation
In Compose, everything is composable. And Navigation is not an exception. To perform a navigation, every screen should have a unique route (composable path) and, we will also need a NavHost. Let’s check how to build it.
Suppose that we have two screens in our app: Home and Profile. And we want to navigate from Home to Profile. It is as simple as creating both screens and building a NavHost with the required routes. In this case both composables will be created and navigated in the MainActivity.
MainActivity
HomeScreen
ProfileScreen
Note that the navController is created in the navigator and passed to the required screens. This follows the principles of state hoisting and allows you to use the NavController and the state it provides via currentBackStackEntryAsState() to be used as the source of truth for updating composables outside of your screens. An example of the usage of this functionality could be the usage of a BottomNavigation: Navigating with Compose
Official Jetpack tutorial
This is the official pathway for Jetpack Compose. It includes articles, videos and codelabs.
Try it out: Jetpack Compose | Android Developers
Conclusion: In Android, Compose represents a complete UI redesign
The common practice for building a UI has been Imperative programming. It’s a robust practice but it becomes quite complex when an app is too big and has too many UI elements. Declarative UI, has shown in other frameworks like React and Flutter, that the adoption and implementation is much easier and the development performance increases.
Specifically in Android, Compose has been a complete redesign of the UI system. It has some advantages over traditional XML system like:
- Less code required
- Only Kotlin is needed
- Intuitive and faster development
- Easier to build custom views because of its flexibility
If you have been working with XML views until now, it could be that it will require some time to get used to the technology, but its learning curve is not hard at all, so don’t worry!
Get hands on with Compose and see you in the next blog post!
Useful resources
Jetpack Compose | Android Developers
Why Compose | Jetpack Compose | Android Developers
Thinking in Compose | Jetpack Compose | Android Developers
State and Jetpack Compose | Android Developers
Lifecycle of composables | Jetpack Compose | Android Developers
Lists and grids | Jetpack Compose | Android Developers
Navigating with Compose | Jetpack Compose | Android Developers