1. Before you begin
In this codelab, you use Jetpack Compose to build a simple Android app that displays a birthday message on the screen.
Prerequisites
- How to create an app in Android Studio.
- How to run an app on an emulator or your Android device.
What you'll learn
- How to write composable functions, such as
Text
,Column
andRow
composable functions. - How to display text in your app in a layout.
- How to format the text, such as changing text size.
What you'll build
- An Android app that displays a birthday greeting in text format, which looks like this screenshot when done:
What you'll need
- A computer with Android Studio installed
2. Watch the code-along video (Optional)
If you'd like to watch one of the course instructors complete the codelab, play the below video.
It's recommended to expand the video to full screen. In the video player, click the Full screen icon so you can see Android Studio and the code more clearly.
This step is optional. You can also skip the video and start the codelab instructions right away.
3. Set up a Happy Birthday app
In this task, you set up a project in Android Studio with an Empty Compose Activity template and change the text message to a personalized birthday greeting.
Create an Empty Compose Activity project
- In the Welcome to Android Studio dialog, select New Project.
- In the New Project dialog, select Empty Compose Activity and then click Next.
- Enter
Happy Birthday
in the Name field and then select a minimum API level of 24 (Nougat) in the Minimum SDK field and click Finish.
- Wait for Android Studio to create the project files and build the project.
- Click
Run ‘app'.
The app should look like this screenshot:
When you created this Happy Birthday app with the Empty Compose Activity template, Android Studio set up resources for a basic Android app, including a Hello Android! message on the screen. In this codelab, you learn how that message gets there, how to change its text to a birthday greeting, and how to add and format additional messages.
What is a user interface (UI)?
The user interface (UI) of an app is what you see on the screen: text, images, buttons, and many other types of elements, and how it's laid out on the screen. It's how the app shows things to the user and how the user interacts with the app.
This image contains a clickable button, text message, and text-input field where users can enter data.
Clickable button
Text message
Text-input field
Each of these elements is called a UI component. Almost everything you see on the screen of your app is a UI element (also known as a UI component). They can be interactive, like a clickable button or an editable input field, or they can be decorative images.
In the following apps, try to find as many UI components as you can.
In this codelab, you work with a UI element that displays text called a "Text
element."
4. What is Jetpack Compose?
Jetpack Compose is a modern toolkit for building Android UIs. Compose simplifies and accelerates UI development on Android with less code, powerful tools, and intuitive Kotlin capabilities. With Compose, you can build your UI by defining a set of functions, called composable functions, that take in data and emit UI elements.
Composable functions
Composable functions are the basic building block of a UI in Compose. A composable function:
- Describes some part of your UI.
- Doesn't return anything.
- Takes some input and generates what's shown on the screen.
- Might emit several UI elements.
Annotations
Annotations are means of attaching extra information to code. This information helps tools like the Jetpack Compose compiler, and other developers understand the app's code.
An annotation is applied by prefixing its name (the annotation) with the @
character at the beginning of the declaration you are annotating. Different code elements, including properties, functions, and classes, can be annotated. Later on in the course, you'll learn about classes.
The following diagram is an example of annotated function:
The following code snippet has examples of annotated properties. You will be using these in the coming codelabs.
// Example code, do not copy it over
@Json
val imgSrcUrl: String
@Volatile
private var INSTANCE: AppDatabase? = null
Annotations with parameters
Annotations can take parameters. Parameters provide extra information to the tools processing them. The following are some examples of the @Preview
annotation with and without parameters.
Annotation without parameters
Annotation previewing background
Annotation with a preview title
You can pass multiple parameters to the annotation, as shown here.
Annotation with a preview title and the system UI (the phone screen)
Jetpack Compose includes a wide range of built-in annotations, you have already seen @Composable
and @Preview
annotations so far in the course. You will learn more annotations and their usages in the latter part of the course.
Example of a composable function
The Composable function is annotated with the @Composable
annotation. All composable functions must have this annotation. This annotation informs the Compose compiler that this function is intended to convert data into UI. As a reminder, a compiler is a special program that takes the code you wrote, looks at it line by line, and translates it into something the computer can understand (machine language).
This code snippet is an example of a simple composable function that is passed data (the name
function parameter) and uses it to render a text element on the screen.
@Composable
fun Greeting(name: String) {
Text(text = "Hello $name!")
}
A few notes about the composable function:
- Composable functions can accept parameters, which let the app logic describe or modify the UI. In this case, your UI element accepts a
String
so that it can greet the user by name. - The function doesn't return anything. Composable functions that emit UI don't need to return anything because they describe the desired screen state instead of constructing UI elements. In other words, composable functions only describe the UI, they don't construct or create the UI, so there is nothing to return.
Notice the composable functions in code
- In Android Studio, open the
MainActivity.kt
file. - Scroll to the
DefaultPreview()
function and delete it. Add a new composable function,BirthdayCardPreview()
to preview theGreeting()
function, as follows. As a good practice, functions should always be named or renamed to describe their functionality.
@Preview(showBackground = true)
@Composable
fun BirthdayCardPreview() {
HappyBirthdayTheme {
Greeting("Android")
}
}
Composable functions can call other composable functions. In this code snippet, the preview function is calling the Greeting()
composable function.
Notice the previous function also has another annotation, a @Preview
annotation, with a parameter before the @Composable
annotation. You learn more about the arguments passed to the @Preview
annotation later in the course.
Composable function names
The compose function that returns nothing and bears the @Composable
annotation MUST be named using pascal case. Pascal case refers to a naming convention in which the first letter of each word in a compound word is capitalized. The difference between pascal case and camel case is that all words in pascal case are capitalized. In camel case, the first word is not capitalized.
The Compose function:
- MUST be a noun:
DoneButton()
- NOT a verb or verb phrase:
DrawTextField()
- NOT a nouned preposition:
TextFieldWithLink()
- NOT an adjective:
Bright()
- NOT an adverb:
Outside()
- Nouns MAY be prefixed by descriptive adjectives:
RoundIcon()
This guideline applies whether the function emits UI elements or not. To learn more see Naming Composable functions.
Example code. Do not copy over
// Do: This function is a descriptive PascalCased noun as a visual UI element
@Composable
fun FancyButton(text: String) {}
// Do: This function is a descriptive PascalCased noun as a non-visual element
// with presence in the composition
@Composable
fun BackButtonHandler() {}
// Don't: This function is a noun but is not PascalCased!
@Composable
fun fancyButton(text: String) {}
// Don't: This function is PascalCased but is not a noun!
@Composable
fun RenderFancyButton(text: String) {}
// Don't: This function is neither PascalCased nor a noun!
@Composable
fun drawProfileImage(image: ImageAsset) {}
5. Design pane in Android Studio
Android Studio lets you preview your composable functions within the IDE, instead of installing the app to an Android device or emulator. As you learned in the previous pathway, you can preview what your app looks like in the Design pane in Android Studio.
The composable function must provide default values for any parameters to preview it. For this reason, you can't preview the Greeting()
function directly. Instead, you need to add another function, the BirthdayCardPreview()
function in this case, that calls the Greeting()
function with an appropriate parameter.
@Preview(showBackground = true)
@Composable
fun BirthdayCardPreview() {
HappyBirthdayTheme {
Greeting("Android")
}
}
To view your preview:
- Build your code.
The preview should automatically update.
Another way to update or refresh the preview is to click Build & Refresh in the Design pane.
- In the
BirthdayCardPreview()
function, change the"Android"
argument in theGreeting()
function to your name.
fun BirthdayCardPreview() {
HappyBirthdayTheme {
Greeting("James")
}
}
- Click
Build & Refresh in the Design pane.
You should see the updated preview.
6. Add a new text element
In this task, you remove the Hello $name!
greeting and add a birthday greeting.
Add a new composable function
- In the
MainActivity.kt
file, delete theGreeting()
function definition. You will add your own function to display the greeting in the codelab later.
@Composable
fun Greeting(name: String) {
Text(text = "Hello $name!")
}
- Hover over
Greeting()
function. Notice that Android Studio highlights theGreeting()
function call and then hover the cursor over this function call to identify the error.
- Delete the
Greeting()
function call along with its arguments from theonCreate()
andBirthdayCardPreview()
functions. YourMainActivity.kt
file will look similar to this:
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
HappyBirthdayTheme {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colors.background
) {
}
}
}
}
}
@Preview(showBackground = true)
@Composable
fun BirthdayCardPreview(){
HappyBirthdayTheme {
}
}
- Before the
BirthdayCardPreview()
function, add a new function calledBirthdayGreetingWithText()
. Don't forget to add the@Composable
annotation before the function because this will be a compose function emitting aText
composable.
@Composable
fun BirthdayGreetingWithText() {
}
- It's a best practice to have your Composable accept a
Modifier
parameter, and pass thatmodifier
to its first child that emits UI. You will learn more aboutModifier
and child elements in the subsequent tasks and codelabs. For now, add aModifier
parameter to theBirthdayGreetingWithText()
function.
@Composable
fun BirthdayGreetingWithText(modifier: Modifier = Modifier) {
}
- Add a
message
parameter of typeString
to theBirthdayGreetingWithText()
composable function.
@Composable
fun BirthdayGreetingWithText(message: String, modifier: Modifier = Modifier) {
}
- In the
BirthdayGreetingWithText()
function, add aText
composable passing in the text message as a named argument.
@Composable
fun BirthdayGreetingWithText(message: String, modifier: Modifier = Modifier) {
Text(
text = message
)
}
This BirthdayGreetingWithText()
function displays text in the UI. It does so by calling the Text()
composable function.
Preview the function
In this task you will preview the BirthdayGreetingWithText()
function in the Design pane.
- Call the
BirthdayGreetingWithText()
function inside theBirthdayCardPreview()
function. - Pass a
String
argument to theBirthdayGreetingWithText()
function, a birthday greeting to your friend. You can customize it with their name if you like, such as"Happy Birthday Sam!"
.
@Preview(showBackground = true)
@Composable
fun BirthdayCardPreview() {
HappyBirthdayTheme {
BirthdayGreetingWithText(message = "Happy Birthday Sam!")
}
}
- In the Design pane, click
Build & Refresh and wait for the build to complete to preview your function.
7. Change font size
You added text to your user interface, but it doesn't look like the final app yet. In this task, you learn how to change the size, text color, and other attributes that affect the appearance of the text element. You can also experiment with different font sizes and colors.
Scalable pixels
The scalable pixels (SP) is a unit of measure for the font size. UI elements in Android apps use two different units of measurement: density-independent pixels (DP), which you use later for the layout, and scalable pixels (SP). By default, the SP unit is the same size as the DP unit, but it resizes based on the user's preferred text size under phone settings.
- In the
MainActivity.kt
file, scroll to theText()
composable in theBirthdayGreetingWithText()
function. - Pass the
Text()
function afontSize
argument as a second named argument and set it to a value of36.
sp
.
Text(
text = message,
fontSize = 36.sp
)
Android Studio highlights the .sp
code because you need to import some classes or properties to compile your app.
- Click
.sp
, which is highlighted by Android Studio. - Click Import in the popup to import the
androidx.compose.ui.unit.sp
to use the.sp
extension property.
- Scroll to the top of the file and notice the
import
statements, where you should see animport androidx.compose.ui.unit.sp
statement, which means that the package is added to your file by Android Studio.
- Click Build & Refresh in the Design pane to view the updated preview. Notice the change of the font size in the greeting preview.
Now you can experiment with different font sizes.
8. Add another text element
In your previous tasks, you added a birthday-greeting message to your friend. In this task, you sign your card with your name.
- In the
MainActivity.kt
file, scroll to theBirthdayGreetingWithText()
function. - Pass the function a
from
parameter of typeString
for your signature.
fun BirthdayGreetingWithText(message: String, from: String, modifier: Modifier = Modifier)
- After the birthday message
Text
composable, add anotherText
composable that accepts atext
argument set to thefrom
value.
Text(
text = from
)
- Add a
fontSize
named argument set to a value of24.sp
.
Text(
text = from,
fontSize = 24.sp
)
- Scroll to the
BirthdayCardPreview()
function. - Add another
String
argument to sign your card, such as"- from Emma"
.
BirthdayGreetingWithText(message = "Happy Birthday Sam!", from ="- from Emma")
- Click on Build & Refresh in the Design pane.
- Notice the preview.
A composable function might emit several UI elements. However, if you don't provide guidance on how to arrange them, Compose might arrange the elements in a way that you don't like. For example, the previous code generates two text elements that overlap each other because there's no guidance on how to arrange the two composables.
In your next task, you will learn how to arrange the composables in a row and in a column.
9. Arrange the text elements in a row and column
UI Hierarchy
The UI hierarchy is based on containment, meaning one component can contain one or more components, and the terms parent and child are sometimes used. The context here is that the parent UI elements contain children UI elements, which in turn can contain children UI elements. In this section, you will learn about Column
, Row
, and Box
composables, which can act as parent UI elements.
The three basic, standard layout elements in Compose are Column
, Row
, and Box
composables. You learn more about the Box
composable in the next codelab.
Column
, Row
, and Box
are composable functions that take composable content as arguments, so you can place items inside these layout elements. For example, each child element inside a Row
composable is placed horizontally next to each other in a row.
// Don't copy.
Row {
Text("First Column")
Text("Second Column")
}
These text elements display next to each other on the screen as seen in this image.
The blue borders are only for demonstration purposes and don't display.
Trailing lambda syntax
Notice in the previous code snippet that curly braces are used instead of parentheses in the Row
composable function. This is called Trailing Lambda Syntax. You learn about lambdas and trailing lambda syntax in detail later in the course. For now, get familiar with this commonly used Compose syntax.
Kotlin offers a special syntax for passing functions as parameters to functions, when the last parameter is a function.
If you want to pass a function as that parameter, you can use trailing lambda syntax. Instead of putting the function body along with the function name within the parentheses({}
), you put the parentheses along with the function body after the function name. This is a common practice in Compose, so you need to be familiar with how the code looks.
For example, the last parameter in the Row()
composable function is the content
parameter, a function that emits the child UI elements. Suppose you wanted to create a row that contains three text elements. This code would work, but it's very cumbersome:
Row(
content = {
Text("Some text")
Text("Some more text")
Text("Last text")
}
)
Because the content
parameter is the last one in the function signature and you pass its value as a lambda expression (for now, it's okay if you don't know what a lambda is, just familiarize yourself with the syntax), you can remove the content
parameter and the parentheses as follows:
Row {
Text("Some text")
Text("Some more text")
Text("Last text")
}
Arrange the text elements in a row
In this task, you arrange the text elements in your app in a row to avoid overlap.
- In the
MainActivity.kt
file, scroll to theBirthdayGreetingWithText()
function. - Add the
Row
composable around the text elements so that it shows a column with two text elements.
Now the function should look like this code snippet:
@Composable
fun BirthdayGreetingWithText(message: String, from: String, modifier: Modifier = Modifier) {
Row{
Text(
text = message,
fontSize = 36.sp,
)
Text(
text = from,
fontSize = 24.sp,
)
}
}
- Click
Row
, in the code snippet, which is highlighted. - Notice that Android Studio gives you multiple import choices for
Row
. - Click Import.
- From the package
androidx.compose.foundation.layout
, select the function that begins withRow(androidx.compose.ui.Modifier, androidx.compose.foundation.layout.Argument.Horizontal, androidx
....
- Click Build & Refresh to update the preview in the Design pane.
The preview looks much better now that there's no overlap. However, this isn't what you want because there's not enough room for your signature. In your next task, you arrange the text elements in a column to resolve this issue.
Arrange the text elements in a column
In this task, it is your turn to change the BirthdayGreetingWithText()
function to arrange the text elements in a column. Once you're done, don't forget to click Build & Refresh to update the preview, which should look like this screenshot:
Now that you've tried doing this on your own, feel free to check your code against the solution code in this snippet:
@Composable
fun BirthdayGreetingWithText(message: String, from: String, modifier: Modifier = Modifier) {
Column {
Text(
text = message,
fontSize = 36.sp,
)
Text(
text = from,
fontSize = 24.sp,
)
}
}
Import this package when prompted by Android Studio:
import androidx.compose.foundation.layout.Column
- Recollect that you need to pass the modifier parameter to the child elements in the composables.That means you need to pass the modifier parameter to the
Column
composable.
@Composable
fun BirthdayGreetingWithText(message: String, from: String, modifier: Modifier = Modifier) {
Column(modifier = modifier) {
Text(
text = message,
fontSize = 36.sp,
)
Text(
text = from,
fontSize = 24.sp,
)
}
}
10. Display on the device
Once you're happy with the preview, it's time to run your app on your device or emulator.
- In the
MainActivity.kt
file, scroll to theonCreate()
function. - Call the
BirthdayGreetingWithText()
function from theSurface
block. - Pass the
BirthdayGreetingWithText()
function, your birthday greeting and signature.
The completed onCreate()
function should look like this code snippet:
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
HappyBirthdayTheme {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colors.background
) {
BirthdayGreetingWithText( message = "Happy Birthday Sam!", from = "- from Emma")
}
}
}
}
}
- Build and run your app on the emulator.
11. Get the solution code
The completed MainActivity.kt
:
package com.example.happybirthday
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.sp
import com.example.happybirthday.ui.theme.HappyBirthdayTheme
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
HappyBirthdayTheme {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colors.background
) {
BirthdayGreetingWithText( message = "Happy Birthday Sam!", from = "- from Emma")
}
}
}
}
}
@Composable
fun BirthdayGreetingWithText(message: String, from: String, modifier: Modifier = Modifier) {
Column(modifier = modifier) {
Text(
text = message,
fontSize = 36.sp,
)
Text(
text = from,
fontSize = 24.sp,
)
}
}
@Preview(showBackground = true)
@Composable
fun BirthdayCardPreview() {
HappyBirthdayTheme {
BirthdayGreetingWithText(message = "Happy Birthday Sam!", from ="- from Emma")
}
}
12. Conclusion
You created your Happy Birthday app.
In the next codelab, you add a picture to your app, and change the alignment of the text elements to beautify it.
Summary
- Jetpack Compose is a modern toolkit for building Android UI. Jetpack Compose simplifies and accelerates UI development on Android with less code, powerful tools, and intuitive Kotlin APIs.
- The user interface (UI) of an app is what you see on the screen: text, images, buttons, and many other types of elements.
- Composable functions are the basic building block of Compose. A composable function is a function that describes some part of your UI.
- The Composable function is annotated with the
@Composable
annotation; this annotation informs the Compose compiler that this function is intended to convert data into UI. - The three basic standard layout elements in Compose are
Column
,Row,
andBox
. They are Composable functions that take Composable content, so you can place items inside. For example, each child within aRow
will be placed horizontally next to each other.