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. Set up a Happy Birthday app
In this task, you set up a project in Android Studio with the Empty Activity template and change the text message to a personalized birthday greeting.
Create an Empty Activity project
- In the Welcome to Android Studio dialog, select New Project.
- In the New Project dialog, select Empty Activity and then click Next.
- In the Name field enter
Happy Birthday
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 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 inside a Card
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.
3. 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 describe 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.
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 arguments to the annotation, as shown here.
Android studio screenshot showing code and preview
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:
- Jetpack Compose is built around composable functions. These functions let you define your app's UI programmatically by describing how it should look, 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. - Composable functions can accept arguments, 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.
Notice the composable functions in code
- In Android Studio, open the
MainActivity.kt
file. - Scroll to the
GreetingPreview()
function. This composable function helps preview theGreeting()
function. As a good practice, functions should always be named or renamed to describe their functionality. Change the name of this function toBirthdayCardPreview()
.
@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 can be in either case.
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()
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) {}
4. 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, it is recommended not to 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:
- In the
BirthdayCardPreview()
function, change the"Android"
argument in theGreeting()
function to your name.
@Preview(showBackground = true)
@Composable
fun BirthdayCardPreview() {
HappyBirthdayTheme {
Greeting("James")
}
}
- The preview should automatically update.
You should see the updated preview.
5. 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.
Remove the following code
@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
Text(
text = "Hello $name!",
modifier = modifier
)
}
- Inside the
onCreate()
function, notice that theGreeting()
function call is now colored red. This red coloring indicates an error. Hover over the cursor over this function call and Android Studio will display information regarding 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.colorScheme.background
) {
}
}
}
}
}
@Preview(showBackground = true)
@Composable
fun BirthdayCardPreview() {
HappyBirthdayTheme {
}
}
- Before the
BirthdayCardPreview()
function, add a new function calledGreetingText()
. Don't forget to add the@Composable
annotation before the function because this will be a compose function describing aText
composable.
@Composable
fun GreetingText() {
}
- It's a best practice to have your Composable accept a
Modifier
parameter, and pass thatmodifier
to its first child. You will learn more aboutModifier
and child elements in the subsequent tasks and codelabs. For now, add aModifier
parameter to theGreetingText()
function.
@Composable
fun GreetingText(modifier: Modifier = Modifier) {
}
- Add a
message
parameter of typeString
to theGreetingText()
composable function.
@Composable
fun GreetingText(message: String, modifier: Modifier = Modifier) {
}
- In the
GreetingText()
function, add aText
composable passing in the text message as a named argument.
@Composable
fun GreetingText(message: String, modifier: Modifier = Modifier) {
Text(
text = message
)
}
This GreetingText()
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 GreetingText()
function in the Design pane.
- Call the
GreetingText()
function inside theBirthdayCardPreview()
function. - Pass a
String
argument to theGreetingText()
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 {
GreetingText(message = "Happy Birthday Sam!")
}
}
- In the Design pane updated automatically. Preview your changes.
6. 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 theGreetingText()
function. - Pass the
Text()
function afontSize
argument as a second named argument and set it to a value of100.
sp
.
Text(
text = message,
fontSize = 100.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 Android Studio adds the package to your file.
- Notice the updated preview of the font size. The reason for overlapping message is you need to specify the line height.
- Update the
Text
composable to include the line height.
@Composable
fun GreetingText(message: String, modifier: Modifier = Modifier) {
Text(
text = message,
fontSize = 100.sp,
lineHeight = 116.sp,
)
}
Now you can experiment with different font sizes.
7. 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 theGreetingText()
function. - Pass the function a
from
parameter of typeString
for your signature.
fun GreetingText(message: String, from: String, modifier: Modifier = Modifier)
- After the birthday message
Text
composable, add anotherText
composable that accepts atext
argument set to thefrom
value.
@Composable
fun GreetingText(message: String, from: String, modifier: Modifier = Modifier) {
Text(
// ...
)
Text(
text = from
)
}
- Add a
fontSize
named argument set to a value of36.sp
.
Text(
text = from,
fontSize = 36.sp
)
- Scroll to the
BirthdayCardPreview()
function. - Add another
String
argument to sign your card, such as"From Emma"
.
GreetingText(message = "Happy Birthday Sam!", from = "From Emma")
- Notice the preview.
A composable function might describe 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.
8. 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.
When you pass a function as that parameter, you can use trailing lambda syntax. Instead of putting the function inside the parentheses, you can place it outside the parentheses in curly braces. This is a recommended and 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 describes 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 to use named parameter for the trailing lambda:
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 theGreetingText()
function. - Add the
Row
composable around the text elements so that it shows a row with two text elements. Select the twoText
composables, click on the light bulb. Select Surround with widget > Surround with Row.
Now the function should look like this code snippet:
@Composable
fun GreetingText(message: String, from: String, modifier: Modifier = Modifier) {
Row {
Text(
text = message,
fontSize = 100.sp,
lineHeight = 116.sp,
)
Text(
text = from,
fontSize = 36.sp
)
}
}
- Android Studio auto imports
Row
function for you. Scroll to the top and notice the import section. Theimport androidx.compose.foundation.layout.Row
should have been added. - Observe the updated preview in the Design pane. Temporarily change the font size for the birthday message to
30.sp
.
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 GreetingText()
function to arrange the text elements in a column. The preview 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 GreetingText(message: String, from: String, modifier: Modifier = Modifier) {
Column {
Text(
text = message,
fontSize = 100.sp,
lineHeight = 116.sp,
)
Text(
text = from,
fontSize = 36.sp
)
}
}
Notice the auto imported package by Android Studio:
import androidx.compose.foundation.layout.Column
Recollect that you need to pass the modifier parameter to the child element in the composables. That means you need to pass the modifier parameter to the Column
composable.
@Composable
fun GreetingText(message: String, from: String, modifier: Modifier = Modifier) {
Column(modifier = modifier) {
Text(
text = message,
fontSize = 100.sp,
lineHeight = 116.sp,
)
Text(
text = from,
fontSize = 36.sp
)
}
}
9. Add greeting to the app
Once you're happy with the preview, it's time to add the composable to your app on your device or emulator.
- In the
MainActivity.kt
file, scroll to theonCreate()
function. - Call the
GreetingText()
function from theSurface
block. - Pass the
GreetingText()
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.colorScheme.background
) {
GreetingText(message = "Happy Birthday Sam!", from = "From Emma")
}
}
}
}
}
- Build and run your app on the emulator.
Align greeting to the center
- To align the greeting in the center of the screen add a parameter called
verticalArrangement
set it toArrangement.Center
. You will learn more on theverticalArrangement
in a later codelab.
@Composable
fun GreetingText(message: String, from: String, modifier: Modifier = Modifier) {
Column(
verticalArrangement = Arrangement.Center,
modifier = modifier
) {
// ...
}
}
- Add
8.dp
padding around the column. It is a good practice to use padding values in increments of4.dp
.
@Composable
fun GreetingText(message: String, from: String, modifier: Modifier = Modifier) {
Column(
verticalArrangement = Arrangement.Center,
modifier = modifier.padding(8.dp)
) {
// ...
}
}
- To further beautify your app, align the greeting text to the center using
textAlign
.
Text(
text = message,
fontSize = 100.sp,
lineHeight = 116.sp,
textAlign = TextAlign.Center
)
In the above screenshot, only the greeting is center aligned because of the textAlign
parameter. The signature, From Emma has the default alignment which is left.
- Add padding to the signature and align it to the right.
Text(
text = from,
fontSize = 36.sp,
modifier = Modifier
.padding(16.dp)
.align(alignment = Alignment.End)
)
Adopt good practice
It is a good practice to pass the modifier attribute(s) along with the modifier from the parent composable. Update the modifier parameter in the GreetingText()
as follows:
onCreate()
Surface(
//...
) {
GreetingText(
message = "Happy Birthday Sam!",
from = "From Emma",
modifier = Modifier.padding(8.dp)
)
}
GreetingText()
@Composable
fun GreetingText(message: String, from: String, modifier: Modifier = Modifier) {
Column(
verticalArrangement = Arrangement.Center,
modifier = modifier
) {
// ...
}
}
Build and run your app on the emulator to see the final result.
10. 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.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
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.colorScheme.background
) {
GreetingText(
message = "Happy Birthday Sam!",
from = "From Emma",
modifier = Modifier.padding(8.dp)
)
}
}
}
}
}
@Composable
fun GreetingText(message: String, from: String, modifier: Modifier = Modifier) {
Column(
verticalArrangement = Arrangement.Center,
modifier = modifier
) {
Text(
text = message,
fontSize = 100.sp,
lineHeight = 116.sp,
textAlign = TextAlign.Center
)
Text(
text = from,
fontSize = 36.sp,
modifier = Modifier
.padding(16.dp)
.align(alignment = Alignment.End)
)
}
}
@Preview(showBackground = true)
@Composable
fun BirthdayCardPreview() {
HappyBirthdayTheme {
GreetingText(message = "Happy Birthday Sam!", from = "From Emma")
}
}
11. 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.