Android Jetpack Components Explained: LiveData, ViewModel, and Navigation for Beginners
I was building an Android app that displayed a list of articles. Every time I rotated my phone, the app crashed. The data I had loaded from the network just… vanished. I spent hours trying to fix it with onSaveInstanceState, bundles, and manual state restoration. It was a mess.
Then I discovered Jetpack’s architecture components. Three months later, I’ve rewritten the entire app using LiveData, ViewModel, and Navigation. No more crashes on rotation. No more memory leaks. And navigation code that I can actually read.
The Three Problems That Drove Me Crazy
Before Jetpack, I dealt with these issues on every project:
┌─────────────────────────────────────────────────────────────┐│ PROBLEM 1: Screen Rotation = Data Loss ││ ────────────────────────────────────────────────────────────││ Rotate phone → Activity destroyed → Activity recreated ││ → Network data gone → User sees blank screen → Crash? │└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐│ PROBLEM 2: Memory Leaks from Async Callbacks ││ ────────────────────────────────────────────────────────────││ Activity starts network request → User exits Activity ││ → Callback fires → Tries to update destroyed Activity ││ → Memory leak or crash │└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐│ PROBLEM 3: Fragment Navigation Spaghetti ││ ────────────────────────────────────────────────────────────││ FragmentTransaction.add().replace().addToBackStack() ││ → 50 lines of boilerplate → Deep links nightmare ││ → Back stack bugs everywhere │└─────────────────────────────────────────────────────────────┘I tried various solutions. EventBus for data propagation (memory leaks). Static variables (more memory leaks). onRetainNonConfigurationInstance (deprecated). Nothing worked cleanly.
ViewModel: The Survivor
ViewModel was the first Jetpack component that made me say “oh, that’s how it should work.”
The key insight: ViewModel survives configuration changes. When you rotate your phone, the Activity is destroyed and recreated. But the ViewModel stays alive.
class ArticleViewModel( private val repository: ArticleRepository) : ViewModel() {
// Backing property - mutable internally private val _articles = MutableLiveData<List<Article>>()
// Public property - immutable externally // Other classes can only observe, not modify val articles: LiveData<List<Article>> = _articles
private val _loading = MutableLiveData<Boolean>() val loading: LiveData<Boolean> = _loading
fun loadArticles(topicId: String) { viewModelScope.launch { _loading.value = true _articles.value = repository.fetchArticles(topicId) _loading.value = false } }}Notice the pattern: _articles is private and mutable, while articles is public and immutable. This prevents other classes from accidentally modifying your data. I learned this the hard way after a junior developer directly modified my LiveData and caused weird UI bugs.
┌──────────────────────────────────────────────────────────────┐│ ││ Activity A created ││ │ ││ ▼ ││ ViewModel created ─────────────────┐ ││ │ │ ││ ▼ │ ││ Activity A started │ ││ │ │ ││ ▼ │ ││ User rotates phone │ ViewModel lives on! ││ │ │ ││ ▼ │ ││ Activity A destroyed │ ││ │ │ ││ ▼ │ ││ Activity B created │ ││ │ │ ││ └──────────────────────────────┘ ││ │ ││ ▼ ││ Activity B gets SAME ViewModel instance ││ │ ││ ▼ ││ Data still there! No reload needed ││ │└──────────────────────────────────────────────────────────────┘LiveData: The Lifecycle-Aware Observer
LiveData solves the second problem: updating UI safely. It only notifies active observers. When your Activity is stopped or destroyed, LiveData stops sending updates.
class ArticleActivity : AppCompatActivity() {
// Lazy initialize ViewModel private val viewModel: ArticleViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_article)
// LiveData automatically handles lifecycle // This observer ONLY receives updates when Activity is STARTED or RESUMED viewModel.articles.observe(this) { articles -> // This won't crash if Activity is destroyed recyclerView.adapter = ArticleAdapter(articles) }
viewModel.loading.observe(this) { isLoading -> progressBar.isVisible = isLoading }
// Trigger data load viewModel.loadArticles("jetpack") }
// No need to manually remove observers - LiveData handles it! // No need to check if Activity is destroyed - LiveData handles it!}The magic is in observe(this). The this refers to the Activity’s lifecycle. LiveData knows when the Activity is active and only sends updates then.
┌─────────────────────────────────────────────────────────────┐│ EventBus (Old Way) │ LiveData (Jetpack) │├──────────────────────────────────────┼─────────────────────┤│ Fire events regardless of lifecycle │ Lifecycle-aware ││ Manual unregistration needed │ Auto-cleanup ││ Memory leaks common │ No memory leaks ││ Crashes on destroyed Activities │ Safe by design ││ Testing is painful │ Easy to test │└─────────────────────────────────────────────────────────────┘Navigation: The End of FragmentTransaction Hell
Navigation component was the final piece. I used to have 50+ lines of FragmentTransaction code just to navigate between screens. Now it’s one line.
First, define your navigation graph in XML:
<?xml version="1.0" encoding="utf-8"?><navigation xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/nav_graph" app:startDestination="@id/homeFragment">
<fragment android:id="@+id/homeFragment" android:name="com.app.HomeFragment" android:label="Home">
<action android:id="@+id/action_home_to_detail" app:destination="@id/detailFragment" app:enterAnim="@anim/slide_in_right" app:exitAnim="@anim/slide_out_left"/> </fragment>
<fragment android:id="@+id/detailFragment" android:name="com.app.DetailFragment" android:label="Detail">
<argument android:name="articleId" app:type="string"/> </fragment>
</navigation>Then navigate with a single line:
class HomeFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { val navController = Navigation.findNavController(view)
articleAdapter.setOnItemClickListener { article -> // Single line instead of FragmentTransaction boilerplate navController.navigate( R.id.action_home_to_detail, bundleOf("articleId" to article.id) ) } }}┌─────────────────────────────────────────────────────────────┐│ nav_graph.xml │├─────────────────────────────────────────────────────────────┤│ ││ ┌─────────────┐ action_home_to_detail ││ │ Home │─────────────────────────────┐ ││ │ Fragment │ │ ││ └─────────────┘ ▼ ││ ┌─────────────┐ ││ │ Detail │ ││ │ Fragment │ ││ └─────────────┘ ││ ▲ ││ │ ││ Arguments passed: articleId (string) ──────┘ ││ ││ Animation: slide_in_right / slide_out_left ││ │└─────────────────────────────────────────────────────────────┘How They Work Together: MVVM Architecture
These three components form the foundation of MVVM (Model-View-ViewModel) architecture on Android:
┌────────────────────────────────────────────────────────────────┐│ ││ ┌─────────────┐ ┌─────────────┐ ┌──────────┐ ││ │ Model │ │ ViewModel │ │ View │ ││ │ (Repo + │────────▶│ (LiveData │────────▶│ (Activity│ ││ │ Data) │ │ + Logic) │ │ + XML) │ ││ └─────────────┘ └─────────────┘ └──────────┘ ││ ▲ │ │ ││ │ │ │ ││ │ ▼ │ ││ │ ┌─────────────┐ │ ││ │ │ Navigation │◀─────────────────┘ ││ │ │ Component │ ││ │ └─────────────┘ ││ │ │ ││ └───────────────────────┘ ││ (Navigate to detail) ││ │└────────────────────────────────────────────────────────────────┘The View observes the ViewModel via LiveData. The ViewModel holds UI state and survives rotations. Navigation handles all screen transitions declaratively.
Common Mistakes I Made
Mistake 1: Exposing MutableLiveData
class BadViewModel : ViewModel() { // DON'T DO THIS val articles = MutableLiveData<List<Article>>()
// Now any class can modify this directly}class GoodViewModel : ViewModel() { private val _articles = MutableLiveData<List<Article>>() val articles: LiveData<List<Article>> = _articles
// Only the ViewModel can modify _articles // Others can only observe articles}Mistake 2: Putting Everything in ViewModel
I initially stored network responses, database objects, and business logic in ViewModel. Wrong. ViewModel should only hold UI-related state.
┌─────────────────────────────────────────────────────────────┐│ ViewModel SHOULD hold: │ ViewModel should NOT: │├──────────────────────────────────┼─────────────────────────┤│ UI state (loading, error, etc.) │ Database objects ││ Display data (formatted strings) │ Raw API responses ││ Navigation events │ Business logic ││ User input validation state │ Network configuration │└─────────────────────────────────────────────────────────────┘Mistake 3: Ignoring Navigation Back Stack
Navigation handles the back stack automatically. I wasted time manually managing it.
// DON'T DO THISfragmentManager.popBackStack()fragmentManager.beginTransaction() .replace(R.id.container, DetailFragment()) .addToBackStack(null) .commit()// Just navigate - Navigation handles everythingnavController.navigate(R.id.action_home_to_detail)
// Back button works automatically// Deep links work automatically// Animations work automaticallyWhen to Use Each Component
| Component | Use When | Don’t Use When |
|---|---|---|
| ViewModel | UI data survives rotation, share data between fragments | Simple static UI, no configuration change concerns |
| LiveData | Need lifecycle-aware updates, observing data changes | One-time events (use SharedFlow/Channel instead) |
| Navigation | Complex fragment flow, deep links, animations | Single-activity app with one screen, simple Viewpager |
What’s Next
Once you’re comfortable with these three, explore other Jetpack components:
- Room: SQLite abstraction with LiveData support
- Paging: Load large datasets incrementally
- WorkManager: Background tasks that survive app restarts
- DataBinding: Connect UI directly to ViewModel
I started with LiveData, ViewModel, and Navigation. Now I can’t imagine building Android apps without them. The code is cleaner, bugs are fewer, and I spend less time fighting the framework and more time building features.
Final Words + More Resources
My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me
Here are also the most important links from this article along with some further resources that will help you in this scope:
- 👨💻 Android Jetpack Official Documentation
- 👨💻 ViewModel Overview - Android Developers
- 👨💻 LiveData Overview - Android Developers
- 👨💻 Navigation Component - Android Developers
Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!
Comments