Skip to content

fix(app): add R8 keep rules for Compose animation/runtime/ui#5146

Merged
jamesarich merged 1 commit into
mainfrom
fix/release-animation-stuck
Apr 15, 2026
Merged

fix(app): add R8 keep rules for Compose animation/runtime/ui#5146
jamesarich merged 1 commit into
mainfrom
fix/release-animation-stuck

Conversation

@jamesarich

Copy link
Copy Markdown
Collaborator

R8 (AGP 9.x) aggressively optimizes Compose animation internals, causing all animations to silently freeze on their first frame in release builds — indeterminate progress spinners, crossfade transitions, AnimatedVisibility, animateFloatAsState, etc.

The bundled animation-core consumer rules only protect throw*Exception methods; no keep rules exist for the actual animation classes, frame clock, or recomposer.

Adds explicit keeps for compose.runtime, compose.ui, compose.animation.core, and compose.animation.

  • Verified on Pixel 6a: 483 frames / 8s (~60 FPS), 0% janky, no crashes

R8 (AGP 9.x) aggressively optimizes Compose animation internals, causing
all animations to silently freeze on their first frame in release builds
— indeterminate progress spinners, crossfade transitions, AnimatedVisibility,
animateFloatAsState, etc.

The bundled animation-core consumer rules only protect throw*Exception
methods; no keep rules exist for the actual animation classes, frame clock,
or recomposer.  Add explicit keeps for compose.runtime, compose.ui,
compose.animation.core, and compose.animation.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@jamesarich jamesarich enabled auto-merge April 15, 2026 14:22
@github-actions github-actions Bot added the bugfix PR tag label Apr 15, 2026
@jamesarich jamesarich added this pull request to the merge queue Apr 15, 2026
Merged via the queue into main with commit dea364d Apr 15, 2026
12 checks passed
@jamesarich jamesarich deleted the fix/release-animation-stuck branch April 15, 2026 14:46
jamesarich added a commit that referenced this pull request Apr 15, 2026
CMP 1.11 consumer rules ship -assumenosideeffects on Composer.<clinit>()
and ComposerImpl.<clinit>(), plus -assumevalues on ComposeRuntimeFlags
and ComposeStackTraceMode. These optimization directives let R8 rewrite
call sites even when target classes are preserved by -keep rules — the
result is that the recomposer/frame-clock/animation state machines
silently freeze on their first frame in release builds.

