Kotlin
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Null Safety
-
Section 4: Object-Oriented Kotlin
-
Section 5: Functional Kotlin
-
Section 6: Coroutines
-
Section 7: Collections Deep Dive
-
Section 8: Type System Deep Dive
-
Section 9: Interop and Tooling
-
Section 10: Kotlin DSLs and Patterns
-
Section 11: Testing and Quality
-
Section 12: Server-Side Kotlin
-
Section 13: Practical Projects
-
Section 14: Interview Practice
-
Section 15: More Practice Exercises
-
Section 16: More Standard Library
-
Section 17: Multiplatform Kotlin
-
Section 18: kotlin.collections In Depth
-
Section 19: kotlin.text In Depth
-
Section 20: kotlin.ranges and kotlin.sequences
-
Section 21: kotlin.io and File Handling
-
Section 22: kotlinx.coroutines Deep Dive
-
Section 23: kotlin.reflect
-
Section 24: Android Development with Kotlin Overview
-
Section 25: Kotlin Multiplatform Deep Dive
-
Section 26: Kotlin for Backend Deep Dive
-
Section 27: Kotlin Design Patterns
-
Section 28: Advanced Language Features
-
Section 29: More Practice Exercises
-
Section 30: More Interview Practice
-
Section 31: Kotlin Type System Deep Dive
-
Section 32: Kotlin Null Safety Advanced
-
Section 33: Kotlin Testing Deep Dive
-
Section 34: Kotlin Build Tooling Deep Dive
-
Section 35: Kotlin Serialization
-
Section 36: Kotlin Performance Considerations
-
Section 37: Kotlin Native Overview
-
Section 38: Kotlin for Data and Scripting
-
Section 39: More Coroutines Practice
-
Section 40: More Android-Adjacent Patterns
-
Section 41: More Practical Projects
-
Section 42: More Design and Architecture Practice
-
Section 43: Kotlin Language Evolution
-
Section 44: More Interview and Review
-
Section 45: Kotlin Delegation Patterns Deep Dive
-
Section 46: Kotlin Annotations Deep Dive
-
Section 47: Kotlin for Gradle Plugin Development
-
Section 48: Kotlin Concurrency Beyond Coroutines
-
Section 49: Kotlin Compiler Internals
-
Section 50: Real-World Kotlin Case Studies
-
Section 51: Final Practice Projects
-
Section 52: Kotlin for Server-Side Reactive Programming
-
Section 53: More Practice and Drills
-
Section 54: Kotlin Security Practices
110: ViewModel and LiveData in Kotlin
When you first start building Android apps, it's tempting to treat your Activity or Fragment as the brain of your operation. You fetch some data, store it in a local variable, and update the UI. It works perfectly—until the user rotates their phone. Suddenly, the Activity is destroyed and recreated, your variables are wiped clean, and your app either flickers or crashes. I've seen countless developers try to fix this by stuffing everything into onSaveInstanceState, but that's a nightmare for anything more complex than a single string or integer.
The Rotation Wipeout and Memory Leaks
Imagine we're building a simple User Profile screen. In a naive implementation, you'd probably have a var userProfile: User? = null right inside your ProfileActivity. You trigger a network call in onCreate, and when the result comes back, you call textView.text = userProfile?.name. This feels intuitive, but it's fragile.
If the user rotates the screen while that network call is in flight, the original Activity instance is killed. When the network response finally arrives, the callback tries to update a UI element that no longer exists in the current window. You've just created a memory leak, and if you're unlucky, a NullPointerException. Even if you handle the crash, the new Activity instance starts from scratch, triggering the network call all over again. It's wasteful and creates a jarring experience for the user.
// The "Naive" Way - Don't do this for business logic class ProfileActivity : AppCompatActivity() { private var userName: String? = null // This vanishes on rotation override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_profile) fetchUserData { name -> this.userName = name findViewById<TextView>(R.id.nameText).text = name } } }Letting the ViewModel Outlive the Activity
This is where the
ViewModelcomes in. Think of the ViewModel as a data warehouse that sits just outside the Activity's lifecycle. When the Activity is destroyed during a configuration change, the ViewModel stays put in memory. When the new Activity instance spins up, it simply reconnects to the existing ViewModel.But a ViewModel alone isn't enough. If the ViewModel just held a raw variable, the Activity would have to manually poll it or "ask" for the data every time it restarted. That's where
LiveDatafills the gap. LiveData is a data holder class that is "lifecycle-aware." It doesn't just hold the value; it allows the Activity to observe the value. The magic here is that LiveData knows when the Activity is active. If the Activity is in the background or destroyed, LiveData stops sending updates, preventing those nasty crashes we talked about.// The Professional Way class ProfileViewModel : ViewModel() { private val _userName = MutableLiveData<String>() val userName: LiveData<String> = _userName // Exposed as immutable fun loadUser() { // Simulate network call fetchUserData { name -> _userName.value = name } } } class ProfileActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_profile) val viewModel = ViewModelProvider(this).get(ProfileViewModel::class.java) // We observe the data. This automatically handles rotation // and prevents leaks because it knows the Activity's lifecycle. viewModel.userName.observe(this) { name -> findViewById<TextView>(R.id.nameText).text = name } viewModel.loadUser() } }The Trade-off: Boilerplate vs. Stability
I'll be honest with you: this approach requires more files and more code upfront. You're creating a separate class and wrapping your variables in
MutableLiveData. It can feel like overkill for a tiny app. However, the trade-off is an app that feels "solid."By separating the state (ViewModel) from the view (Activity), you've achieved a clean separation of concerns. Your ViewModel doesn't know anything about
TextViewsorContexts, which makes it incredibly easy to unit test. You can test your business logic in the ViewModel using a standard JUnit test without needing to launch an Android emulator. In my experience, that's the real win—spending an extra ten minutes on the architecture now saves you ten hours of debugging weird lifecycle crashes later.
📋 Practical Task
Exercise: Migrating the "Live Score Tracker"
You have been handed a legacy piece of code for a sports app. Currently, the ScoreActivity fetches the current game score from a simulated API and stores it in a local variable. Every time the user rotates the screen, the app makes a fresh API call, causing the screen to flicker and the score to jump.
Your Task:
- Create a
ScoreViewModelclass. - Move the
currentScore: Stringvariable into the ViewModel, wrapping it in aMutableLiveDataobject. - Implement a function in the ViewModel to update the score.
- Modify the
ScoreActivityto remove the local variable and insteadobservethe LiveData from the ViewModel. - Ensure that the API call (the score update) is only triggered once, not every time
onCreateis called during a rotation.
Goal: The score should remain visible and stable on the screen even after multiple device rotations, without triggering redundant network requests.
There are no comments for now.