jetc.dev Newsletter Issue #85

Published: 2021-10-05

This week, we examine the new 1.0.3 and 1.1.0-alpha05 releases. We explore using Compose “for realz”, rememberUpdatedState(), and a particle graphics engine. And, we look at scope interfaces for creating APIs for use by children of a composable. That in turn requires you to think through API design, so related composables can have related scope APIs.

Patch Preview and Alpha Analysis

Reviewing the release notes for the latest Jetpack Compose update!

Compose 1.0.3 is out! This adds support for Kotlin 1.5.30, as Compose struggles to keep up with JetBrains’ blistering Kotlin release pace. There seems to be no other significant bug fixes in this release, though.

Also, 1.1.0-alpha05 was released. This supports Kotlin 1.5.31. It also adds:

One Off the Stack, One Off the Slack

You’ve got questions. That’s understandable!

Scoping Composables to Particular Parents

Sometimes, you want to have a set of composable functions (or possibly other functions) that can only be used from the content of some specific parent. You see this pattern with composables like LazyColumn() and LazyRow() with functions like items(). See how to set this up using scope interfaces in this week’s highlighted Stack Overflow question.

Why Is It Called “CompositionLocal”

Naming things is hard. That is how we went from “ambients” to “composition locals”. Learn more about why that construct is called what it is, in this week’s highlighted Kotlinlang #compose Slack thread.

Composable Commentary

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

Compose for Wear Slack Channel

Kotlinlang Slack has added a new channel for supporting developers working on writing Compose apps for Wear OS!

Slides: Migrating a Large-Scale Banking App to Compose

Fatih Giris delivered a presentation on how his firm (DNB) is migrating their banking app to Compose UI, replacing individual UI elements with ComposeView, crafting a design system, and how they are continuing to use fragments (for the time being).

ViewModels using Compose: MutableStateFlows or MutableStates?

Fabio Collini explores various options for using suspend functions for calculating states derived from other states. Fabio specifically looks at snapshotFlow(), using mutableStateOf() in a ViewModel, the issues involved in configuration changes and process termination, and more.

Jetpack Compose Side-Effects III — rememberUpdatedState

Udit Verma returns, this time looking at rememberUpdatedState(). As Udit puts it, “rememberUpdatedState() is used when we want to keep an updated reference to a variable in a long-running side-effect without having the side-effect… restart on composition”.

Compose Destinations: a simpler, safer way to navigate in Jetpack Compose

Two weeks ago, I pointed out a library by Rafael Costa that uses a KSP processor to let you define navigation graphs using @Destination annotations. In this post, Rafael explains why he elected to create this library and how you can use it to streamline your Navigation for Compose route construction.

Medium: How to Share Composable as Bitmap?

Vipul Thawre offers another take on a common problem: how do we grab a “screenshot” of our own UI? The answer involves ComposeView and classic solutions to capturing the contents of a View as a Bitmap.

Resource Roundup

100% pure code!

GitHub: CuriousNikhil / compose-particle-system

Nikhil Chaudhari created a library with a CreateParticles() composable that renders a stream of particles. You get to control the pattern, size, physics (e.g., acceleration), colors, and more!

GitHub: doctor-blue / compose-navigation

Nguyen Van Tan offers another wrapper around Navigation for Compose to help ease passing arguments between screens, leveraging a ComposeScreen base class to organize those screens.

GitHub: ch4rl3x / BottomDrawerScaffold

Alexander Karkossa created a BottomDrawerScaffold() composable for bottom sheets, including support for peeking, scrollable contents, and more.

Gist: XanderZhu / Calendar.kt

GitHub user XanderZhu supplies a small set of composables for rendering a typical grid-shaped calendar.

GitHub: bytebeats / compose-charts

Chen Pan is working on a charting library for Compose for all Compose targets: Android, desktop, and Web!

…And One More Thing

While we focus on composables as functions, classes certainly play their role. One role is for scopes: the receiver (this) for the lambda expressions that you pass into many other composables. We saw this in this week’s highlighted Stack Overflow question. For example:

Row {
  // TODO something really nice
}

this in that lambda expression will be an instance of the RowScope interface. That interface gives you access to modifiers like alignBy() and weight(). Similarly, ColumnScope is used for the content lambda for a Column(), also offering alignBy() and weight() modifiers.

However, as a reader pointed out, these two interfaces do not have a common ancestor. Yet, particularly for handling different screen orientations, there will be cases where we want a Row() in one state and a Column() in another:

if (isLandscape) {
  Column {
    Buttons()
  }
} else {
  Row {
    Buttons()
  }
}

However, as this issue illustrates, that does not work well if the content uses those scope-supplied modifiers. If ItemsToShow() wants to use weight(), either it needs the scope passed in as a parameter or it needs to be an extension function for the scope. Since RowScope and ColumnScope share no common ancestor, you wind up needing two ItemsToShow() implementations, even if the items are the same, just because you get the weight() modifier from different scope types.

The solution proposed in the issue is for RowScope and ColumnScope to inherit from a LinearScope that defines the common modifier API:

interface LinearScope {
  abstract fun Modifier.weight(weight: Float, fill: Boolean): Modifier
}

interface RowScope : LinearScope {
  override fun Modifier.weight(weight: Float, fill: Boolean): Modifier {
    // TODO real implementation
  }
}

That way, ItemsToShow() could either take a LinearScope as a parameter or be an extension function on LinearScope. Whether the weight() modifier affects a Row() or a Column() does not matter to ItemsToShow().

There may be some workarounds, such as using lambda expressions as “weight factories” that work with the appropriate scope. However, ideally, that would not be required.

Compose is going to force us to figure out different ways of applying classic API design rules. On the surface, decisions about inheritance do not play as much of a role with a function-based API. But, if you create related composables with scopes, consider whether the scopes need inheritance to reflect the relationship between the corresponding composables.