Using Android Jetpack Compose

Kayvan Kaseb
Software Development
10 min readMay 28, 2022
The picture is provided by Unsplash

As a matter of fact, the software industry has started shifting to a declarative UI model in order to simplify the process for creating and updating UIs. This advanced technique works by re-generating the whole screen from scratch, then applying just only the requisite changes. Nowadays, Jetpack Compose has been announced by Google as a declarative component-based paradigm for building your UIs easily and quickly. In other words, Compose promotes a programming model that is completely different from the existing model of creating UI on Android. This article will provide you with some main concepts in using Jetpack Compose for Android developers.

Introduction and Overview

Fundamentally, an Android view hierarchy has been demonstrated as a tree of UI widgets. When the state of the Android app changes due to various reasons, like user interactions, the UI hierarchy requires to be updated for representing the current data. The most typical approach for updating the UI is to crawl the tree using functions, like findViewByID(), and change nodes by calling methods, like button.setText(String). These methods can change the internal state of the widget. However, the major problem in this area is that the software maintenance complexity grows with the number of views that need to be updated because manipulating views manually strengthens the possibility of errors in your code. Most recently, the software industry has started shifting to a declarative UI model in order to simplify the process for creating and updating UIs. This technique works by re-generating the entire screen from scratch, then applying just only the requisite changes.

Basically, Jetpack Compose is an Android’s new modern UI toolkit. It can simplify accelerate your UI development by allowing you to build more robust and responsive UIs. It is written entirely in Kotlin to build high quality Android apps easily and quickly. In other words, Jetpack Compose uses a declarative component-based paradigm for creating your UIs effectively. Besides, it allows for interop with the existing view system, and it is built fully in user space, unbundled from the underlying platform, that means you can take advantage of feature enhancements and bug fixes on your own time. In fact, Compose promotes a programming model that is quite different from the existing model of creating UI on Android.

To understand what Jetpack Compose is and why it is different from the existing UI Toolkit, you should consider some samples. First of all, we will begin with a simple function. It has a single annotation called @Composable and that is almost all you require to create a new widget, what we call composable in the Compose world. Initially, you do not have to extend a class; you do not have to overwrite constructors or methods. You just only create a function or method. Also, it can be private, public, or whatever you like. Therefore, as it is clear, Jetpack Compose is built around composable functions.

@Composable
fun ProductLabel() {
}

Then, to generate the UI from that, you should have some inputs. For instance, we can produce a label for a product. So, we can set product as a parameter of the function.

@Composable
fun ProductLabel(product: Product) {
}

After passing this step, we can be able to perform what we call “emit the UI”. Thus, we can invoke another composable function. This function is one of the default composable functions that are part of Jetpack Compose, it is called Text. It just creates a label on screen, and the text consists of a string that is built from our product. Jetpack Compose utilizes a Kotlin compiler plugin to convert these composable functions into the app’s UI elements.

@Composable
fun ProductLabel(product: Product) {
Text(“${product.quantity}* ${product.name}”)
}

The important point is that composable functions could only be invoked from composable functions. This means they are similar to suspend functions in coroutines. Obviously, @Composable changes the type of the function, and you can only call them in the proper context. To be specific, your composable functions are just only functions, which take data and transform into your UI. In a word, your UI is a function of the data.

F(Data)--------> UI

Even though this function paradigm could be simpler to write the code, it is simpler to refactor. So, the data should flow down from your business logic to the functions.

As it has been mentioned, the main reason for choosing Kotlin to write your UI in this way is that you have access to all the advanced features of the Kotlin language. For example, we want to only show our label when the quantity of the product is greater than 0. We do not have to inform Compose how to do this, or we do not have to find this label: if it is visible, make it invisible. As an alternative, we just make aware Compose what we want and then you can observe the code on screen simply. In this case, if the quantity is greater than 0, there is a label. Otherwise, there is nothing. So, Compose will do everything else by default.

@Composable
fun ProductLabel(product: Product) {
if (product.quantity > 0) {
Text(“${product.quantity}* ${product.name}”)
}
}

Finally, whenever the value of the product changes, Compose will re-invoke our Composable, which it is called Recomposition. This means you do not have to worry about eliminating and hiding the items. Essentially, one of the significant challenges with updating and regenerating the whole screen is that it is potentially expensive in terms of time and battery usage. To diminish this cost efficiently, Jetpack Compose intelligently selects which parts of the User Interface need to be redrawn at any given time.

Composable Functions

As it was showed, you can be able to build your UI by defining a set of composable functions that take in data and emit UI elements in Jetpack Compose. In particular, some important points could be mentioned for composable functions as follows:

  1. The function does not return anything.

2. The composable functions can execute in parallel.

3. The composable functions can execute in any order as you want.

Intelligent Recomposition

As it has been explained, in an imperative UI model, to change a widget, you should call a setter on the widget to modify its state. In Jetpack Compose, you call the composable function again with new data. As a result, the Compose framework can intelligently recompose only the components that changed with new data. This means other functions in your code that do not depend on the value are not recomposed. For instance, in previous example, we have a for loop to build a column of labels. If the quantity updates for the product, the number of emitted items will changes, but we do not have to do anything. Compose takes care of all of it for us.

The Compose runtime might re-execute the Composable function after they have been called the first time in order to update the current state of the UI. We call this process Recomposition.

