Chapters

Hide chapters

Jetpack Compose by Tutorials

First Edition · Android 11 · Kotlin 1.4 · Android Studio Canary

Before You Begin

Section 0: 3 chapters
Show chapters Hide chapters

5. Combining Composables
Written by Denis Buketa

Great job on completing the first section of this book! Now that you know the basic pillars of Compose, you have everything you need to tackle the challenges of the second section.

The goal of the second section is to show you Jetpack Compose in action. Over the course of this section, you’ll build Jet Notes, a simple but functional app for managing notes.

Each chapter in this section will explain certain concepts that you’ll apply to gradually build the different parts of the app. Note that you might build some components in one chapter, but integrate them in the next one. Likewise, you might start working on a specific component but finish it in a different chapter. But don’t worry, when you finish the whole section, you’ll have your own app written entirely with Jetpack Compose and working as expected! :]

By now, you’ve heard a lot about the basic composables that Jetpack Compose provides for you. In this chapter:

  • You’ll learn how to think about UI design when building it with Jetpack Compose.
  • You’ll see how you can combine basic composables to create complex UI.
  • You’ll create two components with different complexity for Jet Notes.

Let’s first explore the features you’ll build for your app.

Application features

Before you start writing code, have a look at the app concept and its features:

Application Overview
Application Overview

Don’t worry about the details on each screen. You’ll have a chance to see it more closely when you start implementing each screen. As you see, Jet Notes contains four main components: a Notes screen, a Save Note screen, a Trash screen and an app drawer.

The Notes screen displays the list of created notes. From here, the user can open an existing note, create a new one or open the app drawer.

The Save Note screen has two modes: an edit mode and a create a new note mode. When the user clicks on a note in the Notes screen, the Save Note screen will open in edit mode. The user can then edit the note or simply move it to the Trash screen by clicking a trash icon on the app bar.

To create a new note, the user taps on the Floating Action Button (FAB) available in the Notes screen. That opens the Save Note screen in the mode for creating a new note.

There are two types of notes: regular notes and checkable notes. Checkable notes are notes that the user can mark — or check — as done. The user can make any note checkable by using a switch component in the Save Note screen. In the Notes screen, checkable notes have a checkbox to mark the note as done.

Tapping the navigation icon on the app bar or swiping from the left border of the screen opens the app drawer. The app drawer switches between the Notes and the Trash screens. Using the drawer, a user can also change the app’s theme from light to dark.

In the Trash screen, the user can switch between regular and checkable notes using two tabs. The user can select notes and restore them or delete them permanently.

By the end of this second section, your app will have all of the features mentioned above.

Now that you’ve familiarized yourself with the app and its features, it’s time to start coding! :]

Project overview

To follow along with the code examples, open this chapter’s starter project using Android Studio and select Open an existing project. Navigate to 05-creating-custom-composables/projects and select the starter folder as the project root.

Once the project opens, let it build and sync and you’ll be ready to go!

Project Structure
Project Structure

Here are the packages that are already set up for you and what they contain:

  • data: Contains the code related to the database that stores the notes. It lets you add, remove and update notes.

  • dependencyinjection: Has one class that’s responsible for creating and providing the dependencies you’ll need.

  • domain: Contains two domain models named NoteModel and ColorModel, which represent notes and colors, respectively.

  • routing: Has logic that lets you navigate between screens.

  • theme: Contains color definitions and a composable function that lets you change the app’s theme.

  • util: Contains a utility composable function to handle the back press. It also provides an extension method for creating Color instances from String color hex definitions.

  • viewmodel: Contains the ViewModel you’ll need to implement to manage notes.

  • JetNotesApplication.kt: An Application that initializes the dependency injector.

  • MainActivity.kt: Contains the setContent() call, which sets the first composable function and behaves as the root UI component.

  • SplashActivity.kt: Responsible for the splash screen that you see when you open the app.

Once you’re familiar with the file organization, build and run the app. You’ll see an empty screen with no content, as shown below:

Empty Starter Project — App State
Empty Starter Project — App State

