The CommonsBlog


Again, Be Wary of Random Gradle Projects

Since Google declined to supply Gradle security advice to developers, here is my periodic reminder:

  • Only use the Gradle Wrapper scripts and JAR from a project if you completely trust where they came from (e.g., were generated by Android Studio when you created the project). In particular, do not use the Gradle Wrapper from an arbitrary project that you grab off of GitHub or elsewhere on the Internet. Delete it or replace it with a locally-generated wrapper (gradle wrapper command).

  • Always check the distributionUrl in the gradle-wrapper.properties file before importing a project into Android Studio or using the Gradle wrapper scripts, to see if the URL looks reasonable (e.g., points to gradle.org). Even better, if it has a distributionSha256Sum value, confirm that it is one that matches a known-good Gradle version.

Otherwise — especially if you decline to use Safe Mode in Android Studio — you may wind up the victim of an attack, as I wrote about 2.5 years ago.

It may also be worthwhile to examine the Gradle plugins and compile-time annotation processors to see if there is anything unusual, though “unusual” is difficult to quantify.

Oct 12, 2025


8 October 2025 Artifact Wave

No new artifacts this week, but we did get updates to 842 of them, including:

  • A stable 1.1.0 release for androidx.metrics:metrics-performance

  • A new minor release for heifwriter

  • New patch releases for Camera, Compose, HealthConnect, Room, and Wear Compose

View all the changes here!

Oct 08, 2025


Busting drawWithCache() in Compose

There are only two hard things in Computer Science: cache invalidation and naming things.

– Phil Karton, via Martin Fowler

For a particular project, I need to take a Compose-defined UI and completely shift all of its colors a bit in the blue direction of the color spectrum. This is to compensate for a red shift being introduced as part of the physical display. This includes all screens, with whatever those screens are rendering, including content from third-party libraries that use the classic View system.

After some fussing around, including asking on Stack Overflow, I came up with this solution:

@Composable
fun Modifier.scaleTint(redScale: Float = 0.533333f, greenScale: Float = 0.8f, blueScale: Float = 1f, alphaScale: Float = 1f): Modifier =
    this.drawWithCache {
        val graphicsLayer = obtainGraphicsLayer()
        val matrix = ColorMatrix().apply {
            setToSaturation(0f)
            setToScale(redScale, greenScale, blueScale, alphaScale)
        }

        graphicsLayer.apply {
            record {
                drawContent()
            }
            this.colorFilter = ColorFilter.colorMatrix(matrix)
        }

        onDrawWithContent {
            drawLayer(graphicsLayer)
        }
    }

You can then apply the modifier to something you want tinted, such as:

@Composable
fun Something(message: String, modifier: Modifier = Modifier) {
    Box(modifier = modifier.fillMaxWidth().scaleTint()) {
        BasicText(message)
    }
}

I thought it worked. Indeed, it does work, so long as you never need to change the composable being tinted.

In the example shown above, imagine the following series of events:

  • Something() gets invoked with "foo" passed as the value for message
  • foo gets rendered on the screen, with the tint applied
  • The user does something which causes a state change
  • As a result, Something() gets called again to recompose, this time with "bar" as the value for message

We would expect bar to now be rendered on the screen, with the tint applied. Instead, we still see foo.

🧐

The cause of the issue stems from drawWithCache(). The KDocs for drawWithCache() lead off with:

Draw into a DrawScope with content that is persisted across draw calls as long as the size of the drawing area is the same or any state objects that are read have not changed. In the event that the drawing area changes, or the underlying state values that are being read change, this method is invoked again to recreate objects to be used during drawing.

In this case, message is not part of the state that drawWithCache() pays attention to. It only knows about the parameters to the scaleTint() modifier, and in my example, those are not changing. drawWithCache() is oblivious to the BasicText() having changed, so it continues to use its drawing cache.

So, we need to bust that cache somehow.

My instinctive reaction was “OK, there must be a key somewhere”. key is used in many places to say “please invalidate this composable if the key value changes”. Alas, drawWithCache() does not take a key parameter, only the lambda expression (or other function type) that represents what is to be drawn and cached.

Adding a key parameter to the scaleTint() declaration is easy enough:

