MVVM and coroutines
In this article I discuss the refactoring I made in the MVVM project already discussed in the previous article.
The IDE
I actually decided to use IntelliJ IDEA 2019.1.1 for this. I’ve been trying to use Android Studio 3.5 Canary, but it doesn’t display the Android perspective correctly when Kotlin 1.3.30 plugin is installed.
The disadvantage of using IntelliJ as opposed to Android Studio 3.5 is such that you can’t use Apply Changes functionality. On the other hand, IntelliJ integrates with Mate’s Global Application Menu, so I am sticking for now with it.
The project
The project discussed in the present article is MVVM.
I overwrote the code discussed in the previous article, but you can still watch the git history to see which changes exactly I made.
I removed all dependencies on RxJava, and the class ActivityDescViewModel
extends ViewModel
directly, using one additional extension function to handle lifecycle.
The model
The model class that is used to convey activity recognition events:
Please note that I am setting capacity of the Channel
to Channel.CONFLATED
, so that every time the consumer calls receive()
, it will either suspend or receive the very last element sent to it. The elements are not buffered in the Channel
.
This is the BroadcastReceiver
that is triggered on activity recognition events:
Please note that I am using the method channel.offer(acvivityDesc)
, as opposed to an equivalent suspend
function channel.send(activityDesc)
.
Because it is a CONFLATED
channel, inserting values to it never needs to suspend. The only time a Channel
needs to suspend when values are inserted to it is when it needs to wait for the consumer to receive them. In case of a CONFLATED
channel particularly, the inserted values are simply discarded when the consumer hasn’t received them yet when a new value comes in.
Alternatively, the following code would have an identical effect:
You may have to use the above version if you are using any other Channel
than CONFLATED
.
The viewmodel
This is the viewModel that is used to pass to an instance of LiveData
the values generated by the above ChannelAcvitivyRecognition
:
THe above code initiates lazily an instance of MutableLiveData
and exposes it as LiveData
, so that it is not seen as mutable outside of the scope of this class.
This is the extension function that creates the lazy itinialization:
Because most of the time the above code is waiting for values, I use the Dispatchers.IO
dispatcher. I have to use postValue()
as opposed to setValue()
, though, so that the views will only respond to the changes on the main thread.
I use a while(true)
loop, as it is going to be canceled anyway when the viewModelScope
is canceled. Each time the receive()
function suspends, it will react immediately when its scope is canceled.
Testing
This is the test version of the ChannelActivityRecogtition
:
It is not relevant at all whether you use channel.send()
or channel.offer()
in this case, as it is being executed inside of a CoroutineScope
anyway, and because this Channel
isCONFLATED
, both functions work identically.
I use repeat(Int.MAX_VALUE)
loop, as I want to emulate the real situation in which activity recognition never ends until you either close the application or deliberately cancel it. I didn’t want to use a while
loop, because I wanted to use it
inside the block, which here represents successive integers.
The previous article contains instructions on how to set up your Espresso tests to use this particular mock implementation of activity recognition.