If you’ve ever opened your beautifully designed app on a tablet only to see it looking like a stretched-out phone screen, you know the pain. Or maybe you’ve watched your carefully crafted layout break apart on a foldable device. Trust me, I’ve been there. The good news..? Responsive and Adaptive UI in Jetpack Compose makes...
Android 12 introduced one of the biggest visual upgrades in Android history: Material You.At the heart of this design shift is a small but powerful feature called Monochrome Icons. If you’ve ever noticed your app icons changing color to match your wallpaper, that’s Monochrome Icons doing their job. In this guide, we’ll break down: What...
Jetpack Compose introduces a very different mental model compared to XML-based Android UI. One line that often confuses beginners (and even experienced Android devs at first) is: KotlinonValueChange = { value = it } Especially when value is defined like this: Kotlinvar value by remember { mutableStateOf(0) } At first glance, this line looks almost...
If you’ve ever worked with Android’s BroadcastReceiver, you know there’s a golden rule: keep your work quick. The system expects your receiver to finish in about 10 seconds, or it’ll label your app as unresponsive (ANR). But what happens when you need just a bit more time? That’s exactly where goAsync() comes to the rescue....
State management is the backbone of any modern Android app. If state is messy, your UI becomes unpredictable, buggy, and hard to maintain. Jetpack Compose was designed to solve many of these problems, but only if you understand how State Management in Jetpack Compose actually works. In this post, we will break down modern state...
Have you ever opened an app on your Android phone, switched to another app for a while, and then returned only to find everything reset? Your form data gone, your scroll position lost, or the app back at the home screen?
Understanding Process Death in Android is crucial for building apps that feel reliable and professional. In this guide, we’ll explore what it is, why it happens, and most importantly, how to handle it properly in your Android apps.
What Exactly Is Process Death in Android?
Process Death in Android occurs when the Android operating system kills your app’s process to free up memory for other apps. Think of it like your phone doing some housekeeping — when memory gets tight, Android decides which apps to close to keep everything running smoothly.
Here’s the tricky part: when your app’s process dies, your activities might still appear to be in the back stack. When the user returns, Android recreates the activity, but all your runtime data is gone unless you’ve saved it properly.
This is different from the normal activity lifecycle. Your app doesn’t just pause or stop — it’s completely terminated and then brought back to life.
Why Does Process Death Happen?
Android needs to manage limited device resources efficiently. Here are the main reasons Process Death in Android occurs:
Low Memory Situations
When your device runs low on RAM, Android starts killing background processes. Apps you haven’t used recently are the first to go.
Developer Options Testing
During development, you can enable “Don’t keep activities” in Developer Options. This immediately destroys activities when they leave the foreground, making it easier to test Process Death scenarios.
Long Background Duration
If your app stays in the background for an extended period while the user uses other memory-intensive apps, there’s a higher chance your process will be killed.
System Updates or Crashes
Sometimes system events or crashes can trigger process termination across multiple apps.
Real-World Example: The Shopping Cart Problem
Let me share a common scenario that illustrates Process Death in Android perfectly.
Imagine you’re building a shopping app. A user adds five items to their cart, then switches to their messaging app to check a friend’s recommendation. They spend 10 minutes chatting, during which Android kills your shopping app’s process to free memory.
When they return to your app, if you haven’t handled Process Death properly, their cart is empty.
Frustrating, right?
This is exactly why understanding and handling Process Death in Android matters for user experience.
How to Detect Process Death in Android
Before we fix it, let’s learn how to detect it. Add this code to your activity:
Kotlin
classMainActivity : AppCompatActivity() {overridefunonCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)if (savedInstanceState != null) {// Activity was recreated after process death Log.d("ProcessDeath", "Activity restored after process death") } else {// Normal first-time creation Log.d("ProcessDeath", "Activity created normally") } }}
The onCreate() method receives a savedInstanceState parameter. When this parameter is not null, it means Android is restoring your activity after Process Death. If it’s null, your activity is being created for the first time or normally resumed.
This simple check helps you understand when restoration is happening.
Saving State with onSaveInstanceState
The primary way to handle Process Death in Android is by overriding onSaveInstanceState(). This method is called before your activity might be destroyed, giving you a chance to save important data.
Kotlin
classShoppingCartActivity : AppCompatActivity() {privatevar cartItems = mutableListOf<String>()privatevar totalPrice = 0.0privatevar userName = ""overridefunonCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_shopping_cart)// Restore saved state if available savedInstanceState?.let { cartItems = it.getStringArrayList("CART_ITEMS")?.toMutableList() ?: mutableListOf() totalPrice = it.getDouble("TOTAL_PRICE", 0.0) userName = it.getString("USER_NAME", "") Log.d("ProcessDeath", "Restored ${cartItems.size} items") }updateUI() }overridefunonSaveInstanceState(outState: Bundle) {super.onSaveInstanceState(outState)// Save critical data before potential process death outState.putStringArrayList("CART_ITEMS", ArrayList(cartItems)) outState.putDouble("TOTAL_PRICE", totalPrice) outState.putString("USER_NAME", userName) Log.d("ProcessDeath", "Saved ${cartItems.size} items to bundle") }privatefunupdateUI() {// Update your UI with restored datafindViewById<TextView>(R.id.itemCount).text = "Items: ${cartItems.size}"findViewById<TextView>(R.id.totalPrice).text = "Total: $$totalPrice" }}
Here, we override two key methods:
onSaveInstanceState(): This is where we save our data to a Bundle. Think of a Bundle as a container that Android keeps safe even during Process Death. We use methods like putStringArrayList(), putDouble(), and putString() to store different data types.
onCreate(): We check if savedInstanceState exists. If it does, we restore our data using corresponding get methods like getStringArrayList() and getDouble().
The ?.let syntax is Kotlin’s safe call operator, ensuring we only access the bundle if it’s not null.
What Data Can You Save in a Bundle?
Bundles support many common data types, but there are limitations. Here’s what you can save:
Important Limitation: Bundles have a size limit (typically around 500KB to 1MB). Don’t try to save large images, videos, or extensive datasets. For large data, use other persistence methods like databases or files.
Using ViewModel to Survive Configuration Changes
While onSaveInstanceState() handles Process Death in Android, ViewModels help with configuration changes like screen rotation. However, ViewModels alone don’t survive process death.
Here’s how to combine both approaches:
Kotlin
classUserProfileViewModel : ViewModel() {// This survives configuration changes but NOT process deathvar userName = MutableLiveData<String>()var userAge = MutableLiveData<Int>()var profileImageUrl = MutableLiveData<String>()funupdateUserData(name: String, age: Int, imageUrl: String) { userName.value = name userAge.value = age profileImageUrl.value = imageUrl }}classUserProfileActivity : AppCompatActivity() {privatelateinitvar viewModel: UserProfileViewModeloverridefunonCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_user_profile) viewModel = ViewModelProvider(this).get(UserProfileViewModel::class.java)// If recovering from process death, restore to ViewModel savedInstanceState?.let {val name = it.getString("USER_NAME", "")val age = it.getInt("USER_AGE", 0)val imageUrl = it.getString("PROFILE_IMAGE_URL", "") viewModel.updateUserData(name, age, imageUrl) }observeViewModel() }overridefunonSaveInstanceState(outState: Bundle) {super.onSaveInstanceState(outState)// Save ViewModel data to survive process death outState.putString("USER_NAME", viewModel.userName.value ?: "") outState.putInt("USER_AGE", viewModel.userAge.value ?: 0) outState.putString("PROFILE_IMAGE_URL", viewModel.profileImageUrl.value ?: "") }privatefunobserveViewModel() { viewModel.userName.observe(this) { name ->findViewById<TextView>(R.id.nameText).text = name } viewModel.userAge.observe(this) { age ->findViewById<TextView>(R.id.ageText).text = "Age: $age" } }}
The ViewModel holds our UI data and survives configuration changes automatically. However, to survive Process Death in Android, we still need to:
Save ViewModel data in onSaveInstanceState()
Restore that data back to the ViewModel in onCreate()
This gives us the best of both worlds: automatic configuration change handling from ViewModel, plus process death recovery from saved instance state.
SavedStateHandle: The Modern Approach
Android Jetpack provides SavedStateHandle, which combines ViewModel benefits with automatic state saving. This is the recommended approach for handling Process Death in Android:
Kotlin
classShoppingViewModel(privateval savedStateHandle: SavedStateHandle) : ViewModel() {// Automatically saved and restored across process deathvar cartItems: MutableLiveData<List<String>> = savedStateHandle.getLiveData("cart_items", emptyList())var totalPrice: MutableLiveData<Double> = savedStateHandle.getLiveData("total_price", 0.0)funaddItem(item: String, price: Double) {val currentItems = cartItems.value?.toMutableList() ?: mutableListOf() currentItems.add(item) cartItems.value = currentItemsval currentTotal = totalPrice.value ?: 0.0 totalPrice.value = currentTotal + price// Automatically saved to SavedStateHandle }funclearCart() { cartItems.value = emptyList() totalPrice.value = 0.0 }}classShoppingActivity : AppCompatActivity() {privateval viewModel: ShoppingViewModelbyviewModels()overridefunonCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_shopping)// No manual state restoration needed!// SavedStateHandle does it automatically viewModel.cartItems.observe(this) { items ->updateCartUI(items) } viewModel.totalPrice.observe(this) { total ->findViewById<TextView>(R.id.totalText).text = "Total: $$total" }findViewById<Button>(R.id.addButton).setOnClickListener { viewModel.addItem("Product ${System.currentTimeMillis()}", 29.99) } }privatefunupdateCartUI(items: List<String>) {// Update RecyclerView or ListView with items }}
SavedStateHandle is magical for handling Process Death in Android. Here’s why:
getLiveData(): This method creates LiveData that’s automatically backed by saved state. When process death occurs, the data is saved. When the process restarts, the data is restored automatically.
No manual saving: Unlike the previous examples, we don’t need to override onSaveInstanceState(). The SavedStateHandle does it for us.
Type-safe: We can store various types, and they’re automatically serialized and deserialized.
The by viewModels() delegate is a Kotlin property delegate that creates or retrieves the ViewModel with SavedStateHandle automatically injected.
Testing Process Death in Android
Testing is crucial to ensure your app handles Process Death in Android correctly. Here are practical ways to test:
Method 1: Developer Options
Go to Settings → Developer Options
Enable “Don’t keep activities”
Navigate through your app, switching between activities
Every time an activity goes to the background, it’s destroyed
Method 2: Using ADB Command
Force your app’s process to be killed using Android Debug Bridge:
adb shell am kill com.yourapp.package
Then return to your app from the recent apps menu.
Method 3: Memory Pressure Testing
Use Android Studio’s Profiler to simulate low memory conditions:
Open Android Profiler
Select Memory
Click “Force garbage collection” multiple times
Android may kill your app’s process naturally
Common Mistakes to Avoid
When dealing with Process Death in Android, developers often make these mistakes:
Mistake 1: Saving Large Objects
Kotlin
// DON'T DO THISoverridefunonSaveInstanceState(outState: Bundle) {super.onSaveInstanceState(outState) outState.putSerializable("LARGE_IMAGE", largeImageBitmap) // Too big!}
Why it’s wrong: Bundles have size limits. Saving large objects causes TransactionTooLargeException.
Better approach: Save only a reference or ID, then reload the data from a persistent source.
Kotlin
// DO THIS INSTEADoverridefunonSaveInstanceState(outState: Bundle) {super.onSaveInstanceState(outState) outState.putString("IMAGE_URL", imageUrl) // Save URL, not bitmap}
Mistake 2: Assuming ViewModel Survives Process Death
Kotlin
// INCORRECT ASSUMPTIONclassMyActivity : AppCompatActivity() {privatelateinitvar viewModel: MyViewModeloverridefunonCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState) viewModel = ViewModelProvider(this).get(MyViewModel::class.java)// Assuming viewModel.userData is always available after process death// This is WRONG - ViewModel doesn't survive process death without SavedStateHandle }}
Fix: Use SavedStateHandle or manually save/restore ViewModel data.
Mistake 3: Not Testing Thoroughly
Many developers never test Process Death scenarios until users report bugs. Always enable “Don’t keep activities” during development.
Best Practices for Handling Process Death in Android
1. Use SavedStateHandle for Simple Data
For primitive types and small objects, SavedStateHandle is your best friend:
For critical data that users can’t afford to lose, use Room database or other persistent storage:
Kotlin
classFormActivity : AppCompatActivity() {privatelateinitvar database: AppDatabaseoverridefunonCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState) database = Room.databaseBuilder( applicationContext, AppDatabase::class.java,"form-database" ).build()// Restore form from database if exists lifecycleScope.launch {val savedForm = database.formDao().getUnsubmittedForm() savedForm?.let { restoreForm(it) } } }overridefunonPause() {super.onPause()// Save form to database when leaving activity lifecycleScope.launch {val formData = collectFormData() database.formDao().saveForm(formData) } }}
3. Combine Multiple Approaches
Use the right tool for each type of data:
SavedStateHandle: UI state (scroll position, selected tab, form inputs)
ViewModel: Temporary runtime data that survives configuration changes
Database/SharedPreferences: Persistent user data
Memory cache: Easily re-fetchable data
4. Keep Bundles Small
Only save essential state information. Calculate or reload other data:
Kotlin
overridefunonSaveInstanceState(outState: Bundle) {super.onSaveInstanceState(outState)// Save only IDs, not entire objects outState.putInt("SELECTED_ITEM_ID", selectedItem.id) outState.putString("SEARCH_QUERY", searchQuery)// Don't save the entire search results list// Reload it using the search query instead}
Advanced: Handling Process Death with Compose
If you’re using Jetpack Compose, handling Process Death in Android looks a bit different:
With Compose, you still use ViewModel with SavedStateHandle. The difference is:
observeAsState(): Converts LiveData from ViewModel into Compose State
Automatic recomposition: When the ViewModel data changes (including after process death restoration), Compose automatically updates the UI
No manual lifecycle management: Compose handles the observation lifecycle for you
The underlying Process Death handling still uses SavedStateHandle in the ViewModel, but the UI layer becomes much simpler.
Monitoring Process Death in Production
To understand how often Process Death in Android affects your users, implement analytics:
Kotlin
classAnalyticsHelper(privateval context: Context) {funtrackProcessDeath(activityName: String) {// Log to your analytics service FirebaseAnalytics.getInstance(context).logEvent("process_death_recovery") {param("activity_name", activityName)param("timestamp", System.currentTimeMillis()) } }}classMainActivity : AppCompatActivity() {overridefunonCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)if (savedInstanceState != null) {AnalyticsHelper(this).trackProcessDeath("MainActivity") } }}
This helps you understand:
How frequently users experience process death
Which activities are most affected
Whether users successfully recover their state
Conclusion
Process Death in Android is an essential concept every Android developer must master. It’s not just about preventing crashes — it’s about creating a seamless user experience that feels reliable and polished.
Remember these key takeaways:
For simple UI state: Use SavedStateHandle with ViewModel. It automatically handles Process Death in Android with minimal code.
For important user data: Persist to a database. Don’t rely solely on saved instance state for data users can’t afford to lose.
Always test: Enable “Don’t keep activities” during development. Test your app thoroughly by simulating process death scenarios.
Keep bundles small: Only save essential state information. Reload complex data when the activity restores.
By properly handling Process Death in Android, you’ll build apps that feel professional, reliable, and respectful of your users’ time and data. Your users might never know about the complexity you’ve handled behind the scenes — and that’s exactly the point.
Start implementing these patterns in your next project, and you’ll be amazed at how much more robust your apps become.
If you’ve been writing object-oriented code for a while, you’ve probably used inheritance a lot. It feels natural. You create a base class, extend it, override a few methods, and move on. But as projects grow, inheritance often becomes hard to manage. Classes get tightly coupled. Changes ripple through the codebase. Small tweaks break unexpected...
Alarms, reminders, and notifications are some of the most deceptively complex features in Android development. On the surface, they look simple: “Schedule something at a time and show a notification.” In reality, you’re fighting with: Doze mode OEM background restrictions Process death App restarts Device reboots Android 12+ background execution limits Android 13+ notification permissions...
The Factory Pattern is a creational design pattern that handles object creation for you.
Instead of creating objects directly using constructors, you delegate that responsibility to a factory.
In simple terms:
A factory decides which object to create and how to create it.
Your main code just asks for an object and uses it. It doesn’t care about the details.
This separation is what makes the Factory Pattern Simplify Complex Object Creation so effective.
Why Object Creation Becomes Complex
In real-world applications, object creation often depends on:
User input
Configuration files
API responses
Environment (development, testing, production)
Example without a factory:
Kotlin
val paymentProcessor = when (paymentType) {"CARD"->CardPaymentProcessor()"UPI"->UpiPaymentProcessor()"WALLET"->WalletPaymentProcessor()else->throwIllegalArgumentException("Invalid payment type")}
Now imagine this logic repeated across multiple files.
Problems appear quickly:
Code duplication
Hard-to-maintain logic
Difficult testing
Violations of the Single Responsibility Principle
This is why developers rely on patterns that simplify complex object creation.
How the Factory Pattern Helps
The Factory Pattern solves these issues by:
Centralizing object creation
Reducing tight coupling
Making code easier to extend
Improving testability
Most importantly, it lets your business logic focus on what to do, not how objects are created.
That’s the real power behind Factory Pattern Simplify Complex Object Creation.
Modern software design isn’t just about writing code that works; it’s about writing code that’s maintainable, scalable, and easy to understand. One way to achieve this is by using Use Case Patterns — a structured approach to modeling software functionality based on real-world user interactions. In this guide, we’ll break down everything you need to know about...