-keep rules (added in #5146) only prevent shrinking and obfuscation;
they do NOT cancel -assumenosideeffects/-assumevalues processing.
-dontoptimize is the only directive that disables these optimization
passes while still allowing tree-shaking for APK size reduction.

Also adds foundation and material3 to the keep rules as defence-in-depth
against tree-shaking of Compose classes referenced through compiler-
generated state machines.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
jamesarich pushed a commit that referenced this pull request Apr 17, 2026
Removes explicit -keep/-dontwarn wildcards whose behavior is already
provided by the library's own bundled consumer-rules.pro, by Room's
generated static references, or by narrower annotation-targeted keeps.
The merged R8 configuration shrinks by 34 lines (1981→1947 googleRelease,
1653→1619 fdroidRelease), giving R8 more tree-shaking freedom.

Removed (covered by library-bundled consumer rules):
- kotlin.Metadata, kotlin.reflect.**, kotlin.coroutines.Continuation,
  kotlinx.coroutines.** (kotlin-stdlib + kotlinx-coroutines-core)
- androidx.datastore.**, androidx.paging.**, androidx.lifecycle.**,
  androidx.navigation3.**, androidx.sqlite.**
- coil3.**, okio.**, co.touchlab.kermit.**,
  com.mikepenz.aboutlibraries.**, com.mikepenz.markdown.**,
  com.juul.kable.**, io.ktor.**, io.ktor.client.engine.java.**
  (HttpClientEngineFactory ServiceLoader keep retained)

Room (room3) narrowed:
- Dropped org.meshtastic.core.database.{dao,entity,Converters}.** and
  **_Impl wildcards. RoomDatabaseConstructor +
  MeshtasticDatabaseConstructor/MeshtasticDatabase keeps retained.
  Room 3.0 KMP generates static references rather than reflective
  lookups, so reachable DAO/entity/_Impl code survives tree-shaking.

Wire protobuf narrowed:
- Dropped com.squareup.wire.**, org.meshtastic.proto.**, meshtastic.**
  wildcards and deleted core/proto/consumer-rules.pro (20 per-class keeps).
- Replaced with targeted -keepclassmembers for ADAPTER fields on
  Message subclasses and ProtoAdapter member preservation. Verified
  no Class.forName lookups into org.meshtastic.proto namespace.

Meshtastic model + DI:
- Dropped org.meshtastic.core.model.** wildcard and deleted
  core/model/consumer-rules.pro (DataPacket is @CommonParcelize'd;
  no reflective access to core.model).
- Replaced org.meshtastic.**.di.** wildcard with a narrow
  @KoinViewModel class annotation keep (the @module, @componentscan,
  @single, @factory keeps above already cover the rest of Koin).

Compose resources refinement:
- Narrowed org.meshtastic.core.resources.** to Res + Res$* members
  only (sufficient for the fdroid startup-crash workaround in #5146).

Intentionally retained (documented workarounds / policy):
- -dontoptimize, -dontobfuscate, -printconfiguration
- All Koin annotation keeps + org.koin.**
- kotlinx-serialization @serializable keeps
- Room RoomDatabaseConstructor + MeshtasticDatabase(-Constructor)
- Ktor HttpClientEngineFactory ServiceLoader keep
- Desktop MainKt + org.meshtastic.desktop.** entry points
- All -dontwarn rules for JVM/Android platform gaps
- app/proguard-rules.pro Compose runtime/ui/animation/foundation/
  material3 keeps (defence-in-depth with -dontoptimize; may interact
  with the parallel CMP freeze RCA investigation).

Verified:
- ./gradlew :app:assembleFdroidRelease — SUCCESS (after each batch)
- ./gradlew :app:assembleGoogleRelease — SUCCESS
- ./gradlew spotlessApply detekt — SUCCESS
- :desktop:createReleaseDistributable — pre-existing failure on
  origin/main (Vico/Skia warnings), unrelated to these changes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
jamesarich pushed a commit that referenced this pull request Apr 17, 2026
Removes explicit -keep/-dontwarn wildcards whose behavior is already
provided by the library's own bundled consumer-rules.pro, by Room's
generated static references, or by narrower annotation-targeted keeps.
The merged R8 configuration shrinks by 34 lines (1981→1947 googleRelease,
1653→1619 fdroidRelease), giving R8 more tree-shaking freedom.

Removed (covered by library-bundled consumer rules):
- kotlin.Metadata, kotlin.reflect.**, kotlin.coroutines.Continuation,
  kotlinx.coroutines.** (kotlin-stdlib + kotlinx-coroutines-core)
- androidx.datastore.**, androidx.paging.**, androidx.lifecycle.**,
  androidx.navigation3.**, androidx.sqlite.**
- coil3.**, okio.**, co.touchlab.kermit.**,
  com.mikepenz.aboutlibraries.**, com.mikepenz.markdown.**,
  com.juul.kable.**, io.ktor.**, io.ktor.client.engine.java.**
  (HttpClientEngineFactory ServiceLoader keep retained)

Room (room3) narrowed:
- Dropped org.meshtastic.core.database.{dao,entity,Converters}.** and
  **_Impl wildcards. RoomDatabaseConstructor +
  MeshtasticDatabaseConstructor/MeshtasticDatabase keeps retained.
  Room 3.0 KMP generates static references rather than reflective
  lookups, so reachable DAO/entity/_Impl code survives tree-shaking.

Wire protobuf narrowed:
- Dropped com.squareup.wire.**, org.meshtastic.proto.**, meshtastic.**
  wildcards and deleted core/proto/consumer-rules.pro (20 per-class keeps).
- Replaced with targeted -keepclassmembers for ADAPTER fields on
  Message subclasses and ProtoAdapter member preservation. Verified
  no Class.forName lookups into org.meshtastic.proto namespace.

Meshtastic model + DI:
- Dropped org.meshtastic.core.model.** wildcard and deleted
  core/model/consumer-rules.pro (DataPacket is @CommonParcelize'd;
  no reflective access to core.model).
- Replaced org.meshtastic.**.di.** wildcard with a narrow
  @KoinViewModel class annotation keep (the @module, @componentscan,
  @single, @factory keeps above already cover the rest of Koin).

Compose resources refinement:
- Narrowed org.meshtastic.core.resources.** to Res + Res$* members
  only (sufficient for the fdroid startup-crash workaround in #5146).

Intentionally retained (documented workarounds / policy):
- -dontoptimize, -dontobfuscate, -printconfiguration
- All Koin annotation keeps + org.koin.**
- kotlinx-serialization @serializable keeps
- Room RoomDatabaseConstructor + MeshtasticDatabase(-Constructor)
- Ktor HttpClientEngineFactory ServiceLoader keep
- Desktop MainKt + org.meshtastic.desktop.** entry points
- All -dontwarn rules for JVM/Android platform gaps
- app/proguard-rules.pro Compose runtime/ui/animation/foundation/
  material3 keeps (defence-in-depth with -dontoptimize; may interact
  with the parallel CMP freeze RCA investigation).

Verified:
- ./gradlew :app:assembleFdroidRelease — SUCCESS (after each batch)
- ./gradlew :app:assembleGoogleRelease — SUCCESS
- ./gradlew spotlessApply detekt — SUCCESS
- :desktop:createReleaseDistributable — pre-existing failure on
  origin/main (Vico/Skia warnings), unrelated to these changes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bugfix PR tag

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant