xref: /aosp_15_r20/external/dagger2/javatests/dagger/hilt/android/ViewModelSavedStateOwnerTest.java (revision f585d8a307d0621d6060bd7e80091fdcbf94fe27)
1*f585d8a3SJacky Wang /*
2*f585d8a3SJacky Wang  * Copyright (C) 2022 The Dagger Authors.
3*f585d8a3SJacky Wang  *
4*f585d8a3SJacky Wang  * Licensed under the Apache License, Version 2.0 (the "License");
5*f585d8a3SJacky Wang  * you may not use this file except in compliance with the License.
6*f585d8a3SJacky Wang  * You may obtain a copy of the License at
7*f585d8a3SJacky Wang  *
8*f585d8a3SJacky Wang  * http://www.apache.org/licenses/LICENSE-2.0
9*f585d8a3SJacky Wang  *
10*f585d8a3SJacky Wang  * Unless required by applicable law or agreed to in writing, software
11*f585d8a3SJacky Wang  * distributed under the License is distributed on an "AS IS" BASIS,
12*f585d8a3SJacky Wang  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13*f585d8a3SJacky Wang  * See the License for the specific language governing permissions and
14*f585d8a3SJacky Wang  * limitations under the License.
15*f585d8a3SJacky Wang  */
16*f585d8a3SJacky Wang 
17*f585d8a3SJacky Wang package dagger.hilt.android;
18*f585d8a3SJacky Wang 
19*f585d8a3SJacky Wang import static com.google.common.truth.Truth.assertThat;
20*f585d8a3SJacky Wang import static org.junit.Assert.assertThrows;
21*f585d8a3SJacky Wang 
22*f585d8a3SJacky Wang import android.os.Build;
23*f585d8a3SJacky Wang import android.os.Bundle;
24*f585d8a3SJacky Wang import androidx.fragment.app.Fragment;
25*f585d8a3SJacky Wang import androidx.fragment.app.FragmentActivity;
26*f585d8a3SJacky Wang import androidx.annotation.Nullable;
27*f585d8a3SJacky Wang import androidx.annotation.OptIn;
28*f585d8a3SJacky Wang import androidx.lifecycle.SavedStateHandle;
29*f585d8a3SJacky Wang import androidx.lifecycle.ViewModel;
30*f585d8a3SJacky Wang import androidx.lifecycle.ViewModelProvider;
31*f585d8a3SJacky Wang import androidx.lifecycle.ViewModelStoreOwner;
32*f585d8a3SJacky Wang import androidx.navigation.NavController;
33*f585d8a3SJacky Wang import androidx.navigation.Navigation;
34*f585d8a3SJacky Wang import androidx.test.core.app.ActivityScenario;
35*f585d8a3SJacky Wang import androidx.test.ext.junit.runners.AndroidJUnit4;
36*f585d8a3SJacky Wang import dagger.hilt.android.lifecycle.ActivityRetainedSavedState;
37*f585d8a3SJacky Wang import dagger.hilt.android.lifecycle.HiltViewModel;
38*f585d8a3SJacky Wang import dagger.hilt.android.testing.HiltAndroidRule;
39*f585d8a3SJacky Wang import dagger.hilt.android.testing.HiltAndroidTest;
40*f585d8a3SJacky Wang import dagger.hilt.android.testing.HiltTestApplication;
41*f585d8a3SJacky Wang import javax.inject.Inject;
42*f585d8a3SJacky Wang import javax.inject.Provider;
43*f585d8a3SJacky Wang import org.junit.Rule;
44*f585d8a3SJacky Wang import org.junit.Test;
45*f585d8a3SJacky Wang import org.junit.runner.RunWith;
46*f585d8a3SJacky Wang import org.robolectric.annotation.Config;
47*f585d8a3SJacky Wang 
48*f585d8a3SJacky Wang /** Test that you can use the Hilt ViewModel factory with other owners. */
49*f585d8a3SJacky Wang @OptIn(markerClass = UnstableApi.class)
50*f585d8a3SJacky Wang @HiltAndroidTest
51*f585d8a3SJacky Wang @RunWith(AndroidJUnit4.class)
52*f585d8a3SJacky Wang // Robolectric requires Java9 to run API 29 and above, so use API 28 instead
53*f585d8a3SJacky Wang @Config(sdk = Build.VERSION_CODES.P, application = HiltTestApplication.class)
54*f585d8a3SJacky Wang public class ViewModelSavedStateOwnerTest {
55*f585d8a3SJacky Wang 
56*f585d8a3SJacky Wang   @Rule public final HiltAndroidRule rule = new HiltAndroidRule(this);
57*f585d8a3SJacky Wang 
58*f585d8a3SJacky Wang   @Test
activityRetainedComponentSaveState_configurationChange_successfullySavedState()59*f585d8a3SJacky Wang   public void activityRetainedComponentSaveState_configurationChange_successfullySavedState() {
60*f585d8a3SJacky Wang     try (ActivityScenario<TestActivity> scenario = ActivityScenario.launch(TestActivity.class)) {
61*f585d8a3SJacky Wang       scenario.onActivity(
62*f585d8a3SJacky Wang           activity -> {
63*f585d8a3SJacky Wang             assertThat((String) activity.savedStateHandle.get("argument_key")).isNull();
64*f585d8a3SJacky Wang             activity.savedStateHandle.set("other_key", "activity_other_key");
65*f585d8a3SJacky Wang           });
66*f585d8a3SJacky Wang       scenario.recreate();
67*f585d8a3SJacky Wang       scenario.onActivity(
68*f585d8a3SJacky Wang           activity -> {
69*f585d8a3SJacky Wang             assertThat((String) activity.savedStateHandle.get("argument_key")).isNull();
70*f585d8a3SJacky Wang             assertThat((String) activity.savedStateHandle.get("other_key"))
71*f585d8a3SJacky Wang                 .isEqualTo("activity_other_key");
72*f585d8a3SJacky Wang           });
73*f585d8a3SJacky Wang     }
74*f585d8a3SJacky Wang   }
75*f585d8a3SJacky Wang 
76*f585d8a3SJacky Wang   @Test
firstTimeAccessToActivityRetainedSaveState_inActivityOnDestroy_fails()77*f585d8a3SJacky Wang   public void firstTimeAccessToActivityRetainedSaveState_inActivityOnDestroy_fails() {
78*f585d8a3SJacky Wang     Exception exception =
79*f585d8a3SJacky Wang         assertThrows(
80*f585d8a3SJacky Wang             NullPointerException.class,
81*f585d8a3SJacky Wang             () -> {
82*f585d8a3SJacky Wang               try (ActivityScenario<ErrorTestActivity> scenario =
83*f585d8a3SJacky Wang                   ActivityScenario.launch(ErrorTestActivity.class)) {}
84*f585d8a3SJacky Wang             });
85*f585d8a3SJacky Wang     assertThat(exception)
86*f585d8a3SJacky Wang         .hasMessageThat()
87*f585d8a3SJacky Wang         .contains(
88*f585d8a3SJacky Wang             "The first access to SavedStateHandle should happen between super.onCreate() and"
89*f585d8a3SJacky Wang                 + " super.onDestroy()");
90*f585d8a3SJacky Wang   }
91*f585d8a3SJacky Wang 
92*f585d8a3SJacky Wang   @Test
testViewModelSavedState()93*f585d8a3SJacky Wang   public void testViewModelSavedState() {
94*f585d8a3SJacky Wang     try (ActivityScenario<TestActivity> scenario = ActivityScenario.launch(TestActivity.class)) {
95*f585d8a3SJacky Wang       scenario.onActivity(
96*f585d8a3SJacky Wang           activity -> {
97*f585d8a3SJacky Wang             NavController navController =
98*f585d8a3SJacky Wang                 Navigation.findNavController(activity, R.id.nav_host_fragment);
99*f585d8a3SJacky Wang             TestFragment startFragment = findTestFragment(activity);
100*f585d8a3SJacky Wang 
101*f585d8a3SJacky Wang             MyViewModel activityVm =
102*f585d8a3SJacky Wang                 getViewModel(activity, activity.getDefaultViewModelProviderFactory());
103*f585d8a3SJacky Wang             MyViewModel fragmentVm =
104*f585d8a3SJacky Wang                 getViewModel(startFragment, startFragment.getDefaultViewModelProviderFactory());
105*f585d8a3SJacky Wang             MyViewModel fragmentBackStackVm =
106*f585d8a3SJacky Wang                 getViewModel(
107*f585d8a3SJacky Wang                     navController.getBackStackEntry(R.id.start_destination),
108*f585d8a3SJacky Wang                     startFragment.getDefaultViewModelProviderFactory());
109*f585d8a3SJacky Wang             MyViewModel navGraphVm =
110*f585d8a3SJacky Wang                 getViewModel(
111*f585d8a3SJacky Wang                     navController.getBackStackEntry(R.id.nav_graph),
112*f585d8a3SJacky Wang                     startFragment.getDefaultViewModelProviderFactory());
113*f585d8a3SJacky Wang 
114*f585d8a3SJacky Wang             // The activity shouldn't have any arguments since it was only set on the fragment.
115*f585d8a3SJacky Wang             assertThat((String) activityVm.savedStateHandle.get("argument_key")).isNull();
116*f585d8a3SJacky Wang             activityVm.savedStateHandle.set("other_key", "activity_other_key");
117*f585d8a3SJacky Wang 
118*f585d8a3SJacky Wang             // The fragment argument (set in the navgraph xml) should be set.
119*f585d8a3SJacky Wang             assertThat((String) fragmentVm.savedStateHandle.get("argument_key"))
120*f585d8a3SJacky Wang                 .isEqualTo("fragment_argument");
121*f585d8a3SJacky Wang             fragmentVm.savedStateHandle.set("other_key", "fragment_other_key");
122*f585d8a3SJacky Wang 
123*f585d8a3SJacky Wang             // The back stack entry also has the fragment arguments
124*f585d8a3SJacky Wang             assertThat((String) fragmentBackStackVm.savedStateHandle.get("argument_key"))
125*f585d8a3SJacky Wang                 .isEqualTo("fragment_argument");
126*f585d8a3SJacky Wang             fragmentBackStackVm.savedStateHandle.set("other_key", "fragment_backstack_other_key");
127*f585d8a3SJacky Wang 
128*f585d8a3SJacky Wang             // When the nav graph itself is the owner, then there should be no arguments.
129*f585d8a3SJacky Wang             assertThat((String) navGraphVm.savedStateHandle.get("argument_key")).isNull();
130*f585d8a3SJacky Wang             navGraphVm.savedStateHandle.set("other_key", "nav_graph_other_key");
131*f585d8a3SJacky Wang 
132*f585d8a3SJacky Wang             navController.navigate(R.id.next_destination);
133*f585d8a3SJacky Wang           });
134*f585d8a3SJacky Wang 
135*f585d8a3SJacky Wang       // Now move to the next fragment to compare
136*f585d8a3SJacky Wang       scenario.onActivity(
137*f585d8a3SJacky Wang           activity -> {
138*f585d8a3SJacky Wang             NavController navController =
139*f585d8a3SJacky Wang                 Navigation.findNavController(activity, R.id.nav_host_fragment);
140*f585d8a3SJacky Wang 
141*f585d8a3SJacky Wang             TestFragment nextFragment = findTestFragment(activity);
142*f585d8a3SJacky Wang 
143*f585d8a3SJacky Wang             MyViewModel activityVm =
144*f585d8a3SJacky Wang                 getViewModel(activity, activity.getDefaultViewModelProviderFactory());
145*f585d8a3SJacky Wang             MyViewModel fragmentVm =
146*f585d8a3SJacky Wang                 getViewModel(nextFragment, nextFragment.getDefaultViewModelProviderFactory());
147*f585d8a3SJacky Wang             MyViewModel navGraphVm =
148*f585d8a3SJacky Wang                 getViewModel(
149*f585d8a3SJacky Wang                     navController.getBackStackEntry(R.id.nav_graph),
150*f585d8a3SJacky Wang                     nextFragment.getDefaultViewModelProviderFactory());
151*f585d8a3SJacky Wang             MyViewModel fragmentBackStackVm =
152*f585d8a3SJacky Wang                 getViewModel(
153*f585d8a3SJacky Wang                     navController.getBackStackEntry(R.id.next_destination),
154*f585d8a3SJacky Wang                     nextFragment.getDefaultViewModelProviderFactory());
155*f585d8a3SJacky Wang 
156*f585d8a3SJacky Wang             // The activity still shouldn't have any arguments, but since it is the same
157*f585d8a3SJacky Wang             // owner (since the activity didn't change), the other key should still be set
158*f585d8a3SJacky Wang             // from before.
159*f585d8a3SJacky Wang             assertThat((String) activityVm.savedStateHandle.get("argument_key")).isNull();
160*f585d8a3SJacky Wang             assertThat((String) activityVm.savedStateHandle.get("other_key"))
161*f585d8a3SJacky Wang                 .isEqualTo("activity_other_key");
162*f585d8a3SJacky Wang 
163*f585d8a3SJacky Wang             // The fragment argument should be set via the navgraph xml again. Also, since
164*f585d8a3SJacky Wang             // this is a new fragment, the other key should not be set.
165*f585d8a3SJacky Wang             assertThat((String) fragmentVm.savedStateHandle.get("argument_key"))
166*f585d8a3SJacky Wang                 .isEqualTo("next_fragment_argument");
167*f585d8a3SJacky Wang             assertThat((String) fragmentVm.savedStateHandle.get("other_key")).isNull();
168*f585d8a3SJacky Wang 
169*f585d8a3SJacky Wang             // Same as using the fragment as the owner.
170*f585d8a3SJacky Wang             assertThat((String) fragmentBackStackVm.savedStateHandle.get("argument_key"))
171*f585d8a3SJacky Wang                 .isEqualTo("next_fragment_argument");
172*f585d8a3SJacky Wang             assertThat((String) fragmentBackStackVm.savedStateHandle.get("other_key")).isNull();
173*f585d8a3SJacky Wang 
174*f585d8a3SJacky Wang             // Similar to the activity case, the navgraph is the same so we expect the same
175*f585d8a3SJacky Wang             // key to be set from before. Arguments should still be missing.
176*f585d8a3SJacky Wang             assertThat((String) navGraphVm.savedStateHandle.get("argument_key")).isNull();
177*f585d8a3SJacky Wang             assertThat((String) navGraphVm.savedStateHandle.get("other_key"))
178*f585d8a3SJacky Wang                 .isEqualTo("nav_graph_other_key");
179*f585d8a3SJacky Wang           });
180*f585d8a3SJacky Wang     }
181*f585d8a3SJacky Wang   }
182*f585d8a3SJacky Wang 
findTestFragment(FragmentActivity activity)183*f585d8a3SJacky Wang   private TestFragment findTestFragment(FragmentActivity activity) {
184*f585d8a3SJacky Wang     return (TestFragment)
185*f585d8a3SJacky Wang         activity
186*f585d8a3SJacky Wang             .getSupportFragmentManager()
187*f585d8a3SJacky Wang             .findFragmentById(R.id.nav_host_fragment)
188*f585d8a3SJacky Wang             .getChildFragmentManager()
189*f585d8a3SJacky Wang             .getPrimaryNavigationFragment();
190*f585d8a3SJacky Wang   }
191*f585d8a3SJacky Wang 
getViewModel(ViewModelStoreOwner owner, ViewModelProvider.Factory factory)192*f585d8a3SJacky Wang   private MyViewModel getViewModel(ViewModelStoreOwner owner, ViewModelProvider.Factory factory) {
193*f585d8a3SJacky Wang     return new ViewModelProvider(owner, factory).get(MyViewModel.class);
194*f585d8a3SJacky Wang   }
195*f585d8a3SJacky Wang 
196*f585d8a3SJacky Wang   @AndroidEntryPoint(FragmentActivity.class)
197*f585d8a3SJacky Wang   public static class TestActivity extends Hilt_ViewModelSavedStateOwnerTest_TestActivity {
198*f585d8a3SJacky Wang     @Inject @ActivityRetainedSavedState Provider<SavedStateHandle> provider;
199*f585d8a3SJacky Wang     SavedStateHandle savedStateHandle;
200*f585d8a3SJacky Wang 
201*f585d8a3SJacky Wang     @Override
onCreate(@ullable Bundle savedInstanceState)202*f585d8a3SJacky Wang     protected void onCreate(@Nullable Bundle savedInstanceState) {
203*f585d8a3SJacky Wang       super.onCreate(savedInstanceState);
204*f585d8a3SJacky Wang       savedStateHandle = provider.get();
205*f585d8a3SJacky Wang       setContentView(R.layout.navigation_activity);
206*f585d8a3SJacky Wang     }
207*f585d8a3SJacky Wang   }
208*f585d8a3SJacky Wang 
209*f585d8a3SJacky Wang   @AndroidEntryPoint(FragmentActivity.class)
210*f585d8a3SJacky Wang   public static class ErrorTestActivity
211*f585d8a3SJacky Wang       extends Hilt_ViewModelSavedStateOwnerTest_ErrorTestActivity {
212*f585d8a3SJacky Wang     @Inject @ActivityRetainedSavedState Provider<SavedStateHandle> provider;
213*f585d8a3SJacky Wang 
214*f585d8a3SJacky Wang     @SuppressWarnings("unused")
215*f585d8a3SJacky Wang     @Override
onDestroy()216*f585d8a3SJacky Wang     protected void onDestroy() {
217*f585d8a3SJacky Wang       super.onDestroy();
218*f585d8a3SJacky Wang       SavedStateHandle savedStateHandle = provider.get();
219*f585d8a3SJacky Wang     }
220*f585d8a3SJacky Wang   }
221*f585d8a3SJacky Wang 
222*f585d8a3SJacky Wang   @AndroidEntryPoint(Fragment.class)
223*f585d8a3SJacky Wang   public static class TestFragment extends Hilt_ViewModelSavedStateOwnerTest_TestFragment {
224*f585d8a3SJacky Wang     @Override
onCreate(@ullable Bundle savedInstanceState)225*f585d8a3SJacky Wang     public void onCreate(@Nullable Bundle savedInstanceState) {
226*f585d8a3SJacky Wang       super.onCreate(savedInstanceState);
227*f585d8a3SJacky Wang     }
228*f585d8a3SJacky Wang   }
229*f585d8a3SJacky Wang 
230*f585d8a3SJacky Wang   @HiltViewModel
231*f585d8a3SJacky Wang   static class MyViewModel extends ViewModel {
232*f585d8a3SJacky Wang     final SavedStateHandle savedStateHandle;
233*f585d8a3SJacky Wang 
234*f585d8a3SJacky Wang     @Inject
MyViewModel(SavedStateHandle savedStateHandle)235*f585d8a3SJacky Wang     MyViewModel(SavedStateHandle savedStateHandle) {
236*f585d8a3SJacky Wang       this.savedStateHandle = savedStateHandle;
237*f585d8a3SJacky Wang     }
238*f585d8a3SJacky Wang   }
239*f585d8a3SJacky Wang }
240