ViewModel & LiveData (Java)

ViewModel and LiveData are part of Android Jetpack’s Architecture Components. They solve two major problems:

  • Retain UI state across configuration changes (e.g., rotation).
  • Observe data changes in a lifecycle-aware way (no memory leaks, no manual unregister).

Big Picture

Repository

Fetches data (network/db)

ViewModel

Holds state, exposes LiveData

LiveData

Notifies observers (Activity/Fragment)

UI

Updates automatically

ViewModel Basics

// CounterViewModel.java
public class CounterViewModel extends ViewModel {
    private final MutableLiveData<Integer> counter = new MutableLiveData<>(0);

    public LiveData<Integer> getCounter() { return counter; }

    public void increment() {
        counter.setValue(counter.getValue() + 1);
    }
}
Explanation:
  • ViewModel survives rotation. When Activity recreates, the same instance is reused.
  • LiveData is observable and lifecycle-aware.

Using in Activity

// MainActivity.java
public class MainActivity extends AppCompatActivity {

    private CounterViewModel vm;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        TextView tv = findViewById(R.id.tvCount);
        Button btn = findViewById(R.id.btnInc);

        vm = new ViewModelProvider(this).get(CounterViewModel.class);

        vm.getCounter().observe(this, value -> tv.setText(String.valueOf(value)));

        btn.setOnClickListener(v -> vm.increment());
    }
}
Explanation:
  • observe(this, ...) attaches observer tied to Activity lifecycle.
  • UI auto-updates when setValue() is called in ViewModel.
  • Even after rotation, counter value persists.

LiveData Types

TypeDescriptionUsage
LiveData<T>Read-only observableExpose from ViewModel
MutableLiveData<T>Mutable subclassUpdate inside ViewModel
MediatorLiveData<T>Combine multiple sourcesMerge DB + network updates
Transformations.mapDerive datae.g., format strings
Transformations.switchMapSwitch sourcee.g., new query triggers new LiveData

Shared ViewModel Between Fragments

// In Activity hosting fragments
SharedVM vm = new ViewModelProvider(this).get(SharedVM.class);

// In Fragment A
vm.getSelected().observe(getViewLifecycleOwner(), item -> { /* update */ });
vm.select("User42");

// In Fragment B
vm.getSelected().observe(getViewLifecycleOwner(), item -> { /* receive update */ });
Explanation: requireActivity() scopes ViewModel to Activity so all its fragments share the same instance.

Lifecycle Awareness

Activity/Fragment lifecycle ├─ onStart() → LiveData delivers updates ├─ onResume() → continues observing └─ onStop() → observers automatically paused

Mini Charts

Benefits of ViewModel

Survives rotation
Encapsulates state
Testable & modular

Benefits of LiveData

Lifecycle aware
No leaks (auto cleanup)
Works with Room/DB easily

Troubleshooting

UI not updating? → Ensure you call setValue() on main thread or postValue() on background thread.

Value lost after rotation? → Check you are using ViewModel, not just LiveData in Activity.

Multiple observers firing? → Verify you scope observers to getViewLifecycleOwner(), not getActivity().

Hands-On Practice

  1. Create a TimerViewModel that counts seconds with Handler.
  2. Observe the time in Activity and display it in a TextView.
  3. Rotate the screen – the timer should continue from last value.
  4. Add another Fragment observing the same timer via shared ViewModel.
Repository (API/DB) └─ ViewModel (holds LiveData) └─ LiveData (exposed) ├─ Activity observes └─ Fragment observes → Both update automatically