Skip ahead to the final project and you’ll see that you’ll build components in this chapter, but you won’t fully finish them or integrate them into Jet Notes yet. However, you can use the final project as a reference to track your progress while you build the composables. In the next chapters, you’ll iterate upon them and improve the app.

Thinking in Compose

Before you start coding, look at the Notes screen design once more and try to think in Compose. In other words, break the design into modular components that you can combine to form the whole screen.

Notes Screen — Components
Notes Screen — Components

For example, you can break the Notes screen into the following components:

  • Notes Screen: This component represents the whole Notes screen.
  • Top App Bar: Responsible for displaying the top app bar, which holds the navigation action and the title.
  • Notes List: Renders the list of created notes.
  • Floating Action Button: Opens the Save Note screen so the user can create a new note.
  • Note: Represents an individual note.
  • App Drawer: Contains the drawer that displays when the user swipes from the side or taps on the navigation icon.

In Compose, these components are all represented by composable functions. As a developer, you get to decide how deep you want to break down a specific design into its components.

It’s also important to consider how you’ll use each component. For example, look at the design of the Notes and the Trash screens. Both screens use the same Note component, so creating a reusable Note composable makes sense.

This is a great example of thinking in Compose.

Bottom-up approach

When building your apps with Jetpack Compose, it’s smart to start with smaller composables and build your way up through the design. You call this way of working a bottom-up approach.

This is smart because building your app from the smallest components lets you decouple and reuse your code from the very start. By the time you reach the highest-level components, such as the Notes & Trash Screens, you’ll have built all the fundamental components, so you can easily reuse them in those two screens. This saves time and helps with stability, by reducing the amount of code you need to write!

Now, look at the Notes screen again and consider which component is a good candidate to start with.

If you follow the bottom-up approach and choose the most fundamental components, you need to start with the Note composable. After building the Note, you’ll be able to use it all over the app.

Note Component
Note Component

When you try to break down the Note component, you’ll notice that you can build it with the basic composables you learned about in the previous section. The color widget, the note’s description and the checkbox are organized in a Row. The note’s title and its description are organized in a Column.

Now that you have an idea about how to break down your composable, it’s time to start coding! :]

Creating the Note composable

Use Android Studio to create a new package called ui.components. Then, in that package, create a new Kotlin file named Note.kt. Finally, add the following code to Note.kt:

import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview

@Composable
fun Note() {

}

@Preview
@Composable
private fun NotePreview() {
  Note()
}

In this code, you simply created a composable function to represent your note. You also added a NotePreview to preview the composable that you’re building in Android Studio.

For this to work, make sure you’ve selected the Split option at the top-right corner in Android Studio. This option allows you to preview your composables in the Design panel, while still being able to modify the code.

Build your project now. At this stage, you still won’t see anything in the Preview panel because haven’t added any composables that emit your UI. Let’s do that next!

Android Studio - Preview
Android Studio - Preview

Emitting the note’s content

Now that you’ve built the Note composable, your next step is to add the code that will emit the note’s content. Add the following code to Note()’s body:

Box(
  modifier = Modifier
    .size(40.dp)
    .background(rwGreen)
)
Text(text = "Title", maxLines = 1)
Text(text = "Content", maxLines = 1)
Checkbox(
  checked = false,
  onCheckedChange = { },
  modifier = Modifier.padding(start = 8.dp)
)

For this to work, add the following imports to Note.kt:

import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.Checkbox
import androidx.compose.material.Text
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.raywenderlich.android.jetnotes.theme.rwGreen

You’ve just added composables to represent the color widget, title, content and checkbox. Don’t bother with the color widget for now — you won’t style it in this chapter. That widget relies heavily on modifiers, which you’ll learn about in Chapter 6, “Using Compose Modifiers”. At this point, if you see modifiers in code, don’t spend too much time thinking about them. Just know that they make the UI look a bit nicer. :]

Build your project and you should see something like this in your preview:

Note Composable — Preview
Note Composable — Preview

By adding these composables, you’ve managed to accomplish something: The note’s components now appear in the Preview panel. However, as you can see, they’re stacked on top of each other. Remember that when you add composable functions, you’re describing the hierarchy of the elements to render on the screen.

