Jetpack Compose Puzzlers
Tags: android, kotlin, jetpackcompose
Published: 2024-01-23 11:45:04

Since its [public announcement](https://twitter.com/JimSproch/status/1126236514805936128) during Google I/O 2019, Jetpack Compose has come a long way. It is not only the [UI framework of choice for new Android apps](https://developer.android.com/modern-android-development), but may also be used for macOS-, iOS-, Windows-, Linux-, and Web apps, thanks to [Kotlin Multiplatform](https://kotlinlang.org/docs/multiplatform.html) and [Compose Multiplatform](https://www.jetbrains.com/lp/compose-multiplatform/). While Jetpack Compose certainly [drew inspiration from its elder brother, Flutter](https://x.com/JimSproch/status/1398308631406211084?s=20), and other declarative UI frameworks, it perfectly integrates with Kotlin and functional programming. Which, I firmly believe, is why Jetpack Compose is such a beautiful framework. It is easy to grasp and easy to use. 

Well, most of the time. Still, while writing my [Compose book](https://packt.link/Cs2Cg), I was puzzled now and then. Sometimes the parameters of some function were hard to understand, or a building block seemed to make no sense. Reasons for my confusion were:

- Bugs (fortunately, not often)
- Lack of, or unclear, documentation
- Unexpected behavior

The first two aren't puzzlers. A bug is something that needs to be fixed. Period. Unclear documentation should be made clearer. Missing documentation should be added. But what about unexpected behavior? If something works as described in the documentation, or as showcased in an example, and we still don't get why, we clearly lack context, or are missing some facts. That is the perfect puzzler. We scratch our head and think *What?! Wait a minute*. In this series of articles, I will share my favorite Jetpack Compose Puzzlers with you. But before we dive in, allow me to credit Joshua Bloch and Neal Gafter, who published a book called *Java Puzzlers* almost 20 years ago. That book inspired me to give a talk called *Android Puzzlers* back in 2015, and it inspired me to start this series.

### How can `Unit` help building trees?

The perceived majority of things we deal with when writing a Compose user interface are functions. Sure, we also use classes and objects, but the UI tree (the manifestation of the user interface, if you will) is defined &ndash; *declared* &ndash; by nesting functions. Here's how:

```kotlin
@Composable
fun HelloCompose() {
  Box(
    modifier = Modifier
      .size(64.dp)
      .background(color = Color.Green),
    contentAlignment = Alignment.Center
  ) {
    Text(
      modifier = Modifier
        .background(color = Color.Black)
        .padding(all = 4.dp),
      color = Color.White,
      text = "Hello"
    )
  }
}
```

![Screenshot of the HelloCompose() composable function](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/oc0qa4pzcqawv4nidtw0.png)

A function named `HelloCompose()` invokes another one, `Box()`, which in turn calls `Text()`. Let's appreciate this: instead of instantiating UI elements and adding them to an object tree (yes, that's what happens when we inflate our XML layout files), we just nest function calls. But what happens when the invoked function returns? To understand what I am up to, let's look at the definitions of `Box()` and `Text()`.

```kotlin
@Composable
inline fun Box(
  modifier: Modifier = Modifier,
  contentAlignment: Alignment = Alignment.TopStart,
  propagateMinConstraints: Boolean = false,
  content: @Composable BoxScope.() -> Unit
)
```

```kotlin
fun Text(
  text: String,
  modifier: Modifier = Modifier,
  color: Color = Color.Unspecified,
  fontSize: TextUnit = TextUnit.Unspecified,
  fontStyle: FontStyle? = null,
  fontWeight: FontWeight? = null,
  fontFamily: FontFamily? = null,
  letterSpacing: TextUnit = TextUnit.Unspecified,
  textDecoration: TextDecoration? = null,
  textAlign: TextAlign? = null,
  lineHeight: TextUnit = TextUnit.Unspecified,
  overflow: TextOverflow = TextOverflow.Clip,
  softWrap: Boolean = true,
  maxLines: Int = Int.MAX_VALUE,
  minLines: Int = 1,
  onTextLayout: ((TextLayoutResult) -> Unit)? = null,
  style: TextStyle = LocalTextStyle.current
) 
```

Both don't specify a return (result) type. When no explicit return type is given, the default one, `Unit`, is assumed. To test this, you can wrap the `Box()` inside `println()`. `HelloCompose()` will still work as expected, and `kotlin.Unit` will be printed. Now, with such a general result we surely can't build a component tree. So, here's the puzzler: while invoking `Box()` and `Text()` makes corresponding UI elements appear on screen, returning from the functions apparently does not make their visual counterparts disappear. What makes them stay?

To answer that questions, let's find out what `Box()` and `Text()` do. To see their source code, just click on the name of the composable while pressing the **Cmd** (macOS) or **Control** (Windows, Linux) key. `Box()` immediately invokes `androidx.compose.ui.layout.Layout()`. `Text()` arrives there, too, but visits `androidx.compose.foundation.text.BasicText()` first. It turns out, that `Layout()` is the final destination for a lot of composables. So, let's have a quick look:

![Screenshot of the Jetpack Compose Layout() composable](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/orryf8qdb2b17cjuuu3c.png)

Well, that looks somewhat scary, right? Don't worry, I just said *final destination* for a reason. At some point you may want to invoke `Layout()` from your code, but you likely won't need to use `ReusableComposeNode()`. To quote from the docs, this composable emits

> a recyclable node into the composition of type T. Nodes emitted inside of content will become children of the emitted node.

The key to solving our first puzzler is the beautifully short word **emit**. Jetpack Compose maintains an internal data structure which keeps representations of composable functions that have been invoked while declaring the user interface. Even though a composable has returned, its representation is still available in that structure, until Compose can prove that it is no longer needed.

So, nesting composable functions indeed creates a UI tree. Unlike with imperative UI toolkits (among many others, the Android `View` system) we just don't need to manipulate it. And that is a great improvement. If you want to learn more about what is going on under the hood, I highly recommend [Jetpack Compose internals](https://leanpub.com/composeinternals) by Jorge Castillo.

Let's turn to another puzzler. 

### So `clickable {}` doesn't always click?

Many imperative UI frameworks are based on class hierarchies. For example, `android.widget.Switch`, contains methods and properties to define both visual appearance and behavior of a special button type (a switch). Some of these are unique to `Switch` (for example `setSwitchTextAppearance()`). Others are inherited from ancestors (for example, `setOnCheckedChangeListener()` is defined in `CompoundButton`, the parent class). If we want to use a `Switch` in our UI, we configure it by setting properties and registering listeners (to do something in response to a user interaction). To change the behavior and visual appearance of all instances, we need to create a derived class. What is not easily possible with such a class based approach is to alter only parts of parent behavior and visuals, as this would require either multiple inheritance or [mixin](https://en.wikipedia.org/wiki/Mixin)-like concepts.

That is why declarative UI frameworks often follow a concept called *composition over inheritance*. Visual appearance and behavior is achieved by combining small reusable bits and pieces. In Jetpack Compose, this can be:

- composable functions
- function arguments
- modifiers

The first one is obvious. We build our user interface by nesting lots of single-purpose composables. For example, `Text()` does nothing but show some text. It doesn't provide means to get the text either from resources or from a `String`. If we want to get the text from resources, we use `stringResource()` to obtain it.

What about the second one, function arguments? Instead of keeping state (that's what the properties of imperative UI elements do), composable functions should try to remain stateless: ideally, everything a composable needs to render is passed as arguments. Behavior, too, should not be hard-coded, but passed to the composable as a lambda. As with every rule, there are valid exceptions. To learn more, I recommend having a look at [State and Jetpack Compose](https://developer.android.com/jetpack/compose/state).

Modifiers truly are one of the super powers of Jetpack Compose. As their name implies, they are used to alter both visual appearance and behavior of a composable function. Here's some code:

```kotlin
@Composable
@Preview
fun MadModifier() {
  var toggle1 by remember { mutableStateOf(false) }
  var toggle2 by remember { mutableStateOf(false) }
  val color = { isTrue: Boolean ->
                    if (isTrue) Color.White else Color.Black }
  Row(
    modifier = Modifier.size(100.dp)
  ) {
    Box(
      modifier = Modifier
        .weight(0.5F)
        .fillMaxHeight()
        .background(color = color(toggle1))
        .clickable { toggle1 = !toggle1 }
        .padding(all = 8.dp)
        .background(color = color(!toggle1))
    )
    Box(
      modifier = Modifier
        .weight(0.5F)
        .fillMaxHeight()
        .background(color = color(toggle2))
        .padding(all = 8.dp)
        .background(color = color(!toggle2))
        .clickable { toggle2 = !toggle2 }
    )
  }
}
```

`MadModifier()` puts two black and white boxes in a row. When you click on either one, the colors are swapped. Although the two `Box()` versions looks almost identical, they behave subtly different. Take a look.

{% youtube NC8DAoGdYME %}

Here's the puzzler: why does clicking in the outer areas of the left box trigger a color swap, but not in the right one? 

If you compare the two implementations, you'll spot that, besides using different variables, the second version uses the `clickable {}` modifier later; to be more precisely, it's the last element in the modifier chain. 

We can read the modifier chain like a recipe:

1. Set the width to half of the available space
2. Use the whole available height
3. Fill the background with some color
4. Create a padding facing to the center of the area
5. Draw the background again, using some other color
6. Make the remaining space clickable

Now, please try this with the first version.

In imperative UI frameworks, we can change properties of components in any order we want. That's because they are all part of one object. If the order matters, that usually indicates an unwanted side effect. In Jetpack Compose, however, the order of the modifiers does matter, and that's a good thing. If it didn't, the result of our code would become unpredictable.

Let's look under the hood.

![The source code of the fillMaxSize() modifier](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l6uc3mxwt3htauszhqe1.png)

`fillMaxSize()` may well be one of the most used modifiers. It's an extension function of `Modifier`; it doesn't do much besides invoking `then()`, passing either `FillWholeMaxSize` or `FillElement.size(fraction)`. Both, in the end, are `Modifier.Element` instances. As a nice exercise, you could look at the code to learn about the stops along the way. `Element` is an interface that extends `Modifier`.

![Source code of the Element interface](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wl736da00lhrbh6vcv3n.png)

As you can see, its four methods implement the parent declarations. But what about `then()`?

![Screenshot of the source code of then()](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/129h9wrx8dekcevrf89a.png)

`then()` concatenates two modifiers, creating a chain.

### Wrap-up

In this first part of *Jetpack Compose puzzlers*, I introduced you to what might be called the mothers of all Jetpack Compose puzzlers (as an homage to [The Mother of All Demos](https://de.wikipedia.org/wiki/The_Mother_of_All_Demos)). Once you have grasped the essence of Jetpack Compose, both are perfectly clear. For Compose newbies, they, however, may well cause some head scratching.

I sincerely hope you enjoyed reading this. What are your favorite Compose puzzlers? Which ones would you like me to cover? Please share your thoughts in the comments.