Search icon CANCEL
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Conferences
Free Learning
Arrow right icon
Arrow up icon
GO TO TOP
Android UI Development with Jetpack Compose

You're reading from   Android UI Development with Jetpack Compose Bring declarative and native UI to life quickly and easily on Android using Jetpack Compose and Kotlin

Arrow left icon
Product type Paperback
Published in Nov 2023
Publisher Packt
ISBN-13 9781837634255
Length 278 pages
Edition 2nd Edition
Languages
Tools
Arrow right icon
Author (1):
Arrow left icon
Thomas Künneth Thomas Künneth
Author Profile Icon Thomas Künneth
Thomas Künneth
Arrow right icon
View More author details
Toc

Table of Contents (18) Chapters Close

Preface 1. Part 1: Fundamentals of Jetpack Compose FREE CHAPTER
2. Chapter 1: Building Your First Compose App 3. Chapter 2: Understanding the Declarative Paradigm 4. Chapter 3: Exploring the Key Principles of Compose 5. Part 2: Building User Interfaces
6. Chapter 4: Laying Out UI Elements in Compose 7. Chapter 5: Managing State of Your Composable Functions 8. Chapter 6: Building a Real-World App 9. Chapter 7: Exploring App Architecture 10. Part 3: Advanced Topics
11. Chapter 8: Working with Animations 12. Chapter 9: Exploring Interoperability APIs 13. Chapter 10: Testing and Debugging Compose Apps 14. Chapter 11: Developing for Different Form Factors 15. Chapter 12: Bringing Your Compose UI to Different Platforms 16. Index 17. Other Books You May Enjoy

Saying hello to composable functions

As you will see shortly, composable functions are the essential building blocks of Compose apps; these elements make up the UI.

To take a first look at them, I will walk you through a simple app called Hello (Figure 1.1). If you have already cloned or downloaded the repository of this book, its project folder is located inside chapter_01. Otherwise, please do so now. To follow this section, open the project in Android Studio and open MainActivity.kt. The use case of our first Compose app is very simple. After you have entered your name and clicked on the Done button, you will see a greeting message:

Figure 1.1 – The Hello app

Figure 1.1 – The Hello app

Conceptually, the app consists of the following:

  • The welcome text
  • A row with an EditText equivalent and a button
  • A greeting message

Let’s look at how to create the app.

Showing a welcome text

Let’s start with the welcome text, our first composable function:

@Composable
fun Welcome() {
    Text(
        text = stringResource(id = R.string.welcome),
        style = MaterialTheme.typography.bodyLarge
    )
}

Composable functions can be easily identified by the @Composable annotation. They do not need to have a particular return type but instead emit UI elements. This is usually done by invoking other composables (for the sake of brevity, I will sometimes omit the word “function”). Chapter 3, Exploring the Key Principles of Compose, will cover this in greater detail.

In this example, Welcome() summons a text. Text() is a built-in composable function and belongs to the androidx.compose.material3 package. To invoke Text() just by its name, you need to import it, as follows:

import androidx.compose.material3.Text

Note that you can save import lines by using the * wildcard. To use Text() and other Material Design elements, your build.gradle file must include an implementation dependency to androidx.compose.material3:material3.

Note

Jetpack Compose supports two versions of Material Design. The androidx.compose.material package represents the older one, Material 2. Android 12 introduced a new iteration named Material You (Material 3). This book uses Material You and the androidx.compose.material3 package.

Looking back at the welcome text code, the Text() composable inside Welcome() is configured through two parameters, text and style. The first, text, specifies what text will be displayed. R.string may look familiar; it refers to definitions inside the strings.xml files. Just as in view-based apps, you define text for UI elements there. The stringResource() is a predefined composable function. It belongs to the androidx.compose.ui.res package.

The style parameter modifies the visual appearance of text. Its value, MaterialTheme.typography.bodyLarge, causes the output to look like a large body text. You will learn more about Compose styling and theming in the Styling a Compose app section of Chapter 6, Building a Real-World App.

The next composable looks quite similar. Can you spot the differences?

@Composable
fun Greeting(name: String) {
    Text(
        text = stringResource(id = R.string.hello, name),
        textAlign = TextAlign.Center,
        style = MaterialTheme.typography.bodyLarge
    )
}

Here, stringResource() receives an additional parameter. This is very convenient for replacing placeholders with actual text. The string is defined in strings.xml, as follows:

<string name="hello">Hello, %1$s.\nNice to meet you.</string>

The textAlign parameter specifies how text is positioned horizontally. Here, each line is centered.

Using rows, text fields, and buttons

Next, let’s turn to the text input field (Your name) and the Done button, which both appear on the same row. This is a very common pattern; therefore, Jetpack Compose provides a composable named Row(), which belongs to the androidx.compose.foundation.layout package. Just as with all composable functions, Row() can receive a comma-separated list of parameters inside ( ), and its children are put inside curly braces:

@Composable
fun TextAndButton(name: MutableState<String>,
                  nameEntered: MutableState<Boolean>) {
  Row(modifier = Modifier.padding(top = 8.dp)) {
      ...
  }
}

TextAndButton() requires two parameters, name and nameEntered. You will see what they are used for in the Showing a greeting message section. For now, please ignore their MutableState type.

Row() receives a parameter called modifier. Modifiers are a key technique in Jetpack Compose to influence both the look and behavior of composable functions. I will explain them in greater detail in Chapter 3, Exploring the Key Principles of Compose.