Previously, you read how, at its core, Compose only knows how to work with trees to emit specific items. So, you can represent the hierarchy that you’re describing with composable functions by a tree where the nodes are composables.

The four composables inside Note() will produce the following tree with four nodes:

Note Composable - Tree Hierarchy
Note Composable - Tree Hierarchy

This is exactly what describing Jetpack Compose as a declarative toolkit means. The body of the function describes how the UI will look. In this case, the UI will contain four elements. Since no layout policy is described here, the composables will stack upon one other. This is exactly what the preview panel displayed.

It’s not, however, what you want. For your next step, you’ll add layout structure to your composable. Start by replacing the Note() body with the following code:

Row(modifier = Modifier.fillMaxWidth()) {
  Box(
    modifier = Modifier
      .size(40.dp)
      .background(rwGreen)
  )
  Column(modifier = Modifier.weight(1f)) {
    Text(text = "Title", maxLines = 1)
    Text(text = "Content", maxLines = 1)
  }
  Checkbox(
    checked = false,
    onCheckedChange = { },
    modifier = Modifier.padding(start = 8.dp)
  )
}

Add the necessary imports as well:

import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth

In the previous code, you organized the title and content in a column. You then aligned the resulting column in a row, along with the Box() and Checkbox() composables.

Build your project again or refresh the Preview panel, and you’ll see something like this:

At this point, your Note() doesn’t look quite like it did in the initial design. To make them match, you need modifiers. But hey, no worries! You’ll continue working on it in Chapter 6, “Using Compose Modifiers”.

Right now, you’ll focus on building the remaining complex composables that make up your app.

Building the app drawer composable

The next thing you’ll do is a little more complex. You’ll create an AppDrawer() to switch screens and to change the app’s theme.

Once again, before you start coding, look at the design and try to break it into smaller components.

App Drawer — Components
App Drawer — Components

As shown in the figure, you can split this UI component into the following composables:

  • AppDrawer: Your root composable for the drawer.
  • AppDrawerHeader: Contains a header with a drawer icon and the app’s title.
  • ScreenNavigationButton: Represents a button that the user can tap to switch between screens.
  • LightDarkThemeItem: Lets the user change between light and dark themes.

Let’s build these components!

Adding a header to the drawer

Once again, you’ll take the bottom-up approach to building the AppDrawer(). You’ll implement smaller components first, then combine them.

In ui.components, create a new file named AppDrawer.kt. Then, add the following code to it:

@Composable
private fun AppDrawerHeader() {
  Row(modifier = Modifier.fillMaxWidth()) {
    Image(
      imageVector = Icons.Filled.Menu,
      colorFilter = ColorFilter
        .tint(MaterialTheme.colors.onSurface),
      modifier = Modifier.padding(16.dp)
    )
    Text(
      text = "JetNotes",
      modifier = Modifier
        .align(alignment = Alignment.CenterVertically)
    )
  }
}

@Preview
@Composable
fun AppDrawerHeaderPreview() {
  JetNotesTheme {
    AppDrawerHeader()
  }
}

For this to work, add the following imports as well:

import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Menu
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.raywenderlich.android.jetnotes.theme.JetNotesTheme

With this code, you’ve created a composable for the app drawer header. It’s a relatively simple composable, where you use an Image() and a Text() and organize them in a Row().

You also added modifiers to add padding and alignment to these components. Again, don’t bother yourself with modifiers as much. You’ll learn more about them in the next chapter.

For the Image(), you used a colorFilter to set its color. Specifically, you used MaterialTheme.colors.onSurface for the tint. The MaterialTheme.colors palette is drawn from the system and the root composable functions you use. If you check out the code in AppDrawerHeaderPreview() you’ll see the following:

@Preview
@Composable
fun AppDrawerHeaderPreview() {
  JetNotesTheme {
    AppDrawerHeader()
  }
}

The root composable is JetNotesTheme() in your case. This is a predefined composable function in the Theme.kt file. As you use that for the root component, it passed down all its defined colors to the rest of the components, effectively styling them all in the same way. However, that theme doesn’t specify the onSurface color yet, so the default value is Color.Black.

