RORK LABJP
R8 — From Expo SDK 58, R8 is enabled by default for Android release builds. Measuring the same code before and after shows both what shrank and what brokeXCODE26.6 — The Xcode 27 image for EAS Build is still coming soon, and latest is still Xcode 26.6. Which bugs you hit depends on whether your local Xcode is already on 2711/01 — Extension requests for the Google Play target API level close on November 1, forty days away. New and updated apps need API 36 or higher, existing apps API 35 or higherFETCH — expo/fetch is reported to hang without settling on iOS, while Android resolves a truncated body as a plain 200. Nothing throws, so the timeout has to be yoursNEW — A build that stops at exit code 0: the flag that silenced the logs, and the check that let an empty file throughSCENE — expo@57.0.23 added an opt-in for launching under Xcode 27 while staying on SDK 57. Enable ios.enableSceneSupport through expo-build-propertiesR8 — From Expo SDK 58, R8 is enabled by default for Android release builds. Measuring the same code before and after shows both what shrank and what brokeXCODE26.6 — The Xcode 27 image for EAS Build is still coming soon, and latest is still Xcode 26.6. Which bugs you hit depends on whether your local Xcode is already on 2711/01 — Extension requests for the Google Play target API level close on November 1, forty days away. New and updated apps need API 36 or higher, existing apps API 35 or higherFETCH — expo/fetch is reported to hang without settling on iOS, while Android resolves a truncated body as a plain 200. Nothing throws, so the timeout has to be yoursNEW — A build that stops at exit code 0: the flag that silenced the logs, and the check that let an empty file throughSCENE — expo@57.0.23 added an opt-in for launching under Xcode 27 while staying on SDK 57. Enable ios.enableSceneSupport through expo-build-properties
Articles/Dev Tools
Dev Tools/2026-09-22Intermediate

Four Numbers to Record Before R8 Becomes the Default — Android Measurements While We Wait for Expo SDK 58

Expo SDK 58 turns on R8 by default for Android release builds. Here is how I capture four numbers — download size, DEX total, cold start, and build time — so the before and after can be compared with the same ruler.

Expo211Android51R82Measurement3SDK 58

I was rereading the SDK 58 beta announcement late one evening when the Android section stopped me. One short line: R8 is enabled by default for release builds.

What it reminded me of was the last time I moved a project across a big SDK jump. The app afterwards felt a little lighter, but I still cannot tell you whether that came from the build settings or from the images I happened to swap out the same week. I had kept no numbers from before.

The "before" numbers only exist before. While the beta period is still running, this is a good moment to capture where your Expo project stands today.

I am writing this for people who build a Rork-exported Expo project on their own machine. New Rork projects lean toward Swift and Kotlin now, but existing Expo apps keep building, so a baseline taken today still pays off.

What actually becomes the default

According to the Expo SDK 58 Beta release notes, Android release builds get android.enableMinifyInReleaseBuilds=true by default. R8 shrinks, optimizes, and obfuscates your Java and Kotlin code using the proguard-android-optimize.txt preset.

Three things are worth holding onto:

  • Libraries that rely on reflection may need keep rules
  • You can opt out by setting enableMinifyInReleaseBuilds to false through expo-build-properties
  • The size win varies enormously per app, so your own numbers beat any published percentage

The stable date is not in the primary source yet. The beta began on 2026-09-15, and the announcement says only that it will last three to four weeks, and that the stable release follows React Native 0.88. I would rather measure while the date is still open than scramble once it lands.

Why I narrowed it down to four

For a while I tried to capture everything measurable — memory, dropped frames, the resource breakdown inside the APK. The more rows I added, the harder it became to repeat the second run under the same conditions. That approach did not hold up.

Now I keep four: what R8 directly touches, and where the result reaches an actual user.

NumberWhere it comes fromUnitWatch out for
Download sizebundletool get-size totalbytesNot the AAB file size — what a device actually downloads
DEX totalclasses*.dex inside the AABbytesThis is mostly what R8 shrinks, so the delta shows up here first
Cold startTotalTime from am start-activity -WmsA single run wanders; take the median
Release build durationtime ./gradlew :app:bundleReleasesecondsGrows by the cost of the R8 pass — useful for CI planning

I take download size and DEX together because either one alone can mislead you. If DEX shrinks but native libraries and images dominate the binary, users feel nothing. If what you actually want is a breakdown of the binary itself, When Your Rork App Binary Tops 150MB: 5 Causes to Isolate and a Step-by-Step Slim-Down Plan covers that ground more directly.

Fix the conditions, then run the same path twice

What ruins a comparison is almost never the measuring — it is a premise that quietly shifted. I write four things in a note before starting: same commit, same physical device, same JDK and Gradle, and do not touch versionCode. Taking one run on CI and the other on your laptop is enough to void the whole thing.

You can try the change on SDK 57 today. It is one plugin entry.

