How to mock Android's ProcessLifecycleOwner - android

I am working on a utility class that subscribes to the app going foreground/background via ProcessLifecycleOwner api. The class takes in a ProcessLifecycleOwner instance, and has the following methods which observe on lifecycle events:
#OnLifecycleEvent(Lifecycle.Event.ON_START)
fun startSomething() {
appStatusSubject.onNext(AppStatus.FOREGROUNDED)
}
#OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun stopSomething() {
appStatusSubject.onNext(AppStatus.BACKGROUNDED)
}
I would like to write a unit test for this utility class. Is there anyway I can mock the ProcessLifecycleOwner object, have it emit certain lifecycle events, and assert that appStatusSubject.onNext(...) is called?
Would really appreciate any advice.

There is a TestLifecycleOwner, which probably does what you want.
https://cs.android.com/androidx/platform/frameworks/support/+/androidx-master-dev:lifecycle/lifecycle-runtime-testing/src/main/java/androidx/lifecycle/testing/TestLifecycleOwner.kt;l=38
It's part of the androidx.lifecycle:lifecycle-runtime-testing artifact.

If you want to create a super simple fake for the LifecycleOwner, you can do so as follows:
class TestLifecycleOwner() : LifecycleOwner {
private val registry = LifecycleRegistry(this).apply {
currentState = Lifecycle.State.RESUMED
}
override fun getLifecycle(): Lifecycle = registry
}
Then you can simply call:
testLifecycleOwner.lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_STOP)

Related

Alternative for observeForever using Transformations.map

I am observing a liveData to do some queries using observeForever inside ViewModel,
query.observeForever {
//
}
the logic is working fine, but i can't remove the observer in onCleared as i have no access to lifecycle from viewModel and i shouldn't, i tried with Transformation.map
Transformations.map(query){
//
}
and failed to observe the changes.any suggestions how to use Transformation.map to listen to livedata changes and act on them
You could make your viewmodel extend LifecycleObserver like this -
class MyViewModel() : LifecycleObserver {
val queryObserver = Observer {
// do stuff
}
#OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
fun onResumed() {
query.observeForever(queryObserver)
}
#OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
fun onPasued() {
query.removeObserver(queryObserver)
}
}
Don't forget to add getLifecycle().addObserver(mViewModel) in your Activity!

How to observe ViewModel that makes a request for a call back

I am trying to make a request to a library that gives me a call back.
Manager.getInstance().request(new CallBack())
I want to put this in a ViewModel so that I can observe it from the Activity.
class RequestViewModel : ViewModel, CallBack {
fun request() {
Manager.getInstance().request(this)
}
override fun onFinished(result : List<String>?) {
}
override fun onFailed() {
}
}
How can I make it so that I can observe when this has finished? I know I could make my Activity implement this CallBack, but I don't want to couple Activity to this.
Ideally this would be a LiveData or Observable.
If I understand the question correctly, you can submit the data acquired in onFinished method to the LiveData instance that should be observed by a view component, e.g.
class RequestViewModel : ViewModel, CallBack {
private val _liveData = MutableLiveData<SomeResult<List<String>>>
val liveData: LiveData<SomeResult<List<String>>> get() = _liveData
fun request() {
Manager.getInstance().request(this)
}
override fun onFinished(result : List<String>?) {
if (result != null) {
_liveData.postValue(SomeResult.success(result))
} else {
_liveData.postValue(SomeResult.failure())
}
}
override fun onFailed() {
_liveData.postValue(SomeResult.failure())
}
}
And somewhere in your object that corresponds to a view component:
viewModel.liveData.observe(lifecycleOwner, Observer<List<String>> {
handleResponse(it)
})
whereas lifecycleOwner typically is your AppCompatActivity or android.support.v4.Fragment inheritor.
I would advise you to decouple requesting from ViewModel and create a class called Repository to handle all the requests. In this class you could have a MutableLiveData object which can be observed and whenever new requested data is retrieved, use mutableLiveData.postValue(retrievedData) for MutableLiveData which notifies the observes about the new changes.
To read more about repository, you can follow these links:
Google's Guide to App Architecture
Codelab tutorial with Repository pattern