The code is provided by Google resources

All in all, Recomposition is the process of calling your composable functions again when inputs change.

Composition: a description of the UI built by Jetpack Compose when it executes composables.

Recomposition: re-running composables to update the Composition when data changes.

If you need to perform expensive operations, such as reading from shared preferences, you should do it in a background coroutine and pass the result to the composable function as a parameter. This means the composable should not read or write from shared preferences by itself.

Handling States

In general, state in an app is any value that can change over time. Moreover, all Android applications show state to the user. For example, a Snackbar that displays while a network connection cannot be reached. So, because of the importance of this issue, Compose designed to help you about where and how you store and use state in Android apps.

Mostly, you want to operate only on input parameters, the immutable parameters to your function, however, you want a state in some special cases. For instance, we have a list of products and we want the user to be able to filter their list based on something they will type on the query. To accomplish task, we start by creating a state. This is only a string and we can use this function, called state, as a delegate. So, it creates a state object that compose will remember it.

The code is provided by Google resources

Afterwards, we can create a TextField, and give the filter string as the initial value to show the inside of TextField. Furthermore, the TextField can call a lambda whenever the user enters something inside it. In this case, we provide a lambda to onValueChange, and we will update the state. Therefore, whenever the user types something, our lambda will be invoked, and Compose will be triggered a Recomposition. Obviously, this will update the displayed value inside the TextField.

Eventually, after using that filter, we can go through the list of products and just display the ones that match the query. So, when the user types something into the TextField, a Recomposition will happen and our loop will automatically re-execute.

Initial composition: creation of a Composition by running composables the first time.

Some important notes for managing states:

  1. Composable functions can store a single object in memory by using the remember composable. This value stored over initial composition, and the stored value is returned during Recomposition.
  2. remember can be used to store both mutable and immutable objects.
  3. Compose will automatically recompose from reading State<T> objects.
  4. If you use another observable type, like LiveData in Compose, you should convert it to State<T> before reading it in a composable.
  5. A composable that uses remember makes the composable stateful. In contrast, A stateless composable does not hold any state. To achieve stateless easily, you should use state hoisting.
  6. To restore your UI state after an activity or process is recreated, you should use rememberSaveable.
  7. Initially, simple state hoisting can be handled in the composable functions itself. Nevertheless, if the amount of state increases for keepig track, or the logic to perform in composable functions grows, the best solution is delegating the logic and state responsibilities to other classes: state holders. The below diagram represents a summary of the relationships among the entities involved in Compose state management.
The picture is provided by Google resources

Modifiers

Essentially, Modifiers decorate a single element (a composable). They can supply layout parameters, metadata, and additional behaviors. You can consider modifiers as a chain of declarations or wrapping to apply to a given element. The order of modifier functions is extremely important. As each function makes changes to the Modifier returned by the previous function, the sequence makes effect on the final output. So, the order that you read in code for Modifiers is the same order that it ends up with on screen. For instance, you can see the following code that has been written for understanding Modifiers:

The code is provided by Google resources

A sample for learning Compose: JetChat

In fact, Jetchat is a sample chat app built with Jetpack Compose. Thus, this sample could be helpful for learning the main concepts in Compose. This sample can provide you with following issues:

  • UI state management
  • Integration with Architecture Components: Navigation, Fragments, ViewModel
  • Back button handling
  • Text Input and focus management
  • Multiple types of animations and transitions
  • Saved state across configuration changes
  • Material Design 3 theming and Material You dynamic color
  • UI tests

Implementing Lists in Compose

One of the significant questions could be asked in Compose is implementing RecyclerViews and Lists in this modern approach. For example, we can implement a shopping cart, which includes multiple items by Compose. Additionally, we can scroll through them, and have the recycling list of items. You need to take a list as an input. In this case, we could take a simple Kotlin list or a mutable list, however, we have used LiveData. Google has also supported RxJava and Flow. So, when you use LiveData, RxJava, or Flow, you need to observe the data stream as a Compose state. When we pull the Extension Function, called observeAsState, you can see it in action here. Here, we can call obsereveAsState, and we can give back a state object. Also, we can pass the state to our AdapterList, which is the name of the RecyclerView in Compose.

The code is provided by Google resources

Then, using a trailing lambda, each time that the AdapterList requires a new item or to replace an item, we can just describe the UI that we want to emit. So, here, we will build a shopping cart item based on the product that we received as an input.

As you can see, you do not need to write an adapter; you do not need to do anything more than that. As soon as the LiveData object changes, if the number of items in your list increases or decreases, or the value of any of the items changes, Jetpack Compose will automatically recompose everything. This means it re-invoke your trailing lambda for the AdapterList, and then the UI will update easily.

In conclusion

In fact, Jetpack Compose is an Android’s advanced modern UI toolkit, which has been introduced by Google recently. It can simplify accelerate your UI development by allowing you to create more robust and responsive UIs. This essay discussed some main concepts in using Jetpack Compose in Android development based on Google documents and resources.

--

--

Kayvan Kaseb
Software Development

Senior Android Developer, Technical Writer, Researcher, Artist, Founder of PURE SOFTWARE YAZILIM LİMİTED ŞİRKETİ https://www.linkedin.com/in/kayvan-kaseb