Day163 — ViewModels, LiveData and Lifecycles

Jacky Tsang
5 min readJan 13, 2023

Here records the notes I took when watching “droidcon NYC 2017 — ViewModels, LiveData and Lifecycles, oh my!”. The talk is fairly old. Some of the mentioned methods are outdated and the code here is translated into Kotlin.

Problems to be solved:

  • interruptions at any time
  • rotation
  • asynchronous tasks + destroyed UI = Memory Leaks
  • OS killing apps

Reactive UI

ViewModels are objects that provide data for UI components and survive configuration changes.

LiveData is a data holder class that is lifecycle aware. It keeps a value and allows this value to be observed.

class MainActivityViewModel : ViewModel() {
private val _scoreTeamA = MutableLiveData<Int>()

init {
_scoreTeamA.setValue(0)
}

fun addToTeamA(amount: Int) {
_scoreTeamA.setValue(_scoreTeamA.value?.plus(amount))
}

fun getScoreTeamA(): LiveData<Int> {
return _scoreTeamA
}
}
class MainActivity : ComponentActivity() {

private var mViewModel: MainActivityViewModel? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mViewModel = ViewModelProvider(this).get(MainActivityViewModel::class.java)
mViewModel?.getScoreTeamA()?.observe(this) { newScore ->
displayForTeamA(newScore)
}
}

private fun displayForTeamA(score: Int) {
// display score
}
}
  • Reactive because of data observation
  • UI data survives configuration changes
  • Clearer class responsibility

ViewModel Lifecycle

Be note that if the process is killed due to memory restrictions, the ViewModel is destroyed.

Create a factory to pass values for instantiating ViewModel. It is by design to be hard to pass value to ViewModel.

ViewModels should not store Context / Activity / Fragment.

Lifecycle

Lifecycle — An object that defines an Android Lifecycle.

LifecycleOwner — LifecycleOwner is an interface: Objects with Lifecycles e.g. Activities and Fragments

LifecycleObserver — Observes LifecycleOwner

  1. Can check state
  2. automatically clean-up
class MyLocationListener : LifecycleObserver {

var lifecycle: Lifecycle

constructor(context: Context, lifecycle: Lifecycle, callback: Callback) {
this.lifecycle = lifecycle
this.lifecycle.addObserver(this)
}

var enabled = false

fun enable() {
enabled = true
if (lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) {
// connect if not connected
}
}

@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun stop() {
enabled = false
// disconnect if connected
}

}

Observer

mViewModel?.getScoreTeamA()?.observe(this) { newScore ->
displayForTeamA(newScore)
}

Observer only forwards events if Lifecycle is STARTED or RESUMED.

Observer cleans itself up when activity is finished. If Observer connects or becomes active again, intelligently sends the most recent data.

Transformation

Transformation.map() — applies function to each value emitted by source LiveData and returns a LiveData that emits the resulting values.

class MainActivityViewModel(startingScore: Int) : ViewModel() {
private val _scoreTeamA: MutableLiveData<Int> = MutableLiveData<Int>()
val scoreStringTeamA: LiveData<String>

init {
scoreStringTeamA = Transformations.map(_scoreTeamA) { it.toString() }
_scoreTeamA.setValue(0)
}
}

MutableLiveData exposes setValue() (main thread) and postValue() (background thread)

LiveData is “ready only”

Therefore, we should only expose LiveData to outside, while MutableLiveData is used inside the ViewModel.

LiveData is synchronous, which ensures destroyed observer will not get updates. It is not a stream so it does not have a history, only the current state. (Don’t use it for chat history stream)

Summary

  • All Activity does is update UI
  • ViewModel holds and processes data for UI. 1. use map to help 2. proper encapsulation
  • LiveData makes data observable, also lifecycle-aware.

Outdated codes

replace

private var mViewModel: MainActivityViewModel? = null
mViewModel = ViewModelProvider(this).get(MainActivityViewModel::class.java)

with

