A Short Intro to Thinking in Jetpack Compose

downtherabbithole

The Struggling Dev

Posted on January 16, 2023

A Short Intro to Thinking in Jetpack Compose

Disclaimer

This is not an in-depth tutorial for Compose. I won't go over what dependencies you need to add, ... - you can find that here Android Developers - Adding Jetpack Compose to your app.

This post is a short introduction to Jetpack Compose, mainly about what makes it different from other UI frameworks.

As you might've guessed, Jetpack Compose is a UI framework. What makes it different from more common UI frameworks like WinForms, WPF, Swing, Tkinter, ... is that it is a declarative UI framework. What does that mean? In short: You declare a UI and that's it, you're out of the picture. You can't programmatically "interact" with the UI components as you would in a non-declarative framework.

For example if you wanted to change the text of a text view in response to a button click, you'd do something like this in a non-declarative UI framework.

StackPanel stackPanel = new StackPanel();
TextBox textBox = new TextBox();
stackPanel.Children.Add(textBox)
Button button = new Button();
stackPanel.Children.Add(button);
button.Click += (sender, ea) => { textBox.Text = "Button Pressed"; }
Enter fullscreen mode Exit fullscreen mode

Here we create the UI and directly interact with other UI elements, we can change their properties. This doesn't work in Compose. Although you can keep references to Composables (≈ UI components), you can't change their properties.

val textComposable = Text(text = "Hello Compose.")
textComposable.text = "This won't compile, because there's no text property."
Enter fullscreen mode Exit fullscreen mode

Compose is designed around the Unidirectional Data Flow (UDF) pattern. The UI triggers events that allow you to change state, which in turn triggers the recomposition (~ rebuilding/drawing the UI) of the UI.

    +---------------+
    |      UI       |
    +---------------+
         |     ^
  event  |     |  state
         v     |
    +---------------+
    |     State     |
    +---------------+    
Enter fullscreen mode Exit fullscreen mode

Having used only non-declarative UI frameworks, the use case that helped me most in understanding how to think in Compose was showing a dialog. The following composable function does exactly this.

@Composable
fun SampleScreen() {
  val showDialog = remember { mutableStateOf(false) }   // changes to states trigger recomposition 
  TextButton(onClick = { showDialog.value = true }) {
    Text(text = "Click to show the dialog")
  }
  if (showDialog.value) {
    Dialog(onDismissRequest = { showDialog.value = false }) {
      Text(text = "Tap outside the dialog to close it.")
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

What's happening here? When the Composable is executed for the first time the showDialog variable is set to false and we therefore don't add the dialog to our GUI. If we click on the button and set the value of showDialog to true, we trigger a re-composition of the Composable (b/c showDialog is a mutable state). The Composable is 're-executed', this time around showDialog is true and we show our dialog.

Remember Me

remember allows a composable function to store information in memory so that that information is still available after recomposition.

  • If we would just write val showDialog = mutableStateOf(false), the UI would be recomposed, but we would re-initialize the showDialog to false and therefore not show the dialog.
  • And var showDialog = false wouldn't even trigger recomposition, therefore no dialog either.

Instead of remember we can also use a view model.

That's it for this short introduction to thinking in Compose.

Keep on struggling.

💖 💪 🙅 🚩
downtherabbithole
The Struggling Dev

Posted on January 16, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related