Building UI with Jetpack Compose

Elif Boncuk
Monday Hero
Published in
9 min readApr 26, 2020

--

To read the article in Turkish you can use the link1 or link2.

Over the years, we have implemented, measured and shared that it is better to inflate view from XML instead of dynamically creating the widgets in code part. If we go back before ConstraintLayout what we talked about UI was that, when we will use LinearLayout and when RelativeLayout, at which point using RelativeLayout or LinearLayout is more suitable for UI performance. But frankly, developing UI wasn’t the most enjoyable part of the app development for every Android developer(I still like it).

We met with ConstraintLayout and UI Editor to develop UI easier, faster and at a stabil UI performance. Although the UI Editor did not work perfectly at the beginning, it has come to a much better point over time and we have begun to develop our UI easily with it. On the other hand, Compose appeared with a very different perspective, can be developed only by Kotlin.

Experiencing Compose since being announced at Google IO last year was one of the items waiting on my to-do list. Finally, I could have time to write a post about it. According to developer.android, Compose’s official definition is “Jetpack Compose is a modern toolkit for building native Android UI. Jetpack Compose simplifies and accelerates UI development on Android with less code, powerful tools, and intuitive Kotlin APIs.”. In fact, as can be understood from the definition, the starting point of everything is simplification and acceleration. While developing Compose, it was inspired by React, Vue.js and Flutter.

Compose is still in developer preview and not recommended for the production environment. One point where Compose is different from UI development we get used to is its being declarative. When the data used by the UI is updated, this change is automatically reflected in the UI. I will try to make it a bit more understandable by quoting from the most I liked article on Declarative vs Imperative. It says “Declarative Programming is like asking your friend to draw a landscape. You don’t care how they draw it, that’s up to them. Imperative Programming is like your friend listening to Bob Ross tell them how to paint a landscape. While good ole Bob Ross isn’t exactly commanding, he is giving them step by step directions to get the desired result.

Let’s try now. By examining the Compose Basics codelab under Google Codelabs, you can have a basic knowledge of how Compose can be used. You should have Android Studio with min version 4.0 to experience Compose, latest Canary Preview to get the best experience. When you install this version, you will also see Compose under the project templates. You can simply create a hello world application from here. Also, examining the JetNews application written in compose in Google’s code samples can provide more detailed information about how to build a structure.

UI as function: UI is defined as a function with Compose and this function converts the data into view hierarchy. If we look at the code example below, input is a simple piece of data, name; output is a text widget. When we want to update the name, it will be enough to call the function again with new data, the ui hierarchy will contain a new text within text widget. Since we will no longer need two different blocks of code to inflate and update the code, this will significantly shorten our code. If we call this code with an appropriate name, such as “world”, “Hello world!” will be what we see.

@Composable
fun Greeting(name: String) {
Text (text = "Hello $name!")
}

I mentioned above that we can create an empty Activity class using Compose template. The Activity Class created from the template will look like the following. When we create it in this way, we can use Compose in our project without doing anything additional, the dependency is added in the gradle file and it will be set as buildFeatures {compose true}, which is necessary for Android Studio to work with Compose.

setContent block defines the layout of the activity. Instead of inflating xml here we call the composable function. Jetpack Compose uses a custom Kotlin compiler plugin to convert composable functions into the UI elements of the application. For example, the Text() function is defined in the Compose UI library, we can call this function to define a text element in the application.

We also see a new feature that we have not met in Android Studio before, default preview. We can see the compose function that we annotate with @Preview in the default preview section, just like design tab of xml.

Well, we just said Composable Function, so what is Composable Function, Composable Function is a function annotated with @Composable and it can be called only in the scope of another composable function. This allows the Composable functions we wrote to call other @Composable functions. It just reminds the suspendable functions.

Compose follows Single Responsibility Principle which is first principle of SOLID. @Composable functions are only responsible for a single functionality. For example, if we want to give background to some functions like the example below, we need to use the Surface Composable function. We can’t do this with another built-in component.

@Composable
fun Greeting(name: String) {
Surface(color = Color.Yellow) {
Text (text = "Hello $name!")
}
}

When we run this code in the emulator, the text will be stuck to the top left of the screen. When we say how we can change the look and layout of the views, Modifiers comes into play.

When I first tried and wrote the blog post in Turkish which I linked at the top of the article, current version was dev03 and now dev09. Modifiers changed a lot between the versions, more stabil, more developer friendly. At first version to be able give a Modifier to the widget we need to know the whole name, but after dev08 it’s easy to reach with Modifier.<modifier> .

One of the most used modifiers, probably will be padding. As seen in the code below, while creating text, padding can be obtained by giving it to the modifier.

@Composable
fun Greeting(name: String) {
Text (text = "Hello $name!", modifier = Modifier.padding(24.dp))
}

Another commonly need modifier will be layout the widgets horizontal or vertical. We can do this with using Row/Column. Column is like using LinearLayout with vertical orientation and Row is like horizontal.

@Composable
fun SpeakerInformation(speaker: SpeakerInfo) {

Column(
modifier = Modifier.padding(16.dp)
) {
Text(speaker.name)
Text(speaker.title)
Text(speaker.sessionTitle)
}
}

When we run the code sample above, we’ll face with the screen below.

So let’s say we want to create a container with all common configurations in our application. To make a generic container, our Composable function needs to take a Composable function returning a Unit as a parameter. Those who have not used the lambda before can view the link for more information.