private val mViewModel: MainActivityViewModel by viewModels()

replace

class MainActivityViewModel : ViewModel() {
private val _scoreTeamA = MutableLiveData<Int>()

init {
_scoreTeamA.setValue(0)
}

fun addToTeamA(amount: Int) {
_scoreTeamA.setValue(_scoreTeamA.value?.plus(amount))
}

fun getScoreTeamA(): LiveData<Int> {
return _scoreTeamA
}
}

with

class MainActivityViewModel : ViewModel() {
private val _scoreTeamA = MutableLiveData<Int>()
val scoreTeamA: LiveData<Int>
get() = _scoreTeamA

init {
_scoreTeamA.setValue(0)
}

fun addToTeamA(amount: Int) {
_scoreTeamA.setValue(_scoreTeamA.value?.plus(amount))
}

}

@OnLifecycleEvent is deprecated

class MyLocationListener : LifecycleObserver {

var lifecycle: Lifecycle

constructor(context: Context, lifecycle: Lifecycle, callback: Callback) {
this.lifecycle = lifecycle
this.lifecycle.addObserver(this)
}

var enabled = false

fun enable() {
enabled = true
if (lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) {
// connect if not connected
}
}

@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun stop() {
enabled = false
// disconnect if connected
}

}

replace with DefaultLifecycleObserver

class MyLocationListener2 : DefaultLifecycleObserver {

var lifecycle: Lifecycle

constructor(context: Context, lifecycle: Lifecycle, callback: Callback) {
this.lifecycle = lifecycle
this.lifecycle.addObserver(this)
}

var enabled = false

fun enable() {
enabled = true
if (lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) {
// connect if not connected
}
}

override fun onStop(owner: LifecycleOwner) {
enabled = false
// disconnect if connected
}

}

OR LifecycleEventObserver

private val lifecycleEventObserver = LifecycleEventObserver { source, event ->
when (event) {
Lifecycle.Event.ON_RESUME -> onAppInForeground()
Lifecycle.Event.ON_PAUSE -> onAppInBackground()
Lifecycle.Event.ON_DESTROY-> onAppDestroyed()
Lifecycle.Event.ON_CREATE -> {}
Lifecycle.Event.ON_START -> {}
Lifecycle.Event.ON_STOP -> {}
Lifecycle.Event.ON_ANY -> {}
}
}

Common error

Don’t do this

class MainViewModel : ViewModel() {
fun search(searchTerm: String): LiveData<List<Song>> {
return songRepository.search(searchTerm)
}
}

A new LiveData is created when another keyword “Shake” is searched, which is redundant.

The solution is to use Transformations.switchMap(). It switches the observed LiveData based off of another LiveData’s value

class MainViewModel : ViewModel() {
val mSongs: LiveData<List<Song>>
private val mSearchTerm: MutableLiveData<String> = MutableLiveData()

init {
mSongs = Transformations.switchMap(mSearchTerm) { newTerm ->
songRepository.search(newTerm)
}
}

fun search(searchTerm: String) {
if (searchTerm.isNotBlank()) mSearchTerm.setValue(searchTerm)
}
}

map() and siwtchMap() run on the main thread.

Don’t use map() to perform a database lookup or network sync.

You can create LiveData for switchMap on the main thread and then post updates to them on a background thread.

Memory related process death

use onSaveInstanceState() to recover. “Plan B”.

The smallest amount of data to restore state.

Bundle too big = TransactionTooLargeException

we have SavedStateHandle now.

Activity death

  • finish()
  • Swipe off recents
  • Navigate back
  • Configuration change
  • Process death in case of low memory, if app is in background

Come back to a new state

  • finish()
  • Swipe off recents
  • Navigate back

Come back to the same state

  • Configuration change
  • Process death in case of low memory, if app is in background

In the comment section,

LiveData should be replaced with MutableStateFlow from Kotlin coroutines. To not update the UI when the lifecycle is not started you do, flowWithLifecycle().

I do not verify above statement.

--

--