1/* 2 * Copyright (C) 2017 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.lifecycle; 18 19import static androidx.lifecycle.Lifecycle.Event.ON_RESUME; 20 21import static org.hamcrest.CoreMatchers.is; 22import static org.hamcrest.CoreMatchers.not; 23import static org.hamcrest.CoreMatchers.notNullValue; 24import static org.hamcrest.MatcherAssert.assertThat; 25 26import android.app.Instrumentation; 27import android.support.test.InstrumentationRegistry; 28import android.support.test.annotation.UiThreadTest; 29import android.support.test.filters.MediumTest; 30import android.support.test.rule.ActivityTestRule; 31import android.support.test.runner.AndroidJUnit4; 32 33import androidx.fragment.app.Fragment; 34import androidx.fragment.app.FragmentActivity; 35import androidx.fragment.app.FragmentManager; 36import androidx.lifecycle.viewmodeltest.TestViewModel; 37import androidx.lifecycle.viewmodeltest.ViewModelActivity; 38import androidx.lifecycle.viewmodeltest.ViewModelActivity.ViewModelFragment; 39 40import org.junit.Rule; 41import org.junit.Test; 42import org.junit.runner.RunWith; 43 44import java.util.concurrent.CountDownLatch; 45import java.util.concurrent.TimeUnit; 46 47@MediumTest 48@RunWith(AndroidJUnit4.class) 49public class ViewModelTest { 50 private static final int TIMEOUT = 2; // secs 51 52 @Rule 53 public ActivityTestRule<ViewModelActivity> mActivityRule = 54 new ActivityTestRule<>(ViewModelActivity.class); 55 56 @Test 57 public void ensureSameViewHolders() throws Throwable { 58 final TestViewModel[] activityModel = new TestViewModel[1]; 59 final TestViewModel[] defaultActivityModel = new TestViewModel[1]; 60 final TestViewModel[] fragment1Model = new TestViewModel[1]; 61 final TestViewModel[] fragment2Model = new TestViewModel[1]; 62 final ViewModelActivity[] viewModelActivity = new ViewModelActivity[1]; 63 viewModelActivity[0] = mActivityRule.getActivity(); 64 mActivityRule.runOnUiThread(new Runnable() { 65 @Override 66 public void run() { 67 ViewModelFragment fragment1 = getFragment(viewModelActivity[0], 68 ViewModelActivity.FRAGMENT_TAG_1); 69 ViewModelFragment fragment2 = getFragment(viewModelActivity[0], 70 ViewModelActivity.FRAGMENT_TAG_2); 71 assertThat(fragment1, notNullValue()); 72 assertThat(fragment2, notNullValue()); 73 assertThat(fragment1.activityModel, is(fragment2.activityModel)); 74 assertThat(fragment1.fragmentModel, not(is(fragment2.activityModel))); 75 assertThat(mActivityRule.getActivity().activityModel, is(fragment1.activityModel)); 76 activityModel[0] = mActivityRule.getActivity().activityModel; 77 defaultActivityModel[0] = mActivityRule.getActivity().defaultActivityModel; 78 assertThat(defaultActivityModel[0], not(is(activityModel[0]))); 79 fragment1Model[0] = fragment1.fragmentModel; 80 fragment2Model[0] = fragment2.fragmentModel; 81 } 82 }); 83 viewModelActivity[0] = recreateActivity(); 84 mActivityRule.runOnUiThread(new Runnable() { 85 @Override 86 public void run() { 87 ViewModelFragment fragment1 = getFragment(viewModelActivity[0], 88 ViewModelActivity.FRAGMENT_TAG_1); 89 ViewModelFragment fragment2 = getFragment(viewModelActivity[0], 90 ViewModelActivity.FRAGMENT_TAG_2); 91 assertThat(fragment1, notNullValue()); 92 assertThat(fragment2, notNullValue()); 93 94 assertThat(fragment1.activityModel, is(activityModel[0])); 95 assertThat(fragment2.activityModel, is(activityModel[0])); 96 assertThat(fragment1.fragmentModel, is(fragment1Model[0])); 97 assertThat(fragment2.fragmentModel, is(fragment2Model[0])); 98 assertThat(fragment1.defaultActivityModel, is(defaultActivityModel[0])); 99 assertThat(fragment2.defaultActivityModel, is(defaultActivityModel[0])); 100 assertThat(mActivityRule.getActivity().activityModel, is(activityModel[0])); 101 assertThat(mActivityRule.getActivity().defaultActivityModel, 102 is(defaultActivityModel[0])); 103 } 104 }); 105 } 106 107 @Test 108 @UiThreadTest 109 public void testGetApplication() { 110 TestViewModel activityModel = mActivityRule.getActivity().activityModel; 111 assertThat(activityModel.getApplication(), 112 is(InstrumentationRegistry.getTargetContext().getApplicationContext())); 113 } 114 115 @Test 116 public void testOnClear() throws Throwable { 117 final ViewModelActivity activity = mActivityRule.getActivity(); 118 final CountDownLatch latch = new CountDownLatch(1); 119 final LifecycleObserver observer = new LifecycleObserver() { 120 @SuppressWarnings("unused") 121 @OnLifecycleEvent(ON_RESUME) 122 void onResume() { 123 try { 124 final FragmentManager manager = activity.getSupportFragmentManager(); 125 Fragment fragment = new Fragment(); 126 manager.beginTransaction().add(fragment, "temp").commitNow(); 127 ViewModel1 vm = ViewModelProviders.of(fragment).get(ViewModel1.class); 128 assertThat(vm.mCleared, is(false)); 129 manager.beginTransaction().remove(fragment).commitNow(); 130 assertThat(vm.mCleared, is(true)); 131 } finally { 132 latch.countDown(); 133 } 134 } 135 }; 136 137 mActivityRule.runOnUiThread(new Runnable() { 138 @Override 139 public void run() { 140 activity.getLifecycle().addObserver(observer); 141 } 142 }); 143 assertThat(latch.await(TIMEOUT, TimeUnit.SECONDS), is(true)); 144 } 145 146 private ViewModelFragment getFragment(FragmentActivity activity, String tag) { 147 return (ViewModelFragment) activity.getSupportFragmentManager() 148 .findFragmentByTag(tag); 149 } 150 151 private ViewModelActivity recreateActivity() throws Throwable { 152 Instrumentation.ActivityMonitor monitor = new Instrumentation.ActivityMonitor( 153 ViewModelActivity.class.getCanonicalName(), null, false); 154 Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation(); 155 instrumentation.addMonitor(monitor); 156 final ViewModelActivity previous = mActivityRule.getActivity(); 157 mActivityRule.runOnUiThread(new Runnable() { 158 @Override 159 public void run() { 160 previous.recreate(); 161 } 162 }); 163 ViewModelActivity result; 164 165 // this guarantee that we will reinstall monitor between notifications about onDestroy 166 // and onCreate 167 //noinspection SynchronizationOnLocalVariableOrMethodParameter 168 synchronized (monitor) { 169 do { 170 // the documentation says "Block until an Activity is created 171 // that matches this monitor." This statement is true, but there are some other 172 // true statements like: "Block until an Activity is destroyed" or 173 // "Block until an Activity is resumed"... 174 175 // this call will release synchronization monitor's monitor 176 result = (ViewModelActivity) monitor.waitForActivityWithTimeout(4000); 177 if (result == null) { 178 throw new RuntimeException("Timeout. Failed to recreate an activity"); 179 } 180 } while (result == previous); 181 } 182 return result; 183 } 184 185 public static class ViewModel1 extends ViewModel { 186 boolean mCleared = false; 187 188 @Override 189 protected void onCleared() { 190 mCleared = true; 191 } 192 } 193} 194