How to ensure ViewModel#onCleared is called in an Android unit test?

This is my MWE test class, which depends on AndroidX, JUnit 4 and MockK 1.9:
class ViewModelOnClearedTest {
#Test
fun `MyViewModel#onCleared calls Object#function`() = mockkObject(Object) {
MyViewModel::class.members
.single { it.name == "onCleared" }
.apply { isAccessible = true }
.call(MyViewModel())
verify { Object.function() }
}
}
class MyViewModel : ViewModel() {
override fun onCleared() = Object.function()
}
object Object {
fun function() {}
}
Note: the method is protected in superclass ViewModel.
I want to verify that MyViewModel#onCleared calls Object#function. The above code accomplished this through reflection. My question is: can I somehow run or mock the Android system so that the onCleared method is called, so that I don't need reflection?
From the onCleared JavaDoc:
This method will be called when this ViewModel is no longer used and will be destroyed.
So, in other words, how do I create this situation so that I know onCleared is called and I can verify its behaviour?
In kotlin you can override the protected visibility using public and then call it from a test.
class MyViewModel: ViewModel() {
public override fun onCleared() {
///...
}
}
I've just created this extension to ViewModel:
/**
* Will create new [ViewModelStore], add view model into it using [ViewModelProvider]
* and then call [ViewModelStore.clear], that will cause [ViewModel.onCleared] to be called
*/
fun ViewModel.callOnCleared() {
val viewModelStore = ViewModelStore()
val viewModelProvider = ViewModelProvider(viewModelStore, object : ViewModelProvider.Factory {
#Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T = this#callOnCleared as T
})
viewModelProvider.get(this#callOnCleared::class.java)
//Run 2
viewModelStore.clear()//To call clear() in ViewModel
}
TL;DR
In this answer, Robolectric is used to have the Android framework invoke onCleared on your ViewModel. This way of testing is slower than using reflection (like in the question) and depends on both Robolectric and the Android framework. That trade-off is up to you.
Looking at Android's source...
...you can see that ViewModel#onCleared is only called in ViewModelStore (for your own ViewModels). This is a storage class for view models and is owned by ViewModelStoreOwner classes, e.g. FragmentActivity. So, when does ViewModelStore invoke onCleared on your ViewModel?
It has to store your ViewModel, then the store has to be cleared (which you cannot do yourself).
Your view model is stored by the ViewModelProvider when you get your ViewModel using ViewModelProviders.of(FragmentActivity activity).get(Class<T> modelClass), where T is your view model class. It stores it in the ViewModelStore of the FragmentActivity.
The store is clear for example when your fragment activity is destroyed. It's a bunch of chained calls that go all over the place, but basically it is:
Have a FragmentActivity.
Get its ViewModelProvider using ViewModelProviders#of.
Get your ViewModel using ViewModelProvider#get.
Destroy your activity.
Now, onCleared should be invoked on your view model. Let's test it using Robolectric 4, JUnit 4, MockK 1.9:
Add #RunWith(RobolectricTestRunner::class) to your test class.
Create an activity controller using Robolectric.buildActivity(FragmentActivity::class.java)
Initialise the activity using setup on the controller, this allows it to be destroyed.
Get the activity with the controller's get method.
Get your view model with the steps described above.
Destroy the activity using destroy on the controller.
Verify the behaviour of onCleared.
Full example class...
...based on the question's example:
#RunWith(RobolectricTestRunner::class)
class ViewModelOnClearedTest {
#Test
fun `MyViewModel#onCleared calls Object#function`() = mockkObject(Object) {
val controller = Robolectric.buildActivity(FragmentActivity::class.java).setup()
ViewModelProviders.of(controller.get()).get(MyViewModel::class.java)
controller.destroy()
verify { Object.function() }
}
}
class MyViewModel : ViewModel() {
override fun onCleared() = Object.function()
}
object Object {
fun function() {}
}
For Java, if you create your test class in the same package (within the test directory) as of the ViewModel class (here, MyViewModel), then you can call onCleared method from the test class; since protected methods are also package private.

Reacting to activity lifecycle in ViewModel

I'm trying to create an app which will use MVVM architecture and there's one thing I quite don't understand.
Official Android docs say that's not a good idea to reference activity context in ViewModel's (as ViewModel may outlive activity) so I've started to wonder about usecase when I want to execute some action when my activity is resumed.
I know ViewModel's shouldn't do business logic themselves but even if I use some service class (let's say GPSService which has to start and pauseeach time activity is resumed on paused), and inside this service I react to activity onResume (using Lifecycle observer) I will still reference this activity from ViewModel as I'm referencing service which holds reference to activity being observed, this may cause activity leak (correct me if I'm wrong).
So my question is, how to react to activity or fragment lifecycle in MVVM architecture?
If you need to have a ViewModel be lifecycle aware, then you can have it implement LifeCycleObserver and override life cycle events as necessary. Example,
public class MyModel extends ViewModel implements
LifecycleObserver {
#OnLifecycleEvent(Lifecycle.Event.ON_STOP)
protected void onLifeCycleStop() {
// do something
}
}
In the activity or fragment then you can add the view model to the activity life cycle owner.
public class MyActivity extends AppCompatActivity {
protected MyModel mMyModel;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mMyModel = ViewModelProviders
.of(this)
.get(MyModel.class);
getLifecycle().addObserver(mMyModel);
}
}
I know ViewModel's shouldn't do business logic themselves
Yes, you're right. ViewModel should not contain business logic but
it should contain UI related logic. So basically, API calls or Some
location related stuffs should be avoided in ViewModel logic.
So what if you wanna make some scenario which can react to any activity lifecycle? I'll suggest you to use LifecycleObserver.
Why?, Because LifecycleObserver will provide you callbacks once it's LifecycleOwner will change it's state.
What is LifecycleOwner here? In our case it may be Activity/Fragment.
So, how you can achieve this?
Let's say you want to make location requests during resume & pause period of any activity.
So, for that you can create a class called LocationUpdates as LifecycleObserver like below:
class LocationUpdates : LifecycleObserver {
constructor(){
// some basic location related initialization here
}
#OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
fun connectListener() {
// this method will respond to resume event of our Lifecycle owner (activity/fragment in our case)
// So let's get location here and provide callback
}
#OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
fun disconnectListener() {
// this method will respond to pause event of our Lifecycle owner (activity/fragment in our case)
// So let's stop receiveing location updates here and remove callback
}
#OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) // Optional if you want to cleanup references
fun cleanUp() {
// this method will respond to destroy event of our Lifecycle owner (activity/fragment in our case)
// Clean up code here
}
}
Now from your activity, you can directly make your LocationUpdates, and receive callback.
class MyActivity : AppCompatActivity() {
private lateinit var mLocationUpdates: LocationUpdates
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//Initialize your LifecycleObserver here & assign it to this activity's lifecycle
lifecycle.addObserver(mLocationUpdates)
}
}
You can refer to how to handle Lifecycle & Codelabs example.
Edit:
If you want to have ViewModel for that job, consider this:
class MyViewModel : ViewModel {
private lateinit var mLocationUpdates: LocationUpdates
constructor() : super() {
// initialize LocationUpdates here
}
// Assign our LifecyclerObserver to LifecycleOwner
fun addLocationUpdates(lifecycle: Lifecycle){
lifecycle.addObserver(mLocationUpdates)
}
//Optional, we really don't need this.
fun removeLocationUpdates(lifecycle: Lifecycle){
lifecycle.removeObserver(mLocationUpdates)
}
}
If your LocationUpdates depends upon Context, consider using AndroidViewModel.
We can now observe our location updates # any activity/fragment using LiveData, and assign our LifecycleObserver like below:
class MyActivity : AppCompatActivity() {
private val viewModel: MyViewModel by lazy {
return#lazy ViewModelProviders.of(this#MyActivity).get(MyViewModel::class.java)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel.addLocationUpdates(lifecycle)
}
}
Please note: there's still lot to cover but making this answer as short as possible. So, if you're still confused about something related then please feel free to ask me in comment. I will edit my answer.
with java 8 LifecycleObserver has been deprecated. According to the [docs][1] it is not recommended to use this class as it uses reflection.
Rather the docs recommend using DefaultLifecycleObserver. To do that, extend your ViewModel class with DefaultLifecycleObserver like:
class MyViewModel : ViewModel(), DefaultLifecycleObserver {//implement default lifecycle observer
override fun onCreate(owner: LifecycleOwner) {//override lifecycle events
super.onCreate(owner)
}
override fun onStart(owner: LifecycleOwner) {
super.onStart(owner)
}
override fun onResume(owner: LifecycleOwner) {
super.onResume(owner)
}
override fun onPause(owner: LifecycleOwner) {
super.onPause(owner)
}
override fun onStop(owner: LifecycleOwner) {
super.onStop(owner)
}
override fun onDestroy(owner: LifecycleOwner) {
super.onDestroy(owner)
}
}
and get all the lifecycle event callbacks in your viewmodel by registering your viewmodel as lifecycle event observer in your view class (e.g. Activity class) like:
class MyActivity : AppCompatActivity() {
private val myViewModel: MyViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
...
lifecycle.addObserver(splashViewModel)//registering observer
...
}
}
its just and update to the answer by #farid_z with kotlin and new sdk.
[1]: https://developer.android.com/reference/androidx/lifecycle/LifecycleObserver

