Crash Resiliency & Graceful Degradation
📖 Concept
Crash resiliency means designing your app to recover gracefully from errors, handle unexpected states, and provide degraded functionality instead of crashing.
Crash prevention strategies:
- Defensive programming — Null checks, bounds checks, input validation
- Sealed types for state — Impossible states become compile errors
- Global exception handler — Catch unhandled exceptions for logging before crash
- Process death handling — Restore state from SavedStateHandle/Room
- Network error handling — Show cached data, retry mechanism, user feedback
Process death handling: Android can kill your app process at any time when in the background. When the user returns, the system recreates the Activity stack but your in-memory data is gone.
Survives process death: Does NOT survive:
- SavedStateHandle (ViewModel) - ViewModel state (without SavedState)
- onSaveInstanceState Bundle - Singletons
- Room database - In-memory caches
- DataStore/SharedPreferences - Static variables
- Files - Retrofit/OkHttp state
Graceful degradation tiers:
- Full functionality — All features work normally
- Degraded — Some features unavailable, core works (offline mode)
- Minimal — Show cached data with "offline" banner
- Error state — Clear error message with retry option
- Crash — Last resort, never silent. Log to Crashlytics.
💻 Code Example
1// Global exception handler — log before crash2class CrashResilience {34 companion object {5 fun install(application: Application) {6 val defaultHandler = Thread.getDefaultUncaughtExceptionHandler()78 Thread.setDefaultUncaughtExceptionHandler { thread, throwable ->9 try {10 // Log crash details for investigation11 CrashReporting.logFatal(throwable)12 // Save critical state to disk13 saveCriticalState(application)14 } finally {15 // Let the default handler show crash dialog16 defaultHandler?.uncaughtException(thread, throwable)17 }18 }19 }20 }21}2223// Process death-safe ViewModel24@HiltViewModel25class FormViewModel @Inject constructor(26 private val savedStateHandle: SavedStateHandle27) : ViewModel() {28 // These survive process death via SavedStateHandle29 val name = savedStateHandle.getStateFlow("name", "")30 val email = savedStateHandle.getStateFlow("email", "")31 val step = savedStateHandle.getStateFlow("step", 0)3233 fun updateName(value: String) { savedStateHandle["name"] = value }34 fun updateEmail(value: String) { savedStateHandle["email"] = value }35 fun nextStep() { savedStateHandle["step"] = (step.value + 1) }36}3738// Graceful degradation in Repository39class ProductRepository @Inject constructor(40 private val api: ProductApi,41 private val dao: ProductDao,42 private val connectivity: NetworkMonitor43) {44 fun getProducts(): Flow<Resource<List<Product>>> = flow {45 // Always emit cached data first46 val cached = dao.getAll().first().map { it.toDomain() }47 if (cached.isNotEmpty()) {48 emit(Resource.Success(cached, isStale = true))49 } else {50 emit(Resource.Loading)51 }5253 // Try to refresh from network54 if (connectivity.isConnected.value) {55 try {56 val fresh = api.getProducts()57 dao.upsertAll(fresh.map { it.toEntity() })58 emit(Resource.Success(fresh.map { it.toDomain() }, isStale = false))59 } catch (e: Exception) {60 if (cached.isEmpty()) {61 emit(Resource.Error(e.message ?: "Failed to load", null))62 }63 // If we have cached data, just log the error64 }65 } else if (cached.isEmpty()) {66 emit(Resource.Error("No internet connection", null))67 }68 }69}7071sealed interface Resource<out T> {72 data object Loading : Resource<Nothing>73 data class Success<T>(val data: T, val isStale: Boolean = false) : Resource<T>74 data class Error<T>(val message: String, val data: T?) : Resource<T>75}
🏋️ Practice Exercise
Practice:
- Test process death handling: enable "Don't keep activities" in Developer Options and navigate your app
- Implement SavedStateHandle for a multi-step form that survives process death
- Design a graceful degradation strategy with 3 fallback tiers for your app
- Set up a global exception handler that logs to Crashlytics before crashing
- Implement a Resource
wrapper that handles loading/success/error/stale states
⚠️ Common Mistakes
Not testing for process death — use 'Don't keep activities' developer option or adb kill command
Relying on ViewModel state surviving process death — ViewModel is recreated, use SavedStateHandle
Catching Exception globally and swallowing errors — always log, never suppress silently
Not providing offline fallback — if the network is down, show cached data instead of an error screen
Showing a generic error message — 'Something went wrong' is not helpful. Be specific and offer actions.
💼 Interview Questions
🎤 Mock Interview
Mock interview is powered by AI for Crash Resiliency & Graceful Degradation. Login to unlock this feature.