Newsletter Issue #99

Published: 2022-01-11

This week, while we wait for the next Compose release, we look at state hoisting and changing TextField() colors. We spend a lot of time exploring testing, particularly automated screenshot testing. We look at two composable-to-a-bitmap libraries and two Wordle clones. And I suggest a bit of code organization to help avoid accidentally mixing and matching your Compose UI and Glance composables.

One Off the Stack, One Off the Slack

You’ve got questions. That’s understandable!

Why Does State Hoisting Use Callbacks?

State hoisting, in the official documentation, is handled not by passing State or MutableState objects around, but rather by passing function types, such as lambda expressions. See how this represents a composable form of encapsulation in this week’s highlighted Stack Overflow question.

How Do We Tie Theme Colors Together?

Compose Material follows Material Design and is rather rigid in how it applies colors. As a result, there is no good way to swap colors for, say, a TextField(), except by creating a full TextFieldColors or MaterialTheme, as we see in this week’s highlighted Kotlinlang #compose Slack thread.

Composable Commentary

Posts, videos, and other new information related to Jetpack Compose!

Automatic screenshot testing for all your Compose @Previews

David Vávra profiles how AirBnB’s Showkase library not only gives you rapid access to all of your @Preview composables, but how it also gives you access to bitmaps for use with your favorite screenshot testing framework. This allows your @Preview composables to perform “double duty”, both for examining the UI manually and for automated testing.

Medium: Compose UI Screenshot Testing with Firebase Test Lab and CircleCI

Dan Nesfeder continues the screenshot testing theme, showing how to set up screenshot tests that run on Firebase Test Lab, automated via a Circle CI task, after setting up the tests locally.

Medium: Test Your Jetpack Compose Layout

If you have not done much in the way of testing your composables, Anang Kurniawan offers a basic introduction to createComposeRule() and Espresso-style “look up a node and assert its state” tests.

Jetpack compose accessibility best practices

Carlos Monzon brings us an extensive post outlining different ways to improve accessibility in our Compose UI interfaces. The post covers things like multiple ways to increase your touch target sizes, using contentDescription and semantic nodes, and more!

Medium: Visual Transformation for any mask in Jetpack Compose TextField

VisualTransformation is Compose UI’s way to let you control how the text in a TextField() is rendered, independently of what the actual text value is. While the classic example is password shrouding, Thiago Souza explores using it for many other scenarios, such as date formatting and phone number formatting.

Medium: Detect text overflow in Jetpack Compose

Sometimes our text is a bit long, and our Text() composable truncates the rendered output, perhaps with an ellipsis. However, we may still need to give the user some idea of what the text is that is not visible. Sinan Kozak explores using the onTextLayout to Text() to determine what text was truncated, so another composable can render some hints based on that data.

Tiny things on big screens

Thomas Künneth continues exploring larger screen devices, including foldables, in the world of Compose UI. Previously, Thomas has focused on how to get your UI to adapt to larger screens by making use of the available space. This time, Thomas looks at cases where you might not have a good idea of how to make use of that space.

Resource Roundup

100% pure code!

GitHub: PatilShreyas / Capturable

Shreyas Patil wrapped up the “use ComposeView to render to a Canvas to get a bitmap” trick into a reusable library.

GitHub: KaustubhPatange / kapture

Kaustubh Patange did the same thing (capture a composable to a bitmap), with a slightly different API.

GitHub: skgmn / ComposeTooltip

Many apps wind up needing tooltips to highlight and document certain UI elements, perhaps a part of a first-time use education mode. GitHub user skgmm created one for Compose UI that can be displayed as an independent popup or be included as part of a ConstraintLayout()-based composable.

GitHub: joreilly / WordMasterKMP

John O’Reilly created a Kotlin/Multiplatform edition of a word game similar to Wordle. John’s supports Android, iOS, and Compose for Desktop. Olivier Patry created the same sort of app, supporting Compose for Desktop and an ASCII terminal mode.

…And One More Thing

Compose UI has Box(). Glance has Box(). They are not interchangeable.

Compose UI has Text(). Glance has Text(). They are not interchangeable.

Compose UI has Button(). Glance has Button(). They are not interchangeable.

And so on.

As the Glance documentation notes:

Glance uses its own set of Composables. Do not combine androidx.compose and androidx.glance composables.

The problem is that if you have both in the same module, auto-complete support in the IDE might steer you to import the wrong one, causing confusion until you track down the flawed import.

One solution for this is to use separate modules for your Compose UI composables and your Glance composables. Do not have the Glance dependency in the Compose UI module, and do not have the Compose UI dependencies in the Glance module.

The implication is that classes that are in common between them — use cases, repositories, etc. — come from some central module(s) that the Compose UI and Glance modules themselves depend upon. That in turn may require some extra work to get your dependency inversion logic set up to be able to inject things into both the Compose UI and Glance modules. That sounds like a headache, and to an extent it will be for the initial setup. However, this appears to be the long-term Android app development approach: many smaller modules making up the app, so you can use dependency lists, Kotlin’s internal keyword, and other things to help shape the API surface of a module and the APIs visible within that module from other modules.

Or, just keep fussing with getting your Glance and Compose UI composables mixed up.