AppCompatActivity, ViewModel and Data Binding

Trying to figure out the Google's recent tools and concepts: LifecycleActivity, ViewModel and Data Binding.
So imagine there is a FooActivity that extends AppCompatActivity(to be able to use Support library) and implements LifecycleOwner interface (from reference: required to use LiveData):
FooActivity.kt:
class FooActivity: AppCompatActivity(), LifecycleObserver {
...
We set the binding:
..
private val mBinding: by lazy {
DataBindingUtil.setContentView<ActivityFooBinding>(this, R.layout.activity_foo)
}
..
We set activity's ViewModel, and attach our barObserver (which should observe changes of bar inside a ViewModel, which is just a List of Strings):
..
val viewModel = ViewModelProviders.of(this).get(FooViewModel::class.java)
viewModel.getBars()?.observe(this, barObserver)
viewModel.init() // changes bar, should trigger Observer's onChanged
..
And finally we define barObserver:
..
class BarObserver : Observer<List<String>> {
override fun onChanged(p0: List<String>?) {
Log.d("Say", "changed:")
}
}
barObserver = BarObserver()
..
Questions:
Why does Observers onChange never triggered?
Should one use LifeCycleOwner``getLifecycle instead of using Observer?
Any other thoughts?
Edit:
As the reference states:
LiveData is a data holder class that can be observed within a given lifecycle. This means that an Observer can be added in a pair with a LifecycleOwner, and this observer will be notified about modifications of the wrapped data only if the paired LifecycleOwner is in active state. LifecycleOwner is considered as active, if its state is STARTED or RESUMED. An observer added via observeForever(Observer) is considered as always active and thus will be always notified about modifications. For those observers, you should manually call removeObserver(Observer).
Changing:
enter code hereviewModel.getBars()?.observe(this, barObserver)
to:
viewModel.getBars()?.observeForever(barObserver)
didn't to the trick. onChange still never triggered.
Edit2:
FooActivity.kt:
replaced with:
class FooActivity: AppCompatActivity(), LifecycleRegistryOwner{
override fun getLifecycle(): LifecycleRegistry {
var lifecycle = LifecycleRegistry(this)
lifecycle.addObserver(barObserver)
return lifecycle
}
FooViewModel:
..
private var mBar = MutableLiveData<List<String>>()
..
mBar.value = listValueOf("1", "2", "3")
..
fun getBar(): LiveData<List<String>>? = mBar
..
If you are not extending LifecycleActivity then your activity should implement LifecycleRegistryOwner instead of LifecycleObserver. Check out the docs.
And then you can pass it to liveData.observe(lifecycleOwner, observer)

Categories

Resources