Over the last years, the software industry has been moving away from the imperative world in favor of the declarative one, and Android is not apart from that.
Just over 9 months from its official launch – 28th July, 2021 – Jetpack Compose, the new toolkit for building native Android UI, became very popular among developers around the world and has slowly started to replace the old view-based toolkit.
The toolkit we have been using for several years is imperative. Every time something on the screen needs to change, we have to walk down the hierarchy of views to find the desired node and update it. All these nodes have an internal state that can be mutated by using one of the methods they expose – e.g. setText. Despite being able to build amazing apps with this toolkit, its weaknesses are evident. This way of manipulating views increases the likelihood of errors and the complex grows with the number of elements on the screen.
Jetpack Compose is a declarative toolkit. The nodes in the hierarchy are stateless and recreated from scratch every time something needs to change. The recreation part sounds really expensive, but we do not have to worry about it, as Compose intelligently chooses which parts of the UI have to be redrawn and keeps as many parts as possible.
This modern toolkit simplifies and accelerates the UI building process. It allows us to do more with less code. This means less to test and debug, less room for bugs and more time to focus on what really matters: take the user experience to the next level. It is not something good just for the developer who is coding, but also for the ones that review the code. There is less code to understand, so we can spend more time to focus on the details and make better suggestions which implies ending up with better code quality.
With Compose, we do not need to use xml files for layouts any more. The screens are written entirely in Kotlin, as a set of nested composable functions. These functions are default Kotlin ones, annotated with @Composable, that receive data and emit UI elements. They must be idempotent and free of side-effects. A function is idempotent, when it returns the same result no matter how many times it is invoked, as long as the same arguments are passed. It is free of side-effects when it does not write or update global variables.
An example of a composable function could be the function Text, which receives a String value and draws it on the screen – pretty much the same thing the old TextView does. This probably makes you wonder about the components we used before the arrival of Jetpack Compose, such as buttons, switches, cards and all the material components supported by the previous toolkit. Well, we have great news about this. All these components are still available as composable functions – some of them keep the same name, others have suffered a minor tweak.
As you already know, these components should be laid out on the screen somehow. For this purpose, Jetpack Compose provides three standard layouts: Column, Row and Box. If we compare these layouts with the view-based toolkit’s ViewGroups, Column and Row work the same way LinearLayout does. Both of them allow us to arrange elements one next to each other. The difference between them is the orientation: Column arranges elements vertically and Row horizontally. Box is like a FrameLayout, it arranges elements one above the other.
Despite the fact we can create almost everything with these standard layouts, this new toolkit provides more sophisticated ways to arrange UI elements, such as Scaffold or the well-known ConstraintLayout. The first one is a slot-based layout. It provides slots that we can fill with composable functions and combines them using a common screen pattern. The latter is the most powerful layout, but the most complex too, so we have to think carefully when to use it. Since Jetpack Compose handles nested layout efficiently, without affecting performance, we must focus on readability and maintainability of our composables. Therefore, we prefer to avoid its usage, unless we are not able to match the design specs by using the layouts mentioned before.
Finally, we have to talk about the powerful tools Android Studio provides. The @Preview annotation allows us to preview the layout we are working on, without the need of running the app in an emulator or deploying it to a physical device. We just have to create a composable function annotated with @Preview invoking the composable function we want to preview. But we can go a step further and take advantage of the parameters accepted by this annotation, that let us mock different scenarios to see how the layout looks under those conditions. It’s possible to set up different languages, font scales or android versions, switch between dark and light modes and a lot of other interesting things.
That is not all! You can also interact with the previews by enabling the interactive mode or deploy the composable to a physical device, avoiding the need of performing navigation from the entry point of the app. Additionally, you can copy the preview render as an .png image and navigate to the composable function that emits a specific item by tapping it in the preview.
This is just a brief summary of what Jetpack Compose has to offer. We get very impressed by its power and cannot wait to put everything we have learnt these days in the next app we build from scratch. If you have not given it a try yet, do not hesitate! We can ensure you will not regret it.