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/isShrinkResourcesflags with a newoptimization { }block. Many online examples still show the old way — a real gotcha we'll hit in the next step.