Discovering Jetpack Compose: key takeaways from my first project
Jetpack Compose has been released as alpha a few weeks ago, now. I took some time to experiment a bit with it in my side project: a custom launcher. In this article, I share my small experience with it.
To discover the framework and how it works, I followed the Jetpack compose pathway from Google. It’s an ordered list of videos, articles and codelabs that help to discover and learn what Compose is, what we can do with it and how to use it. The pathway starts with simple things (creating a simple UI) and ends with more advanced things (custom layouts, theming, managing states).
Jetpack compose is very well introduced and I liked the diversity of format: videos, articles and codelabs. If you’re looking to get started quickly with JetPack Compose, I’d say the three videos are enough without going through the codelabs & articles as well. The more so as if you are familiar with this UI paradigm, which is similar to the one used by Flutter. Codelabs and articles focus on some parts of the videos and explain them more in detail and at your own pace.
Also, you’ll need to download Android Studio 4.2, which is a canary version for now, in order to access the preview of your “views” in Android Studio. Then, you won’t need to assemble and launch the app, building the module is enough to see what the “view” will look like.
Enjoy the power of Kotlin everywhere
Jetpack Compose is written for Kotlin, and all you’ll have to write is Kotlin code. You no longer have to write xml layout files. This has some advantages :
- Code related to views is no longer scattered in Kotlin and XML files nor in different folders (src/main/kotlin, src/main/res/layout, src/main/res/anim, etc.). So UI code is easier and faster to explore, read and write.
- Kotlin is also more typesafe than XML. And with Android Studio, you have access to a better autocompletion in Kotlin than XML, and to a relatively complete documentation of the API. This makes it easier to write correct code and to learn how to use a component.
- Kotlin is more readable than XML, in my opinion. Because the code is not polluted by android: or app: prefixes.
Advantages of the declarative UI paradigm
Jetpack compose embraces the declarative UI paradigm. Which means that all you have to do is to declare how the UI looks like at the current state, and the framework will take care of updating the UI for you. When I tested Flutter some years ago, I really liked this paradigm. I found the learning curve not so hard, and it has some advantages over the current way of coding UI for Android :
- Less code. With Jetpack compose, the UI is redeclared (or recomposed) each time the UI state changes. So, you declare views only if they are shown, and you don’t have to change their state afterward. You only create them with the current state. View creation, initialization and binding are all in the same function.
- One source of truth. The UI state is no longer managed both in the Views and the ViewModel/Presenter. The state is managed in the ViewModel/Presenter and when it changes, the View is recomposed with it. For instance, a TextField in Compose (equivalent of EditText) does not change its text alone. It notifies the ViewModel/Presenter that the text is changed, which triggers a recomposition of the view with the new text. It leads to fewer bugs, because there is no side effect.
- It’s easier to split and reuse views in small components. With the current UI system, to create a component, you have to create an XML and a Kotlin file and maintain both. With Compose, you have only one function to extract.
- Customize components easily thanks to the usage of lambdas as function parameters. They enable the caller of a @Composable function (which describes a component) to choose how a part of the component will be rendered.
- It’s easier to preview one particular state of the app. All you have to do is to provide a @Composable function annotated with @Preview and some mock data, and you will see a preview in Android Studio.
- Theming is easier, and the theme can be changed easily. It can be considered as a part of the UI state and can be changed as simply as changing a function parameter.
- Integration with LiveData and ViewModel is seamless. (And those tools are what I use in this side project).
Simpler view API
On Android, all views are subclasses of View. This class is really complex and deals with so many things such as margin, padding, background and foreground drawables, state, touch, click, scroll, focus, accessibility, autofill, visibility, rotation, etc… (Did you know that `View.java` file is 30,000 lines long ?! That’s why I can’t enumerate all things dealt by View). And all those things can be overridden by subclasses.
This makes the API hard to discover and understand. And leads to some behavior inconsistencies you have to be aware of to master the API.
With Jetpack Compose, @Composable functions are focused on only one responsibility. That makes UI components simple to learn and maintain. And they are still customizable thanks to the parameters of those functions which can be regular data values (Boolean, String, ImageAsset, etc.), functions, or a Modifier.
Function parameters enable us to easily declare inner parts of a component by passing another @Composable function. They are also useful to define listeners to some events.
Modifier is a set of attributes associated with a @Composable function used to customize the appearance and behavior of a component in a way that can be common to all components. It enables to avoid having components from inheriting a common class (composition over inheritance). For instance, a modifier can add padding, a background, a clickable behavior, information about the sizing of the view, etc. Modifier API can vary depending on the layout of parents, thanks to extension functions. So, it’s quite flexible while keeping only one entry point for all those customizations.
Lack of docs and community support
- At the moment, all the documentation we have to learn Jetpack Compose is the pathway, some samples and the API reference and its kotlin doc. The pathway covers basic things and a few advanced things. The API reference is quite well documented for an alpha project. I didn’t browse all the samples, but when I wanted to implement something similar to what I saw in a screenshot of a sample, the feature was marked as TODO in the code. So, you have to be ready to explore by yourself
- The community support (Stack Overflow questions) is very poor. And the worst is that some answers refer to APIs that have been changed afterward.
So, there are some things that I didn’t manage to do (but also didn’t investigate so much) :
- Adding some animations :
- when pressing and releasing a view
- when making view appear and disappear
- Having a Column whose width “wrap content” and where all children have the width of the widest child.
- Setting the focus on a TextField
Poor tooling integration
- As Jetpack Compose uses an AndroidComposeView to render all its content, LayoutInspector brings no useful information about the hierarchy.
- Using @Preview to get a preview inside Android Studio can be hard if you use images. Because images and icons are separate types in Compose, you can’t easily mock an image with an icon, and I didn’t search for a way to mock an image without embedding it in the app.
My small experiment with Jetpack Compose confirmed that I really like the Declarative UI paradigm and I find the Modifier API more convenient than the Flutter API, because it helps to keep the “view” hierarchy smaller.
But it’s too early to introduce it into a production app, because even if the API might have reached a quite stable stage, the documentation and community support are still too poor.
I’ll keep exploring it and playing with it to know if I can make the UI as good as I could with the current View framework.
And I encourage you to give it a try, the more so as if you haven’t used any framework using this paradigm yet (Flutter for instance).