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:
-
A possible breaking change: Compose Material now enforces minimum touch target sizes in certain cases
-
Support for an API to allow modifiers in a chain to communicate with one another
-
Adds some new experimental APIs around pointer and mouse events
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 Giriş 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
.
Other Interesting Links
- Video: Implementing a Retro-Style Game Using Jetpack Compose
- Medium: Creating a rotating card in Jetpack Compose
- Slides: Magic Sprinkles — Adding animations to a Jetpack Compose App
- Medium: Jetpack Compose Side Effects — LaunchedEffect
- Medium: Jetpack Compose Flat and Hierarchical Navigation
- Medium: Truck Art — Android Jetpack Compose Canvas
- Repository Pattern with Jetpack Compose
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.
Or, you can subscribe to the Atom feed or follow Mark Murphy in the Fediverse.
Recent Issues:
- 2024-12-10: A Compose Multiplatform alpha! Hot reload! Presentation! Sprites! Calendars!
- 2024-12-03: Rebecca Franks on clipping and masking! Stefano Natali on graphicsLayer()! FunkyMuse on type-safe nav results! And... if we have enough maps, do we need to store our maps in a Map?!?
- 2024-11-26: Math! Shared element transitions! Custom modifiers! Macrobenchmark! Adapting to platform-specific design systems! And... why does wrapContentSize() not wrap my content size?!?