Dagger-Android: ViewModel not destroyed when fragment destroyed

916 Views Asked by At

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"

1

There are 1 best solutions below

1
On

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?