padding(top = 8.dp) means that the row will have a padding of eight density-independent pixels (.dp) at its upper side, thus separating itself from the welcome message above it.

Now, we will look at the text input field, which allows the user to enter a name:

TextField(
    value = name.value,
    onValueChange = {
        name.value = it
    },
    placeholder = {
        Text(text = stringResource(id = R.string.hint))
    },
    modifier = Modifier
        .alignByBaseline()
        .weight(1.0F),
    singleLine = true,
    keyboardOptions = KeyboardOptions(
        autoCorrect = false,
        capitalization = KeyboardCapitalization.Words,
    ),
    keyboardActions = KeyboardActions(onAny = {
        nameEntered.value = true
    })
)

TextField() belongs to the androidx.compose.material3 package. The composable can receive quite a few arguments; most of them are optional, though. Please note that the previous code fragment uses both the name and nameEntered parameters, which are passed to TextAndButton(). Their type is MutableState. MutableState objects carry changeable values, which you access as name.value or nameEntered.value.

The value parameter of a TextField() composable receives the current value of the text input field—for example, text that has already been input. onValueChange is invoked when changes to the text occur—for example, if the user enters or deletes something. But why is name.value used in both places? I will answer this question in the Showing a greeting message section.

Recomposition

Certain types trigger a so-called recomposition. For now, think of this as repainting an associated composable. MutableState is such a type. If we change its value, the TextField() composable is redrawn or repainted. Please note that neither term is entirely correct. We will cover recomposition in Chapter 3, Exploring the Key Principles of Compose.

Let’s briefly look at the remaining code. With alignByBaseline(), we can nicely align the baselines of other composable functions in a particular Row(). placeholder contains the text that is shown until the user has entered something. singleLine controls whether the user can enter multiple lines of text. Finally, keyboardOptions and keyboardActions describe the behavior of the onscreen keyboard. For example, certain actions will set nameEntered.value to true. I will show you soon why we do this.

However, we need to take a look at the Button() composable first. It also belongs to the androidx.compose.material package:

Button(modifier = Modifier
  .alignByBaseline()
  .padding(8.dp),
  onClick = {
    nameEntered.value = true
  }) {
  Text(text = stringResource(id = R.string.done))
}

Some things will already look familiar. For example, we call alignByBaseline() to align the baseline of the button with the text input field, and we apply a padding of eight density-independent pixels to all sides of the button using padding(). Now, onClick() specifies what to do when the button is clicked. Here, too, we set nameEntered.value to true.

The next composable function, Hello(), finally shows you why this is done.

Showing a greeting message

The following code snippet shows the Hello() composable function. Depending on how the user has already interacted with the app, it either shows a greeting or a welcome message, a button, and some text:

@Composable
fun Hello() {
  val name = remember { mutableStateOf("") }
  val nameEntered = remember { mutableStateOf(false) }
  Box(
    modifier = Modifier
      .fillMaxSize()
      .padding(16.dp),
    contentAlignment = Alignment.Center
  ) {
    if (nameEntered.value) {
      Greeting(name.value)
    } else {
      Column(horizontalAlignment =
             Alignment.CenterHorizontally) {
        Welcome()
        TextAndButton(name, nameEntered)
      }
    }
  }
}

Hello() emits Box(), which (depending on nameEntered.value) contains either a Greeting() or a Column() composable that, in turn, includes Welcome() and TextAndButton(). The Column() composable is quite similar to Row() but arranges its siblings vertically. As with the latter one and Box(), it belongs to the androidx.compose.foundation.layout package.

Box() can contain one or more children. Children are stacked—that is, placed on top of each other along the z axis. They are positioned inside the box according to the contentAlignment parameter. We will be exploring this in greater detail in the Combining basic building blocks section of Chapter 4, Laying Out UI Elements in Compose.

Have you noticed remember and mutableStateOf? Both are very important for creating and maintaining state. Generally speaking, state in an app refers to a value that can change over time. While this also applies to domain data (for example, the result of a web service call), state usually refers to something being displayed or used by a UI element, such as the name in the Hello example. If a composable function has (or relies on) state, it is recomposed (for now, repainted or redrawn) when that state changes. To get an idea of what this means, recall this composable:

@Composable
fun Welcome() {
    Text(
        text = stringResource(id = R.string.welcome),
        style = MaterialTheme.typography.subtitle1
    )
}

Welcome() is said to be stateless; all values that might trigger a recomposition remain the same for the time being. Hello(), on the other hand, is stateful, because it uses the name and nameEntered variables. They change over time. This may not be obvious if you look at the source code of Hello(). Please recall that both name and nameEntered are passed to TextAndButton() and modified there.

Do you recall that, in the previous section, I promised to explain why name.value is used in two places, providing the text to display and receiving changes after the user has entered something? This is a common pattern often used with states; Hello() creates and remembers state by invoking mutableStateOf() and remember, and it passes the state to another composable (TextAndButton()), which is called state hoisting. You will learn more about this in Chapter 5, Managing State of Your Composable Functions.

So far, you have seen the source code of quite a few composable functions but not their output. Android Studio has a very important feature called Compose preview. It allows you to view a composable function without running the app. In the next section, I will show you how to use this feature.

lock icon The rest of the chapter is locked
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at $19.99/month. Cancel anytime