For now, that’s all you need to know about theming. You’ll learn more in Chapter 8, “Applying Material Design to Compose”. You could also preview your composable without it, though, if it’s confusing.

Build your project and you’ll see the following result in your preview panel:

AppDrawerHeader Composable — Preview
AppDrawerHeader Composable — Preview

Creating the navigation button composable

Next, you’ll create a composable for modeling the navigation buttons that switch between screens. To do so, add the following code to AppDrawer.kt:

@Composable
private fun ScreenNavigationButton(
  icon: ImageVector,
  label: String,
  isSelected: Boolean,
  onClick: () -> Unit
) {
  val colors = MaterialTheme.colors

  // Define alphas for the image for two different states 
  // of the button: selected/unselected
  val imageAlpha = if (isSelected) {
    1f
  } else {
    0.6f
  }

  // Define color for the text for two different states 
  // of the button: selected/unselected
  val textColor = if (isSelected) {
    colors.primary
  } else {
    colors.onSurface.copy(alpha = 0.6f)
  }

  // Define color for the background for two different states 
  // of the button: selected/unselected
  val backgroundColor = if (isSelected) {
    colors.primary.copy(alpha = 0.12f)
  } else {
    colors.surface
  }
}

Add the following import as well:

import androidx.compose.ui.graphics.vector.ImageVector

In the design, the Screen Navigation button has an icon and a label. Here, you’ve added two parameters to your composable to allow that: icon and label.

You’ve also given the button two states: selected and unselected. To track which state the button is in, you added the parameter isSelected.

Each state renders differently. The code you added to the body of the function prepares colors for both states. Notice that you used primary, onSurface and surface colors; these colors are defined in your JetNotesTheme() from Theme.kt. As mentioned previously, if some of these colors aren’t specified when you create a color palette for the theme, the default values are used.

Also notice that you’ve added the onClick parameter. Since your button is clickable, it’s a good practice to expose that behavior through a lambda function so that the parent composable can take responsibility for it. You’ll learn more about how to handle clicks and other events in Chapter 7, “Managing State In Compose”.

Next, add the following code to the bottom of ScreenNavigationButton():

Surface( // 1
  modifier = Modifier
    .fillMaxWidth()
    .padding(start = 8.dp, end = 8.dp, top = 8.dp),
  color = backgroundColor,
  shape = MaterialTheme.shapes.small
) {
  Row( // 2
    horizontalArrangement = Arrangement.Start,
    verticalAlignment = Alignment.CenterVertically,
    modifier = Modifier
      .clickable(onClick = onClick)
      .fillMaxWidth()
      .padding(4.dp)
  ) {
    Image(
      imageVector = icon,
      colorFilter = ColorFilter.tint(textColor),
      alpha = imageAlpha
    )
    Spacer(Modifier.preferredWidth(16.dp)) // 3
    Text(
      text = label,
      style = MaterialTheme.typography.body2,
      color = textColor,
      modifier = Modifier.fillMaxWidth()
    )
  }
}

Add the following imports as well:

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.material.Surface
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.preferredWidth

There’s quite a bit of code here, but here’s a breakdown:

  1. You use Surface() to provide the background color and shape for your button.
  2. Inside Surface(), you use a Row() to align the icon and label for the button.
  3. Finally, you used Spacer() to add some space between the icon and the label.

Now, you need to create a preview function to visualize your button in the Preview panel. In the same file, add the following function:

@Preview
@Composable
fun ScreenNavigationButtonPreview() {
  JetNotesTheme {
    ScreenNavigationButton(
      icon = Icons.Filled.Home,
      label = "Notes",
      isSelected = true,
      onClick = { }
    )
  }
}

Don’t forget to import the Home icon:

import androidx.compose.material.icons.filled.Home

As you see, ScreenNavigationButtonPreview() just calls your ScreenNavigationButton(), passing in the parameters it needs. Apart from the icon and label for the button, note how you define its state as selected using isSelected = true. In addition, note how onClick() just uses an empty lambda as its argument, since you don’t need this behavior for the preview.

Build your project and you should see the following result:

ScreenNavigationButton Composable — Preview
ScreenNavigationButton Composable — Preview

