Jetpack Compose is a modern toolkit designed to simplify UI development. It combines a reactive programming model with the conciseness and ease of use of the Kotlin programming language. It is fully declarative, meaning you describe your UI by calling a series of functions that transform data into a UI hierarchy. When the underlying data changes, the framework automatically recalls these functions, updating the view hierarchy for you.

A Compose app is made up of composable functions - just regular functions marked with @Composable, which can call other composable functions. A function is all you need to create a new UI component. The annotation tells Compose to add special support to the function for updating and maintaining your UI over time. Compose lets you structure your code into small chunks. Composable functions are often referred to as "composables" for short.

By making small reusable composables - it's easy to build up a library of UI elements used in your app. Each one is responsible for one part of the screen and can be edited independently.

Prerequisites

What you'll do

In this codelab, you will learn:

What you'll need

To start a new Compose project, open Android Studio Canary and select Start a new Android Studio project as shown:

If the screen above doesn't appear, go to File > New > New Project.

When creating a new project, choose Empty Compose Activity from the available templates.

Click Next and configure your project as usual. Make sure you select a minimumSdkVersion of at least API level 21, which is the minimum API Compose supports.

When choosing the Empty Compose Activity template, the following code is generated for you in your project. This project is already configured to use Compose. The AndroidManifest.xml file is created and the app/build.gradle (or build.gradle (Module: app)) file imports the Compose dependencies and enables Android Studio to work with Compose with the buildFeatures { compose true } flag.

Open the app/build.gradle file, it should look something like this:

android {
    ...
    composeOptions {
        kotlinCompilerExtensionVersion "${compose_version}"

        kotlinCompilerVersion "1.3.70-dev-withExperimentalGoogleExtensions-20200424"
    }
}

dependencies {
    ...
    implementation 'androidx.ui:ui-layout:"${compose_version}"'
    implementation 'androidx.ui:ui-material:"${compose_version}"'
    implementation 'androidx.ui:ui-tooling:"${compose_version}"'
    ...
}

Open MainActivity.kt and check out the code.

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MyApplicationTheme {
                Greeting("Android")
            }
        }
    }
}

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

@Preview(showBackground = true)
@Composable
fun DefaultPreview() {
    MyApplicationTheme {
        Greeting("Android")
    }
}

In the next section, you'll see what each method does, and how you can improve them to create flexible and reusable layouts.

Go through the different classes and methods related to Compose that the template has generated for you.

Composable functions

A composable function is a regular function annotated with @Composable. This enables your function to call other @Composable functions within it. You can see how the Greeting function is marked as @Composable. This function will produce a piece of UI hierarchy displaying the given input, String. Text is a composable function provided by the library.

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

Compose in an Android app

With Compose, Activities remain the entry point to an Android app. In our project, MainActivity is launched when the user opens the app (as it's specified in the AndroidManifest.xml file). You use setContent to define your layout, but instead of using an XML file as you'd do in the traditional View system, you call Composable functions within it.

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MyApplicationTheme {
                Greeting("Android")
            }
        }
    }
}

MyApplicationTheme is a way to style Composable functions. You'll see more about this in the Theming your app section. To see how the text displays on the screen, you can either run the app in an emulator or device, or use the Android Studio preview.

To use the Android Studio preview, you just have to mark any parameterless Composable function or functions with default parameters with the @Preview annotation and build your project. You can already see a Preview Composable function in the MainActivity.kt file. You can have multiple previews in the same file and give them names.

@Preview(showBackground = true, name = "Text preview")
@Composable
fun DefaultPreview() {
    MyApplicationTheme {
        Greeting(name = "Android")
    }
}

The preview might not appear if Code is selected. Click Split to see the preview.

In order to set a background color for the Text and the rest of the screen, you need to define a Surface that contains it:

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

The components nested inside Surface will be drawn on top of that background color (unless specified otherwise by another Surface).

When you add that code to the project, you will see a Build & Refresh button in the top-right corner of Android Studio. Tap on it or build the project to see the new changes in the preview.

You can see the new changes in the preview:

Modifiers

Most Compose UI elements like Surface and Text accept an optional modifier parameter. Modifier parameters tell a UI element how to lay out, display, or behave within its parent layout. Modifiers are regular Kotlin objects.

You can assign them to variables and reuse them. You can also chain several of these modifiers one after the other thanks to the factory-extension functions or with the overloaded operator plus that are merged to a single argument.

The padding modifier will apply an amount of space around the element it decorates. In order to add padding to your text on the screen, you can add the modifier to your Text:

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

Click Build & Refresh to see the new changes.

Compose reusability