@Composable
fun Modifier.scaleTint(key: Any, redScale: Float = 0.533333f, greenScale: Float = 0.8f, blueScale: Float = 1f, alphaScale: Float = 1f): Modifier =
    this.drawWithCache {
        // rest of previous logic here
    }

However, that is insufficient. The key needs to be used inside the lambda supplied to drawWithCache().

So, now we need to decide on how to use key in such a way that it is considered “read” yet has minimum other impact, since we are not using it for anything other than busting the cache.

As it turns out, at least right now, just referencing it is sufficient:

@Composable
fun Modifier.scaleTint(key: Any, redScale: Float = 0.533333f, greenScale: Float = 0.8f, blueScale: Float = 1f, alphaScale: Float = 1f): Modifier =
    this.drawWithCache {
        key // referenced to bust the cache

        val graphicsLayer = obtainGraphicsLayer()
        val matrix = ColorMatrix().apply {
            setToSaturation(0f)
            setToScale(redScale, greenScale, blueScale, alphaScale)
        }

        graphicsLayer.apply {
            record {
                drawContent()
            }
            this.colorFilter = ColorFilter.colorMatrix(matrix)
        }

        onDrawWithContent {
            drawLayer(graphicsLayer)
        }
    }

We then need to supply a value for key that is tied to the state change:

@Composable
fun Something(message: String, modifier: Modifier = Modifier) {
    Box(modifier = modifier.fillMaxWidth().scaleTint(message)) {
        BasicText(message)
    }
}

Now, if message changes, our BasicText() will re-render and we will see the change on-screen.

I thought that the right answer would be to use the key() function, or perhaps remember() (used by LaunchedEffect). However, both of those functions are composables, and so they can only be invoked from another composable. The lambda parameter to drawWithCache() is not marked as @Composable, though, so neither key() nor remember() are available to us.

I am not completely comfortable with this solution, but it is working for now. If you happen to know of a better way to get drawWithCache() to update in this case, please let me know!

Oct 04, 2025


24 September 2025 Artifact Wave

Ten brand new artifacts in this wave:

  • androidx.compose.material3.adaptive:adaptive-navigation3-jvmstubs
  • androidx.compose.material3.adaptive:adaptive-navigation3-linuxx64stubs
  • androidx.compose.runtime:runtime-retain (including mulitplatform artifacts)
  • androidx.credentials.registry:registry-digitalcredentials-openid
  • androidx.credentials.registry:registry-digitalcredentials-sdjwtvc
  • androidx.lifecycle:lifecycle-viewmodel-navigation3-jvmstubs
  • androidx.lifecycle:lifecycle-viewmodel-navigation3-linuxx64stubs
  • androidx.security:security-state-provider
  • androidx.xr.arcore:arcore-openxr
  • androidx.xr.arcore:arcore-testing

Beyond that, you can find the 869 new artifact versions here.

Sep 24, 2025


17 September Lifecycle/SavedState Mini-Wave