Great! Now, your drawer button is done and you’re ready to move on to the next task.

Adding a theme switcher

The theme switcher is a toggle button that lets the user change the app’s theme from light to dark.

Add the following code to the end of AppDrawer.kt:

@Composable
private fun LightDarkThemeItem() {
  Row(
    Modifier
      .padding(8.dp)
  ) {
    Text(
      text = "Turn on dark theme",
      style = MaterialTheme.typography.body2,
      color = MaterialTheme.colors.onSurface.copy(alpha = 0.6f),
      modifier = Modifier
        .weight(1f)
        .padding(start = 8.dp, top = 8.dp, end = 8.dp)
    )
    Switch(
      checked = JetNotesThemeSettings.isDarkThemeEnabled,
      onCheckedChange = { JetNotesThemeSettings.isDarkThemeEnabled = it },
      modifier = Modifier
        .padding(start = 8.dp, end = 8.dp)
        .align(alignment = Alignment.CenterVertically)
    )
  }
}

@Preview
@Composable
fun LightDarkThemeItemPreview() {
  JetNotesTheme {
    LightDarkThemeItem()
  }
}

Also include the following imports, to avoid Android Studio’s complaints:

import androidx.compose.material.Switch
import com.raywenderlich.android.jetnotes.theme.JetNotesThemeSettings

The code you just added is quite straightforward. For this composable, you used a Row() to align the content horizontally. As you see, the content is just a Text() and a Switch(). The interesting part is what the Switch() does.

Look at this composable and you’ll observe that the checked and onCheckedChange parameters rely on JetNotesThemeSettings.isDarkThemeEnabled to handle the state. There are a few new concepts here, but you’ll learn more about them in chapter 7, “Managing State In Compose”. For now, don’t worry about them. Just consider that this mechanism allows you to change the app’s theme internally.

Build the project and, in the preview, you’ll see the following:

LightDarkThemeItem Composable — Preview
LightDarkThemeItem Composable — Preview

Well done! With this, you’ve completed all the necessary components for the app drawer.

Wrapping up the app drawer

In the previous sections, you created the different building blocks that you need to build the drawer. Now, you need to put them all together. To do so, add the following code to AppDrawer.kt:

@Composable
fun AppDrawer(
  currentScreen: Screen, 
  closeDrawerAction: () -> Unit
) {
  Column(modifier = Modifier.fillMaxSize()) {
    AppDrawerHeader()
    Divider(color = MaterialTheme
      .colors.onSurface.copy(alpha = .2f)
    )
    ScreenNavigationButton(
      icon = Icons.Filled.Home,
      label = "Notes",
      isSelected = currentScreen == Screen.Notes,
      onClick = {
        JetNotesRouter.navigateTo(Screen.Notes)
        closeDrawerAction()
      }
    )
    ScreenNavigationButton(
      icon = Icons.Filled.Delete,
      label = "Trash",
      isSelected = currentScreen == Screen.Trash,
      onClick = {
        JetNotesRouter.navigateTo(Screen.Trash)
        closeDrawerAction()
      }
    )
    LightDarkThemeItem()
  }
}

Add the following imports as well:

import androidx.compose.material.icons.filled.Delete
import com.raywenderlich.android.jetnotes.routing.JetNotesRouter
import com.raywenderlich.android.jetnotes.routing.Screen
import androidx.compose.material.Divider
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize

There’s not much new in this code, you’ve just used the composables you created earlier. The important step you’ve taken here is that you’ve organized those composables into a Column() to give your drawer a proper layout.

In the design, you can see that there’s a line between the drawer’s header and buttons. To add that line, you used a Divider(). Then, for the color, you created a new object with the onSurface color, but with a different alpha property.

Check AppDrawer’s parameters: currentScreen and closeDrawerAction. With currentScreen, you control which screen navigation button is selected. For example, if you want to select the Notes button, you’d call the AppDrawer composable with Screen.Notes as an argument.

Remember how you exposed the click event in the ScreenNavigationButton()? Here, you use a similar technique to expose the close drawer event with the closeDrawerAction parameter. By doing that, you let the parent composable react to the event.