This function will produce an equivalent result that we used at the beginning of the article, but much more flexible. Container functions are good experience to improve readability and allow code to be reusable.

@Composable
fun IWDDemoApp(children: @Composable() () -> Unit) {
MaterialTheme(colors = lightThemeColors){
children()
}
}

Let’s continue with another common need from here. Adding images to our application. It is one of most changed functionality over versions. Last one is the easier, I’ll explain with it. We can draw the image with Image function that is taking ImageAsset as input.

@Composable
fun SpeakerImage(image: ImageAsset?) {

image?.let {

val imageModifier = Modifier
.preferredHeightIn(maxHeight = 180.dp)
.fillMaxWidth()
.clip(shape = RoundedCornerShape(4.dp))
Image(image, modifier = imageModifier, contentScale = ContentScale.Crop)

}

}

We can reach all the attributes that we can give to image over Modifier. With using preferredHeight in we can set height, fillMaxWidth() makes behave like match parent, clip makes the corner rounded in this sample. And also thanks to scale type we can define how our image layout.

@Composable
fun SpeakerInformation(speaker: SpeakerInfo) {

Column(
modifier = Modifier.padding(16.dp)
) {
SpeakerImage(image = speaker.image)
Spacer(Modifier.preferredHeight(16.dp))
Text(speaker.name)
Text(speaker.title)
Text(speaker.sessionTitle)
}
}

State in Compose: One of the things Compose advocates is updating the view directly without doing anything extra in data changes. Apart from creating an updated UI called by these functions when data changes, Compose also looks at what data a single Composable needs and recompose only those components. Others are not affected by the data change.

Managing State with @Model: Instead of calling Composable functions with different input parameters to update what appears on the screen, Compose offers us @Model annotation. If the data received by the Compose function as input uses the @Model annotation, it will automatically recompose when the data changes. Thanks to @Model, Compose compiler rewrites the class in a observable and thread-safe manner.

In case we use the Counter widget in the code example below, our counter will increase with every click and we will be able to see the number change on the screen without any extra development for it. (you can take it from Google codelabs.)

@Model
class CounterState(var count: Int = 0)

@Composable
fun Counter(state: CounterState) {
Button(
onClick = {
state.count++
},
modifier = Modifier.padding(24.dp),
text = { Text(text = "I've been clicked ${state.count} times") }
)
}

Flexible Layouts/Giving weight: The weight functionality was first introduced with flexible layouts and flexible keyword at dev03. So I added it, too. If we want some items to fill the screen with a certain weight, we can use the Modifier.weight(xf).

@Composable
fun StateSampleScreen(
names: List<String> = listOf("Android", "there"),
counterState: CounterState = CounterState()
) {
Column(modifier = Modifier.fillMaxHeight()) {
Column(modifier = Modifier.weight(1f)) {
for (name in names) {
Greeting(name = name)
Divider(color = Color.Black)
}
}
Counter(counterState)
}
}

Next is Theming. What we’ve talked about so far has been more statically changed features. So how do we do that if we want to customize it a little?

Theme, like other Composable Functions, is part of the Component hierarchy. In the Container usage example above, we gave our app Material Theme. This means that we can actually use the values defined under Material Theme. Again, in the example, it took colors as a parameter. We can also customize the colors.

val lightThemeColors = lightColorPalette(
primary = Color(0xFF1EB980),
onPrimary = Color.DarkGray,
secondary = Color.White,
onSecondary = Color.Black,
background = Color.DarkGray,
onBackground = Color(0xFF1EB980),
surface = Color.DarkGray,
onSurface = Color(0xFF1EB980)
)

Material Theme is a Composable function that reacts to styling principles according to Material design standards. This styling information passes to all components inside and can be used.

@Composable
fun Title(title: String) {
Text(title,
style = themeTypography.h6)
}

And the last topic is development of lists. It was announced in Android Summit that instead of recylerview, it was working on a widget called scrollinglist or similar. However, a version that we can try was not available under api. Recyclerview-like structures were implemented using verticalscroller in the Jetnews app. Although the adapterlist has been announced on dev05, there has not been an update on jetnews yet, but we understand from its description that it is equivalent to recyclerview. “A vertically scrolling list that only composes and lays out the currently visible items.

@Composable
fun SpeakerList(speakers: List<SpeakerInfo>){

AdapterList(data = speakers) {
SpeakerInformation(speaker = it)
}

}

Hopefully, it would be useful in terms of Compose’s overview and giving an idea about the use of basic components. Generally, Compose takes some ui decisions from the developer’s hand, simplifies them, and provides less code to be written. While these simplifies the process, it raises a question mark on how successful it can be in complex layouts. Currently it is still very new, in the developer preview, it will be useful for us Android developers to examine its development on the road.

You can review the sample project in the link for code samples. :)

References:

https://www.youtube.com/watch?v=VsStyq4Lzxo JetpackCompose Basics
https://codelabs.developers.google.com/codelabs/jetpack-compose-basics/ Understanding Compose (Android Dev Summit ’19) https://www.youtube.com/watch?v=Q9MtlmmN4Q0 What’s New in Jetpack Compose (Android Dev Summit ‘19)
https://developer.android.com/jetpack/compose/tutorial
https://developer.android.com/reference/kotlin/androidx/ui/packages

--

--

Elif Boncuk
Monday Hero

Engineering Manager, Digital Banking @GarantiBBVA | #Android #GDE | formerly volunteer @MobilerDev @gdgIstanbul @wtmist | #CE @Hacettepe1967 #MBA @Bahcesehir