The Lifecycle artifacts got bumped to 2.9.4, SavedState was upgraded to 1.3.3, and the Gradle version catalog BOM is up to 2025.09.01:

  • androidx.gradle:gradle-version-catalog:2025.09.01
  • androidx.lifecycle:lifecycle-common:2.9.4
  • androidx.lifecycle:lifecycle-common-iosarm64:2.9.4
  • androidx.lifecycle:lifecycle-common-iossimulatorarm64:2.9.4
  • androidx.lifecycle:lifecycle-common-iosx64:2.9.4
  • androidx.lifecycle:lifecycle-common-java8:2.9.4
  • androidx.lifecycle:lifecycle-common-js:2.9.4
  • androidx.lifecycle:lifecycle-common-jvm:2.9.4
  • androidx.lifecycle:lifecycle-common-linuxarm64:2.9.4
  • androidx.lifecycle:lifecycle-common-linuxx64:2.9.4
  • androidx.lifecycle:lifecycle-common-macosarm64:2.9.4
  • androidx.lifecycle:lifecycle-common-macosx64:2.9.4
  • androidx.lifecycle:lifecycle-common-mingwx64:2.9.4
  • androidx.lifecycle:lifecycle-common-tvosarm64:2.9.4
  • androidx.lifecycle:lifecycle-common-tvossimulatorarm64:2.9.4
  • androidx.lifecycle:lifecycle-common-tvosx64:2.9.4
  • androidx.lifecycle:lifecycle-common-wasm-js:2.9.4
  • androidx.lifecycle:lifecycle-common-watchosarm32:2.9.4
  • androidx.lifecycle:lifecycle-common-watchosarm64:2.9.4
  • androidx.lifecycle:lifecycle-common-watchosdevicearm64:2.9.4
  • androidx.lifecycle:lifecycle-common-watchossimulatorarm64:2.9.4
  • androidx.lifecycle:lifecycle-common-watchosx64:2.9.4
  • androidx.lifecycle:lifecycle-compiler:2.9.4
  • androidx.lifecycle:lifecycle-livedata:2.9.4
  • androidx.lifecycle:lifecycle-livedata-core:2.9.4
  • androidx.lifecycle:lifecycle-livedata-core-ktx:2.9.4
  • androidx.lifecycle:lifecycle-livedata-ktx:2.9.4
  • androidx.lifecycle:lifecycle-process:2.9.4
  • androidx.lifecycle:lifecycle-reactivestreams:2.9.4
  • androidx.lifecycle:lifecycle-reactivestreams-ktx:2.9.4
  • androidx.lifecycle:lifecycle-runtime:2.9.4
  • androidx.lifecycle:lifecycle-runtime-android:2.9.4
  • androidx.lifecycle:lifecycle-runtime-compose:2.9.4
  • androidx.lifecycle:lifecycle-runtime-compose-android:2.9.4
  • androidx.lifecycle:lifecycle-runtime-compose-desktop:2.9.4
  • androidx.lifecycle:lifecycle-runtime-compose-iosarm64:2.9.4
  • androidx.lifecycle:lifecycle-runtime-compose-iossimulatorarm64:2.9.4
  • androidx.lifecycle:lifecycle-runtime-compose-iosx64:2.9.4
  • androidx.lifecycle:lifecycle-runtime-compose-js:2.9.4
  • androidx.lifecycle:lifecycle-runtime-compose-linuxarm64:2.9.4
  • androidx.lifecycle:lifecycle-runtime-compose-linuxx64:2.9.4
  • androidx.lifecycle:lifecycle-runtime-compose-macosarm64:2.9.4
  • androidx.lifecycle:lifecycle-runtime-compose-macosx64:2.9.4
  • androidx.lifecycle:lifecycle-runtime-compose-mingwx64:2.9.4
  • androidx.lifecycle:lifecycle-runtime-compose-tvosarm64:2.9.4
  • androidx.lifecycle:lifecycle-runtime-compose-tvossimulatorarm64:2.9.4
  • androidx.lifecycle:lifecycle-runtime-compose-tvosx64:2.9.4
  • androidx.lifecycle:lifecycle-runtime-compose-wasm-js:2.9.4
  • androidx.lifecycle:lifecycle-runtime-compose-watchosarm32:2.9.4
  • androidx.lifecycle:lifecycle-runtime-compose-watchosarm64:2.9.4
  • androidx.lifecycle:lifecycle-runtime-compose-watchosdevicearm64:2.9.4
  • androidx.lifecycle:lifecycle-runtime-compose-watchossimulatorarm64:2.9.4
  • androidx.lifecycle:lifecycle-runtime-compose-watchosx64:2.9.4
  • androidx.lifecycle:lifecycle-runtime-desktop:2.9.4
  • androidx.lifecycle:lifecycle-runtime-iosarm64:2.9.4
  • androidx.lifecycle:lifecycle-runtime-iossimulatorarm64:2.9.4
  • androidx.lifecycle:lifecycle-runtime-iosx64:2.9.4
  • androidx.lifecycle:lifecycle-runtime-js:2.9.4
  • androidx.lifecycle:lifecycle-runtime-ktx:2.9.4
  • androidx.lifecycle:lifecycle-runtime-ktx-android:2.9.4
  • androidx.lifecycle:lifecycle-runtime-linuxarm64:2.9.4
  • androidx.lifecycle:lifecycle-runtime-linuxx64:2.9.4
  • androidx.lifecycle:lifecycle-runtime-macosarm64:2.9.4
  • androidx.lifecycle:lifecycle-runtime-macosx64:2.9.4
  • androidx.lifecycle:lifecycle-runtime-mingwx64:2.9.4
  • androidx.lifecycle:lifecycle-runtime-testing:2.9.4
  • androidx.lifecycle:lifecycle-runtime-testing-android:2.9.4
  • androidx.lifecycle:lifecycle-runtime-testing-desktop:2.9.4
  • androidx.lifecycle:lifecycle-runtime-testing-iosarm64:2.9.4
  • androidx.lifecycle:lifecycle-runtime-testing-iossimulatorarm64:2.9.4
  • androidx.lifecycle:lifecycle-runtime-testing-iosx64:2.9.4
  • androidx.lifecycle:lifecycle-runtime-testing-js:2.9.4
  • androidx.lifecycle:lifecycle-runtime-testing-linuxarm64:2.9.4
  • androidx.lifecycle:lifecycle-runtime-testing-linuxx64:2.9.4
  • androidx.lifecycle:lifecycle-runtime-testing-macosarm64:2.9.4
  • androidx.lifecycle:lifecycle-runtime-testing-macosx64:2.9.4
  • androidx.lifecycle:lifecycle-runtime-testing-mingwx64:2.9.4
  • androidx.lifecycle:lifecycle-runtime-testing-tvosarm64:2.9.4
  • androidx.lifecycle:lifecycle-runtime-testing-tvossimulatorarm64:2.9.4
  • androidx.lifecycle:lifecycle-runtime-testing-tvosx64:2.9.4
  • androidx.lifecycle:lifecycle-runtime-testing-wasm-js:2.9.4
  • androidx.lifecycle:lifecycle-runtime-testing-watchosarm32:2.9.4
  • androidx.lifecycle:lifecycle-runtime-testing-watchosarm64:2.9.4
  • androidx.lifecycle:lifecycle-runtime-testing-watchosdevicearm64:2.9.4
  • androidx.lifecycle:lifecycle-runtime-testing-watchossimulatorarm64:2.9.4
  • androidx.lifecycle:lifecycle-runtime-testing-watchosx64:2.9.4
  • androidx.lifecycle:lifecycle-runtime-tvosarm64:2.9.4
  • androidx.lifecycle:lifecycle-runtime-tvossimulatorarm64:2.9.4
  • androidx.lifecycle:lifecycle-runtime-tvosx64:2.9.4
  • androidx.lifecycle:lifecycle-runtime-wasm-js:2.9.4
  • androidx.lifecycle:lifecycle-runtime-watchosarm32:2.9.4
  • androidx.lifecycle:lifecycle-runtime-watchosarm64:2.9.4
  • androidx.lifecycle:lifecycle-runtime-watchosdevicearm64:2.9.4
  • androidx.lifecycle:lifecycle-runtime-watchossimulatorarm64:2.9.4
  • androidx.lifecycle:lifecycle-runtime-watchosx64:2.9.4
  • androidx.lifecycle:lifecycle-service:2.9.4
  • androidx.lifecycle:lifecycle-viewmodel:2.9.4
  • androidx.lifecycle:lifecycle-viewmodel-android:2.9.4
  • androidx.lifecycle:lifecycle-viewmodel-compose:2.9.4
  • androidx.lifecycle:lifecycle-viewmodel-compose-android:2.9.4
  • androidx.lifecycle:lifecycle-viewmodel-compose-jvmstubs:2.9.4
  • androidx.lifecycle:lifecycle-viewmodel-compose-linuxx64stubs:2.9.4
  • androidx.lifecycle:lifecycle-viewmodel-desktop:2.9.4
  • androidx.lifecycle:lifecycle-viewmodel-iosarm64:2.9.4
  • androidx.lifecycle:lifecycle-viewmodel-iossimulatorarm64:2.9.4
  • androidx.lifecycle:lifecycle-viewmodel-iosx64:2.9.4
  • androidx.lifecycle:lifecycle-viewmodel-js:2.9.4
  • androidx.lifecycle:lifecycle-viewmodel-ktx:2.9.4
  • androidx.lifecycle:lifecycle-viewmodel-linuxarm64:2.9.4
  • androidx.lifecycle:lifecycle-viewmodel-linuxx64:2.9.4
  • androidx.lifecycle:lifecycle-viewmodel-macosarm64:2.9.4
  • androidx.lifecycle:lifecycle-viewmodel-macosx64:2.9.4
  • androidx.lifecycle:lifecycle-viewmodel-mingwx64:2.9.4
  • androidx.lifecycle:lifecycle-viewmodel-savedstate:2.9.4
  • androidx.lifecycle:lifecycle-viewmodel-savedstate-android:2.9.4
  • androidx.lifecycle:lifecycle-viewmodel-savedstate-desktop:2.9.4
  • androidx.lifecycle:lifecycle-viewmodel-savedstate-iosarm64:2.9.4
  • androidx.lifecycle:lifecycle-viewmodel-savedstate-iossimulatorarm64:2.9.4
  • androidx.lifecycle:lifecycle-viewmodel-savedstate-iosx64:2.9.4
  • androidx.lifecycle:lifecycle-viewmodel-savedstate-js:2.9.4
  • androidx.lifecycle:lifecycle-viewmodel-savedstate-linuxarm64:2.9.4
  • androidx.lifecycle:lifecycle-viewmodel-savedstate-linuxx64:2.9.4
  • androidx.lifecycle:lifecycle-viewmodel-savedstate-macosarm64:2.9.4
  • androidx.lifecycle:lifecycle-viewmodel-savedstate-macosx64:2.9.4
  • androidx.lifecycle:lifecycle-viewmodel-savedstate-mingwx64:2.9.4
  • androidx.lifecycle:lifecycle-viewmodel-savedstate-tvosarm64:2.9.4
  • androidx.lifecycle:lifecycle-viewmodel-savedstate-tvossimulatorarm64:2.9.4
  • androidx.lifecycle:lifecycle-viewmodel-savedstate-tvosx64:2.9.4
  • androidx.lifecycle:lifecycle-viewmodel-savedstate-wasm-js:2.9.4
  • androidx.lifecycle:lifecycle-viewmodel-savedstate-watchosarm32:2.9.4
  • androidx.lifecycle:lifecycle-viewmodel-savedstate-watchosarm64:2.9.4
  • androidx.lifecycle:lifecycle-viewmodel-savedstate-watchosdevicearm64:2.9.4
  • androidx.lifecycle:lifecycle-viewmodel-savedstate-watchossimulatorarm64:2.9.4
  • androidx.lifecycle:lifecycle-viewmodel-savedstate-watchosx64:2.9.4
  • androidx.lifecycle:lifecycle-viewmodel-testing:2.9.4
  • androidx.lifecycle:lifecycle-viewmodel-testing-android:2.9.4
  • androidx.lifecycle:lifecycle-viewmodel-testing-desktop:2.9.4
  • androidx.lifecycle:lifecycle-viewmodel-testing-iosarm64:2.9.4
  • androidx.lifecycle:lifecycle-viewmodel-testing-iossimulatorarm64:2.9.4
  • androidx.lifecycle:lifecycle-viewmodel-testing-iosx64:2.9.4
  • androidx.lifecycle:lifecycle-viewmodel-testing-js:2.9.4
  • androidx.lifecycle:lifecycle-viewmodel-testing-linuxarm64:2.9.4
  • androidx.lifecycle:lifecycle-viewmodel-testing-linuxx64:2.9.4
  • androidx.lifecycle:lifecycle-viewmodel-testing-macosarm64:2.9.4
  • androidx.lifecycle:lifecycle-viewmodel-testing-macosx64:2.9.4
  • androidx.lifecycle:lifecycle-viewmodel-testing-mingwx64:2.9.4
  • androidx.lifecycle:lifecycle-viewmodel-testing-tvosarm64:2.9.4
  • androidx.lifecycle:lifecycle-viewmodel-testing-tvossimulatorarm64:2.9.4
  • androidx.lifecycle:lifecycle-viewmodel-testing-tvosx64:2.9.4
  • androidx.lifecycle:lifecycle-viewmodel-testing-wasm-js:2.9.4
  • androidx.lifecycle:lifecycle-viewmodel-testing-watchosarm32:2.9.4
  • androidx.lifecycle:lifecycle-viewmodel-testing-watchosarm64:2.9.4
  • androidx.lifecycle:lifecycle-viewmodel-testing-watchosdevicearm64:2.9.4
  • androidx.lifecycle:lifecycle-viewmodel-testing-watchossimulatorarm64:2.9.4
  • androidx.lifecycle:lifecycle-viewmodel-testing-watchosx64:2.9.4
  • androidx.lifecycle:lifecycle-viewmodel-tvosarm64:2.9.4
  • androidx.lifecycle:lifecycle-viewmodel-tvossimulatorarm64:2.9.4
  • androidx.lifecycle:lifecycle-viewmodel-tvosx64:2.9.4
  • androidx.lifecycle:lifecycle-viewmodel-wasm-js:2.9.4
  • androidx.lifecycle:lifecycle-viewmodel-watchosarm32:2.9.4
  • androidx.lifecycle:lifecycle-viewmodel-watchosarm64:2.9.4
  • androidx.lifecycle:lifecycle-viewmodel-watchosdevicearm64:2.9.4
  • androidx.lifecycle:lifecycle-viewmodel-watchossimulatorarm64:2.9.4
  • androidx.lifecycle:lifecycle-viewmodel-watchosx64:2.9.4
  • androidx.savedstate:savedstate:1.3.3
  • androidx.savedstate:savedstate-android:1.3.3
  • androidx.savedstate:savedstate-compose:1.3.3
  • androidx.savedstate:savedstate-compose-android:1.3.3
  • androidx.savedstate:savedstate-compose-desktop:1.3.3
  • androidx.savedstate:savedstate-compose-iosarm64:1.3.3
  • androidx.savedstate:savedstate-compose-iossimulatorarm64:1.3.3
  • androidx.savedstate:savedstate-compose-iosx64:1.3.3
  • androidx.savedstate:savedstate-compose-js:1.3.3
  • androidx.savedstate:savedstate-compose-linuxarm64:1.3.3
  • androidx.savedstate:savedstate-compose-linuxx64:1.3.3
  • androidx.savedstate:savedstate-compose-macosarm64:1.3.3
  • androidx.savedstate:savedstate-compose-macosx64:1.3.3
  • androidx.savedstate:savedstate-compose-mingwx64:1.3.3
  • androidx.savedstate:savedstate-compose-tvosarm64:1.3.3
  • androidx.savedstate:savedstate-compose-tvossimulatorarm64:1.3.3
  • androidx.savedstate:savedstate-compose-tvosx64:1.3.3
  • androidx.savedstate:savedstate-compose-wasm-js:1.3.3
  • androidx.savedstate:savedstate-compose-watchosarm32:1.3.3
  • androidx.savedstate:savedstate-compose-watchosarm64:1.3.3
  • androidx.savedstate:savedstate-compose-watchosdevicearm64:1.3.3
  • androidx.savedstate:savedstate-compose-watchossimulatorarm64:1.3.3
  • androidx.savedstate:savedstate-compose-watchosx64:1.3.3
  • androidx.savedstate:savedstate-desktop:1.3.3
  • androidx.savedstate:savedstate-iosarm64:1.3.3
  • androidx.savedstate:savedstate-iossimulatorarm64:1.3.3
  • androidx.savedstate:savedstate-iosx64:1.3.3
  • androidx.savedstate:savedstate-js:1.3.3
  • androidx.savedstate:savedstate-ktx:1.3.3
  • androidx.savedstate:savedstate-linuxarm64:1.3.3
  • androidx.savedstate:savedstate-linuxx64:1.3.3
  • androidx.savedstate:savedstate-macosarm64:1.3.3
  • androidx.savedstate:savedstate-macosx64:1.3.3
  • androidx.savedstate:savedstate-mingwx64:1.3.3
  • androidx.savedstate:savedstate-tvosarm64:1.3.3
  • androidx.savedstate:savedstate-tvossimulatorarm64:1.3.3
  • androidx.savedstate:savedstate-tvosx64:1.3.3
  • androidx.savedstate:savedstate-wasm-js:1.3.3
  • androidx.savedstate:savedstate-watchosarm32:1.3.3
  • androidx.savedstate:savedstate-watchosarm64:1.3.3
  • androidx.savedstate:savedstate-watchosdevicearm64:1.3.3
  • androidx.savedstate:savedstate-watchossimulatorarm64:1.3.3
  • androidx.savedstate:savedstate-watchosx64:1.3.3

Sep 17, 2025


Older Posts