Here is my BaseViewModel
abstract class BaseViewModel : ViewModel(), CoroutineScope {
private val parentJob = SupervisorJob()
override val coroutineContext: CoroutineContext
get() = parentJob + Dispatchers.Main
override fun onCleared() {
super.onCleared()
parentJob.cancel()
}
}
Then I have LevelFragment with LevelViewModel which is inherited BaseViewModel. And in LevelViewModel I have fun load() = launch {} function.
What I'm trying to do is showing multiple LevelFragment using ViewPager. So I create PagerAdapter and inherit FragmentStatePagerAdapter. What the documentation said about FragmentStatePagerAdapter is When pages are not visible to the user, their entire fragment may be destroyed, only keeping the saved state of that fragment. I'm not sure is the viewModel part of the saved state or not but when I go to the destroyed fragment and the fragment recreated I can't call load() function, it's not compile error or runtime crash, but when I try to put a breakpoint in the function it's not get called. I am still able to call another function that has no coroutine scope.
Also, I already try to use viewModelScope and the result still the same.
Is anyone else running into these issues too, or do you have an idea of what could be causing this?
Related
in my project I am using Dagger2 to inject ViewModels into fragments.
override val viewModel: AllStockListTabViewModel by viewModels ({this}, {viewModelFactory})
To briefly explain my situation, I have a fragment that uses fragment state adapter which contains two fragments. For convinience, I'll call the parent fragment fragment A and child fragments in fragment state adapter fragment B and fragment C.
Typically, when testing the app user spends time in fragment B that contains a recyclerview. When user taps one of the items, it leads to a different fragment with some detailed information. When user enters that detail fragment, fragment B holding that item goes through onPause() and onStop(). At the same time, onStop() is called in fragment C.
The point is, if user spends enough time in fragment B (contained by fragment A), fragment C is destroyed and this is not by surprise because I know that is intended by fragment state adapter. It is supposed to get rid of some fragments when not visible.
My problem is that when fragment C gets destroyed, viewmodel associated with it does not get destroyed. This is bad because now when user goes to fragment C, which still has reference to old viewmodel, app doesn't supply any data to the fragment because when onDestroy() is called, viewmodel of fragment C is cleared and thus viewmodelscope.launch is not working.
I also thought of not using viewmodelscope (use coroutinescope instead) but that is not the issue. What I am curious and eager to know is why viewmodel of fragment C, scoped to lifecycle of fragment C is not destroyed.(I want to get rid of old viewmodel at the demise of fragment C and get new viewmodel instance)
Please understand my clumsy wording and my lack of knowledge that might give out some confusion. I am new to dagger. Please see my code below for better understanding.
AppComponent.kt
#Singleton
#Component(
modules = [
AndroidSupportInjectionModule::class,
ActivityBindingModule::class,
RepositoryModule::class,
DataSourceModule::class,
ServiceModule::class,
DaoModule::class,
ViewModelModule::class,
]
)
ViewModelModule.kt
#MapKey
#Target(
AnnotationTarget.FUNCTION,
AnnotationTarget.PROPERTY_GETTER,
AnnotationTarget.PROPERTY_SETTER
)
annotation class ViewModelKey(val value: KClass<out ViewModel>)
#Module
abstract class ViewModelModule {
#Binds
#IntoMap
#ViewModelKey(AllStockListTabViewModel::class)
abstract fun bindAllStockListTabViewModel(allStockListTabViewModel: AllStockListTabViewModel): ViewModel
}
ViewModelFactory
#Singleton
class ViewModelFactory #Inject constructor(
private val viewModelMap: Map<Class<out ViewModel>, #JvmSuppressWildcards Provider<ViewModel>>
) : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return viewModelMap[modelClass]?.get() as T
}
}
Fragment
class AllStockListTabFragment #Inject constructor() :
ViewModelFragment<FragmentAllStockListBinding>(R.layout.fragment_all_stock_list) {
#Inject
lateinit var viewModelFactory: ViewModelFactory
override val viewModel: AllStockListTabViewModel by viewModels ({this}, {viewModelFactory})
}
Adapter
tradingTabAdapter = TradingTabAdapter(
this.childFragmentManager,
this.lifecycle,
tradingStateTabFragment,
allStockListTabFragment
)
class TradingTabAdapter #Inject constructor(
fragmentManager: FragmentManager,
lifecycle: Lifecycle,
private val tradingStateTabFragment: TradingStateTabFragment,
private val allStockListTabFragment: AllStockListTabFragment
) : FragmentStateAdapter(fragmentManager, lifecycle) {
override fun createFragment(position: Int): Fragment =
when (position) {
0 -> tradingStateTabFragment
else -> allStockListTabFragment
}
override fun getItemCount(): Int = 2
}
SubComponent
#FragmentScope
#Subcomponent(
modules = [
TradingTabBindingModule::class,
TradingTabModule::class,
EventModule::class,
UseCaseModule::class
]
)
AdapterModule
#Module
class TradingTabModule {
#Provides
fun provideTradingTabAdapter(
fragment: TradingTabFragment,
allStockListTabFragment: AllStockListTabFragment,
tradingStateTabFragment: TradingStateTabFragment
) = TradingTabAdapter(
fragment.childFragmentManager,
fragment.lifecycle,
tradingStateTabFragment,
allStockListTabFragment
)
I found that create method of ViewModelFactory is not called when fragment C is destroyed and created again. I think this is because I am using lazy initialization of viewmodel and that is how ViewModelLazy works. It caches viewmodel and invokes factory's create method only when cache is null. I guess what's happening is old viewmodel of fragment C is still referencing the dead viewmodel(which survived viewModelStore.onclear). I put a log statement in the init block of viewmodel of fragment C and I can see that it is called only for the very frist time fragment C is created and never called again even when fragment C is destroyed and created again.
Thank you so much for your patience reading all this haha. So I need help from any expereienced Android gurus who might be able to give some insight.
My goal: make viewmodel destroyed and recreated with the lifecycle of fragment. I want to avoid memory leak due to unused zombie viewmodels.
Current situation: viewmodel never gets destroyed and reborn fragment still references old viewmodel and thus lazy initialisation keeps the cache of old viewmodel, not triggering create method of ViewModelFactory.
--Edit--
version of dagger im using
"com.google.dagger:dagger-android:2.37"
Since your ViewModel is tied to your Activity, it is not getting destroyed when Fragment is destroyed.
#ViewModelKey(MainActivityViewModel::class)
abstract fun bindMainActivityViewModel(mainActivityViewModel: MainActivityViewModel): ViewModel
You can check this answer which explains How to use ViewModel with Fragment.
How to use ViewModel in a fragment?
I'm having a hard time understand what scopes to use for view models and live data when using fragments. Here is my ViewModel:
class MyViewModel: ViewModel() {
var myLiveData = MutableLiveData<WrappedResult<DataResponse>>()
private val repository = MyRespository()
private var job: Job? = null
fun getData(symbol: String) {
job = viewModelScope.launch(Dispatchers.IO) {
try {
val response = repository.getData(symbol)
withContext(Dispatchers.Main) {
myLiveData.value = WrappedResult.Success(response)
}
} catch(e: Exception) {
withContext(Dispatchers.Main) {
myLiveData.value = WrappedResult.Failure(e)
}
}
}
}
}
I can create the view model in the fragment using (where "this" is the fragment):
viewModel = new ViewModelProvider(this).get(MyViewModel.class);
However, I can observe the LiveData with two options:
viewModel.getMyLiveData.observe(this...
or
viewModel.getMyLiveData.observe(getViewLifecycleOwner()...
It would appear that the job I create in the view model is going to be scoped to the fragment's lifecycle (through viewModelScope) and not the fragment's view lifecycle, but I have a choice between these two for the live data.
I could use some guidance and what the best practice is here. Also, does any of this matter whether the fragment has retained instance or not? Currently the fragment has setRetainInstance(true). Finally, from everything I've read I shouldn't need to clear the observer in the fragment or override onCleared when things are setup this way. Is that correct?
refer the doc of view model
https://developer.android.com/topic/libraries/architecture/viewmodel?gclid=Cj0KCQjwtZH7BRDzARIsAGjbK2blIS5rGzBxBdX6HpB5PMKgpUQHvdKXbwrt-ukTnWkpax1otMk4sm4aAuzPEALw_wcB&gclsrc=aw.ds#lifecycle
Viewmodel will only gets destoyed once the activity is finished.As the fragments are on the top of acitivity, the lifecycle of fragment will not affect the Viewmodel.The data will be persisted there on the viewmodel. So you can write a method to reset the data in viewmodel while you are entering in to oncreate of fragment.
In Fragment, OnCreate :
getViewModel.init()
on ViewModel
fun init() {
// clear all varialbes/datas/ etc here
}
How I can use shared viewModel with fragments without activity?
Like in code but in place of requireActivity() use ParentFragment. In this case when ParentFragment will destroyed, SharedViewModel is cleared, but when I provide SharedViewModel from activity, it not cleared when ParentFragment destroyed.
And I use Navigation Components, which mean that I can`t set tag for fragment and then use findFragmentByTag()
class ParentFragment:Fragment{
override fun onCreate(savedInstanceState: Bundle?) {
var viewModel = ViewModelProviders.of(requireActivity()).get(SharedViewModel::class)
}
}
class ChildFragment:Fragmnet{
override fun onCreate(savedInstanceState: Bundle?) {
var viewModel = ViewModelProviders.of(requireActivity()).get(SharedViewModel::class)
}
}
You can try scoped-vm - it allows you to request ViewModel for scope identified by a String key. Scope lives till the last fragment that requested ViewModel gets destroyed, then ViewModel gets cleared.
You can use this code to obtain SharedViewModel both in ParentFragment and ChildFragment.
ScopedViewModelProviders
.forScope(this, "scope")
.of(requireActivity())
.get(SharedViewModel::class.java)
See you can initialize viewModel in all fragments which you want to share viewmodel, and use Rx with viewModel, your all process in these fragments will keep running until you want to cancel it,you can call viewModel.oncleard() from Activity or Fragment.
public override fun onCleared() {
super.onCleared()
compositeDisposable.dispose()
// or cancel any process
}
If you do not know ViewModel Scope, please check this image
Is it a good practice to declare a coroutine scope inside the App class instead of using a Globascope?
class App : Application() {
val applicationJob = Job()
companion object {
lateinit var instance: App
private set
lateinit var appDefaultScope: CoroutineScope
}
override fun onCreate() {
super.onCreate()
instance = this#App
appDefaultScope = CoroutineScope(Dispatchers.Default + applicationJob)
}
}
Here I defined a variable appDefaultScope and initialized it in the App's onCreate
So anywhere in my code I could always do:
App.appDefaultScope.launch {
// some operations
}
The main difference with a GlobalScope that I see is that here I can always cancel the job and shut down all potentially stuck coroutines.
Are there are better alternatives to this?
The reason is that i have some object functions that use coroutines and are not called from an activity (then I cannot provide them with a scope defined inside the activity) but need a scope for their operations.
An example of such object function is a Log that writes on a local database that can be called almost everywhere in the App.
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.