The more components you add to the UI, the more levels of nesting you create, just like other functions in your codebase. This can affect readability if a function becomes really large. By making small reusable components it's easy to build up a library of UI elements used in your app. Each one is responsible for one small part of the screen and can be edited independently.

Differentiate what's a common configuration for your app and what's specific to a particular view. When you refactor your UI code, you have to mark your function with the @Composable annotation to tell the compiler that it is a Composable function, enabling it to call other composable functions. The compiler also enforces that the function has to be called from another Composable function.

Notice how the composable functions in MainActivity.kt are outside of the MainActivity class, declared as top-level functions. The more code you have outside of the Activity, the more you can share and reuse.

First, refactor your code to make it more reusable and create a new @Composable MyApp function that contains the Compose UI logic specific to this Activity.

Second, it doesn't make sense that the background color of the app is placed in the reusable Greeting Composable. That configuration should be applied to every piece of UI on this screen, so move the Surface from Greeting to your new MyApp function:

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MyApp()
        }
    }
}

@Composable
fun MyApp() {
    MyApplicationTheme {
        Surface(color = Color.Yellow) {
            Greeting(name = "Android")
        }
    }
}

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

@Preview
@Composable
fun DefaultPreview() {
    MyApp()
}

You'd like to reuse the MyApp Composable function in different activities since it defines top-level configuration that can be used in multiple places. However, its current state doesn't allow it since it has Greeting embedded in it. Keep reading to learn how to create a container that holds common app configuration.

Making container functions

What if you want to create a container that has all the common configurations of your app?

To make a generic container, create a Composable function that takes as a parameter a Composable function (here called children) which returns Unit. You return Unit because, as you might have noticed, all Composable functions must return Unit:

@Composable
fun MyApp(content: @Composable() () -> Unit) {
    MyApplicationTheme {
        Surface(color = Color.Yellow) {
            content()
        }
    }
}

Inside your function, you define all of the shared configuration you want your container to provide and then invoke the passed children Composable. In this case, you want to apply a MaterialTheme and a yellow surface, and then call content().

You can use it like this (utilizing Kotlin's trailing lambda syntax):

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MyApp {
                Greeting("Android")
            }
        }
    }
}

@Preview("Text preview")
@Composable
fun DefaultPreview() {
    MyApp {
        Greeting("Android")
    }
}

This code is equivalent to what you had in the previous section, but is now more flexible. Making container composable functions is a good practice that improves readability and encourages reusing code.

Calling Composable functions multiple times using Layouts

You extract UI components into Composable functions so that you can reuse them without duplicating code. In the following example, you can show two greetings reusing the same Composable function with different parameters.

To place items in a vertical sequence, use the Column Composable function (similar to a vertical LinearLayout).

@Composable
fun MyScreenContent() {
    Column {
        Greeting("Android")
        Divider(color = Color.Black)
        Greeting("there")
    }
}

As you want MyScreenContent to be what your users see when they open the app, you have to modify the MainActivity code accordingly. Also, you can modify the preview code, so that you can iterate faster in Android Studio without deploying the app to a device or emulator.

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MyApp {
                MyScreenContent()
            }
        }
    }
}

@Preview("MyScreen preview")
@Composable
fun DefaultPreview() {
    MyApp {
        MyScreenContent()
    }
}

If you refresh the preview, you'll see the items placed vertically:

Compose and Kotlin

Compose functions can be called like any other function in Kotlin. This makes building UIs really powerful since you can add statements to influence how the UI will be displayed.

For example, you can use a for loop to add elements to the MyScreenContent's Column:

@Composable
fun MyScreenContent(names: List<String> = listOf("Android", "there")) {
    Column {
        for (name in names) {
            Greeting(name = name)
            Divider(color = Color.Black)
        }
    }
}

Reacting to state changes is at the very heart of Compose. Compose apps transform data into UI by calling Composable functions. If your data changes, you recall these functions with the new data, creating an updated UI. Compose offers tools for observing changes in your app's data, which will automatically recall your functions—this is called recomposing. Compose also looks at what data is needed by an individual composable so that it only needs to recompose components whose data has changed and can skip composing those that are not affected.

Under the hood, Compose uses a custom Kotlin compiler plugin so when the underlying data changes, the composable functions can be reinvoked to update the UI hierarchy.

For example, when you call Greeting("Android")in the MyScreenContent Composable function, you are hard-coding the input ("Android"), so Greeting will get added to the UI tree once and won't ever change, even if the body of MyScreenContent gets recomposed.

