Theory

What makes an app shippable

Pica works. But "works on my phone" and "shippable" are two different things. A shippable app has two properties: it never leaves the user on an empty or broken screen, and it can be signed and uploaded to Google Play.

1. Consistent states. The same MVI shape recurred all course: Loading / Success / Error. The menu (5) and orders (9) already have it. For release you make sure every screen handles all cases, including empty:

sealed interface UiState<out T> {
    data object Loading : UiState<Nothing>
    data class Success<T>(val data: T) : UiState<T>
    data object Empty : UiState<Nothing>          // ← the one people forget
    data class Error(val message: String) : UiState<Nothing>
}

"Empty cart", "No orders yet", "Offline + Retry" — these aren't details, they're the difference between "looks broken" and "all good."

2. A signed, shrunk release. The debug build you've been running is signed with an automatic debug key and unoptimized. A release needs:

  • Signing. Google Play only accepts an app signed with your release key. The key is your identity; lose it and you can't recreate it.
  • R8 (shrink + obfuscate). Removes unused code and shortens names — a smaller APK, harder to reverse-engineer.
  • App Bundle (.aab). Google Play wants an .aab (not an .apk) — its server generates an optimal APK per device from it.

Verify-docs. AGP 9 (9.2.1) replaced the old isMinifyEnabled / isShrinkResources flags with a new optimization { } block. Many online examples still show the old way — a real gotcha we'll hit in the next step.