EEltaCrew
Home › Blog

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

Why our lottery ticket QR scanner showed zero successes: a path you don't log looks like a failureIn our lottery app's weekly report, 19 people had opened the QR screen and GA4 showed zero successful scans. The scanner wasn't broken. The path that real ticket QR codes took had no logging at all. How we found the measurement gap, what four similar apps do, why we switched to the bundled scanning engine, and the numbers one week after the fix.Tapping the logo in the map's corner crashed the app: Naver Map SDK and the AppCompat themeFour "You need to use a Theme.AppCompat theme" crashes showed up in Crashlytics for our lottery store map app. They came not from users but from the Play pre-launch report's test robot tapping the NAVER logo in the bottom-left corner of the map. The cause, a fix that wraps only the map instead of changing the whole app theme, and a second problem that appeared in dark mode.Requesting ads before the SDK finishes initializing: why the same crash came back three times after we added a guardAfter we first fixed the "MobileAds.initialize must be called" crash in our live apps, the same crash came back three more times in different shapes. We had a guard that waited for initialization to finish, but a flag set at the start, a flag set on consent alone, and new ad slots that skipped the guard all left gaps. The cause of each case and the results of checking all 23 apps.