main

square/leakcanary

Last updated at: 29/12/2023 09:38

ViewModelClearedWatcher.kt

TLDR

The ViewModelClearedWatcher class in the provided file is responsible for watching the lifecycle of AndroidX ViewModel instances and adding them to a ReachabilityWatcher when they are cleared.

Classes

ViewModelClearedWatcher

The ViewModelClearedWatcher class is a subclass of ViewModel that watches the lifecycle of ViewModel instances. It holds onto a map of ViewModels backing its store. When the ViewModelClearedWatcher receives the onCleared callback, it adds each live ViewModel from the store to the ReachabilityWatcher.

Methods

install

The install method is a companion method that installs an instance of ViewModelClearedWatcher for a given ViewModelStoreOwner. It creates a ViewModelProvider and uses it to get an instance of ViewModelClearedWatcher.

package leakcanary.internal

import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelProvider.Factory
import androidx.lifecycle.ViewModelStore
import androidx.lifecycle.ViewModelStoreOwner
import leakcanary.ReachabilityWatcher
import leakcanary.internal.ViewModelClearedWatcher.Companion.install
import shark.SharkLog

/**
 * [AndroidXFragmentDestroyWatcher] calls [install] to add a spy [ViewModel] in every
 * [ViewModelStoreOwner] instance (i.e. FragmentActivity and Fragment). [ViewModelClearedWatcher]
 * holds on to the map of [ViewModel]s backing its store. When [ViewModelClearedWatcher] receives
 * the [onCleared] callback, it adds each live [ViewModel] from the store to the [ReachabilityWatcher].
 */
internal class ViewModelClearedWatcher(
  storeOwner: ViewModelStoreOwner,
  private val reachabilityWatcher: ReachabilityWatcher
) : ViewModel() {

  // We could call ViewModelStore#keys with a package spy in androidx.lifecycle instead,
  // however that was added in 2.1.0 and we support AndroidX first stable release. viewmodel-2.0.0
  // does not have ViewModelStore#keys. All versions currently have the mMap field.
  private val viewModelMap: Map<String, ViewModel>? = try {
    val storeClass = ViewModelStore::class.java
    val mapField = try {
      storeClass.getDeclaredField("map")
    } catch (exception: NoSuchFieldException) {
      // Field name changed from mMap to map with Kotlin conversion
      // https://cs.android.com/androidx/platform/frameworks/support/+/8aa6ca1c924ab10d263b21b99b8790d5f0b50cc6
      storeClass.getDeclaredField("mMap")
    }
    mapField.isAccessible = true
    @Suppress("UNCHECKED_CAST")
    mapField[storeOwner.viewModelStore] as Map<String, ViewModel>
  } catch (ignored: Exception) {
    SharkLog.d(ignored) { "Could not find ViewModelStore map of view models" }
    null
  }

  override fun onCleared() {
    viewModelMap?.values?.forEach { viewModel ->
      reachabilityWatcher.expectWeaklyReachable(
        viewModel, "${viewModel::class.java.name} received ViewModel#onCleared() callback"
      )
    }
  }

  companion object {
    fun install(
      storeOwner: ViewModelStoreOwner,
      reachabilityWatcher: ReachabilityWatcher
    ) {
      val provider = ViewModelProvider(storeOwner, object : Factory {
        @Suppress("UNCHECKED_CAST")
        override fun <T : ViewModel?> create(modelClass: Class<T>): T =
          ViewModelClearedWatcher(storeOwner, reachabilityWatcher) as T
      })
      provider.get(ViewModelClearedWatcher::class.java)
    }
  }
}