1 /*
2  * Copyright 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package androidx.lifecycle.viewmodel.savedstate
18 
19 import android.app.Application
20 import androidx.fragment.app.FragmentActivity
21 import androidx.lifecycle.AndroidViewModel
22 import androidx.lifecycle.SAVED_STATE_REGISTRY_OWNER_KEY
23 import androidx.lifecycle.SavedStateHandle
24 import androidx.lifecycle.SavedStateViewModelFactory
25 import androidx.lifecycle.VIEW_MODEL_STORE_OWNER_KEY
26 import androidx.lifecycle.ViewModel
27 import androidx.lifecycle.ViewModelProvider
28 import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.Companion.APPLICATION_KEY
29 import androidx.lifecycle.ViewModelStore
30 import androidx.lifecycle.enableSavedStateHandles
31 import androidx.lifecycle.viewmodel.MutableCreationExtras
32 import androidx.test.annotation.UiThreadTest
33 import androidx.test.ext.junit.runners.AndroidJUnit4
34 import androidx.test.filters.SmallTest
35 import com.google.common.truth.Truth.assertThat
36 import org.junit.Assert.fail
37 import org.junit.Rule
38 import org.junit.Test
39 import org.junit.runner.RunWith
40 
41 @RunWith(AndroidJUnit4::class)
42 @SmallTest
43 class SavedStateFactoryTest {
44 
45     @Suppress("DEPRECATION")
46     @get:Rule
47     var activityRule = androidx.test.rule.ActivityTestRule(MyActivity::class.java)
48 
49     @UiThreadTest
50     @Test
testLegacyCreateAndroidVMnull51     fun testLegacyCreateAndroidVM() {
52         val savedStateVMFactory =
53             SavedStateViewModelFactory(activityRule.activity.application, activityRule.activity)
54         val vm = ViewModelProvider(ViewModelStore(), savedStateVMFactory)
55         assertThat(vm[MyAndroidViewModel::class.java].handle).isNotNull()
56         assertThat(vm[MyViewModel::class.java].handle).isNotNull()
57     }
58 
59     @UiThreadTest
60     @Test
testCreateAndroidVMnull61     fun testCreateAndroidVM() {
62         val savedStateVMFactory = SavedStateViewModelFactory()
63         val component = TestComponent()
64         component.enableSavedStateHandles()
65         val extras = component.extras
66         extras[APPLICATION_KEY] = activityRule.activity.application
67         val vm = ViewModelProvider(component.viewModelStore, savedStateVMFactory, extras)
68         assertThat(vm[MyAndroidViewModel::class.java].handle).isNotNull()
69         assertThat(vm[MyViewModel::class.java].handle).isNotNull()
70     }
71 
72     @UiThreadTest
73     @Test
testCreateAndroidWithStatefulFactoryVMnull74     fun testCreateAndroidWithStatefulFactoryVM() {
75         val savedStateVMFactory = SavedStateViewModelFactory(null, activityRule.activity)
76         val component = TestComponent()
77         component.enableSavedStateHandles()
78         val extras = component.extras
79         extras[APPLICATION_KEY] = activityRule.activity.application
80         val vm = ViewModelProvider(component.viewModelStore, savedStateVMFactory, extras)
81         assertThat(vm[MyAndroidViewModel::class.java].handle).isNotNull()
82         assertThat(vm[MyViewModel::class.java].handle).isNotNull()
83     }
84 
85     @UiThreadTest
86     @Test
testCreateAndroidVMWrongParameterOrdernull87     fun testCreateAndroidVMWrongParameterOrder() {
88         val savedStateVMFactory = SavedStateViewModelFactory()
89         val component = TestComponent()
90         component.enableSavedStateHandles()
91         val extras = component.extras
92         extras[APPLICATION_KEY] = activityRule.activity.application
93         val vm = ViewModelProvider(component.viewModelStore, savedStateVMFactory, extras)
94         try {
95             assertThat(vm[WrongOrderAndroidViewModel::class.java].handle).isNotNull()
96             fail()
97         } catch (e: UnsupportedOperationException) {
98             assertThat(e)
99                 .hasMessageThat()
100                 .isEqualTo(
101                     "Class WrongOrderAndroidViewModel must have parameters in the proper order: " +
102                         "[class android.app.Application, class androidx.lifecycle.SavedStateHandle]"
103                 )
104         }
105     }
106 
107     @UiThreadTest
108     @Test
testLegacyCreateFailAndroidVMnull109     fun testLegacyCreateFailAndroidVM() {
110         val savedStateVMFactory = SavedStateViewModelFactory(null, activityRule.activity)
111         val vm = ViewModelProvider(ViewModelStore(), savedStateVMFactory)
112         try {
113             vm[MyAndroidViewModel::class.java]
114             fail("Creating an AndroidViewModel should fail when no Application is provided")
115         } catch (e: RuntimeException) {
116             // Exception message varies across platform versions, just make sure it's thrown.
117         }
118         assertThat(vm[MyViewModel::class.java].handle).isNotNull()
119     }
120 
121     @UiThreadTest
122     @Test
testCreateFailAndroidVMnull123     fun testCreateFailAndroidVM() {
124         val savedStateVMFactory = SavedStateViewModelFactory()
125         val component = TestComponent()
126         component.enableSavedStateHandles()
127         val vm = ViewModelProvider(component.viewModelStore, savedStateVMFactory, component.extras)
128         try {
129             vm[MyAndroidViewModel::class.java]
130             fail("Creating an AndroidViewModel should fail when no Application extras is provided")
131         } catch (e: RuntimeException) {}
132         assertThat(vm[MyViewModel::class.java].handle).isNotNull()
133     }
134 
135     @UiThreadTest
136     @Test
testLegacyCreateAndroidAbstractVMnull137     fun testLegacyCreateAndroidAbstractVM() {
138         val activity = activityRule.activity
139         val app = activity.application
140         @Suppress("DEPRECATION")
141         val savedStateVMFactory =
142             object : androidx.lifecycle.AbstractSavedStateViewModelFactory(activity, null) {
143                 override fun <T : ViewModel> create(
144                     key: String,
145                     modelClass: Class<T>,
146                     handle: SavedStateHandle
147                 ): T {
148                     return modelClass.cast(MyAndroidViewModel(app, handle))!!
149                 }
150             }
151         val vm = ViewModelProvider(ViewModelStore(), savedStateVMFactory)
152         assertThat(vm[MyAndroidViewModel::class.java].handle).isNotNull()
153     }
154 
155     @UiThreadTest
156     @Test
testLegacyMethodsWithEmptyConstructornull157     fun testLegacyMethodsWithEmptyConstructor() {
158         val factory = SavedStateViewModelFactory()
159         try {
160             factory.create(MyViewModel::class.java)
161             fail()
162         } catch (e: UnsupportedOperationException) {}
163 
164         try {
165             factory.create("a", MyViewModel::class.java)
166             fail()
167         } catch (e: UnsupportedOperationException) {}
168 
169         @Suppress("DEPRECATION")
170         val absFactory =
171             object : androidx.lifecycle.AbstractSavedStateViewModelFactory() {
172                 override fun <T : ViewModel> create(
173                     key: String,
174                     modelClass: Class<T>,
175                     handle: SavedStateHandle
176                 ): T = create(modelClass)
177             }
178         try {
179             absFactory.create(MyViewModel::class.java)
180             fail()
181         } catch (e: UnsupportedOperationException) {}
182     }
183 
184     internal class MyAndroidViewModel(app: Application, val handle: SavedStateHandle) :
185         AndroidViewModel(app)
186 
187     internal class WrongOrderAndroidViewModel(val handle: SavedStateHandle, app: Application) :
188         AndroidViewModel(app)
189 
190     internal class MyViewModel(val handle: SavedStateHandle) : ViewModel()
191 
192     class MyActivity : FragmentActivity()
193 
194     val TestComponent.extras: MutableCreationExtras
195         get() {
196             val extras = MutableCreationExtras()
197             extras[SAVED_STATE_REGISTRY_OWNER_KEY] = this
198             extras[VIEW_MODEL_STORE_OWNER_KEY] = this
199             return extras
200         }
201 }
202