xref: /aosp_15_r20/external/dagger2/javatests/dagger/hilt/android/ViewModelAssistedTest.java (revision f585d8a307d0621d6060bd7e80091fdcbf94fe27)
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