EEltaCrew
HomeBlog

R8 on, build green, app broken: five runtime traps that actually hit across 15 apps

Published 2026-09-16Measured 2026-09-12About 3 min readelta · EltaCrew

I enabled R8 in 15 apps for Play's 2027 code-optimization requirement. Every build passed, but 2 of the first 7 I installed broke at runtime. Gson deserialization, getIdentifier resource lookups, Room _Impl classes, and debuggable builds that silently disable R8: the kinds of failure that never show up in a crash log.

On 26 August 2026 Google announced new Play quality requirements. From February 2027, apps whose DEX exceeds 10 MB (50 MB for games) must ship with code optimization (R8), and the support article says it is "not optional" and "can affect an app's visibility and publishing capabilities". I turned R8 on in the 15 apps that qualify, wrote keep rules per app, and got release builds through. DEX shrank by 71.6% to 87.7%.

The problem came next. I installed release builds of 7 apps that had built cleanly and tapped through the screens. Two were broken. With R8, "build succeeded" does not mean "works".

1. Gson: keep every package you deserialize into

The pet-care app died on launch:

ClassCastException: com.google.gson.internal.LinkedTreeMap cannot be cast to d82

I had kept only the dto package, but the actual model classes lived in data.repository. With generic type information stripped, Gson did not know the target type, produced a LinkedTreeMap, and the cast blew up. The fix was to find every fromJson and TypeToken call and keep every package those target classes live in, including inner classes of the backup reader (BackupManager$*).

2. getIdentifier: resource keys show up on screen

The screen-time app displayed strings like tpl_nightowl_2_wit and title_dawn_scroller. No crash. The code looked resources up by name with getResources().getIdentifier(), R8 could not see that, resource shrinking removed the strings, and a fallback that returns the name when id == 0 made it fail silently.

Crash monitoring will never catch this class of bug. Only a user's eyes do. The fix is res/raw/keep.xml listing the resource name patterns. A full search showed the lottery app (ball_*) and the error-code app (logo_*) use the same pattern, so I patched those pre-emptively. The real fix is to map with ordinary branches instead of getIdentifier.

3. Room finds _Impl by name

The remote-control release, installed on a physical phone, died before the first screen:

Unable to get provider androidx.startup.InitializationProvider
Failed to create an instance of androidx.work.impl.WorkDatabase

Room appends _Impl to the database class name and loads it with Class.forName. R8 could not see that reference and removed the class, and because WorkManager creates its WorkDatabase from the androidx.startup provider, the process dies before anything is drawn. -keep class **_Impl { *; } is one line. If you use Room together with WorkManager, this is the first thing that explodes.

4. Other things that bit at build time

5. Verification builds must be non-debuggable

This is a case where my own handover document was wrong. I had written "verify with a debug build that just has R8 enabled". If you set isMinifyEnabled = true on a debuggable build type, AGP prints one warning and disables obfuscation and optimization:

All code optimizations and obfuscation are disabled for debuggable builds

Testing in that state reproduces none of the traps above, so you pass the app wrongly. The correct shape is a separate build type that inherits from release, sets isDebuggable = false, swaps in the debug signing key and test ad IDs. That installs on a real device while R8 runs exactly as it does in release.

For the same reason, Android Studio's default variant is debug; install that without thinking and you are looking at an app R8 never touched.

Checklist

When enabling R8 on a new app or tightening keep rules:

  1. Search for fromJson / TypeToken and keep every target package.
  2. Search for getIdentifier; if it hits, add res/raw/keep.xml.
  3. If Room is used, keep **_Impl. Mandatory if WorkManager is also present.
  4. Keep the provider packages of libraries that load classes by string (crypto, serialization, DI).
  5. Install a non-debuggable build on a physical device and look at every screen even with zero crashes. Trap 2 is only visible by eye.

What remains uncertain

Google's document does not define what "25% coverage" means: size reduction or class ratio. I do not assert either. Default R8 clears it under any reading. And enabling R8 is not the same as Play judging the requirement met; the final check is the app quality overview in Play Console.

AndroidR8Google Playcode optimization

Related

One merchant account blocked releases for all 21 of my apps: Korean individual developers and Play Console's "additional information required"Right after I set up a merchant account to test a donation in-app product, Play Console demanded a business registration number and an e-commerce license number and blocked every release on the account. Deleting the products did not help, deleting the payout account did not help, and support's final answer was "this cannot be reversed". How an individual developer avoids this trap.Data safety forms with zero declarations are getting caught: auditing 21 apps and fixing them with CSV and the APIA phone number the user types in by hand, belonging to someone else, still counts as "phone number collection" to Google. After one rejection I compared all 21 apps' data-safety declarations against their source: 11 were wrong, and 3 had declared nothing at all. Why ad-only apps cannot declare zero, and how to fix the form with CSV and the API instead of the five-step wizard.The #1 app in Play search has the query in its title, verbatim: how one middle dot killed our keyword matchingI measured 157 Korean Play search queries by hand. The top-ranked app always carried the exact query string, spacing included, in its title. Our apps had been invisible for QR queries because I had joined words with a middle dot to make the title look nicer. The evidence behind retitling 13 apps.