The Android Developer Challenge is back! Submit your idea before December 2.

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 this tutorial, you'll build a simple UI component with declarative functions. You won't be editing any XML layouts or directly creating the UI widgets. Instead, you will call Jetpack Compose functions to say what elements you want, and the Compose compiler will do the rest.

Full Preview
Note: Jetpack Compose is currently in Developer Preview. The API surface is not yet finalized, and should not be used in production apps.

Lesson 1: Composable functions

Jetpack Compose is built around composable functions. These functions let you define your app's UI programmatically by describing its shape and data dependencies, rather than focusing on the process of the UI's construction. To create a composable function, just add the @Composable annotation to the function name.

Add a text element

To begin, follow the Jetpack Compose setup instructions, and create an app using the Empty Compose Activity template. Then add a text element to your blank activity. You do this by defining a content block, and calling the Text() function.

The setContent block defines the activity's layout. Instead of defining the layout contents with an XML file, we call composable functions. Jetpack Compose uses a custom Kotlin compiler plugin to transform these composable functions into the app's UI elements. For example, the Text() function is defined by the Compose UI library; you call that function to declare a text element in your app.

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            Text("Hello world!")
        }
    }
}
        

Define a composable function

Composable functions can only be called from within the scope of other composable functions. To make a function composable, add the @Composable annotation. To try this out, define a Greeting() function which is passed a name, and uses that name to configure the text element.

class MainActivity : AppCompatActivity() {
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContent {
      Greeting("Android")
    }
  }
}
@Composable
fun Greeting(name: String) {
    Text (text = "Hello $name!")
}
    

Preview your function in Android Studio

Beginning with Android Studio 4.0 Canary 1, Android Studio lets you preview your composable functions within the IDE, instead of needing to download the app to an Android device or emulator. The main restriction is, the composable function must not take any parameters. For this reason, you can't preview the Greeting() function directly. Instead, make a second function named PreviewGreeting(), which calls Greeting() with an appropriate parameter. Add the @Preview annotation before @Composable.

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

@Preview
@Composable
fun PreviewGreeting() {
    Greeting("Android")
}

Rebuild your project. The app itself doesn't change, since the new previewGreeting() function isn't called anywhere, but Android Studio adds a preview window. This window shows a preview of the UI elements created by composable function marked with the @Preview annotation. To update the previews at any time, click the refresh button at the top of the preview window.

Figure 1. Using Android Studio to preview your composable functions.

Lesson 2: Layouts

UI elements are hierarchical, with elements contained in other elements. In Compose, you build a UI hierarchy by calling composable functions from other composable functions.

Start with some Text

Go back to your activity, and replace the Greeting() function with a new NewsStory() function. For the rest of the tutorial, you'll be modifying that NewsStory() function, and won't be touching the Activity code any more.

It's a best practice to create separate preview functions that aren't called by the app; having dedicated preview functions improves performance, and also makes it easier to set up multiple previews later on. So, create a default preview function that does nothing but call the NewsStory() function. As you make changes to the NewsStory() through this tutorial, the preview reflects the changes.

This code creates three text elements inside the content view. However, since we haven't provided any information about how to arrange them, the three text elements are drawn on top of each other, making the text unreadable.

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

@Composable
fun NewsStory() {
    Text("A day in Shark Fin Cove")
    Text("Davenport, California")
    Text("December 2018")
}

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

Using a Column

The Column() function lets you stack elements vertically. Add Column() to the NewsStory() function.

The default settings stack all the children directly, one after another, with no spacing. The column itself is put in the content view's top left corner.

@Composable
fun NewsStory() {
    Column {
        Text("A day in Shark Fin Cove")
        Text("Davenport, California")
        Text("December 2018")
    }
}
      

Add style settings to the column

By passing parameters to the Column() call, you can configure the column's size and position, and how the column's children are arranged.

The settings have the following meanings:

  • crossAxisSize: Specifies the size of the column along its cross (horizontal) axis. Setting crossAxisSize to LayoutSize.Expand specifies that the column should be as wide as its parent allows.
  • modifier: Lets you make miscellaneous formatting changes. In this case, we apply a Spacing modifier, which sets the column off from the surrounding view.

Aside from the change in padding, your app won't look any different after you apply these settings. You've overridden the default setting of LayoutSize.Wrap, but the new setting doesn't affect the positioning of the text elements.

@Composable
fun NewsStory() {
    Column(
        crossAxisSize = LayoutSize.Expand,
        modifier=Spacing(16.dp)
    ) {
        Text("A day in Shark Fin Cove")
        Text("Davenport, California")
        Text("December 2018")
    }
}
      

Add a picture

We want to add a graphic above the text. Use the Resource Manager to add this photo to your app's drawable resources, with the name header.

Now modify your NewsStory() function. You'll need the app's context to grab the image from the resources, then call DrawImage() to add the graphic to the app. The image won't be proportioned correctly, but that's okay - you'll fix that in the next step.

@Composable
fun NewsStory() {
    val image = +imageResource(R.drawable.header)

    Column(
        crossAxisSize = LayoutSize.Expand,
        modifier=Spacing(16.dp)
        ) {
        DrawImage(image)

        Text("A day in Shark Fin Cove")
        Text("Davenport, California")
        Text("December 2018")
    }
}
      

The graphic is added to your layout, but it expands to fill the entire view, with the text on top of it. To style the graphic, put it inside a Container, a general-purpose content object for holding and arranging other UI elements. You can then apply size and position settings to the Container.

  • expanded: Specifies the size of the container. The default is false (make the container the size of its children). By setting expanded to true, you specify that the container should be as large as its parent will allow.
  • height: Specifies the height of the container. The height setting takes precedence over the setting for expanded; the result is that the container is sized to a height of 180 DP, but the width is the maximum allowed by its parent.
@Composable
fun NewsStory() {
    val image = +imageResource(R.drawable.header)

    Column(
        crossAxisSize = LayoutSize.Expand,
        modifier=Spacing(16.dp)
        ) {
        Container(expanded = true, height = 180.dp) {
            DrawImage(image)
        }

        Text("A day in Shark Fin Cove")
        Text("Davenport, California")
        Text("December 2018")
    }
}
      

Add a spacer to separate the graphic from the headings.

@Composable
fun NewsStory() {
    val image = +imageResource(R.drawable.header)

    Column(
        crossAxisSize = LayoutSize.Expand,
        modifier=Spacing(16.dp)

        ) {
        Container(expanded = true, height = 180.dp) {
            DrawImage(image)
        }

        HeightSpacer(16.dp)

        Text("A day in Shark Fin Cove")
        Text("Davenport, California")
        Text("December 2018")
    }
}
      

Lesson 3: Material design

Compose is built to support material design principles. Many of its UI elements implement material design out of the box. In this lesson, you'll style your app with material widgets.

Apply a shape

One of the pillars of the Material Design System is Shape. Use the Clip() function to round the corners of the image.

The Shape is invisible, but the graphic is cropped to fit the Shape, so it now has slightly rounded corners.

@Composable
fun NewsStory() {
    val image = +imageResource(R.drawable.header)

    Column(
        crossAxisSize = LayoutSize.Expand,
        modifier=Spacing(16.dp)
    ) {
        Container(expanded = true, height = 180.dp) {
            Clip(shape = RoundedCornerShape(8.dp)) {
                DrawImage(image)
            }
        }

        HeightSpacer(16.dp)

        Text("A day in Shark Fin Cove")
        Text("Davenport, California")
        Text("December 2018")
    }
}
                      

Style the text

Compose makes it easy to take advantage of Material Design principles. Apply MaterialTheme() to the components you've created.

@Composable
fun NewsStory() {
    val image = +imageResource(R.drawable.header)

    MaterialTheme {
        Column(
            crossAxisSize = LayoutSize.Expand,
            modifier=Spacing(16.dp)
        ) {
            Container(expanded = true, height = 180.dp) {
                Clip(shape = RoundedCornerShape(8.dp)) {
                    DrawImage(image)
                }
            }

            HeightSpacer(16.dp)

            Text("A day in Shark Fin Cove")
            Text("Davenport, California")
            Text("December 2018")
        }
    }
}
        

The changes are subtle, but the text now uses MaterialTheme's default text style. Next, apply specific paragraph styles to each text element.

@Composable
fun NewsStory() {
    val image = +imageResource(R.drawable.header)

    MaterialTheme {
        Column(
            crossAxisSize = LayoutSize.Expand,
            modifier=Spacing(16.dp)
        ) {
            Container(expanded = true, height = 180.dp) {
                Clip(shape = RoundedCornerShape(8.dp)) {
                    DrawImage(image)
                }
            }

            HeightSpacer(16.dp)

            Text("A day in Shark Fin Cove",
                style = +themeTextStyle { h6 })
            Text("Davenport, California",
                style = +themeTextStyle { body2 })
            Text("December 2018",
                style = +themeTextStyle { body2 })
        }
    }
}
        

The Material palette uses a few basic colors. To emphasize parts of the text, adjust the text opacity.

@Composable
fun NewsStory() {
    val image = +imageResource(R.drawable.header)

    MaterialTheme {
        Column(
            crossAxisSize = LayoutSize.Expand,
            modifier=Spacing(16.dp)
        ) {
            Container(expanded = true, height = 180.dp) {
                Clip(shape = RoundedCornerShape(8.dp)) {
                    DrawImage(image)
                }
            }

            HeightSpacer(16.dp)

            Text("A day in Shark Fin Cove",
                style = (+themeTextStyle { h6 }).withOpacity(0.87f))
            Text("Davenport, California",
                style = (+themeTextStyle { body2 }).withOpacity(0.87f))
            Text("December 2018",
                style = (+themeTextStyle { body2 }).withOpacity(0.6f))
        }
    }
}
     

In this case, the article's title was quite short. But sometimes an article has a long title, and we don't want the long title to throw off the app's appearance. Try changing the first text element.

@Composable
fun NewsStory() {
    val image = +imageResource(R.drawable.header)

    MaterialTheme {
        Column(
            crossAxisSize = LayoutSize.Expand,
            modifier=Spacing(16.dp)
        ) {
            Container(expanded = true, height = 180.dp) {
                Clip(shape = RoundedCornerShape(8.dp)) {
                    DrawImage(image)
                }
            }

            HeightSpacer(16.dp)

            Text("A day wandering through the sandhills in Shark " +
                "Fin Cove, and a few of the sights I saw",
                style = (+themeTextStyle { h6 }).withOpacity(0.87f))
            Text("Davenport, California",
                style = (+themeTextStyle { body2 }).withOpacity(0.87f))
            Text("December 2018",
                style = (+themeTextStyle { body2 }).withOpacity(0.6f))
        }
    }
}
        

Configure the text element to set a maximum length of 2 lines. The setting has no effect if the text is short enough to fit in that limit, but the displayed text is automatically truncated if it is too long.

@Composable
fun NewsStory() {
    val image = +imageResource(R.drawable.header)

    MaterialTheme {
        Column(
            crossAxisSize = LayoutSize.Expand,
            modifier=Spacing(16.dp)
        ) {
            Container(expanded = true, height = 180.dp) {
                Clip(shape = RoundedCornerShape(8.dp)) {
                    DrawImage(image)
                }
            }

            HeightSpacer(16.dp)

            Text("A day wandering through the sandhills in Shark " +
                "Fin Cove, and a few of the sights I saw",
                maxLines = 2, overflow = TextOverflow.Ellipsis,
                style = (+themeTextStyle { h6 }).withOpacity(0.87f))
            Text("Davenport, California",
                style = (+themeTextStyle { body2 }).withOpacity(0.87f))
            Text("December 2018",
                style = (+themeTextStyle { body2 }).withOpacity(0.6f))
        }
    }
}
        

All done

Great work! You've learned the basics of Compose.

What you've covered:

  • Defining composable functions
  • Using and styling columns to improve layout
  • Styling your app with the Material Design principles

If you want to dig deeper on some of these steps, try the Jetpack Compose Basics codelab.

Start building with Compose

Now that you've learned the basics of Compose, set up your environment so you can try it out yourself.

Resources

Sample

Try out the basics in Jetnews App

Featuring demos of multiple components, the sample app Jetnews provides working code examples for developers interested in understanding how to use Compose across an app.

Reference

Compose UI API

Dive into the docs to learn about the Jetpack Compose APIs referenced in this tutorial:

Guide

Material Design

Jetpack Compose is designed with Material Design out of the box. Material Design guidelines provide everything you need to know about how to design your app, from the user experience flow to visual design, motion, fonts, and more.