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.
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.
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.
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.