jetc.dev Newsletter Issue #211

Published: 2024-04-23

We got all kinds of new stuff: an updated Compose BOM and Compose Compiler, new alphas for Compose and Wear Compose, a new Glance beta, the first steps of Compose Material3 and Jetpack Lifecycle Runtime going multiplatform, and a new Compose Multiplatform beta!

We also migrate away from ClickableText() and nest some lazy containers. We look at a pair of replacement scaffolds. And I chat a bit about Skip, an iOS-first multiplatform option.

Ooooo… What Did We Get?

Reviewing the release notes for the latest Jetpack Compose update!

The Compose BOM is up to 2024.04.01. That maps to the 1.6.6 patch releases of the core Compose artifacts and 1.2.1 of Compose Material3. Those patches have minor bug fixes.

Compose Compiler is out with 1.5.12, containing a couple of bug fixes.

Compose is up to 1.7.0-alpha07, with a variety of interesting changes, including:

  • Support for initializing one composition local from another

  • Support for item appearance/disappearance animations in lazy grids and staggered grids

  • LinkAnnotation in an AnnotatedString is now the official replacement for ClickableText(), which is deprecated

  • AnnotatedString now offers a fromHtml() function to parse HTML

  • Support for pressed state on links

Compose Material3 is up to 1.3.0-alpha05. SearchBar() and DockedSearchBar() now have overloads that take a TextField() slot parameter, so you can style it separately.

Also, we got three new Compose artifacts, as Compose Material3 starts to go multiplatform:

  • androidx.compose.material3:material3-common
  • androidx.compose.material3:material3-common-android
  • androidx.compose.material3:material3-common-desktop

Wear Compose 1.4.0-alpha07 is available, offering a new rotary() Modifier, along with SelectableChip() and SplitSelectableChip() composables.

Glance now has a 1.1.0-beta02 release, adding in the source JARs that were missing from 1.1.0-beta01.

We got three new Compose-adjacent artifacts:

  • androidx.lifecycle:lifecycle-runtime-compose-android
  • androidx.lifecycle:lifecycle-runtime-compose-desktop
  • androidx.navigation:navigation-fragment-compose

…and updates to four more:

  • androidx.activity:activity-compose:1.9.0
  • androidx.fragment:fragment-compose:1.7.0-rc02
  • androidx.fragment:fragment-compose:1.8.0-alpha02
  • androidx.navigation:navigation-compose:2.8.0-alpha07

Also, JetBrains released Compose Multiplatform 1.6.10-beta01. This offers experimental multiplatform versions of Navigation for Compose and the Jetpack ViewModel. It also adds support for multi-module projects that leverage Compose Multiplatform resources.

One Off the Stack, One Off the Slack

You’ve got questions. That’s understandable!

What Is The Difference Between derivedStateOf() and remember() With a Key?

remember() with a key triggers recomposition when the key changes. derivedStateOf() triggers recomposition when the associated lambda expression evaluation result changes. Those are not the same, but the visible impact can be subtle, as we see in this week’s highlighted Stack Overflow question.

How Do We Use Coroutines in Lifecycle Effects?

In other words, can you use coroutines inside LifecycleStartEffect() and kin? The answer is yes, but it’s not the recommended approach, as we see in this week’s highlighted Kotlinlang #compose Slack thread.

Composable Commentary

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

I Removed JetPack Compose from My App

Spoiler alert: Donn Felker did not remove Jetpack Compose from his app. He did, however, write a post explaining that there are times when using View-based widgets makes sense. In this case, Donn relied on third-party libraries that had “out of the box” support for the Toolbar widget, and that resulted in less overall code than using the AppBar() composable.

Migrating from the ClickableText composable to LinkAnnotation

Joe Birch pours one out for the now-deprecated ClickableText() (we hardly knew ye!) and explores what it takes to use LinkAnnotation and buildAnnotatedString() as a replacement.

Medium: Creating a Time Picker Based on the ELSA Speak App Using Jetpack Compose

Andrii Veremiienko walks us through creating a rotary time picker, using a Layout() for the UI and blending in fling and drag support via a custom drag() modifier.

anchoredDraggable For Speed And Fun

Xiaoming liked the playback speed slider from the now-defunct Google Podcast app and set out to recreate it, using the anchoredDraggable() modifier.

Medium: Nested LazyColumn in Jetpack Compose

Narayan Panthi examines lazy containers (LazyColumn(), LazyRow(), LazyGrid()), first in isolation, then nested. Narayan specifically examines two patterns: LazyRow() composables nested inside a LazyColumn() and a LazyColumn() nested inside another LazyColumn().

Medium: Some Best-Practices for State Management in Jetpack Compose

Kayvan Kaseb reviews the use cases for remember(), the role of state hoisting, the value of immutable state holders, and more.

Resource Roundup

100% pure code!

GitHub: stoyan-vuchev / responsive-scaffold

Stoyan Vuchev created a ResponsiveScaffold() composable, designed for supporting various window sizes and postures (landscape/portrait). It includes NavigationRail() support and conditional hiding of the FAB on larger screens.

GitHub: EudyContreras / Snap-Scaffold

Eudy Contreras is also working with scaffolds, in this case making it easier to implement snap behavior in scrollable content: sticky headers, collapsible headers, and so on.

GitHub: takahirom / Rin

Takahiro Menju brings the rememberRetained() concept from Circuit to Compose Multiplatform’s own navigation and viewmodel support.

GitHub: theolm / rinku

Theodoro Loureiro mota brings us a library to simplify deeplink support to Kotlin Multiplatform and Compose Multiplatform, for iOS and Android.

GitHub: w2sv / Composed

Janek Zangenberg has a library with a wide range of Compose utility functions, from dimension, color, and map conversions to permission state to custom Saver implementations.

Notable Releases

Maps for Compose is up to a 4.3.4 release. This adds a stability configuration file, to mark the Maps SDK types used by Maps for Compose as stable. This should improve performance, courtesy of strong skipping mode.

…And One More Thing

I have been seeing a few mentions of Skip recently. Roughly speaking, this is the inverse of the original Kotlin Multiplatform and Compose Multiplatform. Instead of writing in Kotlin and the code being cross-compiled into code for iOS, you write in Swift, and Skip transpiles into Kotlin. And, rather than writing in Compose and using that across both platforms, Skip converts SwiftUI into Compose.

I can see the allure. Lots of apps — rightly or wrongly — are iOS-first. Skip lets you have a maximum-fidelity implementation on iOS, and gives you an Android equivalent. And, simply getting this to work at all is very impressive.

That said, my biggest concern is with dependencies. The only dependencies that work with Skip right now are where “the dependent module has a Skip/skip.yml file”. At this point, probably few Swift libraries are Skip-enabled. This results in a “Catch-22” situation: few libraries will invest in being Skip-enabled unless Skip becomes popular, and Skip may have problems becoming popular because few libraries are Skip-enabled. My guess is that the issue is that Skip is source transpiler — is is unclear how this will work with dependencies for which source code is unavailable.

This is analogous to the state of Kotlin Multiplatform and Compose Multiplatform… except that JetBrains has a substantial head start. It is unclear if Skip will gain an ecosystem of comparable size.

Skip also seems like a much smaller firm than JetBrains. As a result, effectively they are forced to charge for their tooling, at a $999/year rate ($499 for the first year). While they have an open source free option, it is limited to GPL/AGPL/LGPL projects. The Kotlin Multiplatform and Compose Multiplatform environments are “free as in beer” more broadly.

I certainly wish Skip luck. Competition, in business and in ideas, is a good thing.