These days is easy to find more and more articles about Jetpack Compose and this is not surprising. Compose is a major change in the way we design UIs on Android. My goal with this is to summarize lessons learned and simplify the access to this toolkit.
If you have already tried, you would find out some pitfalls of learning Jetpack Compose:
- The library has undergone many changes with every new version. In recent months and especially with the lately launched alpha version, the library is clearly gaining stability.
- Plenty of articles are outdated because Compose API has been changed quite often, making those articles almost useless.
Jetpack Compose starts a new philosophy in UI Android development. The whole app can be written in Kotlin, which brings some benefits such as conciseness, reusability, and readability. As you may have read, this way to developing user interfaces is not new (in fact, is trendy) and is inspired by other languages and frameworks like React, Vue, or even Flutter.
The sample app in the following image retrieves books data from a server and visualize them in a list on UI.
Obviously, this post will focus on the user interface.
A Workshop where play with Jetpack Compose. Contribute to maruiz81/Compose-Workshop development by creating an account…
Project set up
First of all, we need to use a canary version of Android Studio to have the Compose capabilities. The latest version currently available is 4.2 canary 9.
To get started with Compose we need to import the libraries required:
All steps are explained in this link:
I see @composable everywhere
In Jetpack Compose, the main idea is to build interfaces by composing functions. The @composable annotation must be added to the function so the compiler will draw the UI defined by it. This type of function can call other functions inside to create a more complex UI.
A @composable function can only be called from another @composable function, but it can also call a normal Kotlin function.
In the following example, the text “Hello world!” will be displayed.
A @composable function must be called from another @composable function but the first call in the chain (to connect to Activities) must be done from
Note: The convention for the function naming changes for @composable function as we see in the examples, is to start with Pascal case (the first letter of each word is capitalized).
Preview or not to preview
Maybe you are wondering, What about the preview we used to have in the XML editing interface in Android Studio? Don’t worry because this is still available, but in a different way.
In the code, a preview function has to be added, in our simple example it will look like this:
When the code is compiled the preview view will be available in the IDE:
Columns and Rows
The most basic way to group views (@composable functions) is using Columns and Rows. Row groups the view on a horizontal line and Column groups them on a vertical line. Column and Row are, of course, @composable functions. Let’s see an example:
And we will get these output for
Compose in an MVVM architecture
When we are working on an MVVM architecture, we observe the changes in the ViewModel to modify the UI. Following the normal approach, we would do it as follows:
But WAIT! this code doesn’t compile:
The Observer lambda function is not considered a composable “environment”, even if it is invoked within the
setContent block. Jetpack compose provide a better way to do this:
And this is how it looks in our app:
We are ready to start
Let’s first extract the GUI implementation to another file. After that, in order to implement the example presented above, the main section which contains the title, subtitle and year will be created:
The function PaintMainSection will create this section(notice that the text style is not yet implemented, that will be covered later):
Paint a list of books
The above code will only paint one book, but the server returns a list of them. To show the “main section” of all books on the list, we just have to call PaintMainSection defined above for each book. To represent those views we could use a Column function again:
A lot of books
This might be good enough for a small list of books, but in a big amount of books, they can’t fit on the screen without scrolling. We can add a scroll that wraps the book list with a ScrollableColumn container:
This code is not bad, but we are just adding a scroller so in a huge list, the scroll is not smooth and the memory consumed is hight. To improve the rows have to be reused. Basically we need a RecyclerView and the equivalent in Compose is LazyColumFor:
This implementation is a good improvement because implementing a recyclerView requires a lot of boilerplate.
When the data changes…
Fortunately, we don’t have to call the previous functions each time we need to refresh the data. In the code above when the data is loaded from ViewModel’s LiveData, if we inspect what
The data is wrapped in a State. And this state Interface does the magic of reloading the data when it is being changed, in this case, by this LiveData.
If we want to do it “manually” it is possible to wrap the data in a
State like we see below:
When the book variable is passed to a @composable function, the generated UI will automatically change when the variable changes.
Another way to do this is by using
mutableStateOf on each attribute of the class that contains the state.
In Android development, we use themes to introduce design lines into our app and separate the chosen style from the implementation, giving the possibility to change it quickly. Jetpack Compose provides the
MaterialTheme function to apply some Material design settings and also can be personalized. To apply it we only have to pass to
MaterialThemethe function that we want to customize.
MaterialTheme allows us to customize it by configuring its optional parameters.
But themes should usually be reused in the app. So, to facilitate it, we can extract this theme to a function which accepts as a parameter a lamda where the theme will be applied:
Now, this theme can be used in our app. Besides this, In the example below, we will use
Typography, which is provided by
MaterialTheme with some font styles (can be customized if we want):
typography variable is passed to the @composable function which needs to use it. The result of this text styling is this:
In the example above, we had to pass the typography variable through the views tree to use where needed. To fix it, the Compose team has created Ambients. Thanks to Ambients, we can use typography without passing it. The variable
appTypography is initialized using
Providers function. In order to use the parameter, it’s required to call the
current attribute in the ambient variable. Let’s see an example:
Load images from network
Currently, typical libraries for loading images from the network don’t work out of the box. Fortunately, we can use Chris Barnes’s library, which adapts Coil library to use it in Compose.
This library brings easy-to-use composable which can fetch and display images from external sources, such as network…
Let’s see an example of how it works. We are going load the book cover from the network. First of all, we have to add a row to have at the same time the cover and the section with the title, author, and date.
Modifier used in the above code is used to format the views. This will be explained in deep in the next section.
Modifiers are used to configure different aspects of Compose elements. There are many types of modifiers, let’s see some examples:
In this example, the image is setting up with a width of 80 dp and a height of 124 dp. As you can see the Modifier can be concatenated.
In this other example, the modifier is used to configure padding:
You will discover many types of modifiers as you learn more about Compose.
Compose has just reached the alpha version. That means the API is reaching some stability, but it may still have some changes.
This is a simple example, in the lastest releases they have included the ConstraintLayout implementation, which provides additional flexibility compared to the current implementation, but it’s better to start from the beginning and keep this, and other functionality, for the next step.
You can find the full implementation in this Github repository. If you find a bug or any suggestion, feedback is welcome. Please note that the library is not final, so documentation it’s not always easy to find.