1 /* 2 * Copyright (C) 2020 The Dagger Authors. 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 dagger.hilt.android; 18 19 import static com.google.common.truth.Truth.assertThat; 20 import static org.junit.Assert.assertThrows; 21 22 import android.os.Build; 23 import android.os.Bundle; 24 import androidx.fragment.app.Fragment; 25 import androidx.fragment.app.FragmentActivity; 26 import androidx.annotation.Nullable; 27 import androidx.lifecycle.SavedStateHandle; 28 import androidx.lifecycle.ViewModel; 29 import androidx.lifecycle.ViewModelProvider; 30 import androidx.test.core.app.ActivityScenario; 31 import androidx.test.ext.junit.runners.AndroidJUnit4; 32 import dagger.assisted.Assisted; 33 import dagger.assisted.AssistedFactory; 34 import dagger.assisted.AssistedInject; 35 import dagger.hilt.android.lifecycle.HiltViewModel; 36 import dagger.hilt.android.lifecycle.HiltViewModelExtensions; 37 import dagger.hilt.android.scopes.ViewModelScoped; 38 import dagger.hilt.android.testing.HiltAndroidRule; 39 import dagger.hilt.android.testing.HiltAndroidTest; 40 import dagger.hilt.android.testing.HiltTestApplication; 41 import javax.inject.Inject; 42 import org.junit.Rule; 43 import org.junit.Test; 44 import org.junit.runner.RunWith; 45 import org.robolectric.annotation.Config; 46 47 @HiltAndroidTest 48 @RunWith(AndroidJUnit4.class) 49 // Robolectric requires Java9 to run API 29 and above, so use API 28 instead 50 @Config(sdk = Build.VERSION_CODES.P, application = HiltTestApplication.class) 51 public class ViewModelAssistedTest { 52 53 @Rule public final HiltAndroidRule rule = new HiltAndroidRule(this); 54 55 @Test testConfigChange()56 public void testConfigChange() { 57 try (ActivityScenario<TestConfigChangeActivity> scenario = 58 ActivityScenario.launch(TestConfigChangeActivity.class)) { 59 scenario.onActivity( 60 activity -> { 61 assertThat(activity.vm.one.bar).isNotNull(); 62 assertThat(activity.vm.one.bar).isSameInstanceAs(activity.vm.two.bar); 63 assertThat(activity.vm.s).isEqualTo("foo"); 64 }); 65 scenario.recreate(); 66 scenario.onActivity( 67 activity -> { 68 // Check that we still get the same ViewModel instance after config change and the 69 // passed assisted arg has no effect anymore. 70 assertThat(activity.vm.s).isEqualTo("foo"); 71 }); 72 } 73 } 74 75 @Test testKeyedViewModels()76 public void testKeyedViewModels() { 77 try (ActivityScenario<TestKeyedViewModelActivity> scenario = 78 ActivityScenario.launch(TestKeyedViewModelActivity.class)) { 79 scenario.onActivity( 80 activity -> { 81 assertThat(activity.vm1.s).isEqualTo("foo"); 82 assertThat(activity.vm2.s).isEqualTo("bar"); 83 }); 84 } 85 } 86 87 @Test testNoCreationCallbacks()88 public void testNoCreationCallbacks() { 89 Exception exception = 90 assertThrows( 91 IllegalStateException.class, 92 () -> ActivityScenario.launch(TestNoCreationCallbacksActivity.class).close()); 93 assertThat(exception) 94 .hasMessageThat() 95 .contains( 96 "Found @HiltViewModel-annotated class" 97 + " dagger.hilt.android.ViewModelAssistedTest$MyViewModel" 98 + " using @AssistedInject but no creation callback was provided" 99 + " in CreationExtras."); 100 } 101 102 @Test testNoFactory()103 public void testNoFactory() { 104 Exception exception = 105 assertThrows( 106 RuntimeException.class, 107 () -> ActivityScenario.launch(TestNoFactoryActivity.class).close()); 108 assertThat(exception) 109 .hasMessageThat() 110 .contains( 111 "Found creation callback but class" 112 + " dagger.hilt.android.ViewModelAssistedTest$MyInjectedViewModel does not have an" 113 + " assisted factory specified in @HiltViewModel."); 114 } 115 116 @Test testFragmentArgs()117 public void testFragmentArgs() { 118 try (ActivityScenario<TestFragmentArgsActivity> scenario = 119 ActivityScenario.launch(TestFragmentArgsActivity.class)) { 120 scenario.onActivity( 121 activity -> { 122 TestFragment fragment = 123 (TestFragment) activity.getSupportFragmentManager().findFragmentByTag("tag"); 124 assertThat(fragment.vm.handle.<String>get("key")).isEqualTo("foobar"); 125 }); 126 } 127 } 128 129 @Test testIncompatibleFactories()130 public void testIncompatibleFactories() { 131 Exception exception = 132 assertThrows( 133 ClassCastException.class, 134 () -> ActivityScenario.launch(TestIncompatibleFactoriesActivity.class).close()); 135 assertThat(exception) 136 .hasMessageThat() 137 .contains( 138 "class dagger.hilt.android.ViewModelAssistedTest_MyViewModel_Factory_Impl cannot be" 139 + " cast to class" 140 + " dagger.hilt.android.ViewModelAssistedTest$MyViewModel$AnotherFactory"); 141 } 142 143 @AndroidEntryPoint(FragmentActivity.class) 144 public static class TestConfigChangeActivity 145 extends Hilt_ViewModelAssistedTest_TestConfigChangeActivity { 146 147 MyViewModel vm; 148 149 @Override onCreate(@ullable Bundle savedInstanceState)150 protected void onCreate(@Nullable Bundle savedInstanceState) { 151 super.onCreate(savedInstanceState); 152 if (savedInstanceState == null) { 153 vm = 154 new ViewModelProvider( 155 getViewModelStore(), 156 getDefaultViewModelProviderFactory(), 157 HiltViewModelExtensions.withCreationCallback( 158 getDefaultViewModelCreationExtras(), 159 (MyViewModel.Factory factory) -> factory.create("foo"))) 160 .get(MyViewModel.class); 161 } else { 162 vm = 163 new ViewModelProvider( 164 getViewModelStore(), 165 getDefaultViewModelProviderFactory(), 166 HiltViewModelExtensions.withCreationCallback( 167 getDefaultViewModelCreationExtras(), 168 (MyViewModel.Factory factory) -> factory.create("bar"))) 169 .get(MyViewModel.class); 170 } 171 } 172 } 173 174 @AndroidEntryPoint(FragmentActivity.class) 175 public static class TestKeyedViewModelActivity 176 extends Hilt_ViewModelAssistedTest_TestKeyedViewModelActivity { 177 178 MyViewModel vm1; 179 MyViewModel vm2; 180 181 @Override onCreate(@ullable Bundle savedInstanceState)182 protected void onCreate(@Nullable Bundle savedInstanceState) { 183 super.onCreate(savedInstanceState); 184 vm1 = 185 new ViewModelProvider( 186 getViewModelStore(), 187 getDefaultViewModelProviderFactory(), 188 HiltViewModelExtensions.withCreationCallback( 189 getDefaultViewModelCreationExtras(), 190 (MyViewModel.Factory factory) -> factory.create("foo"))) 191 .get("a", MyViewModel.class); 192 193 vm2 = 194 new ViewModelProvider( 195 getViewModelStore(), 196 getDefaultViewModelProviderFactory(), 197 HiltViewModelExtensions.withCreationCallback( 198 getDefaultViewModelCreationExtras(), 199 (MyViewModel.Factory factory) -> factory.create("bar"))) 200 .get("b", MyViewModel.class); 201 } 202 } 203 204 @AndroidEntryPoint(FragmentActivity.class) 205 public static class TestNoCreationCallbacksActivity 206 extends Hilt_ViewModelAssistedTest_TestNoCreationCallbacksActivity { 207 208 MyViewModel vm; 209 210 @Override onCreate(@ullable Bundle savedInstanceState)211 protected void onCreate(@Nullable Bundle savedInstanceState) { 212 super.onCreate(savedInstanceState); 213 vm = new ViewModelProvider(this).get(MyViewModel.class); 214 } 215 } 216 217 @AndroidEntryPoint(FragmentActivity.class) 218 public static class TestNoFactoryActivity 219 extends Hilt_ViewModelAssistedTest_TestNoFactoryActivity { 220 221 MyInjectedViewModel vm; 222 223 @Override onCreate(@ullable Bundle savedInstanceState)224 protected void onCreate(@Nullable Bundle savedInstanceState) { 225 super.onCreate(savedInstanceState); 226 227 vm = 228 new ViewModelProvider( 229 getViewModelStore(), 230 getDefaultViewModelProviderFactory(), 231 HiltViewModelExtensions.withCreationCallback( 232 getDefaultViewModelCreationExtras(), 233 (MyViewModel.Factory factory) -> factory.create("bar"))) 234 .get(MyInjectedViewModel.class); 235 } 236 } 237 238 @AndroidEntryPoint(FragmentActivity.class) 239 public static class TestFragmentArgsActivity 240 extends Hilt_ViewModelAssistedTest_TestFragmentArgsActivity { 241 @Override onCreate(@ullable Bundle savedInstanceState)242 protected void onCreate(@Nullable Bundle savedInstanceState) { 243 super.onCreate(savedInstanceState); 244 if (savedInstanceState == null) { 245 Fragment f = 246 getSupportFragmentManager() 247 .getFragmentFactory() 248 .instantiate(TestFragment.class.getClassLoader(), TestFragment.class.getName()); 249 Bundle b = new Bundle(); 250 b.putString("key", "foobar"); 251 f.setArguments(b); 252 getSupportFragmentManager().beginTransaction().add(0, f, "tag").commitNow(); 253 } 254 } 255 } 256 257 @AndroidEntryPoint(FragmentActivity.class) 258 public static class TestIncompatibleFactoriesActivity 259 extends Hilt_ViewModelAssistedTest_TestIncompatibleFactoriesActivity { 260 261 MyViewModel vm; 262 263 @Override onCreate(@ullable Bundle savedInstanceState)264 protected void onCreate(@Nullable Bundle savedInstanceState) { 265 super.onCreate(savedInstanceState); 266 vm = 267 new ViewModelProvider( 268 getViewModelStore(), 269 getDefaultViewModelProviderFactory(), 270 HiltViewModelExtensions.withCreationCallback( 271 getDefaultViewModelCreationExtras(), 272 (MyViewModel.AnotherFactory factory) -> factory.create("foo"))) 273 .get(MyViewModel.class); 274 } 275 } 276 277 @AndroidEntryPoint(Fragment.class) 278 public static class TestFragment extends Hilt_ViewModelAssistedTest_TestFragment { 279 280 MyViewModel vm; 281 282 @Override onCreate(@ullable Bundle bundle)283 public void onCreate(@Nullable Bundle bundle) { 284 super.onCreate(bundle); 285 vm = 286 new ViewModelProvider( 287 getViewModelStore(), 288 getDefaultViewModelProviderFactory(), 289 HiltViewModelExtensions.withCreationCallback( 290 getDefaultViewModelCreationExtras(), 291 (MyViewModel.Factory factory) -> factory.create("foo"))) 292 .get(MyViewModel.class); 293 } 294 } 295 296 @HiltViewModel(assistedFactory = MyViewModel.Factory.class) 297 static class MyViewModel extends ViewModel { 298 299 final DependsOnBarOne one; 300 final DependsOnBarTwo two; 301 final SavedStateHandle handle; 302 final String s; 303 boolean cleared = false; 304 305 @AssistedInject MyViewModel( DependsOnBarOne one, DependsOnBarTwo two, ViewModelLifecycle lifecycle, SavedStateHandle handle, @Assisted String s)306 MyViewModel( 307 DependsOnBarOne one, 308 DependsOnBarTwo two, 309 ViewModelLifecycle lifecycle, 310 SavedStateHandle handle, 311 @Assisted String s) { 312 this.one = one; 313 this.two = two; 314 this.s = s; 315 this.handle = handle; 316 lifecycle.addOnClearedListener(() -> cleared = true); 317 } 318 319 @AssistedFactory 320 interface Factory { create(String s)321 MyViewModel create(String s); 322 } 323 324 @AssistedFactory 325 interface AnotherFactory { create(String s)326 MyViewModel create(String s); 327 } 328 } 329 330 @HiltViewModel 331 static class MyInjectedViewModel extends ViewModel { 332 333 final DependsOnBarOne one; 334 final DependsOnBarTwo two; 335 final SavedStateHandle handle; 336 boolean cleared = false; 337 338 @Inject MyInjectedViewModel( DependsOnBarOne one, DependsOnBarTwo two, ViewModelLifecycle lifecycle, SavedStateHandle handle)339 MyInjectedViewModel( 340 DependsOnBarOne one, 341 DependsOnBarTwo two, 342 ViewModelLifecycle lifecycle, 343 SavedStateHandle handle) { 344 this.one = one; 345 this.two = two; 346 this.handle = handle; 347 lifecycle.addOnClearedListener(() -> cleared = true); 348 } 349 } 350 351 @ViewModelScoped 352 static class Bar { 353 @Inject Bar()354 Bar() {} 355 } 356 357 static class DependsOnBarOne { 358 final Bar bar; 359 360 @Inject DependsOnBarOne(Bar bar)361 DependsOnBarOne(Bar bar) { 362 this.bar = bar; 363 } 364 } 365 366 static class DependsOnBarTwo { 367 final Bar bar; 368 369 @Inject DependsOnBarTwo(Bar bar)370 DependsOnBarTwo(Bar bar) { 371 this.bar = bar; 372 } 373 } 374 } 375