Using Google ViewModel IRL

Thibault de Lambilly
AndroidPub
Published in
5 min readFeb 7, 2018

Many articles on ViewModel are just doing over the example written in the official documentation. I wanted to share more how I have settled into my project.

After having done the migration from Realm to Google Room, I have followed by migrating my project to use the ViewModel of the Google Android Architecture Components

Many great stories in Medium are already explaining the why, so just my quick sum up:

  • it handles data source, but as a presenter do
  • it handles data lifecycle, so no need to bother with processing async call
  • use observer to update your data “automatically”

The straightforward

My first screen, where I have started to use ViewModel display a single list of value (currently only 3 lines) that is never refreshed after having been launched. That’s the easier case.

  1. ViewModel File

To declare your ViewModel the only thing is to extends ViewModel …..

Here is my class:

@Singleton
class SourceViewModel @Inject constructor(val repository: SourceRepository) : ViewModel() {
var sources : LiveData<List<Source>> = repository.getSources()
}

That is pretty simple, right?

My sources data is directly linked to the data repository. “Straight line little soldier”

2. The Activity

// SourceActivity.java
class SourceActivity : BaseComponentActivity() {
@Inject lateinit var sourceViewModelFactory: ViewModelFactory<SourceRepository>private lateinit var viewModel: SourceViewModeloverride fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
[…]
// initialise my viewModel
viewModel = ViewModelProviders.of(this, sourceViewModelFactory).get(SourceViewModel::class.java)
[…]
}
}

The annoying thing, sorry, the tricky thing is that, because my ViewModel has the data repository object injected through the default constructor (has it should be, right?), you must initialize your viewModel with a factory.

viewModel = ViewModelProviders.of(this, sourceViewModelFactory).get(SourceViewModel::class.java)

Thus, the factory looks like this:

// ViewModelFactory.java@Singleton
@Suppress(“UNCHECKED_CAST”)
class ViewModelFactory<REPO> @Inject constructor(private val sRepo: REPO) : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T { if (modelClass.isAssignableFrom(SourceViewModel::class.java)) {
return SourceViewModel(sRepo as SourceRepository) as T
}
throw IllegalArgumentException(“Unknown ViewModel class”)
}
}

You can read at the end of Mladen Rakonjac medium story, where he is explaining how Chris Banes solve the factory issue with clever annotations for his Tivi application

And then after having initialized your viewModel object, you only need to attach to the data observer that you need for your activity.

viewModel.sources.observe(this, Observer<List<Source>> {
if (it?.isEmpty() == true) {
showHideError(View.VISIBLE)
} else {
displayData(it)
}
})

Easy Peasy !

So, to sum up, my activity is linked to *one* `observer` which observe the `liveData` variable in the `viewModel` directly attached to the repository. Once again, it is a straight line!

The dynamic version

Now, I arrive at the screen where I need some dynamism with my data, then it starts to be less straightforward (but still great).

This screen goes through a list of questions regarding the topic you have selected before. To do that, I get a list of questions from `room database` and/or `network` and I have to display them one by one.

Go through the list

One of the great things I saw when I have migrated from the usual MVC to the ViewModel, is that I had to move all the “intelligence” of question position into the ViewModel.

The activity had no longer the responsibility of what to display, but just to display it.

First thing first, connecting to the data:

Because I am going through a list of questions, my data must change regarding a position request by the activity and react as such to update the data. For that, it is used Transformations.switchMap.

Transformations.switchMap has one parameter, that must be LiveData type and return LiveData data to be observed.

swichMap

it reacts on changes of trigger LiveData, applies the given function to new value of trigger LiveData and sets resulting LiveData as a "backing" LiveData to LiveData. "Backing" LiveData means, that all events emitted by it will retransmitted by LiveData.

So you give him a LiveData variable to digest, then it will react to any change of it, like an observer.

private var mPosition: MutableLiveData<Int> = MutableLiveData()var question: LiveData<QuestionState> =  Transformations.switchMap(mPosition) {
mPosition.value?.let {
val data = MutableLiveData<QuestionState>()
mQuestionState = mQuestionState.copy(
question = mQuestions[it],
index = mPosition.value ?: 0
)
data.postValue(mQuestionState)
return@switchMap data
}
}

As you can see, I have mPosition as MutableLiveData that is “observed” by the Transformations.switchMap. As soon as the mPosition is updated, the switch returns data in LiveData format into my variable “question” to be observed by the Activity.

The list of questions has been gathered by a first call.

fun launchTest(topicId: Long, starFist: Boolean) {
repository.getQuestions(topicId, starFist, fun(data: List<Question>) {
if (data.isNotEmpty()) {
mQuestions = data
mQuestionState.size = data.size
mPosition.value = 0
}
})
}

I get the data from the repository and then by setting mPosition.value = 0 , I trigger the switchMap. Afterwards, the activity calls 2 methods “forward” and “backward” that add/retract 1 to the position, that triggers the switchMap and updates the data.

The usual snippet, switchMap returns an empty list if ever the data is not initiated first, but I don’t need to as I ensure the data to be there before initiating the position first.

How can I have a loader then?

My app is “offline first”. If I have a data connection, I download the data from the server (then save it into the database with Room), otherwise, I load the data directly from sqlite. So sometimes, I need a loader during the network call, sometimes not.

The Activity is linked to the ViewModel which is linked to the Repository. The Repository “decided” if it takes the data from the database or through a network call, so how can the Activity knows to show the loader?

Several articles on ViewModel claims or it makes believe that thanks to the ViewModel architecture, you need and must have only one entry between your activity and your viewModel.

Getting a loader in my activity configuration is to me the good example that it is not (always) true.

I have settled this by adding a second variable observed by the Activity. This variable is linked to an equivalent one in the ViewModel which is attached to the same in the Repository. The repository updates my network status variable and is eventually caught by the activity.

// QuestionRepository
protected var status: MutableLiveData<NetworkStatus> = MutableLiveData()
fun getQuestions(topicId: Long, starFist: Boolean, then: (data: List<Question>) -> Unit) {
// has Network: request data
if (hasInternetConnection()) {
getWebData(topicId, starFist) { data ->
then(data)
}
}
// No network: taking in database
else {

}
}
fun getWebData(topicId: Long, then: (data: List<Question>) -> Unit) { doAsync {
uiThread {
status.postValue(NetworkStatus.LOADING)
dataProvider
.dataGetTopicQuestions(topicId, starFist, lastCall)
.subscribeOn(scheduler.network)
.map { question -> analyseData(topicId, question) }
.map { getDataFromDb(topicId) }
.observeOn(scheduler.main)
.subscribe({ data ->
status.postValue(NetworkStatus.SUCCESS)
then(data)
}, { e ->
status.postValue(NetworkStatus.ERROR)
})
}
}
}

Then the ViewModel is just there to serve it to the activity!

// QuestionViewModel
var networkStatus: LiveData<NetworkStatus> = repository.networkStatus()

Nothing else needed ;-)

And eventually, the activity is observing it

mViewModel.networkStatus.observe(this, Observer<NetworkStatus> {
when (it) {
NetworkStatus.LOADING -> question_rotate.start()
NetworkStatus.SUCCESS -> question_rotate.stop()
else -> run({})
}
})

Is that wrong to have more than one observer on the ViewModel? I don’t know but, I find that simple and efficient ;-)

Here is my app on github:

--

--

Thibault de Lambilly
AndroidPub

Mobile enthusiast, Kotlin lover | full stack by pleasure