Getting Started with Jetpack Compose

We’re going to take a look into Jetpack Compose, the upcoming recommended approach to building UI fast and easily using Kotlin, and how we can build a simple view using it.

Before We Start

We will be making a simple view for the purposes of this article. This view will be a social media post that has an avatar, name, picture, and caption. The code, from this article, is available at the bottom of this article.

Social media post

We’ll need to set up our project to use Jetpack Compose (currently you can only use the Compose API with the latest Android Studio Preview). In the module level build.gradle we’ll need to enable compose and also declare its dependencies.

android { … buildFeatures { compose true } composeOptions { kotlinCompilerVersion "1.4.31" // This should be the same as your project’s kotlin version kotlinCompilerExtensionVersion "1.0.0-beta02" } … } dependencies { … implementation 'androidx.compose.ui:ui:1.0.0-beta02' // Tooling support (Previews, etc.) implementation 'androidx.compose.ui:ui-tooling:1.0.0-beta02' // Foundation (Border, Background, Box, Image, Scroll, shapes, animations, etc.) implementation 'androidx.compose.foundation:foundation:1.0.0-beta02' // Material Design implementation 'androidx.compose.material:material:1.0.0-beta02' // Material design icons implementation 'androidx.compose.material:material-icons-core:1.0.0-beta02' implementation 'androidx.compose.material:material-icons-extended:1.0.0-beta02' // Integration with activities implementation 'androidx.activity:activity-compose:1.3.0-alpha04' // Integration with ViewModels implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:1.0.0-alpha03' // Integration with observables implementation 'androidx.compose.runtime:runtime-livedata:1.0.0-beta02' … }

Try building the project and make sure it succeeds. If there is an error about the compiler version, make sure your com.android.tools.build:gradle is up to date.

I’ve also created a Spacing file that I use to define spacing within the examples. You should create a file and paste the enum class to that file so the code snippets below will work.

enum class Space(val value: Dp) { TWO(32.dp), ONE_AND_HALF(24.dp), ONE(16.dp), HALF(8.dp), QUARTER(4.dp) }

Let’s Dive In

This simple view is made up of multiple smaller views. We will go through each view and put them together to make a Post.

Avatar

Let’s take a look at the avatar first. Create a new file in your project called Avatar.kt and create a composable function using the following code.

@Composable fun Avatar( @DrawableRes avatarResId: Int, contentDescription: String = "", size: Dp = 50.dp ) { Image( painter = painterResource(avatarResId), contentDescription = contentDescription, modifier = Modifier .clip(shape = RoundedCornerShape(50)) .size(size) ) }

We’ve defined a composable called Avatar, a circular image. In XML, we would have defined an image using the ImageView tag and used its associated attributes to set the image and size. When working with Compose, images are defined using the Image composable function provided by the API. We define the drawable we would like to display using painterResource(avatarResId), which in this case is the parameter passed into Avatar and we can additionally set the content description via contentDescription. The real interesting part here is the modifier parameter. This parameter allows us to tweak our view. In this case here, I’ve set the size of the view as well as rounded the corners by 50% to give us a circular image.

One of the cool things you can see here is the use of parameters in the definition of our composable. This is a simple scenario, but you can easily take this idea to create flexible views that adapt to the data you pass to it.

Now that we have this view defined, we can see what it looks like by creating a new function and annotating it with @Preview. Let’s make a method in the same file called PreviewAvatar.

@Preview @Composable fun PreviewAvatar() { Avatar(avatarResId = R.drawable.avatar) }

Preview functions don’t take any arguments and are kind of like little “playgrounds” where you can see and test the composable views you make. In this case, I’m passing an avatar drawable I have and am checking to see if the Avatar showing up in the design tab is a circular image of my drawable. I can additionally pass a content description and size to confirm that everything is working.

Avatar preview

User

The User view is next. This view will be used to show the avatar and username in the post. Create a new file called User.kt and write a composable function called User.

@Composable fun User( @DrawableRes avatarResId: Int, username: String ) { Row( verticalAlignment = Alignment.CenterVertically ) { Avatar( avatarResId = avatarResId, size = 40.dp ) Text( text = username, fontSize = 16.sp, modifier = Modifier.padding(horizontal = Space.HALF.value) ) } }

Parts of this function should look familiar like declaring the parameters and the Avatar view that you just built, but you might be wondering what a Row is. A Row is a layout that will place its children horizontally. If you’ve written XML before, think LinearLayout with the orientation set to horizontal. What we’re doing here is saying that we want to place our views, Avatar and Text, horizontally with a vertical alignment. Similarly, we can write a Preview function to test and debug our view.

User preview

Post

We are finally ready to define our Post now. Like before, create a file called Post.kt and make a composable function called Post.

@Composable fun Post( @DrawableRes avatarResId: Int, name: String, text: String, @DrawableRes drawableRes: Int? = null ) { Column( modifier = Modifier .background(Color.White) .padding(Space.ONE.value, Space.HALF.value) ) { User(avatarResId = avatarResId, username = name) Divider( thickness = 0.5.dp, modifier = Modifier .padding(vertical = Space.QUARTER.value) ) drawableRes?.let { res -> Image( painter = painterResource(id = res), contentDescription = null, modifier = Modifier .fillMaxWidth() .clip(RoundedCornerShape(5)), contentScale = ContentScale.FillWidth ) } Text( text = text, modifier = Modifier.padding(vertical = Space.HALF.value) ) } }

This is the most complex composable we’ve written so far because there are a few things going on. First of all, we define a Column which is a layout that lays out its children vertically. If we didn’t specify how we wanted to place our views, they would just be stacked on top of each other similar to placing multiple children in a FrameLayout through XML.

The coolest part of this function, and an amazing feature of compose, can be seen when we try to unwrap drawableRes. Since we’re working with Kotlin code, we can perform operations like checking a value to decide what to show and hide, or in this case, unwrapping a nullable object. If drawableRes is not null, we can place an Image in the view and pass it the unwrapped id. Write a Preview function and play around with it.


One thing more experienced developers might notice is the use of nested views. Remember, we used a Row in the User composable and then placed that in a Column.

Doesn’t that affect performance like nested layouts defined in XML? Why not use a ConstraintLayout?

These are all great questions! The answer can be found here.

”… Compose handles nested layouts efficiently, making them a great way to design a complicated UI. This is an improvement from Android Views, where you need to avoid nested layouts for performance reasons.” - Android Dev Docs


It’s showtime. Navigate to MainActivity and replace setContent() with the following

setContent { MaterialTheme { Post( avatarResId = R.drawable.avatar, name = "AppLit", text = "Reach out to us at www.applit.io", drawableRes = R.drawable.toronto_skyline ) } }

Here we show the Post with a MaterialTheme. The MaterialTheme will apply a material design-based theme (colors, typography, etc.) to any composables within it. I’ll make a future post that explains theming in compose. Just a note here: you can make a Preview function in any activity just like we did in each file to preview your view. Hit run and check it out!

The code can be found in this repo under getting_started.

Closing

If you’re looking to launch a new product or improve your existing tech, reach out to us at www.applit.io. Our team has years of experience in mobile development and worked on apps used by millions of people around the world. Chances are you’ve probably used something one of us has made.