To add internal state to a composable, use the state function, which gives a composable mutable memory. The value held by the state will be remembered the next time that the composable runs. And, if there are multiple instances of the composable at different places on the screen, each copy will get its own version of the state. You can think of internal state as a private variable in a class.

The composable function will automatically be subscribed to it. If the state changes, composables that read these fields will be recomposed.

Make a counter that keeps track of how many times the user has clicked a Button. Define Counter as a Composable function with a Button that shows how many times it has been clicked:

@Composable
fun Counter() {

    val count = state { 0 }

    Button(onClick = { count.value++ }) {
        Text("I've been clicked ${count.value} times")
    }
}

Since Button reads count.value, Button will be recomposed whenever it changes and will display the new value of count.

You now can add a Counter to your screen:

@Composable
fun MyScreenContent(names: List<String> = listOf("Android", "there")) {
    Column {
        for (name in names) {
            Greeting(name = name)
            Divider(color = Color.Black)
        }
        Divider(color = Color.Transparent, thickness = 32.dp)
        Counter()
    }
}

User interactions don't work with the preview at the moment. To see it working, you have to run the app in an emulator or a real device. If you run the app, you can see how Counter maintains state and it increases on every click.

Source of truth

In Composable functions, state that can be useful to calling functions should be exposed because it's the only way it can be consumed or controlled—this process is called state hoisting.

State hoisting is the way to make internal state controllable by the function that called it. Making state hoistable avoids duplicating state and introducing bugs, helps reuse composables, and makes composables substantially easier to test. State that is not interesting to a composable caller should be internal.

There are situations where the consumer might not care about a certain state (for example, in a scroller, the scrollerPosition state is exposed whereas the maxPosition is not). The source of truth belongs to whoever creates and controls that state.

In the example, since consumers of Counter can be interested in the state, you can defer it to the caller completely by introducing a (count, updateCount) pair as parameters of Counter. In this way, Counter is hoisting its state:

@Composable
fun MyScreenContent(names: List<String> = listOf("Android", "there")) {
    val counterState = state { 0 }

    Column {
        for (name in names) {
            Greeting(name = name)
            Divider(color = Color.Black)
        }
        Divider(color = Color.Transparent, thickness = 32.dp)
        Counter(
            count = counterState.value,
            updateCount = { newCount ->
                counterState.value = newCount
            }
        )
    }
}

@Composable
fun Counter(count: Int, updateCount: (Int) -> Unit) {
    Button(onClick = { updateCount(count+1) }) {
        Text("I've been clicked $count times")
    }
}

You briefly touched on Column before, which is used to place items in a vertical sequence. In the same way, you can use Row to place items horizontally.

Row and Column place their items one after the other. If you want to make some items flexible so they occupy the screen with a certain weight, you can use the weight modifier.

Say you want to put the Button at the bottom of the screen while the other content remains at the top. You can do this with the following steps:

  1. Wrap the flexible items inside another Column with a weight modifier. Since this Column is flexible and the rest of the content is inflexible, it will take up as much space as it can. It will be able to use all the remaining height after the outer Column is sized.
  2. Leave the Counter in the outer Column that, by default, is inflexible.
@Composable
fun MyScreenContent(names: List<String> = listOf("Android", "there")) {
    val counterState = state { 0 }

    Column(modifier = Modifier.fillMaxHeight()) {
        Column(modifier = Modifier.weight(1f)) {
            for (name in names) {
                Greeting(name = name)
                Divider(color = Color.Black)
            }
        }
        Counter(
            count = counterState.value,
            updateCount = { newCount ->
                counterState.value = newCount
            }
        )
    }
}

You use the fillMaxHeight() modifier on the outer Column to make it occupy as much screen as it can (fillMaxSize() and fillMaxWidth() modifiers are also available).

  1. If you refresh the preview, you can see the new changes:

As another example of how you can leverage Kotlin in Compose, you could change the Button's background color depending on the number of times the user tapped on it with an if else statement:

@Composable
fun Counter(count: Int, updateCount: (Int) -> Unit) {
    Button(
        onClick = { updateCount(count+1) },
        backgroundColor = if (count > 5) Color.Green else Color.White
    ) {
        Text("I've been clicked ${state.count} times")
    }
}

Full code for this section

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.Composable
import androidx.compose.MutableState
import androidx.compose.state
import androidx.ui.core.Modifier
import androidx.ui.core.setContent
import androidx.ui.foundation.Text
import androidx.ui.graphics.Color
import androidx.ui.layout.Column
import androidx.ui.layout.fillMaxHeight
import androidx.ui.layout.padding
import androidx.ui.material.Button
import androidx.ui.material.Divider
import androidx.ui.material.MaterialTheme
import androidx.ui.material.Surface
import androidx.ui.tooling.preview.Preview
import androidx.ui.unit.dp