Notice how this composable passes the lambda argument to onClick() of each navigation button. This is used to notify the system that you selected a new screen.

Finally, add the following code to the bottom of AppDrawer.kt:

@Preview
@Composable
fun AppDrawerPreview() {
  JetNotesTheme {
    AppDrawer(Screen.Notes, {})
  }
}

Here, you passed Screen.Notes as the currentScreen. By doing that, you selected the Notes button.

Since you’re calling the AppDrawer() within a composable marked with @Preview, you don’t need to specify any actions for when the app drawer is closed. Therefore, you just passed an empty function as an argument.

Build your project once more and you’ll see your completed drawer in your preview:

LightDarkThemeItem Composable — Preview
LightDarkThemeItem Composable — Preview

You already saw how calling composable functions results in a tree. Whenever you call the AppDrawer(), Compose will generate a tree where each node is a composable that was used to create it.

Right now, this tree contains only the UI elements. Later, you’ll see that there are other types of nodes as well. When you compare the AppDrawer() and the Note(), you’ll see that the first one is more complex. However, if you pay attention, you’ll notice that both are composed of pretty much the same basic composables.

That’s the beauty of Jetpack Compose. It’s so easy to create complex composables from the most basic ones.

AppDrawer Composable — Compose Tree
AppDrawer Composable — Compose Tree

Putting all the pieces together

After all this work, it would be a shame not to see the different composables you built working together in your app. So your final step will be to put the puzzle pieces together.

Go to MainActivity.kt and add the following code inside the setContent():

JetNotesTheme {
  val scaffoldState: ScaffoldState = rememberScaffoldState()
  Scaffold(
    scaffoldState = scaffoldState,
    drawerContent = {
      AppDrawer(
        currentScreen = Screen.Notes,
        closeDrawerAction = { 
          scaffoldState.drawerState.close() 
        }
      )
    },
    bodyContent = {
      Note()
    }
  )
}

Add the following imports as well, to avoid compilation errors:

import androidx.compose.material.Scaffold
import androidx.compose.material.ScaffoldState
import androidx.compose.material.rememberScaffoldState
import com.raywenderlich.android.jetnotes.routing.Screen
import com.raywenderlich.android.jetnotes.theme.JetNotesTheme
import com.raywenderlich.android.jetnotes.ui.components.AppDrawer
import com.raywenderlich.android.jetnotes.ui.components.Note

Don’t bother yourself if you don’t understand some of the concepts or composables in this code. Just note the use of your AppDrawer() and Note(). The other code is here to allow you to easily integrate these composables.

You’ll learn more about the Scaffold()and theming in Chapter 8, “Applying Material Design To Compose”. And you’ll get more context about the ScaffoldState in Chapter 7, “Managing State In Compose”.

Build and run the app. You’ll see the Note composable on the screen, as shown below. To see your app drawer in action, pull the left edge of the screen toward the right.

Right now, the app isn’t that impressive, but don’t worry. You’ll make it look like the expected design throughout the following chapters.

Note Composable and App Drawer Composable
Note Composable and App Drawer Composable

You can find the final code for this chapter in 05-creating-custom-composables/projects/final.

Congratulations on finishing the chapter! I hope it was a nice ride for you! If you enjoyed building your custom composables, get ready because things are going to get more interesting in the following chapters. :]

In this chapter, you learned how you can use basic composables to create complex ones. You also saw how you should think about your UI design and what approach to take when implementing it.

In the next chapter, you’ll learn how to style your composables using modifiers. You’ll also continue adding more composables to improve Jet Notes.

Key points

  • Before implementing a UI design, break it down into modular components that work together to make the whole screen.
  • When implementing a specific UI design, use a bottom-up approach. Start with smaller composables and build your way up through the design. This will let you decouple and reuse code from the very start.
  • Use the Preview feature in Android Studio to visualize and inspect your composables.
  • Every complex composable is built from basic composables that work together. It’s a small puzzle of simple elements.
  • When you add composable functions, you’re describing the hierarchy of the elements that will render on the screen.
  • Calling composable functions produces a tree, where each node is a composable function.
Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2026 Kodeco Inc.