Build a simple app with text composables

Stay organized with collections Save and categorize content based on your preferences.

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 and Row 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:

ca82241f560e8b99.png

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 This symbol shows 4 corners on a square highlighted, to indicate full screen mode. 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

  1. In the Welcome to Android Studio dialog, select New Project.
  2. In the New Project dialog, select Empty Compose Activity and then click Next.
  3. 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.

6ecfe9ae1e5aa850.png

  1. Wait for Android Studio to create the project files and build the project.
  2. Click fd26b2e3c2870c3.png Run ‘app'.

The app should look like this screenshot:

c67f06ea2afef154.png

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.

e5abb8ad9f9ae2a7.png

Clickable button

fded30871a8e0eca.png

Text message

aafb9c476f72d558.png

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:

prefix character is @ annotation is composable followed by the function declaration

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.

15169d39d744c179.png

Annotation without parameters

992de02d7b5dbfda.png

Annotation previewing background

fbc159107d248a84.png

Annotation with a preview title

You can pass multiple parameters to the annotation, as shown here.

510f8443a174f972.png

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

  1. In Android Studio, open the MainActivity.kt file.
  2. Scroll to the DefaultPreview() function and delete it. Add a new composable function, BirthdayCardPreview() to preview the Greeting() 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.

c284448a820d577c.png

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:

  1. Build your code.

The preview should automatically update.

Another way to update or refresh the preview is to click fdd133641cfac2b3.png Build & Refresh in the Design pane.

5cec5263ba04ea1.png

  1. In the BirthdayCardPreview() function, change the "Android" argument in the Greeting() function to your name.
fun BirthdayCardPreview() {
    HappyBirthdayTheme {
        Greeting("James")
    }
}
  1. Click fdd133641cfac2b3.png Build & Refresh in the Design pane.

You should see the updated preview.

4c1634ec586ca3ba.png

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

  1. In the MainActivity.kt file, delete the Greeting() 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!")
}
  1. Hover over Greeting() function. Notice that Android Studio highlights the Greeting() function call and then hover the cursor over this function call to identify the error.

46fed08f63f1b3c6.png

  1. Delete the Greeting() function call along with its arguments from the onCreate()and BirthdayCardPreview() functions. Your MainActivity.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 {
    }
}
  1. Before the BirthdayCardPreview() function, add a new function called BirthdayGreetingWithText(). Don't forget to add the @Composable annotation before the function because this will be a compose function emitting a Text composable.
@Composable
fun BirthdayGreetingWithText() {
}
  1. It's a best practice to have your Composable accept a Modifier parameter, and pass that modifier to its first child that emits UI. You will learn more about Modifier and child elements in the subsequent tasks and codelabs. For now, add a Modifier parameter to the BirthdayGreetingWithText() function.
@Composable
fun BirthdayGreetingWithText(modifier: Modifier = Modifier) {
}
  1. Add a message parameter of type String to the BirthdayGreetingWithText() composable function.
@Composable
fun BirthdayGreetingWithText(message: String, modifier: Modifier = Modifier) {
}
  1. In the BirthdayGreetingWithText() function, add a Text 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.

  1. Call the BirthdayGreetingWithText() function inside the BirthdayCardPreview() function.
  2. Pass a String argument to the BirthdayGreetingWithText() 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!")
    }
}
  1. In the Design pane, click ea3433426a37f49b.png Build & Refresh and wait for the build to complete to preview your function.

eadbd65191fd4632.png

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.

  1. In the MainActivity.kt file, scroll to the Text() composable in the BirthdayGreetingWithText() function.
  2. Pass the Text() function a fontSize argument as a second named argument and set it to a value of 36.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.

6b6e60b13e085a13.png

  1. Click .sp, which is highlighted by Android Studio.
  2. Click Import in the popup to import the androidx.compose.ui.unit.sp to use the .sp extension property.

a1a623c584e7e6dc.png

  1. Scroll to the top of the file and notice the import statements, where you should see an import androidx.compose.ui.unit.sp statement, which means that the package is added to your file by Android Studio.

80ae3819ddfc7306.png

  1. Click Build & Refresh in the Design pane to view the updated preview. Notice the change of the font size in the greeting preview.

Displaying Build and refresh option

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.

  1. In the MainActivity.kt file, scroll to the BirthdayGreetingWithText() function.
  2. Pass the function a from parameter of type String for your signature.
fun BirthdayGreetingWithText(message: String, from: String, modifier: Modifier = Modifier)
  1. After the birthday message Text composable, add another Text composable that accepts a text argument set to the from value.
Text(
   text = from
)
  1. Add a fontSize named argument set to a value of 24.sp.
Text(
   text = from,
   fontSize = 24.sp
)
  1. Scroll to the BirthdayCardPreview() function.
  2. Add another String argument to sign your card, such as "- from Emma".
BirthdayGreetingWithText(message = "Happy Birthday Sam!", from ="- from Emma")
  1. Click on Build & Refresh in the Design pane.
  2. Notice the preview.

c59bb2754ab5db49.png

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.

9270b7e10f954dcb.png

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 showing three elements  arranged vertically and a row showing three elements arranged horizontally

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.

6f74a11c03564a61.png

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.

function parameter is the last parameter

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.

  1. In the MainActivity.kt file, scroll to the BirthdayGreetingWithText() function.
  2. 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,
       )
   }
}
  1. Click Row, in the code snippet, which is highlighted.
  2. Notice that Android Studio gives you multiple import choices for Row.
  3. Click Import.

Row function is highlighted with two popups one showing unresolved error another showing the import

  1. From the package androidx.compose.foundation.layout, select the function that begins with Row(androidx.compose.ui.Modifier, androidx.compose.foundation.layout.Argument.Horizontal, androidx....

dfad9fcfae49aa7a.png

  1. Click Build & Refresh to update the preview in the Design pane.

Birthday greeting and signature are displayed next to each other in a row.

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:

Birthday greeting and signature displayed in a column one below the other.

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

  1. In the MainActivity.kt file, scroll to the onCreate() function.
  2. Call the BirthdayGreetingWithText() function from the Surface block.
  3. 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")
               }
           }
       }
   }
}
  1. Build and run your app on the emulator.

ca82241f560e8b99.png

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, and Box. They are Composable functions that take Composable content, so you can place items inside. For example, each child within a Row will be placed horizontally next to each other.

Learn more