class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MyApp {
                MyScreenContent()
            }
        }
    }
}

@Composable
fun MyApp(content: @Composable() () -> Unit) {
    MyApplicationTheme {
        Surface(color = Color.Yellow) {
            content()
        }
    }
}

@Composable
fun MyScreenContent(names: List<String> = listOf("Android", "there")) {
    val counterState = state { 0 }

    Column(modifier = Modifier.fillMaxHeight()) {
        Column(modifier = Modifier.weight(1f)) {
            for (name in names) {
                Greeting(name = name)
                Divider(color = Color.Black)
            }
        }
        Counter(
            count = counterState.value,
            updateCount = { newCount ->
                counterState.value = newCount
            }
        )
    }
}

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

@Composable
fun Counter(count: Int, updateCount: (Int) -> Unit) {
    Button(
        onClick = { updateCount(count + 1) },
        backgroundColor = if (count > 5) Color.Green else Color.White
    ) {
        Text("I've been clicked $count times")
    }
}

@Preview("MyScreen preview")
@Composable
fun DefaultPreview() {
    MyApp {
        MyScreenContent()
    }
}

We didn't define any styling for any composables in the previous examples of the codelab. How can we theme our app? A theme is part of the hierarchy of Components as any other Composable function. MyApplicationTheme is an example of this.

If you open the Theme.kt file, you'll see that MyApplicationTheme uses MaterialTheme in its implementation. MaterialTheme is a Composable function that reflects the styling principles from the Material design specification. That styling information will cascade down to the components that are inside it, which may read the information to style themselves. In our original simple UI, we can use MyApplicationTheme as follows:

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MyApplicationTheme {
                Greeting(name = "Android")
            }
        }
    }
}

Since MyApplicationTheme wraps MaterialTheme internally, Greeting will be styled with the properties defined in the theme. We can retrieve properties of MaterialTheme and use them to define the style of the Text in this way:

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

The Text composable in the example above sets three arguments, a String to be displayed, modifiers, and a TextStyle. You can create your own TextStyle, or you can retrieve a theme-defined style by using MaterialTheme.typography. This construct gives you access to the Material defined text styles, such as h1, body1 or subtitle1. In our example, we're using the h1 style defined in the theme.

If you see the preview with this code, you'll see the following screenshot:

@Preview(showBackground = true, name = "Text preview")
@Composable
fun DefaultPreview() {
    MyApplicationTheme {
        Greeting("Android")
    }
}

Create your app's theme

You can create an app theme like the one in MyApplicationTheme.kt. Let's see the code in the file in more details so that you can understand what's happening.

As you might want MyApplicationTheme in multiple places of our app (likely in all Activities), we'll create a reusable component.

As we saw in the ‘Theming your app' section, a theme is a Composable function that takes other children Composable functions. To make it reusable, we create a container Composable function as we did in the ‘Declarative UI' section:

import androidx.compose.Composable

@Composable
fun MyApplicationTheme(darkTheme: Boolean = isSystemInDarkTheme(), content: @Composable() () -> Unit) {
    // TODO 
}

MaterialTheme holds configuration for colors and typography, we'll just change some colors at this point to achieve the design we want.

import androidx.compose.Composable
import androidx.ui.graphics.Color
import androidx.ui.material.MaterialTheme
import androidx.ui.material.lightColorPalette


private val DarkColorPalette = darkColorPalette(
    primary = purple200,
    primaryVariant = purple700,
    secondary = teal200
)

private val LightColorPalette = lightColorPalette(
    primary = purple500,
    primaryVariant = purple700,
    secondary = teal200
)

@Composable
fun MyAppTheme(content: @Composable() () -> Unit) {
    val colors = if (darkTheme) {
        DarkColorPalette
    } else {
        LightColorPalette
    }

    MaterialTheme(colors = colors) {
        content()
    }
}

We provided our custom colors overriding those from lightColorPalette and darkColorPalette methods which default colors to the light and dark Material baseline theme unless otherwise provided. That is passed to the constructor of MaterialTheme that as we saw before, implements the styling principles from the Material design specification.

In the same way, you can override the typography and shapes used in the app by passing them to the MaterialTheme function.

To learn more about Compose, check out the Jetnews code sample that showcases the current UI capabilities of Compose. Moreover, if you want to add Compose to your application, learn how to do that in the documentation.

You can also watch these Google talks about Jetpack Compose:

Jetpack Compose is in Technical Preview, please do not use Compose in production apps. You can provide feedback or report bugs here.