<lambda>null1 package leakcanary.internal
2 
3 import androidx.lifecycle.ViewModel
4 import androidx.lifecycle.ViewModelProvider
5 import androidx.lifecycle.ViewModelProvider.Factory
6 import androidx.lifecycle.ViewModelStore
7 import androidx.lifecycle.ViewModelStoreOwner
8 import leakcanary.ReachabilityWatcher
9 import leakcanary.internal.ViewModelClearedWatcher.Companion.install
10 import shark.SharkLog
11 
12 /**
13  * [AndroidXFragmentDestroyWatcher] calls [install] to add a spy [ViewModel] in every
14  * [ViewModelStoreOwner] instance (i.e. FragmentActivity and Fragment). [ViewModelClearedWatcher]
15  * holds on to the map of [ViewModel]s backing its store. When [ViewModelClearedWatcher] receives
16  * the [onCleared] callback, it adds each live [ViewModel] from the store to the [ReachabilityWatcher].
17  */
18 internal class ViewModelClearedWatcher(
19   storeOwner: ViewModelStoreOwner,
20   private val reachabilityWatcher: ReachabilityWatcher
21 ) : ViewModel() {
22 
23   // We could call ViewModelStore#keys with a package spy in androidx.lifecycle instead,
24   // however that was added in 2.1.0 and we support AndroidX first stable release. viewmodel-2.0.0
25   // does not have ViewModelStore#keys. All versions currently have the mMap field.
26   private val viewModelMap: Map<String, ViewModel>? = try {
27     val storeClass = ViewModelStore::class.java
28     val mapField = try {
29       storeClass.getDeclaredField("map")
30     } catch (exception: NoSuchFieldException) {
31       // Field name changed from mMap to map with Kotlin conversion
32       // https://cs.android.com/androidx/platform/frameworks/support/+/8aa6ca1c924ab10d263b21b99b8790d5f0b50cc6
33       storeClass.getDeclaredField("mMap")
34     }
35     mapField.isAccessible = true
36     @Suppress("UNCHECKED_CAST")
37     mapField[storeOwner.viewModelStore] as Map<String, ViewModel>
38   } catch (ignored: Exception) {
39     SharkLog.d(ignored) { "Could not find ViewModelStore map of view models" }
40     null
41   }
42 
43   override fun onCleared() {
44     viewModelMap?.values?.forEach { viewModel ->
45       reachabilityWatcher.expectWeaklyReachable(
46         viewModel, "${viewModel::class.java.name} received ViewModel#onCleared() callback"
47       )
48     }
49   }
50 
51   companion object {
52     fun install(
53       storeOwner: ViewModelStoreOwner,
54       reachabilityWatcher: ReachabilityWatcher
55     ) {
56       val provider = ViewModelProvider(storeOwner, object : Factory {
57         @Suppress("UNCHECKED_CAST")
58         override fun <T : ViewModel?> create(modelClass: Class<T>): T =
59           ViewModelClearedWatcher(storeOwner, reachabilityWatcher) as T
60       })
61       provider.get(ViewModelClearedWatcher::class.java)
62     }
63   }
64 }
65