{
  "expo": {
    "plugins": [
      ["expo-build-properties", { "android": { "enableMinifyInReleaseBuilds": true } }]
    ]
  }
}

Then build and read the sizes in this order.

# Run 1: today's baseline. Run 2: the same commit with minification on.
npx expo prebuild -p android --clean
cd android
./gradlew clean
time ./gradlew :app:bundleRelease
 
AAB=app/build/outputs/bundle/release/app-release.aab
ls -l "$AAB"
unzip -l "$AAB" | grep '\.dex'

Download size is a separate question from the AAB file size. Hand bundletool your signing key and ask for the per-device delivery size.

bundletool build-apks \
  --bundle=app/build/outputs/bundle/release/app-release.aab \
  --output=/tmp/app.apks \
  --ks=$HOME/keystores/release.jks \
  --ks-key-alias=YOUR_KEY_ALIAS
 
bundletool get-size total --apks=/tmp/app.apks --dimensions=SDK,ABI

For cold start, do not accept the wobble as data. Drop the first two launches while caches are still warming, and take the median of the remaining eight.

#!/usr/bin/env bash
# Launch 10 times, discard the first 2, print the median TotalTime
ACT="com.example.app/.MainActivity"
 
for i in $(seq 1 10); do
  adb shell am start-activity -W -S -n "$ACT" \
    | awk -F': ' '/TotalTime/ {print $2}'
  sleep 3
done | tail -n 8 | sort -n | awk '{a[NR]=$1}
  END {print "median=" (NR % 2 ? a[(NR + 1) / 2] : (a[NR / 2] + a[NR / 2 + 1]) / 2)}'

The -S flag stops the app before launching it. Without it, every run after the first is a warm start — I missed that once and nearly congratulated myself on a speedup that was not there.

When the numbers move, where to look

R8 works on DEX, so that is where the delta reads most honestly. If your download size moves far less than DEX did, the weight of your app lives in images, audio, or native libraries instead. My wallpaper apps are exactly that shape, and knowing which part dominates turned out to matter more than the bytes I saved.

Cold start may not move at all. Knowing that it does not move is itself worth keeping. The next time someone tells you R8 makes startup faster, you can answer quietly with your own figure.

The part to be careful about is that breakage shows up only in release builds. A working debug build proves nothing here. Libraries that resolve class names or type information by reflection, layers that depend on annotation processing, and native module name resolution tend to fall over first. For rescuing the classes that got stripped, I wrote up the keep-rule path in Crashes Only in the Release Build — Rescuing Classes R8 Stripped in Expo (Android).

# android/app/proguard-rules.pro — keep only the layer that resolves names reflectively
-keep class com.example.model.** { *; }
-keepattributes Signature, *Annotation*, InnerClasses

One more artifact matters: mapping.txt, which turns an obfuscated stack trace back into something readable, is written on every build.

ls -l android/app/build/outputs/mapping/release/mapping.txt

Lose it and your production crashes arrive as symbols. Store it alongside the build output, one copy per version. The mechanics are documented well in Android's code shrinking guide.

Keep the record on one small table

Numbers evaporate unless they land in a table the same day. I dig a docs/ folder in the repository and keep a single text file there. Not making it fancy is the only reason I have kept doing it.

ItemBeforeAfterDelta
Download size (max)(fill in)(fill in)(fill in)
DEX total(fill in)(fill in)(fill in)
Cold start median(fill in)(fill in)(fill in)
bundleRelease duration(fill in)(fill in)(fill in)
What I noticed on the device(fill in)

That last row exists for everything the numbers refuse to explain: a white flash on the first frame, a slower open when the app is launched from a notification. Those observations tend to reinterpret the figures later.

When the stable release arrives and you measure again, having this table decides how much you are able to say. Some time this week, run ./gradlew clean :app:bundleRelease once and write down the AAB size and the DEX total, even if you record nothing else. Two numbers take about ten minutes.

I appreciate you staying with a post whose whole point is writing two numbers into a text file.

Share

Thank You for Reading

Rork Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $15 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

Dev Tools2026-09-10
I Added a Dim Screen Button, and iPhones Stayed Dark After the App Was Closed
In expo-brightness, setBrightnessAsync applies only to the current activity on Android, but changes the device brightness itself on iOS. Here is why the cleanup burden falls on one platform only, and a hook that restores brightness through AppState.
Dev Tools2026-08-31
Three Minutes After the Screen Locked, My expo-audio Ambient Sound Went Silent
Why expo-audio stops playing when the screen locks, diagnosed as three separate layers: build config, audio session, and lock screen integration. The three-minute stop on Android is documented behavior.
Dev Tools2026-08-21
The four acceptance checks I still run after the build turns green
A successful build does not mean a shippable build. Here are the four failure classes an AI build loop cannot see, and a dependency-free script that inspects the artifact itself before you submit.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links