1/* 2 * Copyright 2018 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 17package androidx.fragment.app; 18 19import static androidx.lifecycle.Lifecycle.Event.ON_DESTROY; 20import static androidx.lifecycle.Lifecycle.Event.ON_RESUME; 21 22import static org.hamcrest.CoreMatchers.is; 23import static org.hamcrest.CoreMatchers.not; 24import static org.hamcrest.CoreMatchers.notNullValue; 25import static org.hamcrest.MatcherAssert.assertThat; 26 27import android.app.Instrumentation; 28import android.support.test.InstrumentationRegistry; 29import android.support.test.annotation.UiThreadTest; 30import android.support.test.filters.MediumTest; 31import android.support.test.rule.ActivityTestRule; 32import android.support.test.runner.AndroidJUnit4; 33 34import androidx.fragment.app.test.TestViewModel; 35import androidx.fragment.app.test.ViewModelActivity; 36import androidx.fragment.app.test.ViewModelActivity.ViewModelFragment; 37import androidx.lifecycle.LifecycleObserver; 38import androidx.lifecycle.OnLifecycleEvent; 39import androidx.lifecycle.ViewModelProvider; 40 41import org.junit.Rule; 42import org.junit.Test; 43import org.junit.runner.RunWith; 44 45import java.util.concurrent.CountDownLatch; 46import java.util.concurrent.TimeUnit; 47 48@MediumTest 49@RunWith(AndroidJUnit4.class) 50public class ViewModelTest { 51 private static final int TIMEOUT = 2; // secs 52 53 @Rule 54 public ActivityTestRule<ViewModelActivity> mActivityRule = 55 new ActivityTestRule<>(ViewModelActivity.class); 56 57 @Test(expected = IllegalStateException.class) 58 @UiThreadTest 59 public void testNotAttachedActivity() { 60 // This is similar to calling getViewModelStore in Activity's constructor 61 new FragmentActivity().getViewModelStore(); 62 } 63 64 @Test(expected = IllegalStateException.class) 65 @UiThreadTest 66 public void testNotAttachedFragment() { 67 // This is similar to calling getViewModelStore in Fragment's constructor 68 new Fragment().getViewModelStore(); 69 } 70 71 @Test 72 public void testSameActivityViewModels() throws Throwable { 73 final TestViewModel[] activityModel = new TestViewModel[1]; 74 final TestViewModel[] defaultActivityModel = new TestViewModel[1]; 75 final ViewModelActivity[] viewModelActivity = new ViewModelActivity[1]; 76 viewModelActivity[0] = mActivityRule.getActivity(); 77 mActivityRule.runOnUiThread(new Runnable() { 78 @Override 79 public void run() { 80 activityModel[0] = viewModelActivity[0].activityModel; 81 defaultActivityModel[0] = viewModelActivity[0].defaultActivityModel; 82 assertThat(defaultActivityModel[0], not(is(activityModel[0]))); 83 84 ViewModelFragment fragment1 = getFragment(viewModelActivity[0], 85 ViewModelActivity.FRAGMENT_TAG_1); 86 ViewModelFragment fragment2 = getFragment(viewModelActivity[0], 87 ViewModelActivity.FRAGMENT_TAG_2); 88 assertThat(fragment1, notNullValue()); 89 assertThat(fragment2, notNullValue()); 90 91 assertThat(fragment1.activityModel, is(activityModel[0])); 92 assertThat(fragment2.activityModel, is(activityModel[0])); 93 94 assertThat(fragment1.defaultActivityModel, is(defaultActivityModel[0])); 95 assertThat(fragment2.defaultActivityModel, is(defaultActivityModel[0])); 96 } 97 }); 98 viewModelActivity[0] = recreateActivity(); 99 mActivityRule.runOnUiThread(new Runnable() { 100 @Override 101 public void run() { 102 assertThat(viewModelActivity[0].activityModel, is(activityModel[0])); 103 assertThat(viewModelActivity[0].defaultActivityModel, 104 is(defaultActivityModel[0])); 105 106 ViewModelFragment fragment1 = getFragment(viewModelActivity[0], 107 ViewModelActivity.FRAGMENT_TAG_1); 108 ViewModelFragment fragment2 = getFragment(viewModelActivity[0], 109 ViewModelActivity.FRAGMENT_TAG_2); 110 assertThat(fragment1, notNullValue()); 111 assertThat(fragment2, notNullValue()); 112 113 assertThat(fragment1.activityModel, is(activityModel[0])); 114 assertThat(fragment2.activityModel, is(activityModel[0])); 115 116 assertThat(fragment1.defaultActivityModel, is(defaultActivityModel[0])); 117 assertThat(fragment2.defaultActivityModel, is(defaultActivityModel[0])); 118 } 119 }); 120 } 121 122 @Test 123 public void testSameFragmentViewModels() throws Throwable { 124 final TestViewModel[] fragment1Model = new TestViewModel[1]; 125 final TestViewModel[] fragment2Model = new TestViewModel[1]; 126 final ViewModelActivity[] viewModelActivity = new ViewModelActivity[1]; 127 viewModelActivity[0] = mActivityRule.getActivity(); 128 mActivityRule.runOnUiThread(new Runnable() { 129 @Override 130 public void run() { 131 ViewModelFragment fragment1 = getFragment(viewModelActivity[0], 132 ViewModelActivity.FRAGMENT_TAG_1); 133 ViewModelFragment fragment2 = getFragment(viewModelActivity[0], 134 ViewModelActivity.FRAGMENT_TAG_2); 135 assertThat(fragment1, notNullValue()); 136 assertThat(fragment2, notNullValue()); 137 138 assertThat(fragment1.fragmentModel, not(is(fragment2.fragmentModel))); 139 fragment1Model[0] = fragment1.fragmentModel; 140 fragment2Model[0] = fragment2.fragmentModel; 141 } 142 }); 143 viewModelActivity[0] = recreateActivity(); 144 mActivityRule.runOnUiThread(new Runnable() { 145 @Override 146 public void run() { 147 ViewModelFragment fragment1 = getFragment(viewModelActivity[0], 148 ViewModelActivity.FRAGMENT_TAG_1); 149 ViewModelFragment fragment2 = getFragment(viewModelActivity[0], 150 ViewModelActivity.FRAGMENT_TAG_2); 151 assertThat(fragment1, notNullValue()); 152 assertThat(fragment2, notNullValue()); 153 154 assertThat(fragment1.fragmentModel, is(fragment1Model[0])); 155 assertThat(fragment2.fragmentModel, is(fragment2Model[0])); 156 } 157 }); 158 } 159 160 @Test 161 public void testActivityOnCleared() throws Throwable { 162 final ViewModelActivity activity = mActivityRule.getActivity(); 163 final CountDownLatch latch = new CountDownLatch(1); 164 final LifecycleObserver observer = new LifecycleObserver() { 165 @SuppressWarnings("unused") 166 @OnLifecycleEvent(ON_DESTROY) 167 void onDestroy() { 168 activity.getWindow().getDecorView().post(new Runnable() { 169 @Override 170 public void run() { 171 try { 172 assertThat(activity.activityModel.mCleared, is(true)); 173 assertThat(activity.defaultActivityModel.mCleared, is(true)); 174 } finally { 175 latch.countDown(); 176 } 177 } 178 }); 179 } 180 }; 181 182 mActivityRule.runOnUiThread(new Runnable() { 183 @Override 184 public void run() { 185 activity.getLifecycle().addObserver(observer); 186 } 187 }); 188 activity.finish(); 189 assertThat(latch.await(TIMEOUT, TimeUnit.SECONDS), is(true)); 190 } 191 192 @Test 193 public void testFragmentOnClearedWhenFinished() throws Throwable { 194 final ViewModelActivity activity = mActivityRule.getActivity(); 195 final ViewModelFragment fragment = getFragment(activity, 196 ViewModelActivity.FRAGMENT_TAG_1); 197 final CountDownLatch latch = new CountDownLatch(1); 198 final LifecycleObserver observer = new LifecycleObserver() { 199 @SuppressWarnings("unused") 200 @OnLifecycleEvent(ON_DESTROY) 201 void onDestroy() { 202 activity.getWindow().getDecorView().post(new Runnable() { 203 @Override 204 public void run() { 205 try { 206 assertThat(fragment.fragmentModel.mCleared, is(true)); 207 } finally { 208 latch.countDown(); 209 } 210 } 211 }); 212 } 213 }; 214 215 mActivityRule.runOnUiThread(new Runnable() { 216 @Override 217 public void run() { 218 activity.getLifecycle().addObserver(observer); 219 } 220 }); 221 activity.finish(); 222 assertThat(latch.await(TIMEOUT, TimeUnit.SECONDS), is(true)); 223 } 224 225 @Test 226 public void testFragmentOnCleared() throws Throwable { 227 final ViewModelActivity activity = mActivityRule.getActivity(); 228 final CountDownLatch latch = new CountDownLatch(1); 229 final LifecycleObserver observer = new LifecycleObserver() { 230 @SuppressWarnings("unused") 231 @OnLifecycleEvent(ON_RESUME) 232 void onResume() { 233 try { 234 final FragmentManager manager = activity.getSupportFragmentManager(); 235 Fragment fragment = new Fragment(); 236 manager.beginTransaction().add(fragment, "temp").commitNow(); 237 ViewModelProvider viewModelProvider = new ViewModelProvider(fragment, 238 new ViewModelProvider.NewInstanceFactory()); 239 TestViewModel vm = viewModelProvider.get(TestViewModel.class); 240 assertThat(vm.mCleared, is(false)); 241 manager.beginTransaction().remove(fragment).commitNow(); 242 assertThat(vm.mCleared, is(true)); 243 } finally { 244 latch.countDown(); 245 } 246 } 247 }; 248 249 mActivityRule.runOnUiThread(new Runnable() { 250 @Override 251 public void run() { 252 activity.getLifecycle().addObserver(observer); 253 } 254 }); 255 assertThat(latch.await(TIMEOUT, TimeUnit.SECONDS), is(true)); 256 } 257 258 private ViewModelFragment getFragment(FragmentActivity activity, String tag) { 259 return (ViewModelFragment) activity.getSupportFragmentManager() 260 .findFragmentByTag(tag); 261 } 262 263 private ViewModelActivity recreateActivity() throws Throwable { 264 Instrumentation.ActivityMonitor monitor = new Instrumentation.ActivityMonitor( 265 ViewModelActivity.class.getCanonicalName(), null, false); 266 Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation(); 267 instrumentation.addMonitor(monitor); 268 final ViewModelActivity previous = mActivityRule.getActivity(); 269 mActivityRule.runOnUiThread(new Runnable() { 270 @Override 271 public void run() { 272 previous.recreate(); 273 } 274 }); 275 ViewModelActivity result; 276 277 // this guarantee that we will reinstall monitor between notifications about onDestroy 278 // and onCreate 279 //noinspection SynchronizationOnLocalVariableOrMethodParameter 280 synchronized (monitor) { 281 do { 282 // the documentation says "Block until an Activity is created 283 // that matches this monitor." This statement is true, but there are some other 284 // true statements like: "Block until an Activity is destroyed" or 285 // "Block until an Activity is resumed"... 286 287 // this call will release synchronization monitor's monitor 288 result = (ViewModelActivity) monitor.waitForActivityWithTimeout(4000); 289 if (result == null) { 290 throw new RuntimeException("Timeout. Failed to recreate an activity"); 291 } 292 } while (result == previous); 293 } 294 return result; 295 } 296} 297