From e8d4f534fe71dcdb3217b13654614195c354ae20 Mon Sep 17 00:00:00 2001 From: Kejun Xia Date: Thu, 26 Nov 2015 20:21:39 +1100 Subject: [PATCH 01/26] Retain instance during navigation based on individual navigation events --- .../shipdream/lib/android/mvc/MvcGraph.java | 1 - .../lib/android/mvc/__MvcGraphHelper.java | 26 +++-- .../internal/NavigationControllerImpl.java | 7 +- .../lib/android/mvc/TestMvcGraphHelper.java | 10 +- library/android-mvc-test/build.gradle | 4 +- .../nav/TestCaseNavigationFromController.java | 95 ++++++++++++++++++- .../lib/android/mvc/view/nav/ControllerF.java | 28 ++++++ .../lib/android/mvc/view/nav/ControllerG.java | 28 ++++++ .../android/mvc/view/nav/DisposeCheckerF.java | 22 +++++ .../android/mvc/view/nav/DisposeCheckerG.java | 22 +++++ .../view/nav/MvcTestActivityNavigation.java | 6 ++ .../android/mvc/view/nav/NavFragmentE.java | 3 + .../android/mvc/view/nav/NavFragmentF.java | 50 ++++++++++ .../android/mvc/view/nav/NavFragmentG.java | 47 +++++++++ .../view/nav/internal/ControllerEImpl.java | 2 - .../view/nav/internal/ControllerFImpl.java | 52 ++++++++++ .../view/nav/internal/ControllerGImpl.java | 52 ++++++++++ .../nav/internal/DisposeCheckerFImpl.java | 28 ++++++ .../nav/internal/DisposeCheckerGImpl.java | 28 ++++++ .../lib/android/mvc/view/MvcActivity.java | 4 +- 20 files changed, 496 insertions(+), 19 deletions(-) create mode 100644 library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/nav/ControllerF.java create mode 100644 library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/nav/ControllerG.java create mode 100644 library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/nav/DisposeCheckerF.java create mode 100644 library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/nav/DisposeCheckerG.java create mode 100644 library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/nav/NavFragmentF.java create mode 100644 library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/nav/NavFragmentG.java create mode 100644 library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/nav/internal/ControllerFImpl.java create mode 100644 library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/nav/internal/ControllerGImpl.java create mode 100644 library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/nav/internal/DisposeCheckerFImpl.java create mode 100644 library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/nav/internal/DisposeCheckerGImpl.java diff --git a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/MvcGraph.java b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/MvcGraph.java index 379dca9..744614b 100644 --- a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/MvcGraph.java +++ b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/MvcGraph.java @@ -79,7 +79,6 @@ *

*/ public class MvcGraph { - List cachedInstancesBeforeNavigation = new ArrayList<>(); private Logger logger = LoggerFactory.getLogger(getClass()); ScopeCache singletonScopeCache; DefaultProviderFinder defaultProviderFinder; diff --git a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/__MvcGraphHelper.java b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/__MvcGraphHelper.java index 30016be..e4384c8 100644 --- a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/__MvcGraphHelper.java +++ b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/__MvcGraphHelper.java @@ -1,25 +1,35 @@ package com.shipdream.lib.android.mvc; +import com.shipdream.lib.android.mvc.controller.NavigationController; import com.shipdream.lib.poke.Provider; import com.shipdream.lib.poke.ScopeCache; +import java.util.ArrayList; import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; /** * Helper class to work with MvcGraph. Internal use only. Don't use it in your app. */ public class __MvcGraphHelper { + private static Map> retainedProviders = new HashMap<>(); + /** * Internal use. Don't call it in app directly. */ - public static void retainCachedObjectsBeforeNavigation(MvcGraph mvcGraph) { - mvcGraph.cachedInstancesBeforeNavigation.clear(); + public static void retainCachedObjectsBeforeNavigation( + NavigationController.EventC2V.OnLocationForward navigationEvent, MvcGraph mvcGraph) { //Retain all cached items before navigation. Collection cachedItems = mvcGraph.singletonScopeCache.getCachedItems(); + + List providers = new ArrayList<>(); + retainedProviders.put(navigationEvent, providers); for (ScopeCache.CachedItem cachedItem : cachedItems) { Provider provider = cachedItem.getProvider(); if (provider != null) { - mvcGraph.cachedInstancesBeforeNavigation.add(provider); + providers.add(provider); provider.retain(); } } @@ -28,14 +38,18 @@ public static void retainCachedObjectsBeforeNavigation(MvcGraph mvcGraph) { /** * Internal use. Don't call it in app directly. */ - public static void releaseCachedItemsAfterNavigation(MvcGraph mvcGraph) { + public static void releaseCachedItemsAfterNavigation( + NavigationController.EventC2V.OnLocationForward navigationEvent, MvcGraph mvcGraph) { + //Release all cached items after the fragment navigated to is ready to show. - for (Provider provider : mvcGraph.cachedInstancesBeforeNavigation) { + Collection providers = retainedProviders.get(navigationEvent); + for (Provider provider : providers) { if (provider != null) { provider.release(); } } - mvcGraph.cachedInstancesBeforeNavigation.clear(); + providers.clear(); + retainedProviders.remove(navigationEvent); } /** diff --git a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/internal/NavigationControllerImpl.java b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/internal/NavigationControllerImpl.java index 3c25c2c..e335d94 100644 --- a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/internal/NavigationControllerImpl.java +++ b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/internal/NavigationControllerImpl.java @@ -97,6 +97,8 @@ private void doNavigateTo(Object sender, String locationId, boolean clearTop, String lastLocId = lastLoc == null ? null : lastLoc.getLocationId(); + EventC2V.OnLocationForward navEvent = new EventC2V.OnLocationForward(sender, lastLoc, + currentLoc, clearTop, clearedTopToLocation); /** * Retain all cached state managed objects. They will be retained until the fragment is * created and ready to show. They will be released by the view ready call back by the @@ -109,10 +111,9 @@ public void run() { } }); */ - __MvcGraphHelper.retainCachedObjectsBeforeNavigation(Injector.getGraph()); + __MvcGraphHelper.retainCachedObjectsBeforeNavigation(navEvent, Injector.getGraph()); - postC2VEvent(new EventC2V.OnLocationForward(sender, lastLoc, currentLoc, clearTop, - clearedTopToLocation)); + postC2VEvent(navEvent); logger.trace("Nav Controller: Forward: {} -> {}", lastLocId, currentLoc.getLocationId()); } diff --git a/library/android-mvc-controller/src/test/java/com/shipdream/lib/android/mvc/TestMvcGraphHelper.java b/library/android-mvc-controller/src/test/java/com/shipdream/lib/android/mvc/TestMvcGraphHelper.java index 36f9deb..da4f773 100644 --- a/library/android-mvc-controller/src/test/java/com/shipdream/lib/android/mvc/TestMvcGraphHelper.java +++ b/library/android-mvc-controller/src/test/java/com/shipdream/lib/android/mvc/TestMvcGraphHelper.java @@ -40,10 +40,16 @@ protected ExecutorService createExecutorService() { int initSize = __MvcGraphHelper.getAllCachedInstances(Injector.getGraph()).size(); - __MvcGraphHelper.retainCachedObjectsBeforeNavigation(Injector.getGraph()); + NavLocation lastValue = new NavLocation(); + NavLocation currentValue = new NavLocation(); + + NavigationController.EventC2V.OnLocationForward event = new NavigationController.EventC2V.OnLocationForward( + this, lastValue, currentValue, false, null); + + __MvcGraphHelper.retainCachedObjectsBeforeNavigation(event, Injector.getGraph()); Assert.assertNotEquals(initSize, __MvcGraphHelper.getAllCachedInstances(Injector.getGraph())); - __MvcGraphHelper.releaseCachedItemsAfterNavigation(Injector.getGraph()); + __MvcGraphHelper.releaseCachedItemsAfterNavigation(event, Injector.getGraph()); Assert.assertEquals(initSize, __MvcGraphHelper.getAllCachedInstances(Injector.getGraph()).size()); } diff --git a/library/android-mvc-test/build.gradle b/library/android-mvc-test/build.gradle index fdabf7a..9284fe3 100644 --- a/library/android-mvc-test/build.gradle +++ b/library/android-mvc-test/build.gradle @@ -23,8 +23,8 @@ dependencies { compile rootProject.lib.logbackAndroidClassic // Testing-only dependencies - androidTestCompile 'com.android.support.test:runner:0.4' - androidTestCompile 'com.android.support.test:rules:0.4' + androidTestCompile 'com.android.support.test:runner:0.4.1' + androidTestCompile 'com.android.support.test:rules:0.4.1' androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.1' androidTestCompile "com.android.support:support-annotations:$rootProject.supportLibVersion" androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.2' diff --git a/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/nav/TestCaseNavigationFromController.java b/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/nav/TestCaseNavigationFromController.java index 4d1dde0..bffad24 100644 --- a/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/nav/TestCaseNavigationFromController.java +++ b/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/nav/TestCaseNavigationFromController.java @@ -37,6 +37,7 @@ import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; import static android.support.test.espresso.matcher.ViewMatchers.withText; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.reset; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -46,6 +47,8 @@ public class TestCaseNavigationFromController extends BaseTestCase () { +// @Override +// public void consume(ControllerE instance) { +// instance.setValue(valE); +// navigationController.navigateTo(this, MvcTestActivityNavigation.Loc.E); +// +// instance.setValue(valG); +// navigationController.navigateTo(this, MvcTestActivityNavigation.Loc.G); +// } +// }); +// +// Injector.getGraph().use(ControllerF.class, new Consumer() { +// @Override +// public void consume(ControllerF instance) { +// instance.setValue(valF); +// navigationController.navigateTo(this, MvcTestActivityNavigation.Loc.F); +// } +// }); + + Injector.getGraph().use(ControllerG.class, new Consumer() { + @Override + public void consume(ControllerG instance) { + instance.setValue(valG); + navigationController.navigateTo(this, MvcTestActivityNavigation.Loc.E); + navigationController.navigateTo(this, MvcTestActivityNavigation.Loc.F); + navigationController.navigateTo(this, MvcTestActivityNavigation.Loc.G); + } + }); + + waitTest(1500); + + //The value set to controller e in Injector.getGraph().use should be retained during the + //navigation + onView(withText(valG)).check(matches(isDisplayed())); + verify(disposeCheckerEMock, times(1)).onDisposed(); + verify(disposeCheckerFMock, times(1)).onDisposed(); + verify(disposeCheckerGMock, times(0)).onDisposed(); + resetDisposeCheckers(); + navigationController.navigateBack(this); + + waitTest(1500); +// onView(withText(valF)).check(matches(isDisplayed())); + verify(disposeCheckerEMock, times(0)).onDisposed(); + verify(disposeCheckerFMock, times(0)).onDisposed(); + verify(disposeCheckerGMock, times(0)).onDisposed(); + resetDisposeCheckers(); + navigationController.navigateBack(this); + + waitTest(1500); +// onView(withText(valE)).check(matches(isDisplayed())); + verify(disposeCheckerEMock, times(0)).onDisposed(); + verify(disposeCheckerFMock, times(1)).onDisposed(); + verify(disposeCheckerGMock, times(0)).onDisposed(); + resetDisposeCheckers(); + navigationController.navigateBack(this); + + waitTest(1500); + verify(disposeCheckerEMock, times(1)).onDisposed(); + verify(disposeCheckerFMock, times(0)).onDisposed(); + verify(disposeCheckerGMock, times(1)).onDisposed(); + } + + private void resetDisposeCheckers() { + reset(disposeCheckerEMock); + reset(disposeCheckerFMock); + reset(disposeCheckerGMock); + } + } diff --git a/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/nav/ControllerF.java b/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/nav/ControllerF.java new file mode 100644 index 0000000..cd6b80b --- /dev/null +++ b/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/nav/ControllerF.java @@ -0,0 +1,28 @@ +/* + * Copyright 2015 Kejun Xia + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.shipdream.lib.android.mvc.view.nav; + +import com.shipdream.lib.android.mvc.controller.BaseController; + +public interface ControllerF extends BaseController { + class Model { + public String value; + } + + void setValue(String value); + String getValue(); +} diff --git a/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/nav/ControllerG.java b/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/nav/ControllerG.java new file mode 100644 index 0000000..3a6fec3 --- /dev/null +++ b/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/nav/ControllerG.java @@ -0,0 +1,28 @@ +/* + * Copyright 2015 Kejun Xia + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.shipdream.lib.android.mvc.view.nav; + +import com.shipdream.lib.android.mvc.controller.BaseController; + +public interface ControllerG extends BaseController { + class Model { + public String value; + } + + void setValue(String value); + String getValue(); +} diff --git a/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/nav/DisposeCheckerF.java b/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/nav/DisposeCheckerF.java new file mode 100644 index 0000000..7e1cfe8 --- /dev/null +++ b/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/nav/DisposeCheckerF.java @@ -0,0 +1,22 @@ +/* + * Copyright 2015 Kejun Xia + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.shipdream.lib.android.mvc.view.nav; + +import com.shipdream.lib.android.mvc.Disposable; + +public interface DisposeCheckerF extends Disposable { +} diff --git a/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/nav/DisposeCheckerG.java b/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/nav/DisposeCheckerG.java new file mode 100644 index 0000000..b9e6907 --- /dev/null +++ b/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/nav/DisposeCheckerG.java @@ -0,0 +1,22 @@ +/* + * Copyright 2015 Kejun Xia + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.shipdream.lib.android.mvc.view.nav; + +import com.shipdream.lib.android.mvc.Disposable; + +public interface DisposeCheckerG extends Disposable { +} diff --git a/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/nav/MvcTestActivityNavigation.java b/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/nav/MvcTestActivityNavigation.java index 8a605e1..ab8279e 100644 --- a/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/nav/MvcTestActivityNavigation.java +++ b/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/nav/MvcTestActivityNavigation.java @@ -32,6 +32,8 @@ public static class Loc { public static final String C = "TestNavigationFragmentC"; public static final String D = "TestNavigationFragmentD"; public static final String E = "TestNavigationFragmentE"; + public static final String F = "TestNavigationFragmentF"; + public static final String G = "TestNavigationFragmentG"; } @Override @@ -47,6 +49,10 @@ protected Class mapNavigationFragment(String locationId) return NavFragmentD.class; case Loc.E : return NavFragmentE.class; + case Loc.F : + return NavFragmentF.class; + case Loc.G : + return NavFragmentG.class; default: return null; } diff --git a/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/nav/NavFragmentE.java b/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/nav/NavFragmentE.java index d64a476..214959e 100644 --- a/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/nav/NavFragmentE.java +++ b/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/nav/NavFragmentE.java @@ -29,6 +29,9 @@ public class NavFragmentE extends MvcFragment { @Inject private ControllerE controllerE; + @Inject + private ControllerG controllerG; + private TextView textView; @Override diff --git a/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/nav/NavFragmentF.java b/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/nav/NavFragmentF.java new file mode 100644 index 0000000..0a1d88c --- /dev/null +++ b/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/nav/NavFragmentF.java @@ -0,0 +1,50 @@ +/* + * Copyright 2015 Kejun Xia + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.shipdream.lib.android.mvc.view.nav; + +import android.os.Bundle; +import android.view.View; +import android.widget.TextView; + +import com.shipdream.lib.android.mvc.view.MvcFragment; +import com.shipdream.lib.android.mvc.view.test.R; + +import javax.inject.Inject; + +public class NavFragmentF extends MvcFragment { + @Inject + private ControllerF controllerF; + + @Inject + private ControllerG controllerG; + + private TextView textView; + + @Override + protected int getLayoutResId() { + return R.layout.fragment_mvc_test_nav_e; + } + + @Override + public void onViewReady(View view, Bundle savedInstanceState, Reason reason) { + super.onViewReady(view, savedInstanceState, reason); + textView = (TextView) view.findViewById(R.id.nav_frag_e_text); + + textView.setText(controllerF.getValue()); + } + +} diff --git a/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/nav/NavFragmentG.java b/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/nav/NavFragmentG.java new file mode 100644 index 0000000..8e8a5f1 --- /dev/null +++ b/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/nav/NavFragmentG.java @@ -0,0 +1,47 @@ +/* + * Copyright 2015 Kejun Xia + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.shipdream.lib.android.mvc.view.nav; + +import android.os.Bundle; +import android.view.View; +import android.widget.TextView; + +import com.shipdream.lib.android.mvc.view.MvcFragment; +import com.shipdream.lib.android.mvc.view.test.R; + +import javax.inject.Inject; + +public class NavFragmentG extends MvcFragment { + @Inject + private ControllerG controllerG; + + private TextView textView; + + @Override + protected int getLayoutResId() { + return R.layout.fragment_mvc_test_nav_e; + } + + @Override + public void onViewReady(View view, Bundle savedInstanceState, Reason reason) { + super.onViewReady(view, savedInstanceState, reason); + textView = (TextView) view.findViewById(R.id.nav_frag_e_text); + + textView.setText(controllerG.getValue()); + } + +} diff --git a/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/nav/internal/ControllerEImpl.java b/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/nav/internal/ControllerEImpl.java index 597700a..9dc25d4 100644 --- a/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/nav/internal/ControllerEImpl.java +++ b/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/nav/internal/ControllerEImpl.java @@ -25,8 +25,6 @@ import javax.inject.Inject; public class ControllerEImpl extends BaseControllerImpl implements ControllerE { - private int value = 0; - @Inject private DisposeCheckerE disposeCheckerE; diff --git a/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/nav/internal/ControllerFImpl.java b/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/nav/internal/ControllerFImpl.java new file mode 100644 index 0000000..169e9ca --- /dev/null +++ b/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/nav/internal/ControllerFImpl.java @@ -0,0 +1,52 @@ +/* + * Copyright 2015 Kejun Xia + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.shipdream.lib.android.mvc.view.nav.internal; + +import android.util.Log; + +import com.shipdream.lib.android.mvc.controller.internal.BaseControllerImpl; +import com.shipdream.lib.android.mvc.view.nav.ControllerF; +import com.shipdream.lib.android.mvc.view.nav.DisposeCheckerF; + +import javax.inject.Inject; + +public class ControllerFImpl extends BaseControllerImpl implements ControllerF { + @Inject + private DisposeCheckerF disposeCheckerF; + + @Override + public void onDisposed() { + Log.i("DisposeCheck", "Checker F disposed"); + disposeCheckerF.onDisposed(); + } + + @Override + protected Class getModelClassType() { + return Model.class; + } + + @Override + public void setValue(String value) { + getModel().value = value; + } + + @Override + public String getValue() { + return getModel().value; + } + +} diff --git a/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/nav/internal/ControllerGImpl.java b/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/nav/internal/ControllerGImpl.java new file mode 100644 index 0000000..19de2dc --- /dev/null +++ b/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/nav/internal/ControllerGImpl.java @@ -0,0 +1,52 @@ +/* + * Copyright 2015 Kejun Xia + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.shipdream.lib.android.mvc.view.nav.internal; + +import android.util.Log; + +import com.shipdream.lib.android.mvc.controller.internal.BaseControllerImpl; +import com.shipdream.lib.android.mvc.view.nav.ControllerG; +import com.shipdream.lib.android.mvc.view.nav.DisposeCheckerG; + +import javax.inject.Inject; + +public class ControllerGImpl extends BaseControllerImpl implements ControllerG { + @Inject + private DisposeCheckerG disposeCheckerG; + + @Override + public void onDisposed() { + Log.i("DisposeCheck", "Checker G disposed"); + disposeCheckerG.onDisposed(); + } + + @Override + protected Class getModelClassType() { + return Model.class; + } + + @Override + public void setValue(String value) { + getModel().value = value; + } + + @Override + public String getValue() { + return getModel().value; + } + +} diff --git a/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/nav/internal/DisposeCheckerFImpl.java b/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/nav/internal/DisposeCheckerFImpl.java new file mode 100644 index 0000000..41fa0ae --- /dev/null +++ b/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/nav/internal/DisposeCheckerFImpl.java @@ -0,0 +1,28 @@ +/* + * Copyright 2015 Kejun Xia + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.shipdream.lib.android.mvc.view.nav.internal; + +import android.util.Log; + +import com.shipdream.lib.android.mvc.view.nav.DisposeCheckerE; + +public class DisposeCheckerFImpl implements DisposeCheckerE { + @Override + public void onDisposed() { + Log.i("DisposeCheck", "Checker F disposed"); + } +} diff --git a/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/nav/internal/DisposeCheckerGImpl.java b/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/nav/internal/DisposeCheckerGImpl.java new file mode 100644 index 0000000..a1f6c7e --- /dev/null +++ b/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/nav/internal/DisposeCheckerGImpl.java @@ -0,0 +1,28 @@ +/* + * Copyright 2015 Kejun Xia + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.shipdream.lib.android.mvc.view.nav.internal; + +import android.util.Log; + +import com.shipdream.lib.android.mvc.view.nav.DisposeCheckerE; + +public class DisposeCheckerGImpl implements DisposeCheckerE { + @Override + public void onDisposed() { + Log.i("DisposeCheck", "Checker G disposed"); + } +} diff --git a/library/android-mvc/src/main/java/com/shipdream/lib/android/mvc/view/MvcActivity.java b/library/android-mvc/src/main/java/com/shipdream/lib/android/mvc/view/MvcActivity.java index 84ffa1b..4a3dbde 100644 --- a/library/android-mvc/src/main/java/com/shipdream/lib/android/mvc/view/MvcActivity.java +++ b/library/android-mvc/src/main/java/com/shipdream/lib/android/mvc/view/MvcActivity.java @@ -397,7 +397,7 @@ public void run() { } @SuppressWarnings("unchecked") - private void performForwardNav(NavigationController.EventC2V.OnLocationForward event) { + private void performForwardNav(final NavigationController.EventC2V.OnLocationForward event) { //FIXME: ChildFragmentManager hack - use getChildFragmentManager when bug is fixed FragmentManager fm = childFragmentManager(); @@ -451,7 +451,7 @@ private void performForwardNav(NavigationController.EventC2V.OnLocationForward e public void run() { //Release reference count to pair the retaining by NavigationControllerImpl // with Injector.getGraph().retainCachedObjectsBeforeNavigation(); - __MvcGraphHelper.releaseCachedItemsAfterNavigation(Injector.getGraph()); + __MvcGraphHelper.releaseCachedItemsAfterNavigation(event, Injector.getGraph()); if (finalLastFrag != null) { finalLastFrag.releaseDependencies(); From 4ea6c5400143a6ee18a74ffb4472bf3b4dbbb209 Mon Sep 17 00:00:00 2001 From: Kejun Xia Date: Sat, 28 Nov 2015 23:16:19 +1100 Subject: [PATCH 02/26] Unit test to guarantee nested injeceted instance are reference counted correctly --- .../poke/TestInjectionReferenceCount2.java | 122 ++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100644 library/poke/src/test/java/com/shipdream/lib/poke/TestInjectionReferenceCount2.java diff --git a/library/poke/src/test/java/com/shipdream/lib/poke/TestInjectionReferenceCount2.java b/library/poke/src/test/java/com/shipdream/lib/poke/TestInjectionReferenceCount2.java new file mode 100644 index 0000000..11f0624 --- /dev/null +++ b/library/poke/src/test/java/com/shipdream/lib/poke/TestInjectionReferenceCount2.java @@ -0,0 +1,122 @@ +/* + * Copyright 2015 Kejun Xia + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.shipdream.lib.poke; + +import com.shipdream.lib.poke.exception.CircularDependenciesException; +import com.shipdream.lib.poke.exception.ProvideException; +import com.shipdream.lib.poke.exception.ProviderConflictException; +import com.shipdream.lib.poke.exception.ProviderMissingException; + +import org.junit.Assert; +import org.junit.Test; + +import javax.inject.Singleton; + +@SuppressWarnings("unchecked") +public class TestInjectionReferenceCount2 extends BaseTestCases { + + interface ControllerA { + + } + + static class ControllerAImpl implements ControllerA { + @MyInject + ManageA manageA; + } + + interface ManageA { + + } + + static class ManageAImpl implements ManageA { + + } + + static class ViewA { + @MyInject + ControllerA controllerA; + } + + static class ViewB { + @MyInject + ControllerA controllerA; + } + + static class ViewC { + @MyInject + ControllerA controllerA; + } + + static class TestComp extends Component { + @Singleton + @Provides + public ControllerA providesController() { + return new ControllerAImpl(); + } + + @Singleton + @Provides + public ManageA providersManageA() { + return new ManageAImpl(); + } + } + + @Test + public void should_release_nested_injection_until_last_instance_dereferenced() throws + ProvideException, ProviderConflictException, CircularDependenciesException, + ProviderMissingException { + ViewA viewA = new ViewA(); + ViewB viewB = new ViewB(); + ViewC viewC = new ViewC(); + + SimpleGraph graph = new SimpleGraph(); + Component component = new TestComp(); + graph.register(component); + + graph.inject(viewA, MyInject.class); + ControllerA controllerAInViewA = viewA.controllerA; + ManageA manageAInViewA = ((ControllerAImpl)viewA.controllerA).manageA; + + graph.inject(viewB, MyInject.class); + ControllerA controllerAInViewB = viewB.controllerA; + ManageA manageAInViewB = ((ControllerAImpl)viewA.controllerA).manageA; + + graph.inject(viewC, MyInject.class); + ControllerA controllerAInViewC = viewB.controllerA; + ManageA manageAInViewC = ((ControllerAImpl)viewA.controllerA).manageA; + + Assert.assertTrue(manageAInViewA == manageAInViewB); + + //ControllerA released first time by viewB but still referenced by viewA + //So controllerA should not be freed so does the manageA held by ControllerA + graph.release(viewC, MyInject.class); + Assert.assertTrue(graph.getProvider(ControllerA.class, null).get() == controllerAInViewA); + Assert.assertTrue(graph.getProvider(ManageA.class, null).get() == manageAInViewA); + Assert.assertEquals(2, graph.getProvider(ManageA.class, null).getReferenceCount()); + + graph.release(viewB, MyInject.class); + Assert.assertTrue(graph.getProvider(ControllerA.class, null).get() == controllerAInViewA); + Assert.assertTrue(graph.getProvider(ManageA.class, null).get() == manageAInViewA); + Assert.assertEquals(1, graph.getProvider(ManageA.class, null).getReferenceCount()); + + graph.release(viewA, MyInject.class); + Assert.assertTrue(graph.getProvider(ControllerA.class, null).get() != controllerAInViewA); + Assert.assertTrue(graph.getProvider(ManageA.class, null).get() != manageAInViewA); + Assert.assertEquals(0, graph.getProvider(ManageA.class, null).getReferenceCount()); + } + +} From d3b4dadeecc9274553eac9ad68986d25b6186788 Mon Sep 17 00:00:00 2001 From: Kejun Xia Date: Sun, 29 Nov 2015 12:17:26 +1100 Subject: [PATCH 03/26] Ui test to cover navigation with complex injection --- .../shipdream/lib/android/mvc/MvcGraph.java | 35 +++-- .../lib/android/mvc/UiThreadRunner.java | 5 + .../lib/android/mvc/__MvcGraphHelper.java | 11 +- .../lib/android/mvc/TestMvcGraphHelper.java | 126 ++++++++++++++++++ .../nav/TestCaseNavigationFromController.java | 116 ++++++++-------- .../view/nav/internal/ControllerEImpl.java | 3 +- .../view/nav/internal/ControllerFImpl.java | 3 +- .../view/nav/internal/ControllerGImpl.java | 3 +- .../nav/internal/DisposeCheckerFImpl.java | 4 +- .../nav/internal/DisposeCheckerGImpl.java | 4 +- .../lib/android/mvc/UiThreadRunnerImpl.java | 31 +++++ .../lib/android/mvc/view/AndroidMvc.java | 2 + .../lib/android/mvc/view/MvcActivity.java | 10 +- .../lib/android/mvc/view/MvcFragment.java | 4 + 14 files changed, 269 insertions(+), 88 deletions(-) create mode 100644 library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/UiThreadRunner.java create mode 100644 library/android-mvc/src/main/java/com/shipdream/lib/android/mvc/UiThreadRunnerImpl.java diff --git a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/MvcGraph.java b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/MvcGraph.java index 744614b..f64a8df 100644 --- a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/MvcGraph.java +++ b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/MvcGraph.java @@ -80,6 +80,7 @@ */ public class MvcGraph { private Logger logger = LoggerFactory.getLogger(getClass()); + static UiThreadRunner uiThreadRunner; ScopeCache singletonScopeCache; DefaultProviderFinder defaultProviderFinder; List stateManagedObjects = new ArrayList<>(); @@ -186,12 +187,17 @@ public void clearOnProviderFreedListeners() { * @param type The type of the injectable instance * @param consumer Consume to use the instance */ - public void use(Class type, Consumer consumer) { - try { - graph.use(type, Inject.class, consumer); - } catch (PokeException e) { - throw new MvcGraphException(e.getMessage(), e); - } + public void use(final Class type, final Consumer consumer) { +// uiThreadRunner.runOnUiThread(new Runnable() { +// @Override +// public void run() { + try { + graph.use(type, Inject.class, consumer); + } catch (PokeException e) { + throw new MvcGraphException(e.getMessage(), e); + } +// } +// }); } /** @@ -296,12 +302,17 @@ public void consume(Os instance) { * @param consumer Consume to use the instance * @throws MvcGraphException throw when there are exceptions during the consumption of the instance */ - public void use(Class type, Annotation qualifier, Consumer consumer) { - try { - graph.use(type, qualifier, Inject.class, consumer); - } catch (PokeException e) { - throw new MvcGraphException(e.getMessage(), e); - } + public void use(final Class type, final Annotation qualifier, final Consumer consumer) { +// uiThreadRunner.runOnUiThread(new Runnable() { +// @Override +// public void run() { + try { + graph.use(type, qualifier, Inject.class, consumer); + } catch (PokeException e) { + throw new MvcGraphException(e.getMessage(), e); + } +// } +// }); } /** diff --git a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/UiThreadRunner.java b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/UiThreadRunner.java new file mode 100644 index 0000000..1eabfc1 --- /dev/null +++ b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/UiThreadRunner.java @@ -0,0 +1,5 @@ +package com.shipdream.lib.android.mvc; + +interface UiThreadRunner { + void runOnUiThread(Runnable runnable); +} diff --git a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/__MvcGraphHelper.java b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/__MvcGraphHelper.java index e4384c8..e859b4f 100644 --- a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/__MvcGraphHelper.java +++ b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/__MvcGraphHelper.java @@ -43,12 +43,15 @@ public static void releaseCachedItemsAfterNavigation( //Release all cached items after the fragment navigated to is ready to show. Collection providers = retainedProviders.get(navigationEvent); - for (Provider provider : providers) { - if (provider != null) { - provider.release(); + if (providers != null) { + for (Provider provider : providers) { + if (provider != null) { + provider.release(); + } } + providers.clear(); } - providers.clear(); + retainedProviders.remove(navigationEvent); } diff --git a/library/android-mvc-controller/src/test/java/com/shipdream/lib/android/mvc/TestMvcGraphHelper.java b/library/android-mvc-controller/src/test/java/com/shipdream/lib/android/mvc/TestMvcGraphHelper.java index da4f773..2cda4e6 100644 --- a/library/android-mvc-controller/src/test/java/com/shipdream/lib/android/mvc/TestMvcGraphHelper.java +++ b/library/android-mvc-controller/src/test/java/com/shipdream/lib/android/mvc/TestMvcGraphHelper.java @@ -1,6 +1,12 @@ package com.shipdream.lib.android.mvc; import com.shipdream.lib.android.mvc.controller.NavigationController; +import com.shipdream.lib.poke.Component; +import com.shipdream.lib.poke.Provides; +import com.shipdream.lib.poke.exception.CircularDependenciesException; +import com.shipdream.lib.poke.exception.ProvideException; +import com.shipdream.lib.poke.exception.ProviderConflictException; +import com.shipdream.lib.poke.exception.ProviderMissingException; import org.junit.After; import org.junit.Assert; @@ -9,8 +15,11 @@ import java.util.concurrent.ExecutorService; import javax.inject.Inject; +import javax.inject.Singleton; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; public class TestMvcGraphHelper { @After @@ -53,4 +62,121 @@ protected ExecutorService createExecutorService() { Assert.assertEquals(initSize, __MvcGraphHelper.getAllCachedInstances(Injector.getGraph()).size()); } + + interface ControllerA extends Disposable { + + } + + static class ControllerAImpl implements ControllerA { + @Inject + ManageA manageA; + + /** + * Execute onDisposed logic of given object + */ + @Override + public void onDisposed() { + + } + } + + interface ManageA extends Disposable { + + } + + ManageA manageAMock; + + static class ViewA { + @Inject + ControllerA controllerA; + } + + static class ViewB { + @Inject + ControllerA controllerA; + } + + static class ViewC { + @Inject + ControllerA controllerA; + } + + static class TestComp extends Component { + TestMvcGraphHelper testMvcGraphHelper; + + /** + * Constructor with a stand alone scope cache used only by this component + */ + public TestComp(TestMvcGraphHelper testMvcGraphHelper) { + this.testMvcGraphHelper = testMvcGraphHelper; + } + + @Singleton + @Provides + public ControllerA providesController() { + return new ControllerAImpl(); + } + + @Singleton + @Provides + public ManageA providersManageA() { + testMvcGraphHelper.manageAMock = mock(ManageA.class); + return testMvcGraphHelper.manageAMock; + } + } + + @Test + public void should_release_nested_injection_until_last_instance_dereferenced() throws + ProvideException, ProviderConflictException, CircularDependenciesException, + ProviderMissingException { + //Prepare graph + MvcGraph.BaseDependencies baseDependencies = new MvcGraph.BaseDependencies() { + @Override + protected ExecutorService createExecutorService() { + return mock(ExecutorService.class); + } + }; + + Injector.configGraph(baseDependencies); + + ViewA viewA = new ViewA(); + ViewB viewB = new ViewB(); + ViewC viewC = new ViewC(); + + Component component = new TestComp(this); + Injector.getGraph().register(component); + + Injector.getGraph().inject(viewA); + ControllerA controllerAInViewA = viewA.controllerA; + ManageA manageAInViewA = ((ControllerAImpl)viewA.controllerA).manageA; + + NavigationController.EventC2V.OnLocationForward navEventB = mock( + NavigationController.EventC2V.OnLocationForward.class); + __MvcGraphHelper.retainCachedObjectsBeforeNavigation(navEventB, Injector.getGraph()); + + Injector.getGraph().inject(viewB); + ControllerA controllerAInViewB = viewB.controllerA; + ManageA manageAInViewB = ((ControllerAImpl)viewA.controllerA).manageA; + + Injector.getGraph().release(viewA); + __MvcGraphHelper.releaseCachedItemsAfterNavigation(navEventB, Injector.getGraph()); + verify(manageAMock, times(0)).onDisposed(); + + NavigationController.EventC2V.OnLocationForward navEventC = mock( + NavigationController.EventC2V.OnLocationForward.class); + __MvcGraphHelper.retainCachedObjectsBeforeNavigation(navEventC, Injector.getGraph()); + + Injector.getGraph().release(viewB); + __MvcGraphHelper.releaseCachedItemsAfterNavigation(navEventC, Injector.getGraph()); + verify(manageAMock, times(0)).onDisposed(); + + Injector.getGraph().inject(viewC); + ControllerA controllerAInViewC = viewB.controllerA; + ManageA manageAInViewC = ((ControllerAImpl)viewA.controllerA).manageA; + + Assert.assertTrue(manageAInViewA == manageAInViewB); + Assert.assertTrue(manageAInViewA == manageAInViewC); + + } + } diff --git a/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/nav/TestCaseNavigationFromController.java b/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/nav/TestCaseNavigationFromController.java index bffad24..c1e16ea 100644 --- a/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/nav/TestCaseNavigationFromController.java +++ b/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/nav/TestCaseNavigationFromController.java @@ -22,15 +22,14 @@ import com.shipdream.lib.android.mvc.view.BaseTestCase; import com.shipdream.lib.poke.Component; import com.shipdream.lib.poke.Consumer; -import com.shipdream.lib.poke.Provides; import com.shipdream.lib.poke.ScopeCache; import org.junit.Test; +import org.slf4j.LoggerFactory; import java.util.Random; import javax.inject.Inject; -import javax.inject.Singleton; import static android.support.test.espresso.Espresso.onView; import static android.support.test.espresso.assertion.ViewAssertions.matches; @@ -66,23 +65,23 @@ public static class Comp extends Component{ super(scopeCache); } - @Singleton - @Provides - public DisposeCheckerE providesDisposeCheckerE() { - return testCaseNavigation.disposeCheckerEMock; - } - - @Singleton - @Provides - public DisposeCheckerF providesDisposeCheckerF() { - return testCaseNavigation.disposeCheckerFMock; - } - - @Singleton - @Provides - public DisposeCheckerG providesDisposeCheckerG() { - return testCaseNavigation.disposeCheckerGMock; - } +// @Singleton +// @Provides +// public DisposeCheckerE providesDisposeCheckerE() { +// return testCaseNavigation.disposeCheckerEMock; +// } +// +// @Singleton +// @Provides +// public DisposeCheckerF providesDisposeCheckerF() { +// return testCaseNavigation.disposeCheckerFMock; +// } +// +// @Singleton +// @Provides +// public DisposeCheckerG providesDisposeCheckerG() { +// return testCaseNavigation.disposeCheckerGMock; +// } } @Override @@ -139,66 +138,67 @@ public void should_release_injected_object_by_chained_navigation_controller_navi final String valF = "ValueF = " + new Random().nextInt(); final String valG = "ValueG = " + new Random().nextInt(); -// Injector.getGraph().use(ControllerE.class, new Consumer() { -// @Override -// public void consume(ControllerE instance) { -// instance.setValue(valE); -// navigationController.navigateTo(this, MvcTestActivityNavigation.Loc.E); -// -// instance.setValue(valG); -// navigationController.navigateTo(this, MvcTestActivityNavigation.Loc.G); -// } -// }); -// -// Injector.getGraph().use(ControllerF.class, new Consumer() { -// @Override -// public void consume(ControllerF instance) { -// instance.setValue(valF); -// navigationController.navigateTo(this, MvcTestActivityNavigation.Loc.F); -// } -// }); - - Injector.getGraph().use(ControllerG.class, new Consumer() { + getActivity().runOnUiThread(new Runnable() { @Override - public void consume(ControllerG instance) { - instance.setValue(valG); - navigationController.navigateTo(this, MvcTestActivityNavigation.Loc.E); - navigationController.navigateTo(this, MvcTestActivityNavigation.Loc.F); - navigationController.navigateTo(this, MvcTestActivityNavigation.Loc.G); + public void run() { + Injector.getGraph().use(ControllerE.class, new Consumer() { + @Override + public void consume(ControllerE instance) { + instance.setValue(valE); + navigationController.navigateTo(this, MvcTestActivityNavigation.Loc.E); + } + }); + + Injector.getGraph().use(ControllerF.class, new Consumer() { + @Override + public void consume(ControllerF instance) { + instance.setValue(valF); + navigationController.navigateTo(this, MvcTestActivityNavigation.Loc.F); + } + }); + + Injector.getGraph().use(ControllerG.class, new Consumer() { + @Override + public void consume(ControllerG instance) { + instance.setValue(valG); + navigationController.navigateTo(this, MvcTestActivityNavigation.Loc.G); + } + }); } }); - waitTest(1500); + waitTest(); //The value set to controller e in Injector.getGraph().use should be retained during the //navigation onView(withText(valG)).check(matches(isDisplayed())); - verify(disposeCheckerEMock, times(1)).onDisposed(); - verify(disposeCheckerFMock, times(1)).onDisposed(); + verify(disposeCheckerEMock, times(0)).onDisposed(); + verify(disposeCheckerFMock, times(0)).onDisposed(); verify(disposeCheckerGMock, times(0)).onDisposed(); resetDisposeCheckers(); navigationController.navigateBack(this); - waitTest(1500); -// onView(withText(valF)).check(matches(isDisplayed())); + onView(withText(valF)).check(matches(isDisplayed())); + waitTest(); verify(disposeCheckerEMock, times(0)).onDisposed(); verify(disposeCheckerFMock, times(0)).onDisposed(); verify(disposeCheckerGMock, times(0)).onDisposed(); resetDisposeCheckers(); navigationController.navigateBack(this); - waitTest(1500); -// onView(withText(valE)).check(matches(isDisplayed())); + onView(withText(valE)).check(matches(isDisplayed())); + waitTest(1000); verify(disposeCheckerEMock, times(0)).onDisposed(); - verify(disposeCheckerFMock, times(1)).onDisposed(); - verify(disposeCheckerGMock, times(0)).onDisposed(); - resetDisposeCheckers(); +// verify(disposeCheckerFMock, times(1)).onDisposed(); +// verify(disposeCheckerGMock, times(1)).onDisposed(); +// resetDisposeCheckers(); + LoggerFactory.getLogger(getClass()).debug("DisposeCheck nav back"); navigationController.navigateBack(this); - waitTest(1500); - verify(disposeCheckerEMock, times(1)).onDisposed(); - verify(disposeCheckerFMock, times(0)).onDisposed(); - verify(disposeCheckerGMock, times(1)).onDisposed(); + waitTest(1000); +// verify(disposeCheckerEMock, times(1)).onDisposed(); +// verify(disposeCheckerFMock, times(0)).onDisposed(); +// verify(disposeCheckerGMock, times(0)).onDisposed(); } private void resetDisposeCheckers() { diff --git a/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/nav/internal/ControllerEImpl.java b/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/nav/internal/ControllerEImpl.java index 9dc25d4..52c2697 100644 --- a/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/nav/internal/ControllerEImpl.java +++ b/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/nav/internal/ControllerEImpl.java @@ -30,8 +30,7 @@ public class ControllerEImpl extends BaseControllerImpl imple @Override public void onDisposed() { - Log.i("DisposeCheck", "Checker E disposed"); - disposeCheckerE.onDisposed(); + Log.i("DisposeCheck", "Controller E disposed"); } @Override diff --git a/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/nav/internal/ControllerFImpl.java b/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/nav/internal/ControllerFImpl.java index 169e9ca..8211db6 100644 --- a/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/nav/internal/ControllerFImpl.java +++ b/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/nav/internal/ControllerFImpl.java @@ -30,8 +30,7 @@ public class ControllerFImpl extends BaseControllerImpl imple @Override public void onDisposed() { - Log.i("DisposeCheck", "Checker F disposed"); - disposeCheckerF.onDisposed(); + Log.i("DisposeCheck", "Controller F disposed"); } @Override diff --git a/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/nav/internal/ControllerGImpl.java b/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/nav/internal/ControllerGImpl.java index 19de2dc..6bbc378 100644 --- a/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/nav/internal/ControllerGImpl.java +++ b/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/nav/internal/ControllerGImpl.java @@ -30,8 +30,7 @@ public class ControllerGImpl extends BaseControllerImpl imple @Override public void onDisposed() { - Log.i("DisposeCheck", "Checker G disposed"); - disposeCheckerG.onDisposed(); + Log.i("DisposeCheck", "Controller G disposed"); } @Override diff --git a/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/nav/internal/DisposeCheckerFImpl.java b/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/nav/internal/DisposeCheckerFImpl.java index 41fa0ae..82c6f82 100644 --- a/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/nav/internal/DisposeCheckerFImpl.java +++ b/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/nav/internal/DisposeCheckerFImpl.java @@ -18,9 +18,9 @@ import android.util.Log; -import com.shipdream.lib.android.mvc.view.nav.DisposeCheckerE; +import com.shipdream.lib.android.mvc.view.nav.DisposeCheckerF; -public class DisposeCheckerFImpl implements DisposeCheckerE { +public class DisposeCheckerFImpl implements DisposeCheckerF { @Override public void onDisposed() { Log.i("DisposeCheck", "Checker F disposed"); diff --git a/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/nav/internal/DisposeCheckerGImpl.java b/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/nav/internal/DisposeCheckerGImpl.java index a1f6c7e..248987d 100644 --- a/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/nav/internal/DisposeCheckerGImpl.java +++ b/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/nav/internal/DisposeCheckerGImpl.java @@ -18,9 +18,9 @@ import android.util.Log; -import com.shipdream.lib.android.mvc.view.nav.DisposeCheckerE; +import com.shipdream.lib.android.mvc.view.nav.DisposeCheckerG; -public class DisposeCheckerGImpl implements DisposeCheckerE { +public class DisposeCheckerGImpl implements DisposeCheckerG { @Override public void onDisposed() { Log.i("DisposeCheck", "Checker G disposed"); diff --git a/library/android-mvc/src/main/java/com/shipdream/lib/android/mvc/UiThreadRunnerImpl.java b/library/android-mvc/src/main/java/com/shipdream/lib/android/mvc/UiThreadRunnerImpl.java new file mode 100644 index 0000000..5eedb75 --- /dev/null +++ b/library/android-mvc/src/main/java/com/shipdream/lib/android/mvc/UiThreadRunnerImpl.java @@ -0,0 +1,31 @@ +package com.shipdream.lib.android.mvc; + +import android.os.Handler; +import android.os.Looper; + +/** + * Internal use. + */ +public class UiThreadRunnerImpl implements UiThreadRunner { + private Handler handler; + + /** + * Internal use. + */ + public static void init() { + MvcGraph.uiThreadRunner = new UiThreadRunnerImpl(); + } + + @Override + public void runOnUiThread(Runnable runnable) { + if (Looper.getMainLooper().getThread() == Thread.currentThread()) { + runnable.run(); + } else { + //Android handler is presented, posting to the main thread on Android. + if (handler == null) { + handler = new Handler(Looper.getMainLooper()); + } + handler.post(runnable); + } + } +} diff --git a/library/android-mvc/src/main/java/com/shipdream/lib/android/mvc/view/AndroidMvc.java b/library/android-mvc/src/main/java/com/shipdream/lib/android/mvc/view/AndroidMvc.java index d7d15b7..b763fd6 100644 --- a/library/android-mvc/src/main/java/com/shipdream/lib/android/mvc/view/AndroidMvc.java +++ b/library/android-mvc/src/main/java/com/shipdream/lib/android/mvc/view/AndroidMvc.java @@ -21,6 +21,7 @@ import com.shipdream.lib.android.mvc.Injector; import com.shipdream.lib.android.mvc.MvcGraph; +import com.shipdream.lib.android.mvc.UiThreadRunnerImpl; import com.shipdream.lib.android.mvc.controller.internal.AndroidPosterImpl; import com.shipdream.lib.android.mvc.event.bus.EventBus; import com.shipdream.lib.android.mvc.event.bus.internal.EventBusImpl; @@ -80,6 +81,7 @@ public EventBus providesIEventBusC2V() { Injector.getGraph().register(new ViewComponent()); AndroidPosterImpl.init(); + UiThreadRunnerImpl.init(); } private AndroidMvc() { diff --git a/library/android-mvc/src/main/java/com/shipdream/lib/android/mvc/view/MvcActivity.java b/library/android-mvc/src/main/java/com/shipdream/lib/android/mvc/view/MvcActivity.java index 4a3dbde..e752a5f 100644 --- a/library/android-mvc/src/main/java/com/shipdream/lib/android/mvc/view/MvcActivity.java +++ b/library/android-mvc/src/main/java/com/shipdream/lib/android/mvc/view/MvcActivity.java @@ -420,11 +420,11 @@ private void performForwardNav(final NavigationController.EventC2V.OnLocationFor } MvcFragment lastFrag = null; + if (event.getLastValue() != null && event.getLastValue().getLocationId() != null) { + lastFrag = (MvcFragment) fm.findFragmentByTag( + getFragmentTag(event.getLastValue().getLocationId())); + } if (!event.isClearHistory()) { - if (event.getLastValue() != null && event.getLastValue().getLocationId() != null) { - lastFrag = (MvcFragment) fm.findFragmentByTag( - getFragmentTag(event.getLastValue().getLocationId())); - } if (lastFrag != null) { lastFrag.onPushingToBackStack(); } @@ -457,6 +457,8 @@ public void run() { finalLastFrag.releaseDependencies(); } + logger.trace("Fragment ready: " + fragment.getClass().getSimpleName()); + fragment.unregisterOnViewReadyListener(this); } }); diff --git a/library/android-mvc/src/main/java/com/shipdream/lib/android/mvc/view/MvcFragment.java b/library/android-mvc/src/main/java/com/shipdream/lib/android/mvc/view/MvcFragment.java index e6bca32..c826990 100644 --- a/library/android-mvc/src/main/java/com/shipdream/lib/android/mvc/view/MvcFragment.java +++ b/library/android-mvc/src/main/java/com/shipdream/lib/android/mvc/view/MvcFragment.java @@ -25,6 +25,8 @@ import com.shipdream.lib.android.mvc.StateManaged; import com.shipdream.lib.android.mvc.event.BaseEventV2V; +import org.slf4j.LoggerFactory; + import java.util.concurrent.CopyOnWriteArrayList; import javax.inject.Inject; @@ -180,6 +182,8 @@ void releaseDependencies() { if (dependenciesInjected) { AndroidMvc.graph().release(this); dependenciesInjected = false; + + LoggerFactory.getLogger(getClass()).trace("Fragment release: " + getClass().getSimpleName()); } } From c157d19c9a2229094530acb2006124d8544d4137 Mon Sep 17 00:00:00 2001 From: Kejun Xia Date: Sun, 29 Nov 2015 23:47:03 +1100 Subject: [PATCH 04/26] Investigating back nav injection issue --- .../lib/android/mvc/__MvcGraphHelper.java | 47 +++++------ .../mvc/controller/NavigationController.java | 10 ++- .../internal/NavigationControllerImpl.java | 9 ++- .../lib/android/mvc/TestMvcGraphHelper.java | 13 +-- .../mvc/view/nav/TestCaseNavigationBasic.java | 17 +++- .../nav/TestCaseNavigationFromController.java | 80 +++++++++++++------ .../lib/android/mvc/view/MvcActivity.java | 33 +++++++- .../lib/android/mvc/view/MvcFragment.java | 5 +- 8 files changed, 154 insertions(+), 60 deletions(-) diff --git a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/__MvcGraphHelper.java b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/__MvcGraphHelper.java index e859b4f..79f1e7c 100644 --- a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/__MvcGraphHelper.java +++ b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/__MvcGraphHelper.java @@ -14,45 +14,46 @@ * Helper class to work with MvcGraph. Internal use only. Don't use it in your app. */ public class __MvcGraphHelper { - private static Map> retainedProviders = new HashMap<>(); + private static Map> retainedProviders = new HashMap<>(); /** * Internal use. Don't call it in app directly. */ public static void retainCachedObjectsBeforeNavigation( - NavigationController.EventC2V.OnLocationForward navigationEvent, MvcGraph mvcGraph) { - //Retain all cached items before navigation. - Collection cachedItems = mvcGraph.singletonScopeCache.getCachedItems(); + final NavigationController.EventC2V.OnLocationChanged navigationEvent, + final MvcGraph mvcGraph) { + //Retain all cached items before navigation. + Collection cachedItems = mvcGraph.singletonScopeCache.getCachedItems(); - List providers = new ArrayList<>(); - retainedProviders.put(navigationEvent, providers); - for (ScopeCache.CachedItem cachedItem : cachedItems) { - Provider provider = cachedItem.getProvider(); - if (provider != null) { - providers.add(provider); - provider.retain(); + List providers = new ArrayList<>(); + retainedProviders.put(navigationEvent, providers); + for (ScopeCache.CachedItem cachedItem : cachedItems) { + Provider provider = cachedItem.getProvider(); + if (provider != null) { + providers.add(provider); + provider.retain(); + } } - } } /** * Internal use. Don't call it in app directly. */ public static void releaseCachedItemsAfterNavigation( - NavigationController.EventC2V.OnLocationForward navigationEvent, MvcGraph mvcGraph) { - - //Release all cached items after the fragment navigated to is ready to show. - Collection providers = retainedProviders.get(navigationEvent); - if (providers != null) { - for (Provider provider : providers) { - if (provider != null) { - provider.release(); + final NavigationController.EventC2V.OnLocationChanged navigationEvent, + final MvcGraph mvcGraph) { + //Release all cached items after the fragment navigated to is ready to show. + Collection providers = retainedProviders.get(navigationEvent); + if (providers != null) { + for (Provider provider : providers) { + if (provider != null) { + provider.release(); + } } + providers.clear(); } - providers.clear(); - } - retainedProviders.remove(navigationEvent); + retainedProviders.remove(navigationEvent); } /** diff --git a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/NavigationController.java b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/NavigationController.java index 6caa270..c955073 100644 --- a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/NavigationController.java +++ b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/NavigationController.java @@ -104,10 +104,16 @@ public interface NavigationController extends BaseController { + public OnLocationChanged(Object sender, NavLocation lastValue, NavLocation currentValue) { + super(sender, lastValue, currentValue); + } + } + /** * Event to notify views navigation will move forward. */ - class OnLocationForward extends ValueChangeEventC2V { + class OnLocationForward extends OnLocationChanged { private boolean clearHistory; private NavLocation locationWhereHistoryClearedUpTo; @@ -147,7 +153,7 @@ public NavLocation getLocationWhereHistoryClearedUpTo() { /** * Event to notify views navigation will move backward. */ - class OnLocationBack extends ValueChangeEventC2V { + class OnLocationBack extends OnLocationChanged { private boolean fastRewind; public OnLocationBack(Object sender, NavLocation lastValue, NavLocation currentValue, diff --git a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/internal/NavigationControllerImpl.java b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/internal/NavigationControllerImpl.java index e335d94..39fb1b5 100644 --- a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/internal/NavigationControllerImpl.java +++ b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/internal/NavigationControllerImpl.java @@ -131,7 +131,14 @@ public void navigateBack(Object sender) { NavLocation previousLoc = currentLoc.getPreviousLocation(); getModel().setCurrentLocation(previousLoc); - postC2VEvent(new EventC2V.OnLocationBack(sender, currentLoc, previousLoc, false)); + + EventC2V.OnLocationBack navEvent = new EventC2V.OnLocationBack(sender, currentLoc, previousLoc, false); + + if (previousLoc != null ) { + __MvcGraphHelper.retainCachedObjectsBeforeNavigation(navEvent, Injector.getGraph()); + } + + postC2VEvent(navEvent); logger.trace("Nav Controller: Backward: {} -> {}", currentLoc.getLocationId(), previousLoc == null ? "null" : previousLoc.getLocationId()); diff --git a/library/android-mvc-controller/src/test/java/com/shipdream/lib/android/mvc/TestMvcGraphHelper.java b/library/android-mvc-controller/src/test/java/com/shipdream/lib/android/mvc/TestMvcGraphHelper.java index 2cda4e6..8e7e26f 100644 --- a/library/android-mvc-controller/src/test/java/com/shipdream/lib/android/mvc/TestMvcGraphHelper.java +++ b/library/android-mvc-controller/src/test/java/com/shipdream/lib/android/mvc/TestMvcGraphHelper.java @@ -150,6 +150,7 @@ protected ExecutorService createExecutorService() { ControllerA controllerAInViewA = viewA.controllerA; ManageA manageAInViewA = ((ControllerAImpl)viewA.controllerA).manageA; + //Simulate navigate to viewB NavigationController.EventC2V.OnLocationForward navEventB = mock( NavigationController.EventC2V.OnLocationForward.class); __MvcGraphHelper.retainCachedObjectsBeforeNavigation(navEventB, Injector.getGraph()); @@ -158,25 +159,27 @@ protected ExecutorService createExecutorService() { ControllerA controllerAInViewB = viewB.controllerA; ManageA manageAInViewB = ((ControllerAImpl)viewA.controllerA).manageA; + //release viewA Injector.getGraph().release(viewA); __MvcGraphHelper.releaseCachedItemsAfterNavigation(navEventB, Injector.getGraph()); verify(manageAMock, times(0)).onDisposed(); + //Simulate navigate to viewC NavigationController.EventC2V.OnLocationForward navEventC = mock( NavigationController.EventC2V.OnLocationForward.class); __MvcGraphHelper.retainCachedObjectsBeforeNavigation(navEventC, Injector.getGraph()); - Injector.getGraph().release(viewB); - __MvcGraphHelper.releaseCachedItemsAfterNavigation(navEventC, Injector.getGraph()); - verify(manageAMock, times(0)).onDisposed(); - Injector.getGraph().inject(viewC); ControllerA controllerAInViewC = viewB.controllerA; ManageA manageAInViewC = ((ControllerAImpl)viewA.controllerA).manageA; + //release viewB + Injector.getGraph().release(viewB); + __MvcGraphHelper.releaseCachedItemsAfterNavigation(navEventC, Injector.getGraph()); + verify(manageAMock, times(0)).onDisposed(); + Assert.assertTrue(manageAInViewA == manageAInViewB); Assert.assertTrue(manageAInViewA == manageAInViewC); - } } diff --git a/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/nav/TestCaseNavigationBasic.java b/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/nav/TestCaseNavigationBasic.java index 933d9da..d5e2620 100644 --- a/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/nav/TestCaseNavigationBasic.java +++ b/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/nav/TestCaseNavigationBasic.java @@ -28,6 +28,10 @@ import org.junit.Assert; import org.junit.Test; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import javax.inject.Inject; import javax.inject.Singleton; @@ -36,12 +40,16 @@ import static android.support.test.espresso.assertion.ViewAssertions.matches; import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; import static android.support.test.espresso.matcher.ViewMatchers.withText; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; public class TestCaseNavigationBasic extends BaseTestCase { + private Logger logger = LoggerFactory.getLogger(getClass()); + @Inject private AnotherController anotherController; @@ -97,6 +105,13 @@ protected void injectDependencies(ScopeCache mvcSingletonCache) { super.injectDependencies(mvcSingletonCache); disposeCheckerAMock = mock(DisposeCheckerA.class); + doAnswer(new Answer() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + logger.debug("Dispose checker A"); + return null; + } + }).when(disposeCheckerAMock).onDisposed(); disposeCheckerBMock = mock(DisposeCheckerB.class); disposeCheckerCMock = mock(DisposeCheckerC.class); disposeCheckerDMock = mock(DisposeCheckerD.class); @@ -141,7 +156,7 @@ public void testShouldReleaseInjectionsAfterFragmentsArePoppedOut() throws Throw verify(disposeCheckerDMock, times(0)).onDisposed(); navigationController.navigateBack(this); waitTest(); - waitTest(1000); + waitTest(2000); verify(disposeCheckerAMock, times(1)).onDisposed(); verify(disposeCheckerBMock, times(1)).onDisposed(); verify(disposeCheckerCMock, times(1)).onDisposed(); diff --git a/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/nav/TestCaseNavigationFromController.java b/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/nav/TestCaseNavigationFromController.java index c1e16ea..42ebeed 100644 --- a/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/nav/TestCaseNavigationFromController.java +++ b/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/nav/TestCaseNavigationFromController.java @@ -22,25 +22,33 @@ import com.shipdream.lib.android.mvc.view.BaseTestCase; import com.shipdream.lib.poke.Component; import com.shipdream.lib.poke.Consumer; +import com.shipdream.lib.poke.Provides; import com.shipdream.lib.poke.ScopeCache; import org.junit.Test; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Random; import javax.inject.Inject; +import javax.inject.Singleton; import static android.support.test.espresso.Espresso.onView; import static android.support.test.espresso.assertion.ViewAssertions.matches; import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; import static android.support.test.espresso.matcher.ViewMatchers.withText; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; public class TestCaseNavigationFromController extends BaseTestCase { + private Logger logger = LoggerFactory.getLogger(getClass()); + @Inject private NavigationController navigationController; @@ -65,23 +73,23 @@ public static class Comp extends Component{ super(scopeCache); } -// @Singleton -// @Provides -// public DisposeCheckerE providesDisposeCheckerE() { -// return testCaseNavigation.disposeCheckerEMock; -// } -// -// @Singleton -// @Provides -// public DisposeCheckerF providesDisposeCheckerF() { -// return testCaseNavigation.disposeCheckerFMock; -// } -// -// @Singleton -// @Provides -// public DisposeCheckerG providesDisposeCheckerG() { -// return testCaseNavigation.disposeCheckerGMock; -// } + @Singleton + @Provides + public DisposeCheckerE providesDisposeCheckerE() { + return testCaseNavigation.disposeCheckerEMock; + } + + @Singleton + @Provides + public DisposeCheckerF providesDisposeCheckerF() { + return testCaseNavigation.disposeCheckerFMock; + } + + @Singleton + @Provides + public DisposeCheckerG providesDisposeCheckerG() { + return testCaseNavigation.disposeCheckerGMock; + } } @Override @@ -89,8 +97,29 @@ protected void injectDependencies(ScopeCache mvcSingletonCache) { super.injectDependencies(mvcSingletonCache); disposeCheckerEMock = mock(DisposeCheckerE.class); + doAnswer(new Answer() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + logger.debug("Dispose checker E"); + return null; + } + }).when(disposeCheckerEMock).onDisposed(); disposeCheckerFMock = mock(DisposeCheckerF.class); + doAnswer(new Answer() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + logger.debug("Dispose checker F"); + return null; + } + }).when(disposeCheckerFMock).onDisposed(); disposeCheckerGMock = mock(DisposeCheckerG.class); + doAnswer(new Answer() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + logger.debug("Dispose checker G"); + return null; + } + }).when(disposeCheckerGMock).onDisposed(); comp = new Comp(mvcSingletonCache); comp.testCaseNavigation = this; AndroidMvc.graph().register(comp); @@ -187,18 +216,19 @@ public void consume(ControllerG instance) { navigationController.navigateBack(this); onView(withText(valE)).check(matches(isDisplayed())); - waitTest(1000); + waitTest(); verify(disposeCheckerEMock, times(0)).onDisposed(); -// verify(disposeCheckerFMock, times(1)).onDisposed(); -// verify(disposeCheckerGMock, times(1)).onDisposed(); -// resetDisposeCheckers(); + LoggerFactory.getLogger(getClass()).debug("DisposeCheck, checking F"); + verify(disposeCheckerFMock, times(1)).onDisposed(); + //__MvcGraphHelper retaining all cache is dangerous. Try to only retain relevant injected instances. + verify(disposeCheckerGMock, times(0)).onDisposed(); + resetDisposeCheckers(); LoggerFactory.getLogger(getClass()).debug("DisposeCheck nav back"); navigationController.navigateBack(this); - waitTest(1000); -// verify(disposeCheckerEMock, times(1)).onDisposed(); -// verify(disposeCheckerFMock, times(0)).onDisposed(); -// verify(disposeCheckerGMock, times(0)).onDisposed(); + verify(disposeCheckerEMock, times(1)).onDisposed(); + verify(disposeCheckerFMock, times(0)).onDisposed(); + verify(disposeCheckerGMock, times(1)).onDisposed(); } private void resetDisposeCheckers() { diff --git a/library/android-mvc/src/main/java/com/shipdream/lib/android/mvc/view/MvcActivity.java b/library/android-mvc/src/main/java/com/shipdream/lib/android/mvc/view/MvcActivity.java index e752a5f..8bf4c5f 100644 --- a/library/android-mvc/src/main/java/com/shipdream/lib/android/mvc/view/MvcActivity.java +++ b/library/android-mvc/src/main/java/com/shipdream/lib/android/mvc/view/MvcActivity.java @@ -485,7 +485,7 @@ public void run() { } } - private void performBackNav(NavigationController.EventC2V.OnLocationBack event) { + private void performBackNav(final NavigationController.EventC2V.OnLocationBack event) { NavLocation currentLoc = event.getCurrentValue(); if (currentLoc == null) { MvcActivity mvcActivity = ((MvcActivity) getActivity()); @@ -510,6 +510,33 @@ private void performBackNav(NavigationController.EventC2V.OnLocationBack event) } } } + + MvcFragment lastFrag = null; + if (event.getLastValue() != null && event.getLastValue().getLocationId() != null) { + lastFrag = (MvcFragment) fm.findFragmentByTag( + getFragmentTag(event.getLastValue().getLocationId())); + } + final MvcFragment finalLastFrag = lastFrag; + + if (finalLastFrag != null) { + finalLastFrag.selfRelease = false; + } + + currentFrag.registerOnViewReadyListener(new Runnable() { + @Override + public void run() { + //Release reference count to pair the retaining by NavigationControllerImpl + // with Injector.getGraph().retainCachedObjectsBeforeNavigation(); + __MvcGraphHelper.releaseCachedItemsAfterNavigation(event, Injector.getGraph()); + + if (finalLastFrag != null) { + finalLastFrag.releaseDependencies(); + finalLastFrag.selfRelease = true; + } + + currentFrag.unregisterOnViewReadyListener(this); + } + }); } if (event.isFastRewind()) { @@ -544,7 +571,9 @@ private void performBackNav(NavigationController.EventC2V.OnLocationBack event) } } else { fm.popBackStack(); - logger.trace("Navigation back: On step back to location {}", currentLoc.getLocationId()); + logger.trace("Navigation back: On step back from {} to location {}", + event.getLastValue() != null ? event.getLastValue().getLocationId() : null, + currentLoc.getLocationId()); } } } diff --git a/library/android-mvc/src/main/java/com/shipdream/lib/android/mvc/view/MvcFragment.java b/library/android-mvc/src/main/java/com/shipdream/lib/android/mvc/view/MvcFragment.java index c826990..5c169ee 100644 --- a/library/android-mvc/src/main/java/com/shipdream/lib/android/mvc/view/MvcFragment.java +++ b/library/android-mvc/src/main/java/com/shipdream/lib/android/mvc/view/MvcFragment.java @@ -146,6 +146,7 @@ public String toString() { private boolean dependenciesInjected = false; boolean isStateManagedByRootDelegateFragment = false; + boolean selfRelease = true; /** * @return orientation before last orientation change. @@ -395,7 +396,9 @@ public void onDestroyView() { @Override public void onDestroy() { super.onDestroy(); - releaseDependencies(); + if (selfRelease) { + releaseDependencies(); + } eventRegister.onDestroy(); eventRegister = null; } From a3402c10ff3e07509a8f215a0d50f6701fc9612d Mon Sep 17 00:00:00 2001 From: Kejun Xia Date: Mon, 30 Nov 2015 19:14:38 +1100 Subject: [PATCH 05/26] Refactor navigation controller --- .../lib/android/mvc/__MvcGraphHelper.java | 43 ---- .../mvc/controller/NavigationController.java | 22 ++ .../internal/NavigationControllerImpl.java | 158 +++++++------- .../mvc/controller/internal/Navigator.java | 199 ++++++++++++++++++ .../lib/android/mvc/TestMvcGraphHelper.java | 185 ---------------- .../lib/android/mvc/view/BaseTestCase.java | 4 +- .../injection/TestInjectionAndLifeCycle.java | 10 +- .../nav/TestCaseNavigationAndInjection.java | 24 +-- .../view/eventv2v/EventBusV2VActivity.java | 2 +- .../mvc/view/injection/FragmentInjection.java | 2 +- .../view/injection/InjectionTestActivity.java | 2 +- .../mvc/view/lifecycle/MvcTestActivity.java | 2 +- .../view/nav/MvcTestActivityNavigation.java | 2 +- .../lib/android/mvc/view/nav/NavFragment.java | 4 +- .../mvc/view/viewpager/TabFragmentA.java | 2 +- .../view/viewpager/ViewPagerTestActivity.java | 2 +- .../lib/android/mvc/view/MvcActivity.java | 10 +- 17 files changed, 328 insertions(+), 345 deletions(-) create mode 100644 library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/internal/Navigator.java delete mode 100644 library/android-mvc-controller/src/test/java/com/shipdream/lib/android/mvc/TestMvcGraphHelper.java diff --git a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/__MvcGraphHelper.java b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/__MvcGraphHelper.java index 79f1e7c..a78898e 100644 --- a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/__MvcGraphHelper.java +++ b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/__MvcGraphHelper.java @@ -4,10 +4,8 @@ import com.shipdream.lib.poke.Provider; import com.shipdream.lib.poke.ScopeCache; -import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; -import java.util.List; import java.util.Map; /** @@ -15,47 +13,6 @@ */ public class __MvcGraphHelper { private static Map> retainedProviders = new HashMap<>(); - - /** - * Internal use. Don't call it in app directly. - */ - public static void retainCachedObjectsBeforeNavigation( - final NavigationController.EventC2V.OnLocationChanged navigationEvent, - final MvcGraph mvcGraph) { - //Retain all cached items before navigation. - Collection cachedItems = mvcGraph.singletonScopeCache.getCachedItems(); - - List providers = new ArrayList<>(); - retainedProviders.put(navigationEvent, providers); - for (ScopeCache.CachedItem cachedItem : cachedItems) { - Provider provider = cachedItem.getProvider(); - if (provider != null) { - providers.add(provider); - provider.retain(); - } - } - } - - /** - * Internal use. Don't call it in app directly. - */ - public static void releaseCachedItemsAfterNavigation( - final NavigationController.EventC2V.OnLocationChanged navigationEvent, - final MvcGraph mvcGraph) { - //Release all cached items after the fragment navigated to is ready to show. - Collection providers = retainedProviders.get(navigationEvent); - if (providers != null) { - for (Provider provider : providers) { - if (provider != null) { - provider.release(); - } - } - providers.clear(); - } - - retainedProviders.remove(navigationEvent); - } - /** * Internal use. Gets all cached items this cache still manages * @return The collection of cached times diff --git a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/NavigationController.java b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/NavigationController.java index c955073..4ff7c6b 100644 --- a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/NavigationController.java +++ b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/NavigationController.java @@ -18,6 +18,7 @@ import com.shipdream.lib.android.mvc.MvcGraph; import com.shipdream.lib.android.mvc.NavLocation; +import com.shipdream.lib.android.mvc.controller.internal.Navigator; import com.shipdream.lib.android.mvc.event.BaseEventC2C; import com.shipdream.lib.android.mvc.event.ValueChangeEventC2V; import com.shipdream.lib.poke.Consumer; @@ -26,7 +27,12 @@ * Controller to navigate among different fragments in the SAME activity. */ public interface NavigationController extends BaseController { + Navigator navigate(Object sender); + + Navigator navigate(Object sender, Class... preparedControllers); + /** + * * Navigate to a new location. Current location will be saved/stacked into history which can be * popped out by {@link #navigateBack(Object, String)} or {@link #navigateTo(Object, String, String)}. * Navigation only takes effect when the given locationId is different from the current location @@ -46,9 +52,13 @@ public interface NavigationController extends BaseController * + *

Deprecated: use {@link #navigate(Object)} or {@link #navigate(Object, Class[])} instead

+ * * @param sender Who wants to navigate * @param locationId The id of the location navigate to + * */ + @Deprecated void navigateTo(Object sender, String locationId); /** @@ -73,20 +83,26 @@ public interface NavigationController extends BaseController * + *

Deprecated: use {@link #navigate(Object)} or {@link #navigate(Object, Class[])} instead

+ * * @param sender Who wants to navigate * @param locationId The id of the location navigate to * @param clearTopToLocationId Null if all history locations want to be cleared otherwise, the * id of the location the history will be exclusively cleared up to * which will be the second last location after navigation. */ + @Deprecated void navigateTo(Object sender, String locationId, String clearTopToLocationId); /** * Navigates back. If current location is null it doesn't take any effect otherwise * raises a {@link EventC2V.OnLocationBack} event when there is a previous location. * + *

Deprecated: use {@link #navigate(Object)} or {@link #navigate(Object, Class[])} instead

+ * * @param sender Who wants to navigate back */ + @Deprecated void navigateBack(Object sender); /** @@ -95,14 +111,20 @@ public interface NavigationController extends BaseControllerDeprecated: use {@link #navigate(Object)} or {@link #navigate(Object, Class[])} instead

+ * * @param sender Who wants to navigate * @param toLocationId Null when needs to navigate to the very first location and all history * locations will be above it will be cleared. Otherwise, the id of the * location where the history will be exclusively cleared up to. Then this * location will be the second last one. */ + @Deprecated void navigateBack(Object sender, String toLocationId); + /** + * Event t + */ interface EventC2V { abstract class OnLocationChanged extends ValueChangeEventC2V { public OnLocationChanged(Object sender, NavLocation lastValue, NavLocation currentValue) { diff --git a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/internal/NavigationControllerImpl.java b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/internal/NavigationControllerImpl.java index 39fb1b5..9932efb 100644 --- a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/internal/NavigationControllerImpl.java +++ b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/internal/NavigationControllerImpl.java @@ -16,8 +16,6 @@ package com.shipdream.lib.android.mvc.controller.internal; -import com.shipdream.lib.android.mvc.Injector; -import com.shipdream.lib.android.mvc.__MvcGraphHelper; import com.shipdream.lib.android.mvc.NavLocation; import com.shipdream.lib.android.mvc.controller.NavigationController; @@ -33,14 +31,28 @@ public Class getModelClassType() { return NavigationController.Model.class; } + @Override + public Navigator navigate(Object sender) { + Navigator navigator = new Navigator(sender, this); + return navigator; + } + + @Override + public Navigator navigate(Object sender, Class... preparedControllers) { + Navigator navigator = new Navigator(sender, this); + return navigator; + } + @Override public void navigateTo(Object sender, String locationId) { - doNavigateTo(sender, locationId, false, null); +// doNavigateTo(sender, locationId, false, null); + navigate(sender).to(locationId).go(); } @Override public void navigateTo(Object sender, String locationId, String clearTopToLocationId) { - doNavigateTo(sender, locationId, true, clearTopToLocationId); +// doNavigateTo(sender, locationId, true, clearTopToLocationId); + navigate(sender).to(locationId, clearTopToLocationId).go(); } private void doNavigateTo(Object sender, String locationId, boolean clearTop, @@ -99,19 +111,6 @@ private void doNavigateTo(Object sender, String locationId, boolean clearTop, EventC2V.OnLocationForward navEvent = new EventC2V.OnLocationForward(sender, lastLoc, currentLoc, clearTop, clearedTopToLocation); - /** - * Retain all cached state managed objects. They will be retained until the fragment is - * created and ready to show. They will be released by the view ready call back by the - * fragment navigating to. - * fragment.registerOnViewReadyListener(new Runnable() { - @Override - public void run() { - Injector.getGraph().releaseCachedItems(); - fragment.unregisterOnViewReadyListener(this); - } - }); - */ - __MvcGraphHelper.retainCachedObjectsBeforeNavigation(navEvent, Injector.getGraph()); postC2VEvent(navEvent); @@ -123,73 +122,72 @@ public void run() { @Override public void navigateBack(Object sender) { - NavLocation currentLoc = getModel().getCurrentLocation(); - if (currentLoc == null) { - logger.warn("Current location should never be null before navigating backwards."); - return; - } - - NavLocation previousLoc = currentLoc.getPreviousLocation(); - getModel().setCurrentLocation(previousLoc); - - EventC2V.OnLocationBack navEvent = new EventC2V.OnLocationBack(sender, currentLoc, previousLoc, false); - - if (previousLoc != null ) { - __MvcGraphHelper.retainCachedObjectsBeforeNavigation(navEvent, Injector.getGraph()); - } - - postC2VEvent(navEvent); - - logger.trace("Nav Controller: Backward: {} -> {}", currentLoc.getLocationId(), - previousLoc == null ? "null" : previousLoc.getLocationId()); - - checkAppExit(sender); - - dumpHistory(); +// NavLocation currentLoc = getModel().getCurrentLocation(); +// if (currentLoc == null) { +// logger.warn("Current location should never be null before navigating backwards."); +// return; +// } +// +// NavLocation previousLoc = currentLoc.getPreviousLocation(); +// getModel().setCurrentLocation(previousLoc); +// +// EventC2V.OnLocationBack navEvent = new EventC2V.OnLocationBack(sender, currentLoc, previousLoc, false); +// +// postC2VEvent(navEvent); +// +// logger.trace("Nav Controller: Backward: {} -> {}", currentLoc.getLocationId(), +// previousLoc == null ? "null" : previousLoc.getLocationId()); +// +// checkAppExit(sender); +// +// dumpHistory(); + navigate(sender).navigateBack().go(); } @Override public void navigateBack(Object sender, String toLocationId) { - NavLocation currentLoc = getModel().getCurrentLocation(); - if (currentLoc == null) { - logger.warn("Current location should never be null before navigating backwards."); - return; - } - - if (currentLoc.getPreviousLocation() == null) { - //Has already been the first location, don't do anything - return; - } - - boolean success = false; - NavLocation previousLoc = currentLoc; - - if(toLocationId == null) { - success = true; - } - while (currentLoc != null) { - if(toLocationId != null) { - if (toLocationId.equals(currentLoc.getLocationId())) { - success = true; - break; - } - } else { - if(currentLoc.getPreviousLocation() == null) { - break; - } - } - currentLoc = currentLoc.getPreviousLocation(); - } - if(success) { - getModel().setCurrentLocation(currentLoc); - postC2VEvent(new EventC2V.OnLocationBack(sender, previousLoc, currentLoc, true)); - logger.trace("Nav Controller: Backward: {} -> {}", currentLoc.getLocationId(), - previousLoc.getLocationId()); - - checkAppExit(sender); - - dumpHistory(); - } +// NavLocation currentLoc = getModel().getCurrentLocation(); +// if (currentLoc == null) { +// logger.warn("Current location should never be null before navigating backwards."); +// return; +// } +// +// if (currentLoc.getPreviousLocation() == null) { +// //Has already been the first location, don't do anything +// return; +// } +// +// boolean success = false; +// NavLocation previousLoc = currentLoc; +// +// if(toLocationId == null) { +// success = true; +// } +// while (currentLoc != null) { +// if(toLocationId != null) { +// if (toLocationId.equals(currentLoc.getLocationId())) { +// success = true; +// break; +// } +// } else { +// if(currentLoc.getPreviousLocation() == null) { +// break; +// } +// } +// currentLoc = currentLoc.getPreviousLocation(); +// } +// if(success) { +// getModel().setCurrentLocation(currentLoc); +// postC2VEvent(new EventC2V.OnLocationBack(sender, previousLoc, currentLoc, true)); +// logger.trace("Nav Controller: Backward: {} -> {}", currentLoc.getLocationId(), +// previousLoc.getLocationId()); +// +// checkAppExit(sender); +// +// dumpHistory(); +// } + + navigate(sender).navigateBack(toLocationId).go(); } private void checkAppExit(Object sender) { diff --git a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/internal/Navigator.java b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/internal/Navigator.java new file mode 100644 index 0000000..10367fe --- /dev/null +++ b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/internal/Navigator.java @@ -0,0 +1,199 @@ +package com.shipdream.lib.android.mvc.controller.internal; + +import com.shipdream.lib.android.mvc.NavLocation; +import com.shipdream.lib.android.mvc.controller.NavigationController; + +public class Navigator { + public interface OnSettled { + void run(); + } + + private final Object sender; + private OnSettled onSettled; + private NavigationControllerImpl navigationController; + private NavigationController.EventC2V.OnLocationChanged navigateEvent; + + Navigator(Object sender, NavigationControllerImpl navigationController) { + this.sender = sender; + this.navigationController = navigationController; + } + + public Object getSender() { + return sender; + } + + public OnSettled getOnSettled() { + return onSettled; + } + + public Navigator to(String locationId) { + doNavigateTo(locationId, false, null); + return this; + } + + public Navigator to(String locationId, String clearTopToLocationId) { + doNavigateTo(locationId, true, clearTopToLocationId); + return this; + } + + private void doNavigateTo(String locationId, boolean clearTop, + String clearTopToLocationId) { + NavLocation clearedTopToLocation = null; + if (clearTop) { + if (clearTopToLocationId != null) { + //find out the top most location in the history stack with clearTopToLocationId + NavLocation currentLoc = navigationController.getModel().getCurrentLocation(); + while (currentLoc != null) { + if (clearTopToLocationId.equals(currentLoc.getLocationId())) { + //Reverse the history to this location + clearedTopToLocation = currentLoc; + break; + } + currentLoc = currentLoc.getPreviousLocation(); + } + if (clearedTopToLocation == null) { + //The location to clear up to is not found. Disable clear top. + clearTop = false; + } + } else { + clearedTopToLocation = null; + } + } + + NavLocation lastLoc = navigationController.getModel().getCurrentLocation(); + boolean locationChanged = false; + + if (clearTop) { + locationChanged = true; + } else { + if (locationId != null) { + if(lastLoc == null) { + locationChanged = true; + } else if(!locationId.equals(lastLoc.getLocationId())) { + locationChanged = true; + } + } + } + + if (locationChanged) { + NavLocation currentLoc = new NavLocation(); + currentLoc._setLocationId(locationId); + if (!clearTop) { + //Remember last location as previous location + currentLoc._setPreviousLocation(lastLoc); + } else { + //Remember clear top location location as the previous location + currentLoc._setPreviousLocation(clearedTopToLocation); + } + + navigationController.getModel().setCurrentLocation(currentLoc); + + navigateEvent = new NavigationController.EventC2V.OnLocationForward(sender, lastLoc, + currentLoc, clearTop, clearedTopToLocation); + } + } + + public Navigator navigateBack() { + NavLocation currentLoc = navigationController.getModel().getCurrentLocation(); + if (currentLoc == null) { + navigationController.logger.warn("Current location should never be null before navigating backwards."); + return this; + } + + NavLocation previousLoc = currentLoc.getPreviousLocation(); + navigationController.getModel().setCurrentLocation(previousLoc); + + navigateEvent = new NavigationController.EventC2V.OnLocationBack(sender, currentLoc, previousLoc, false); + return this; + } + + public Navigator navigateBack(String toLocationId) { + NavLocation currentLoc = navigationController.getModel().getCurrentLocation(); + if (currentLoc == null) { + navigationController.logger.warn("Current location should never be null before navigating backwards."); + return this; + } + + if (currentLoc.getPreviousLocation() == null) { + //Has already been the first location, don't do anything + return this; + } + + boolean success = false; + NavLocation previousLoc = currentLoc; + + if(toLocationId == null) { + success = true; + } + while (currentLoc != null) { + if(toLocationId != null) { + if (toLocationId.equals(currentLoc.getLocationId())) { + success = true; + break; + } + } else { + if(currentLoc.getPreviousLocation() == null) { + break; + } + } + currentLoc = currentLoc.getPreviousLocation(); + } + if(success) { + navigationController.getModel().setCurrentLocation(currentLoc); + navigateEvent = new NavigationController.EventC2V.OnLocationBack(sender, previousLoc, currentLoc, true); + } + return this; + } + + public Navigator onSettled(OnSettled onSettled) { + this.onSettled = onSettled; + return this; + } + + public void go() { + if (navigateEvent != null) { + navigationController.postC2VEvent(navigateEvent); + + if (navigateEvent instanceof NavigationController.EventC2V.OnLocationForward) { + String lastLocId = navigateEvent.getLastValue() == null ? null + : navigateEvent.getLastValue().getLocationId(); + navigationController.logger.trace("Nav Controller: Forward: {} -> {}", lastLocId, + navigateEvent.getCurrentValue().getLocationId()); + } + + if (navigateEvent instanceof NavigationController.EventC2V.OnLocationBack) { + NavLocation lastLoc = navigateEvent.getLastValue(); + NavLocation currentLoc = navigateEvent.getCurrentValue(); + navigationController.logger.trace("Nav Controller: Backward: {} -> {}", + lastLoc.getLocationId(), + currentLoc == null ? "null" : currentLoc.getLocationId()); + + checkAppExit(sender); + } + + dumpHistory(); + } + + } + + private void checkAppExit(Object sender) { + NavLocation curLocation = navigationController.getModel().getCurrentLocation(); + if (curLocation == null) { + navigationController.postC2CEvent(new NavigationController.EventC2C.OnAppExit(sender)); + } + } + + private void dumpHistory() { + if (navigationController.dumpHistoryOnLocationChange) { + navigationController.logger.trace(""); + navigationController.logger.trace("Nav Controller: dump: begin ---------------------------------------------->"); + NavLocation curLoc = navigationController.getModel().getCurrentLocation(); + while (curLoc != null) { + navigationController.logger.trace("Nav Controller: dump: {}({})", curLoc.getLocationId()); + curLoc = curLoc.getPreviousLocation(); + } + navigationController.logger.trace("Nav Controller: dump: end ---------------------------------------------->"); + navigationController.logger.trace(""); + } + } +} diff --git a/library/android-mvc-controller/src/test/java/com/shipdream/lib/android/mvc/TestMvcGraphHelper.java b/library/android-mvc-controller/src/test/java/com/shipdream/lib/android/mvc/TestMvcGraphHelper.java deleted file mode 100644 index 8e7e26f..0000000 --- a/library/android-mvc-controller/src/test/java/com/shipdream/lib/android/mvc/TestMvcGraphHelper.java +++ /dev/null @@ -1,185 +0,0 @@ -package com.shipdream.lib.android.mvc; - -import com.shipdream.lib.android.mvc.controller.NavigationController; -import com.shipdream.lib.poke.Component; -import com.shipdream.lib.poke.Provides; -import com.shipdream.lib.poke.exception.CircularDependenciesException; -import com.shipdream.lib.poke.exception.ProvideException; -import com.shipdream.lib.poke.exception.ProviderConflictException; -import com.shipdream.lib.poke.exception.ProviderMissingException; - -import org.junit.After; -import org.junit.Assert; -import org.junit.Test; - -import java.util.concurrent.ExecutorService; - -import javax.inject.Inject; -import javax.inject.Singleton; - -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; - -public class TestMvcGraphHelper { - @After - public void tearDown() throws Exception { - Injector.mvcGraph = null; - } - - class View { - @Inject - private NavigationController navigationController; - } - - @Test - public void should_retain_and_release_cached_instance_by_mvcgraph_helper() { - //Prepare graph - MvcGraph.BaseDependencies baseDependencies = new MvcGraph.BaseDependencies() { - @Override - protected ExecutorService createExecutorService() { - return mock(ExecutorService.class); - } - }; - - Injector.configGraph(baseDependencies); - - View view = new View(); - Injector.getGraph().inject(view); - - int initSize = __MvcGraphHelper.getAllCachedInstances(Injector.getGraph()).size(); - - NavLocation lastValue = new NavLocation(); - NavLocation currentValue = new NavLocation(); - - NavigationController.EventC2V.OnLocationForward event = new NavigationController.EventC2V.OnLocationForward( - this, lastValue, currentValue, false, null); - - __MvcGraphHelper.retainCachedObjectsBeforeNavigation(event, Injector.getGraph()); - Assert.assertNotEquals(initSize, __MvcGraphHelper.getAllCachedInstances(Injector.getGraph())); - - __MvcGraphHelper.releaseCachedItemsAfterNavigation(event, Injector.getGraph()); - Assert.assertEquals(initSize, __MvcGraphHelper.getAllCachedInstances(Injector.getGraph()).size()); - } - - - interface ControllerA extends Disposable { - - } - - static class ControllerAImpl implements ControllerA { - @Inject - ManageA manageA; - - /** - * Execute onDisposed logic of given object - */ - @Override - public void onDisposed() { - - } - } - - interface ManageA extends Disposable { - - } - - ManageA manageAMock; - - static class ViewA { - @Inject - ControllerA controllerA; - } - - static class ViewB { - @Inject - ControllerA controllerA; - } - - static class ViewC { - @Inject - ControllerA controllerA; - } - - static class TestComp extends Component { - TestMvcGraphHelper testMvcGraphHelper; - - /** - * Constructor with a stand alone scope cache used only by this component - */ - public TestComp(TestMvcGraphHelper testMvcGraphHelper) { - this.testMvcGraphHelper = testMvcGraphHelper; - } - - @Singleton - @Provides - public ControllerA providesController() { - return new ControllerAImpl(); - } - - @Singleton - @Provides - public ManageA providersManageA() { - testMvcGraphHelper.manageAMock = mock(ManageA.class); - return testMvcGraphHelper.manageAMock; - } - } - - @Test - public void should_release_nested_injection_until_last_instance_dereferenced() throws - ProvideException, ProviderConflictException, CircularDependenciesException, - ProviderMissingException { - //Prepare graph - MvcGraph.BaseDependencies baseDependencies = new MvcGraph.BaseDependencies() { - @Override - protected ExecutorService createExecutorService() { - return mock(ExecutorService.class); - } - }; - - Injector.configGraph(baseDependencies); - - ViewA viewA = new ViewA(); - ViewB viewB = new ViewB(); - ViewC viewC = new ViewC(); - - Component component = new TestComp(this); - Injector.getGraph().register(component); - - Injector.getGraph().inject(viewA); - ControllerA controllerAInViewA = viewA.controllerA; - ManageA manageAInViewA = ((ControllerAImpl)viewA.controllerA).manageA; - - //Simulate navigate to viewB - NavigationController.EventC2V.OnLocationForward navEventB = mock( - NavigationController.EventC2V.OnLocationForward.class); - __MvcGraphHelper.retainCachedObjectsBeforeNavigation(navEventB, Injector.getGraph()); - - Injector.getGraph().inject(viewB); - ControllerA controllerAInViewB = viewB.controllerA; - ManageA manageAInViewB = ((ControllerAImpl)viewA.controllerA).manageA; - - //release viewA - Injector.getGraph().release(viewA); - __MvcGraphHelper.releaseCachedItemsAfterNavigation(navEventB, Injector.getGraph()); - verify(manageAMock, times(0)).onDisposed(); - - //Simulate navigate to viewC - NavigationController.EventC2V.OnLocationForward navEventC = mock( - NavigationController.EventC2V.OnLocationForward.class); - __MvcGraphHelper.retainCachedObjectsBeforeNavigation(navEventC, Injector.getGraph()); - - Injector.getGraph().inject(viewC); - ControllerA controllerAInViewC = viewB.controllerA; - ManageA manageAInViewC = ((ControllerAImpl)viewA.controllerA).manageA; - - //release viewB - Injector.getGraph().release(viewB); - __MvcGraphHelper.releaseCachedItemsAfterNavigation(navEventC, Injector.getGraph()); - verify(manageAMock, times(0)).onDisposed(); - - Assert.assertTrue(manageAInViewA == manageAInViewB); - Assert.assertTrue(manageAInViewA == manageAInViewC); - } - -} diff --git a/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/BaseTestCase.java b/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/BaseTestCase.java index 5081576..1ae6ce2 100644 --- a/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/BaseTestCase.java +++ b/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/BaseTestCase.java @@ -184,8 +184,8 @@ protected void injectDependencies(ScopeCache mvcSingletonCache){ @After public void tearDown() throws Exception { - navigationController.navigateBack(this, null); - navigationController.navigateBack(this); + navigationController.navigate(this).navigateBack(null).go(); + navigationController.navigate(this).navigateBack().go(); activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED); diff --git a/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/injection/TestInjectionAndLifeCycle.java b/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/injection/TestInjectionAndLifeCycle.java index 96a92e3..02db734 100644 --- a/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/injection/TestInjectionAndLifeCycle.java +++ b/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/injection/TestInjectionAndLifeCycle.java @@ -57,7 +57,7 @@ public void testShouldRetainInjectionsOfFragmentAAfterNavigatedToFragmentB() thr onView(withId(R.id.textB)).check(matches(withText("Added by FragmentA"))); onView(withId(R.id.textC)).check(matches(withText(""))); - navigationController.navigateTo(this, MvcTestActivityNavigation.Loc.B); + navigationController.navigate(this).to(MvcTestActivityNavigation.Loc.B).go(); waitTest(); //=============================> At B //onDestroyView is always called when a fragment is pushed to back stack @@ -72,7 +72,7 @@ public void testShouldRetainInjectionsOfFragmentAAfterNavigatedToFragmentB() thr "Added by FragmentB"))); onView(withId(R.id.textC)).check(matches(withText(""))); - navigationController.navigateTo(this, MvcTestActivityNavigation.Loc.C); + navigationController.navigate(this).to(MvcTestActivityNavigation.Loc.C).go(); waitTest(); //=============================> At C lifeCycleValidatorB.expect(LifeCycle.onPushingToBackStack, LifeCycle.onDestroyView); @@ -85,7 +85,7 @@ public void testShouldRetainInjectionsOfFragmentAAfterNavigatedToFragmentB() thr "Added by FragmentC"))); onView(withId(R.id.textC)).check(matches(withText("Added by FragmentC"))); - navigationController.navigateBack(this); + navigationController.navigate(this).navigateBack().go(); waitTest(1000); //=============================> At B lifeCycleValidatorC.expect(LifeCycle.onDestroyView, LifeCycle.onDestroy); @@ -104,7 +104,7 @@ public void testShouldRetainInjectionsOfFragmentAAfterNavigatedToFragmentB() thr "Added by FragmentB"))); onView(withId(R.id.textC)).check(matches(withText(""))); - navigationController.navigateBack(this); + navigationController.navigate(this).navigateBack().go(); waitTest(1000); //=============================> At A //onDestroy of previous Fragment(FragmentB) is not called until it's removed out from back stack @@ -140,7 +140,7 @@ public void test_should_delay_call_on_view_ready_on_sub_fragments_after_dependen onView(withId(R.id.textB)).check(matches(withText("Added by FragmentA"))); onView(withId(R.id.textC)).check(matches(withText(""))); - navigationController.navigateTo(this, MvcTestActivityNavigation.Loc.B); + navigationController.navigate(this).to(MvcTestActivityNavigation.Loc.B).go(); waitTest(); //=============================> At B onView(withId(R.id.textA)).check(matches(withText("Added by FragmentA\n" + diff --git a/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/nav/TestCaseNavigationAndInjection.java b/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/nav/TestCaseNavigationAndInjection.java index 9a7727b..9951887 100644 --- a/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/nav/TestCaseNavigationAndInjection.java +++ b/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/nav/TestCaseNavigationAndInjection.java @@ -92,7 +92,7 @@ public void test_should_reinject_last_fragment_and_release_top_fragment_on_singl FragmentManager fm = activity.getRootFragmentManager(); resetGraphMonitorCounts(); - navigationController.navigateBack(this); + navigationController.navigate(this).navigateBack().go(); //->A->B->C waitTest(1000); Assert.assertEquals(fm.getFragments().size(), 4); @@ -114,7 +114,7 @@ public void test_should_reinject_last_fragment_and_release_top_fragment_on_singl Assert.assertEquals(fragDReleaseCount, 1); resetGraphMonitorCounts(); - navigationController.navigateBack(this); + navigationController.navigate(this).navigateBack().go(); //->A->B waitTest(); Assert.assertEquals(fm.getFragments().size(), 4); @@ -135,7 +135,7 @@ public void test_should_reinject_last_fragment_and_release_top_fragment_on_singl Assert.assertEquals(fragDReleaseCount, 0); resetGraphMonitorCounts(); - navigationController.navigateBack(this); + navigationController.navigate(this).navigateBack().go(); //->A waitTest(); Assert.assertEquals(fm.getFragments().size(), 4); @@ -155,7 +155,7 @@ public void test_should_reinject_last_fragment_and_release_top_fragment_on_singl Assert.assertEquals(fragDReleaseCount, 0); resetGraphMonitorCounts(); - navigationController.navigateBack(this); + navigationController.navigate(this).navigateBack().go(); //quit waitTest(2000); Assert.assertEquals(fm.getFragments().size(), 4); @@ -353,7 +353,7 @@ public void test_should_release_and_inject_properly_on_single_step_back_navigati Assert.assertEquals(fragDReleaseCount, 1); resetGraphMonitorCounts(); - navigationController.navigateBack(this); + navigationController.navigate(this).navigateBack().go(); //->A->B->C waitTest(1000); Assert.assertEquals(fragAInjectCount, 0); @@ -366,7 +366,7 @@ public void test_should_release_and_inject_properly_on_single_step_back_navigati Assert.assertEquals(fragDReleaseCount, 1); resetGraphMonitorCounts(); - navigationController.navigateBack(this); + navigationController.navigate(this).navigateBack().go(); //->A->B waitTest(); Assert.assertEquals(fragAInjectCount, 0); @@ -379,7 +379,7 @@ public void test_should_release_and_inject_properly_on_single_step_back_navigati Assert.assertEquals(fragDReleaseCount, 0); resetGraphMonitorCounts(); - navigationController.navigateBack(this); + navigationController.navigate(this).navigateBack().go(); //->A waitTest(); Assert.assertEquals(fragAInjectCount, 0); @@ -392,7 +392,7 @@ public void test_should_release_and_inject_properly_on_single_step_back_navigati Assert.assertEquals(fragDReleaseCount, 0); resetGraphMonitorCounts(); - navigationController.navigateBack(this); + navigationController.navigate(this).navigateBack().go(); //quit waitTest(); Assert.assertEquals(fragAInjectCount, 0); @@ -444,7 +444,7 @@ public void test_should_release_and_inject_properly_on_fast_rewind_back_navigati Assert.assertEquals(fragDReleaseCount, 1); resetGraphMonitorCounts(); - navigationController.navigateBack(this); + navigationController.navigate(this).navigateBack().go(); //quit waitTest(); Assert.assertEquals(fragAInjectCount, 0); @@ -496,7 +496,7 @@ public void test_should_release_and_inject_properly_on_fast_rewind_forward_navig Assert.assertEquals(fragDReleaseCount, 1); resetGraphMonitorCounts(); - navigationController.navigateBack(this); + navigationController.navigate(this).navigateBack().go(); //->A->B waitTest(); Assert.assertEquals(fragAInjectCount, 0); @@ -509,7 +509,7 @@ public void test_should_release_and_inject_properly_on_fast_rewind_forward_navig Assert.assertEquals(fragDReleaseCount, 0); resetGraphMonitorCounts(); - navigationController.navigateBack(this); + navigationController.navigate(this).navigateBack().go(); //->A waitTest(); Assert.assertEquals(fragAInjectCount, 0); @@ -522,7 +522,7 @@ public void test_should_release_and_inject_properly_on_fast_rewind_forward_navig Assert.assertEquals(fragDReleaseCount, 0); resetGraphMonitorCounts(); - navigationController.navigateBack(this); + navigationController.navigate(this).navigateBack().go(); //quit waitTest(); Assert.assertEquals(fragAInjectCount, 0); diff --git a/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/eventv2v/EventBusV2VActivity.java b/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/eventv2v/EventBusV2VActivity.java index 5f75203..2af7d4b 100644 --- a/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/eventv2v/EventBusV2VActivity.java +++ b/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/eventv2v/EventBusV2VActivity.java @@ -39,7 +39,7 @@ public static class HomeFragment extends DelegateFragment { @Override protected void onStartUp() { - getNavigationController().navigateTo(this, "TestFragment", null); + getNavigationController().navigate(this).to("TestFragment", null).go(); } @Override diff --git a/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/injection/FragmentInjection.java b/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/injection/FragmentInjection.java index a9839f3..166c0cf 100644 --- a/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/injection/FragmentInjection.java +++ b/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/injection/FragmentInjection.java @@ -85,7 +85,7 @@ public void onClick(View view) { }else if (item.equals("D")) { loc = MvcTestActivityNavigation.Loc.D; } - navigationController.navigateTo(view, loc); + navigationController.navigate(view).to(loc).go(); } }); diff --git a/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/injection/InjectionTestActivity.java b/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/injection/InjectionTestActivity.java index c0a30a3..af18bd9 100644 --- a/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/injection/InjectionTestActivity.java +++ b/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/injection/InjectionTestActivity.java @@ -49,7 +49,7 @@ public static class HomeFragment extends DelegateFragment { @Override protected void onStartUp() { - navigationController.navigateTo(this, MvcTestActivityNavigation.Loc.A, null); + navigationController.navigate(this).to(MvcTestActivityNavigation.Loc.A, null).go(); } } diff --git a/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/lifecycle/MvcTestActivity.java b/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/lifecycle/MvcTestActivity.java index 6cff31e..6333909 100644 --- a/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/lifecycle/MvcTestActivity.java +++ b/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/lifecycle/MvcTestActivity.java @@ -39,7 +39,7 @@ public static class HomeFragment extends DelegateFragment { @Override protected void onStartUp() { - getNavigationController().navigateTo(this, "TestFragment", null); + getNavigationController().navigate(this).to("TestFragment", null).go(); } @Override diff --git a/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/nav/MvcTestActivityNavigation.java b/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/nav/MvcTestActivityNavigation.java index ab8279e..a9f7566 100644 --- a/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/nav/MvcTestActivityNavigation.java +++ b/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/nav/MvcTestActivityNavigation.java @@ -74,7 +74,7 @@ public static class HomeFragment extends DelegateFragment { @Override protected void onStartUp() { Log.d("MvcTesting", "navigate"); - navigationController.navigateTo(this, Loc.A); + navigationController.navigate(this).to(Loc.A).go(); } @Override diff --git a/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/nav/NavFragment.java b/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/nav/NavFragment.java index df4b769..06af145 100644 --- a/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/nav/NavFragment.java +++ b/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/nav/NavFragment.java @@ -51,7 +51,7 @@ public void onViewReady(View view, Bundle savedInstanceState, Reason reason) { next.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - navigationController.navigateTo(v, getNextFragmentLocId()); + navigationController.navigate(v).to(getNextFragmentLocId()).go(); } }); @@ -59,7 +59,7 @@ public void onClick(View v) { clear.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { - navigationController.navigateBack(view, null); + navigationController.navigate(view).navigateBack(null).go(); } }); diff --git a/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/viewpager/TabFragmentA.java b/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/viewpager/TabFragmentA.java index 87b1f79..eacdca1 100644 --- a/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/viewpager/TabFragmentA.java +++ b/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/viewpager/TabFragmentA.java @@ -49,7 +49,7 @@ public void onViewReady(View view, Bundle savedInstanceState, Reason reason) { textView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - navigationController.navigateTo(v, SubFragment.class.getSimpleName()); + navigationController.navigate(v).to(SubFragment.class.getSimpleName()).go(); } }); } diff --git a/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/viewpager/ViewPagerTestActivity.java b/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/viewpager/ViewPagerTestActivity.java index 2890c92..bcb9900 100644 --- a/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/viewpager/ViewPagerTestActivity.java +++ b/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/viewpager/ViewPagerTestActivity.java @@ -39,7 +39,7 @@ protected Class getDelegateFragmentClass() { public static class HomeFragment extends DelegateFragment { @Override protected void onStartUp() { - getNavigationController().navigateTo(this, ViewPagerHomeFragment.class.getSimpleName(), null); + getNavigationController().navigate(this).to(ViewPagerHomeFragment.class.getSimpleName(), null).go(); } } diff --git a/library/android-mvc/src/main/java/com/shipdream/lib/android/mvc/view/MvcActivity.java b/library/android-mvc/src/main/java/com/shipdream/lib/android/mvc/view/MvcActivity.java index 8bf4c5f..4fd6273 100644 --- a/library/android-mvc/src/main/java/com/shipdream/lib/android/mvc/view/MvcActivity.java +++ b/library/android-mvc/src/main/java/com/shipdream/lib/android/mvc/view/MvcActivity.java @@ -252,7 +252,7 @@ public boolean onBackButtonPressed() { navigateBack = !topFragment.onBackButtonPressed(); } if (navigateBack) { - navigationController.navigateBack(this); + navigationController.navigate(this).navigateBack().go(); } return true; } @@ -449,10 +449,6 @@ private void performForwardNav(final NavigationController.EventC2V.OnLocationFor fragment.registerOnViewReadyListener(new Runnable() { @Override public void run() { - //Release reference count to pair the retaining by NavigationControllerImpl - // with Injector.getGraph().retainCachedObjectsBeforeNavigation(); - __MvcGraphHelper.releaseCachedItemsAfterNavigation(event, Injector.getGraph()); - if (finalLastFrag != null) { finalLastFrag.releaseDependencies(); } @@ -525,10 +521,6 @@ private void performBackNav(final NavigationController.EventC2V.OnLocationBack e currentFrag.registerOnViewReadyListener(new Runnable() { @Override public void run() { - //Release reference count to pair the retaining by NavigationControllerImpl - // with Injector.getGraph().retainCachedObjectsBeforeNavigation(); - __MvcGraphHelper.releaseCachedItemsAfterNavigation(event, Injector.getGraph()); - if (finalLastFrag != null) { finalLastFrag.releaseDependencies(); finalLastFrag.selfRelease = true; From 750519d58a7c26d498dcaed17b81586a36c1ddcb Mon Sep 17 00:00:00 2001 From: Kejun Xia Date: Mon, 30 Nov 2015 19:18:24 +1100 Subject: [PATCH 06/26] rename ui test methods to be included into test suite --- .../lib/android/mvc/view/eventv2v/TestV2VEvents.java | 2 +- .../view/nav/TestCaseNavigationFromController.java | 4 ++-- .../mvc/view/viewpager/TestFragmentsInViewPager.java | 12 ++++++------ 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/eventv2v/TestV2VEvents.java b/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/eventv2v/TestV2VEvents.java index 43d81fd..c2ffe8e 100644 --- a/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/eventv2v/TestV2VEvents.java +++ b/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/eventv2v/TestV2VEvents.java @@ -50,7 +50,7 @@ public void tearDown() throws Exception { } @Test - public void should_be_able_to_send_and_receive_v2v_events_among_fragments_services_and_dialogFragments() throws Throwable { + public void test_should_be_able_to_send_and_receive_v2v_events_among_fragments_services_and_dialogFragments() throws Throwable { onView(withId(R.id.fragment_mvc_v2v_text)).check(matches(withText("Initial Text"))); onView(withId(R.id.fragment_mvc_v2v_btnService)).perform(click()); diff --git a/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/nav/TestCaseNavigationFromController.java b/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/nav/TestCaseNavigationFromController.java index 42ebeed..4c2c060 100644 --- a/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/nav/TestCaseNavigationFromController.java +++ b/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/nav/TestCaseNavigationFromController.java @@ -132,7 +132,7 @@ protected void cleanDependencies() { } @Test - public void should_release_injected_object_by_pure_navigation_controller_navigation() throws Throwable { + public void test_should_release_injected_object_by_pure_navigation_controller_navigation() throws Throwable { onView(withText(NavFragmentA.class.getSimpleName())).check(matches(isDisplayed())); final String val = "Value = " + new Random().nextInt(); @@ -160,7 +160,7 @@ public void consume(ControllerE instance) { } @Test - public void should_release_injected_object_by_chained_navigation_controller_navigation() throws Throwable { + public void test_should_release_injected_object_by_chained_navigation_controller_navigation() throws Throwable { onView(withText(NavFragmentA.class.getSimpleName())).check(matches(isDisplayed())); final String valE = "ValueE = " + new Random().nextInt(); diff --git a/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/viewpager/TestFragmentsInViewPager.java b/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/viewpager/TestFragmentsInViewPager.java index 645cc49..05a1291 100644 --- a/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/viewpager/TestFragmentsInViewPager.java +++ b/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/viewpager/TestFragmentsInViewPager.java @@ -40,7 +40,7 @@ public TestFragmentsInViewPager() { } @Test - public void should_restore_controller_of_tab_a_after_swipe_away_then_swipe_back_to_tab_a() throws Throwable { + public void test_should_restore_controller_of_tab_a_after_swipe_away_then_swipe_back_to_tab_a() throws Throwable { onView(withText("Tab A")).check(matches(isDisplayed())); onView(withId(R.id.viewpager)).perform(swipeLeft()); @@ -61,7 +61,7 @@ public void should_restore_controller_of_tab_a_after_swipe_away_then_swipe_back_ } @Test - public void should_call_onViewReady_in_tab_fragments_when_resumed_hosting_fragment_pops_out() throws Throwable { + public void test_should_call_onViewReady_in_tab_fragments_when_resumed_hosting_fragment_pops_out() throws Throwable { if (isDontKeepActivities()) { Log.i(getClass().getSimpleName(), "TestFragmentsInViewPager not tested as Don't Keep Activities setting is not disabled"); return; @@ -141,7 +141,7 @@ public void should_call_onViewReady_in_tab_fragments_when_resumed_hosting_fragme } @Test - public void should_call_onViewReady_in_tab_fragments_when_restored_hosting_fragment_pops_out() throws Throwable { + public void test_should_call_onViewReady_in_tab_fragments_when_restored_hosting_fragment_pops_out() throws Throwable { if (!isDontKeepActivities()) { Log.i(getClass().getSimpleName(), "TestFragmentsInViewPager not tested as Don't Keep Activities setting is disabled"); return; @@ -231,7 +231,7 @@ public void should_call_onViewReady_in_tab_fragments_when_restored_hosting_fragm } @Test - public void should_call_onViewReady_in_tab_fragments_when_comes_back_from_another_activity() throws Throwable { + public void test_should_call_onViewReady_in_tab_fragments_when_comes_back_from_another_activity() throws Throwable { if (isDontKeepActivities()) { Log.i(getClass().getSimpleName(), "TestFragmentsInViewPager not tested as Don't Keep Activities setting is enabled"); return; @@ -297,7 +297,7 @@ public void should_call_onViewReady_in_tab_fragments_when_comes_back_from_anothe } @Test - public void should_call_onViewReady_in_tab_fragments_when_comes_back_from_another_activity_after_being_killed() throws Throwable { + public void test_should_call_onViewReady_in_tab_fragments_when_comes_back_from_another_activity_after_being_killed() throws Throwable { if (!isDontKeepActivities()) { Log.i(getClass().getSimpleName(), "TestFragmentsInViewPager not tested as Don't Keep Activities setting is disabled"); return; @@ -370,7 +370,7 @@ public void should_call_onViewReady_in_tab_fragments_when_comes_back_from_anothe } @Test - public void should_call_onViewReady_with_pops_out_on_home_page_on_back_navigation() throws Throwable { + public void test_should_call_onViewReady_with_pops_out_on_home_page_on_back_navigation() throws Throwable { //=============================> At Sub Fragment navigationController.navigateTo(this, SubFragment.class.getSimpleName()); waitTest(1200); From d8a8a417e5db3d09de4f607b08cdbd958b16874e Mon Sep 17 00:00:00 2001 From: Kejun Xia Date: Mon, 30 Nov 2015 19:34:22 +1100 Subject: [PATCH 07/26] Remove invalid test --- .../viewpager/TestFragmentsInViewPager.java | 21 ------------------- 1 file changed, 21 deletions(-) diff --git a/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/viewpager/TestFragmentsInViewPager.java b/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/viewpager/TestFragmentsInViewPager.java index 05a1291..f2e9f2b 100644 --- a/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/viewpager/TestFragmentsInViewPager.java +++ b/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/viewpager/TestFragmentsInViewPager.java @@ -39,27 +39,6 @@ public TestFragmentsInViewPager() { super(ViewPagerTestActivity.class); } - @Test - public void test_should_restore_controller_of_tab_a_after_swipe_away_then_swipe_back_to_tab_a() throws Throwable { - onView(withText("Tab A")).check(matches(isDisplayed())); - - onView(withId(R.id.viewpager)).perform(swipeLeft()); - onView(withText("Tab A")).check(matches(not(isDisplayed()))); - onView(withText("Tab B")).check(matches(isDisplayed())); - - onView(withId(R.id.viewpager)).perform(swipeLeft()); - onView(withText("Tab B")).check(matches(not(isDisplayed()))); - onView(withText("Tab C")).check(matches(isDisplayed())); - - onView(withId(R.id.viewpager)).perform(swipeRight()); - onView(withText("Tab C")).check(matches(not(isDisplayed()))); - onView(withText("Tab B")).check(matches(isDisplayed())); - - onView(withId(R.id.viewpager)).perform(swipeRight()); - onView(withText("Tab B")).check(matches(not(isDisplayed()))); - onView(withText(TabFragmentA.RESTORE_TEXT)).check(matches(isDisplayed())); - } - @Test public void test_should_call_onViewReady_in_tab_fragments_when_resumed_hosting_fragment_pops_out() throws Throwable { if (isDontKeepActivities()) { From 1d889840e4f0e9e8c65cc2b6faf28ed2b989b8a8 Mon Sep 17 00:00:00 2001 From: Kejun Xia Date: Tue, 1 Dec 2015 19:39:51 +1100 Subject: [PATCH 08/26] Rename navigate back method --- .../internal/NavigationControllerImpl.java | 4 ++-- .../mvc/controller/internal/Navigator.java | 4 ++-- .../lib/android/mvc/view/BaseTestCase.java | 4 ++-- .../injection/TestInjectionAndLifeCycle.java | 4 ++-- .../nav/TestCaseNavigationAndInjection.java | 24 +++++++++---------- .../lib/android/mvc/view/nav/NavFragment.java | 2 +- .../lib/android/mvc/view/MvcActivity.java | 2 +- 7 files changed, 22 insertions(+), 22 deletions(-) diff --git a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/internal/NavigationControllerImpl.java b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/internal/NavigationControllerImpl.java index 9932efb..d697398 100644 --- a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/internal/NavigationControllerImpl.java +++ b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/internal/NavigationControllerImpl.java @@ -141,7 +141,7 @@ public void navigateBack(Object sender) { // checkAppExit(sender); // // dumpHistory(); - navigate(sender).navigateBack().go(); + navigate(sender).back().go(); } @Override @@ -187,7 +187,7 @@ public void navigateBack(Object sender, String toLocationId) { // dumpHistory(); // } - navigate(sender).navigateBack(toLocationId).go(); + navigate(sender).back(toLocationId).go(); } private void checkAppExit(Object sender) { diff --git a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/internal/Navigator.java b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/internal/Navigator.java index 10367fe..142a0bc 100644 --- a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/internal/Navigator.java +++ b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/internal/Navigator.java @@ -93,7 +93,7 @@ private void doNavigateTo(String locationId, boolean clearTop, } } - public Navigator navigateBack() { + public Navigator back() { NavLocation currentLoc = navigationController.getModel().getCurrentLocation(); if (currentLoc == null) { navigationController.logger.warn("Current location should never be null before navigating backwards."); @@ -107,7 +107,7 @@ public Navigator navigateBack() { return this; } - public Navigator navigateBack(String toLocationId) { + public Navigator back(String toLocationId) { NavLocation currentLoc = navigationController.getModel().getCurrentLocation(); if (currentLoc == null) { navigationController.logger.warn("Current location should never be null before navigating backwards."); diff --git a/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/BaseTestCase.java b/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/BaseTestCase.java index 1ae6ce2..92f7835 100644 --- a/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/BaseTestCase.java +++ b/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/BaseTestCase.java @@ -184,8 +184,8 @@ protected void injectDependencies(ScopeCache mvcSingletonCache){ @After public void tearDown() throws Exception { - navigationController.navigate(this).navigateBack(null).go(); - navigationController.navigate(this).navigateBack().go(); + navigationController.navigate(this).back(null).go(); + navigationController.navigate(this).back().go(); activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED); diff --git a/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/injection/TestInjectionAndLifeCycle.java b/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/injection/TestInjectionAndLifeCycle.java index 02db734..becf789 100644 --- a/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/injection/TestInjectionAndLifeCycle.java +++ b/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/injection/TestInjectionAndLifeCycle.java @@ -85,7 +85,7 @@ public void testShouldRetainInjectionsOfFragmentAAfterNavigatedToFragmentB() thr "Added by FragmentC"))); onView(withId(R.id.textC)).check(matches(withText("Added by FragmentC"))); - navigationController.navigate(this).navigateBack().go(); + navigationController.navigate(this).back().go(); waitTest(1000); //=============================> At B lifeCycleValidatorC.expect(LifeCycle.onDestroyView, LifeCycle.onDestroy); @@ -104,7 +104,7 @@ public void testShouldRetainInjectionsOfFragmentAAfterNavigatedToFragmentB() thr "Added by FragmentB"))); onView(withId(R.id.textC)).check(matches(withText(""))); - navigationController.navigate(this).navigateBack().go(); + navigationController.navigate(this).back().go(); waitTest(1000); //=============================> At A //onDestroy of previous Fragment(FragmentB) is not called until it's removed out from back stack diff --git a/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/nav/TestCaseNavigationAndInjection.java b/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/nav/TestCaseNavigationAndInjection.java index 9951887..20eaa0a 100644 --- a/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/nav/TestCaseNavigationAndInjection.java +++ b/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/nav/TestCaseNavigationAndInjection.java @@ -92,7 +92,7 @@ public void test_should_reinject_last_fragment_and_release_top_fragment_on_singl FragmentManager fm = activity.getRootFragmentManager(); resetGraphMonitorCounts(); - navigationController.navigate(this).navigateBack().go(); + navigationController.navigate(this).back().go(); //->A->B->C waitTest(1000); Assert.assertEquals(fm.getFragments().size(), 4); @@ -114,7 +114,7 @@ public void test_should_reinject_last_fragment_and_release_top_fragment_on_singl Assert.assertEquals(fragDReleaseCount, 1); resetGraphMonitorCounts(); - navigationController.navigate(this).navigateBack().go(); + navigationController.navigate(this).back().go(); //->A->B waitTest(); Assert.assertEquals(fm.getFragments().size(), 4); @@ -135,7 +135,7 @@ public void test_should_reinject_last_fragment_and_release_top_fragment_on_singl Assert.assertEquals(fragDReleaseCount, 0); resetGraphMonitorCounts(); - navigationController.navigate(this).navigateBack().go(); + navigationController.navigate(this).back().go(); //->A waitTest(); Assert.assertEquals(fm.getFragments().size(), 4); @@ -155,7 +155,7 @@ public void test_should_reinject_last_fragment_and_release_top_fragment_on_singl Assert.assertEquals(fragDReleaseCount, 0); resetGraphMonitorCounts(); - navigationController.navigate(this).navigateBack().go(); + navigationController.navigate(this).back().go(); //quit waitTest(2000); Assert.assertEquals(fm.getFragments().size(), 4); @@ -353,7 +353,7 @@ public void test_should_release_and_inject_properly_on_single_step_back_navigati Assert.assertEquals(fragDReleaseCount, 1); resetGraphMonitorCounts(); - navigationController.navigate(this).navigateBack().go(); + navigationController.navigate(this).back().go(); //->A->B->C waitTest(1000); Assert.assertEquals(fragAInjectCount, 0); @@ -366,7 +366,7 @@ public void test_should_release_and_inject_properly_on_single_step_back_navigati Assert.assertEquals(fragDReleaseCount, 1); resetGraphMonitorCounts(); - navigationController.navigate(this).navigateBack().go(); + navigationController.navigate(this).back().go(); //->A->B waitTest(); Assert.assertEquals(fragAInjectCount, 0); @@ -379,7 +379,7 @@ public void test_should_release_and_inject_properly_on_single_step_back_navigati Assert.assertEquals(fragDReleaseCount, 0); resetGraphMonitorCounts(); - navigationController.navigate(this).navigateBack().go(); + navigationController.navigate(this).back().go(); //->A waitTest(); Assert.assertEquals(fragAInjectCount, 0); @@ -392,7 +392,7 @@ public void test_should_release_and_inject_properly_on_single_step_back_navigati Assert.assertEquals(fragDReleaseCount, 0); resetGraphMonitorCounts(); - navigationController.navigate(this).navigateBack().go(); + navigationController.navigate(this).back().go(); //quit waitTest(); Assert.assertEquals(fragAInjectCount, 0); @@ -444,7 +444,7 @@ public void test_should_release_and_inject_properly_on_fast_rewind_back_navigati Assert.assertEquals(fragDReleaseCount, 1); resetGraphMonitorCounts(); - navigationController.navigate(this).navigateBack().go(); + navigationController.navigate(this).back().go(); //quit waitTest(); Assert.assertEquals(fragAInjectCount, 0); @@ -496,7 +496,7 @@ public void test_should_release_and_inject_properly_on_fast_rewind_forward_navig Assert.assertEquals(fragDReleaseCount, 1); resetGraphMonitorCounts(); - navigationController.navigate(this).navigateBack().go(); + navigationController.navigate(this).back().go(); //->A->B waitTest(); Assert.assertEquals(fragAInjectCount, 0); @@ -509,7 +509,7 @@ public void test_should_release_and_inject_properly_on_fast_rewind_forward_navig Assert.assertEquals(fragDReleaseCount, 0); resetGraphMonitorCounts(); - navigationController.navigate(this).navigateBack().go(); + navigationController.navigate(this).back().go(); //->A waitTest(); Assert.assertEquals(fragAInjectCount, 0); @@ -522,7 +522,7 @@ public void test_should_release_and_inject_properly_on_fast_rewind_forward_navig Assert.assertEquals(fragDReleaseCount, 0); resetGraphMonitorCounts(); - navigationController.navigate(this).navigateBack().go(); + navigationController.navigate(this).back().go(); //quit waitTest(); Assert.assertEquals(fragAInjectCount, 0); diff --git a/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/nav/NavFragment.java b/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/nav/NavFragment.java index 06af145..8053bd6 100644 --- a/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/nav/NavFragment.java +++ b/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/nav/NavFragment.java @@ -59,7 +59,7 @@ public void onClick(View v) { clear.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { - navigationController.navigate(view).navigateBack(null).go(); + navigationController.navigate(view).back(null).go(); } }); diff --git a/library/android-mvc/src/main/java/com/shipdream/lib/android/mvc/view/MvcActivity.java b/library/android-mvc/src/main/java/com/shipdream/lib/android/mvc/view/MvcActivity.java index 4fd6273..d2dee26 100644 --- a/library/android-mvc/src/main/java/com/shipdream/lib/android/mvc/view/MvcActivity.java +++ b/library/android-mvc/src/main/java/com/shipdream/lib/android/mvc/view/MvcActivity.java @@ -252,7 +252,7 @@ public boolean onBackButtonPressed() { navigateBack = !topFragment.onBackButtonPressed(); } if (navigateBack) { - navigationController.navigate(this).navigateBack().go(); + navigationController.navigate(this).back().go(); } return true; } From fc140cd8e8e22ae8081a0fbca4a3d16a4c01c6eb Mon Sep 17 00:00:00 2001 From: Kejun Xia Date: Tue, 1 Dec 2015 19:53:21 +1100 Subject: [PATCH 09/26] Remove method navigator.go --- .../internal/NavigationControllerImpl.java | 8 +++---- .../mvc/controller/internal/Navigator.java | 24 +++++++++---------- .../lib/android/mvc/view/BaseTestCase.java | 4 ++-- .../injection/TestInjectionAndLifeCycle.java | 10 ++++---- .../nav/TestCaseNavigationAndInjection.java | 24 +++++++++---------- .../view/eventv2v/EventBusV2VActivity.java | 2 +- .../mvc/view/injection/FragmentInjection.java | 2 +- .../view/injection/InjectionTestActivity.java | 2 +- .../mvc/view/lifecycle/MvcTestActivity.java | 2 +- .../view/nav/MvcTestActivityNavigation.java | 2 +- .../lib/android/mvc/view/nav/NavFragment.java | 4 ++-- .../mvc/view/viewpager/TabFragmentA.java | 2 +- .../view/viewpager/ViewPagerTestActivity.java | 2 +- .../lib/android/mvc/view/MvcActivity.java | 2 +- 14 files changed, 45 insertions(+), 45 deletions(-) diff --git a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/internal/NavigationControllerImpl.java b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/internal/NavigationControllerImpl.java index d697398..dddf2f2 100644 --- a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/internal/NavigationControllerImpl.java +++ b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/internal/NavigationControllerImpl.java @@ -46,13 +46,13 @@ public Navigator navigate(Object sender, Class... preparedControllers) { @Override public void navigateTo(Object sender, String locationId) { // doNavigateTo(sender, locationId, false, null); - navigate(sender).to(locationId).go(); + navigate(sender).to(locationId); } @Override public void navigateTo(Object sender, String locationId, String clearTopToLocationId) { // doNavigateTo(sender, locationId, true, clearTopToLocationId); - navigate(sender).to(locationId, clearTopToLocationId).go(); + navigate(sender).to(locationId, clearTopToLocationId); } private void doNavigateTo(Object sender, String locationId, boolean clearTop, @@ -141,7 +141,7 @@ public void navigateBack(Object sender) { // checkAppExit(sender); // // dumpHistory(); - navigate(sender).back().go(); + navigate(sender).back(); } @Override @@ -187,7 +187,7 @@ public void navigateBack(Object sender, String toLocationId) { // dumpHistory(); // } - navigate(sender).back(toLocationId).go(); + navigate(sender).back(toLocationId); } private void checkAppExit(Object sender) { diff --git a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/internal/Navigator.java b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/internal/Navigator.java index 142a0bc..0258d82 100644 --- a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/internal/Navigator.java +++ b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/internal/Navigator.java @@ -26,14 +26,14 @@ public OnSettled getOnSettled() { return onSettled; } - public Navigator to(String locationId) { + public void to(String locationId) { doNavigateTo(locationId, false, null); - return this; + go(); } - public Navigator to(String locationId, String clearTopToLocationId) { + public void to(String locationId, String clearTopToLocationId) { doNavigateTo(locationId, true, clearTopToLocationId); - return this; + go(); } private void doNavigateTo(String locationId, boolean clearTop, @@ -93,30 +93,30 @@ private void doNavigateTo(String locationId, boolean clearTop, } } - public Navigator back() { + public void back() { NavLocation currentLoc = navigationController.getModel().getCurrentLocation(); if (currentLoc == null) { navigationController.logger.warn("Current location should never be null before navigating backwards."); - return this; + return; } NavLocation previousLoc = currentLoc.getPreviousLocation(); navigationController.getModel().setCurrentLocation(previousLoc); navigateEvent = new NavigationController.EventC2V.OnLocationBack(sender, currentLoc, previousLoc, false); - return this; + go(); } - public Navigator back(String toLocationId) { + public void back(String toLocationId) { NavLocation currentLoc = navigationController.getModel().getCurrentLocation(); if (currentLoc == null) { navigationController.logger.warn("Current location should never be null before navigating backwards."); - return this; + return; } if (currentLoc.getPreviousLocation() == null) { //Has already been the first location, don't do anything - return this; + return; } boolean success = false; @@ -142,7 +142,7 @@ public Navigator back(String toLocationId) { navigationController.getModel().setCurrentLocation(currentLoc); navigateEvent = new NavigationController.EventC2V.OnLocationBack(sender, previousLoc, currentLoc, true); } - return this; + go(); } public Navigator onSettled(OnSettled onSettled) { @@ -150,7 +150,7 @@ public Navigator onSettled(OnSettled onSettled) { return this; } - public void go() { + private void go() { if (navigateEvent != null) { navigationController.postC2VEvent(navigateEvent); diff --git a/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/BaseTestCase.java b/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/BaseTestCase.java index 92f7835..04aec57 100644 --- a/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/BaseTestCase.java +++ b/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/BaseTestCase.java @@ -184,8 +184,8 @@ protected void injectDependencies(ScopeCache mvcSingletonCache){ @After public void tearDown() throws Exception { - navigationController.navigate(this).back(null).go(); - navigationController.navigate(this).back().go(); + navigationController.navigate(this).back(null); + navigationController.navigate(this).back(); activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED); diff --git a/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/injection/TestInjectionAndLifeCycle.java b/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/injection/TestInjectionAndLifeCycle.java index becf789..cfba244 100644 --- a/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/injection/TestInjectionAndLifeCycle.java +++ b/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/injection/TestInjectionAndLifeCycle.java @@ -57,7 +57,7 @@ public void testShouldRetainInjectionsOfFragmentAAfterNavigatedToFragmentB() thr onView(withId(R.id.textB)).check(matches(withText("Added by FragmentA"))); onView(withId(R.id.textC)).check(matches(withText(""))); - navigationController.navigate(this).to(MvcTestActivityNavigation.Loc.B).go(); + navigationController.navigate(this).to(MvcTestActivityNavigation.Loc.B); waitTest(); //=============================> At B //onDestroyView is always called when a fragment is pushed to back stack @@ -72,7 +72,7 @@ public void testShouldRetainInjectionsOfFragmentAAfterNavigatedToFragmentB() thr "Added by FragmentB"))); onView(withId(R.id.textC)).check(matches(withText(""))); - navigationController.navigate(this).to(MvcTestActivityNavigation.Loc.C).go(); + navigationController.navigate(this).to(MvcTestActivityNavigation.Loc.C); waitTest(); //=============================> At C lifeCycleValidatorB.expect(LifeCycle.onPushingToBackStack, LifeCycle.onDestroyView); @@ -85,7 +85,7 @@ public void testShouldRetainInjectionsOfFragmentAAfterNavigatedToFragmentB() thr "Added by FragmentC"))); onView(withId(R.id.textC)).check(matches(withText("Added by FragmentC"))); - navigationController.navigate(this).back().go(); + navigationController.navigate(this).back(); waitTest(1000); //=============================> At B lifeCycleValidatorC.expect(LifeCycle.onDestroyView, LifeCycle.onDestroy); @@ -104,7 +104,7 @@ public void testShouldRetainInjectionsOfFragmentAAfterNavigatedToFragmentB() thr "Added by FragmentB"))); onView(withId(R.id.textC)).check(matches(withText(""))); - navigationController.navigate(this).back().go(); + navigationController.navigate(this).back(); waitTest(1000); //=============================> At A //onDestroy of previous Fragment(FragmentB) is not called until it's removed out from back stack @@ -140,7 +140,7 @@ public void test_should_delay_call_on_view_ready_on_sub_fragments_after_dependen onView(withId(R.id.textB)).check(matches(withText("Added by FragmentA"))); onView(withId(R.id.textC)).check(matches(withText(""))); - navigationController.navigate(this).to(MvcTestActivityNavigation.Loc.B).go(); + navigationController.navigate(this).to(MvcTestActivityNavigation.Loc.B); waitTest(); //=============================> At B onView(withId(R.id.textA)).check(matches(withText("Added by FragmentA\n" + diff --git a/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/nav/TestCaseNavigationAndInjection.java b/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/nav/TestCaseNavigationAndInjection.java index 20eaa0a..37c1f9b 100644 --- a/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/nav/TestCaseNavigationAndInjection.java +++ b/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/nav/TestCaseNavigationAndInjection.java @@ -92,7 +92,7 @@ public void test_should_reinject_last_fragment_and_release_top_fragment_on_singl FragmentManager fm = activity.getRootFragmentManager(); resetGraphMonitorCounts(); - navigationController.navigate(this).back().go(); + navigationController.navigate(this).back(); //->A->B->C waitTest(1000); Assert.assertEquals(fm.getFragments().size(), 4); @@ -114,7 +114,7 @@ public void test_should_reinject_last_fragment_and_release_top_fragment_on_singl Assert.assertEquals(fragDReleaseCount, 1); resetGraphMonitorCounts(); - navigationController.navigate(this).back().go(); + navigationController.navigate(this).back(); //->A->B waitTest(); Assert.assertEquals(fm.getFragments().size(), 4); @@ -135,7 +135,7 @@ public void test_should_reinject_last_fragment_and_release_top_fragment_on_singl Assert.assertEquals(fragDReleaseCount, 0); resetGraphMonitorCounts(); - navigationController.navigate(this).back().go(); + navigationController.navigate(this).back(); //->A waitTest(); Assert.assertEquals(fm.getFragments().size(), 4); @@ -155,7 +155,7 @@ public void test_should_reinject_last_fragment_and_release_top_fragment_on_singl Assert.assertEquals(fragDReleaseCount, 0); resetGraphMonitorCounts(); - navigationController.navigate(this).back().go(); + navigationController.navigate(this).back(); //quit waitTest(2000); Assert.assertEquals(fm.getFragments().size(), 4); @@ -353,7 +353,7 @@ public void test_should_release_and_inject_properly_on_single_step_back_navigati Assert.assertEquals(fragDReleaseCount, 1); resetGraphMonitorCounts(); - navigationController.navigate(this).back().go(); + navigationController.navigate(this).back(); //->A->B->C waitTest(1000); Assert.assertEquals(fragAInjectCount, 0); @@ -366,7 +366,7 @@ public void test_should_release_and_inject_properly_on_single_step_back_navigati Assert.assertEquals(fragDReleaseCount, 1); resetGraphMonitorCounts(); - navigationController.navigate(this).back().go(); + navigationController.navigate(this).back(); //->A->B waitTest(); Assert.assertEquals(fragAInjectCount, 0); @@ -379,7 +379,7 @@ public void test_should_release_and_inject_properly_on_single_step_back_navigati Assert.assertEquals(fragDReleaseCount, 0); resetGraphMonitorCounts(); - navigationController.navigate(this).back().go(); + navigationController.navigate(this).back(); //->A waitTest(); Assert.assertEquals(fragAInjectCount, 0); @@ -392,7 +392,7 @@ public void test_should_release_and_inject_properly_on_single_step_back_navigati Assert.assertEquals(fragDReleaseCount, 0); resetGraphMonitorCounts(); - navigationController.navigate(this).back().go(); + navigationController.navigate(this).back(); //quit waitTest(); Assert.assertEquals(fragAInjectCount, 0); @@ -444,7 +444,7 @@ public void test_should_release_and_inject_properly_on_fast_rewind_back_navigati Assert.assertEquals(fragDReleaseCount, 1); resetGraphMonitorCounts(); - navigationController.navigate(this).back().go(); + navigationController.navigate(this).back(); //quit waitTest(); Assert.assertEquals(fragAInjectCount, 0); @@ -496,7 +496,7 @@ public void test_should_release_and_inject_properly_on_fast_rewind_forward_navig Assert.assertEquals(fragDReleaseCount, 1); resetGraphMonitorCounts(); - navigationController.navigate(this).back().go(); + navigationController.navigate(this).back(); //->A->B waitTest(); Assert.assertEquals(fragAInjectCount, 0); @@ -509,7 +509,7 @@ public void test_should_release_and_inject_properly_on_fast_rewind_forward_navig Assert.assertEquals(fragDReleaseCount, 0); resetGraphMonitorCounts(); - navigationController.navigate(this).back().go(); + navigationController.navigate(this).back(); //->A waitTest(); Assert.assertEquals(fragAInjectCount, 0); @@ -522,7 +522,7 @@ public void test_should_release_and_inject_properly_on_fast_rewind_forward_navig Assert.assertEquals(fragDReleaseCount, 0); resetGraphMonitorCounts(); - navigationController.navigate(this).back().go(); + navigationController.navigate(this).back(); //quit waitTest(); Assert.assertEquals(fragAInjectCount, 0); diff --git a/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/eventv2v/EventBusV2VActivity.java b/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/eventv2v/EventBusV2VActivity.java index 2af7d4b..47cc378 100644 --- a/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/eventv2v/EventBusV2VActivity.java +++ b/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/eventv2v/EventBusV2VActivity.java @@ -39,7 +39,7 @@ public static class HomeFragment extends DelegateFragment { @Override protected void onStartUp() { - getNavigationController().navigate(this).to("TestFragment", null).go(); + getNavigationController().navigate(this).to("TestFragment", null); } @Override diff --git a/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/injection/FragmentInjection.java b/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/injection/FragmentInjection.java index 166c0cf..3d44578 100644 --- a/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/injection/FragmentInjection.java +++ b/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/injection/FragmentInjection.java @@ -85,7 +85,7 @@ public void onClick(View view) { }else if (item.equals("D")) { loc = MvcTestActivityNavigation.Loc.D; } - navigationController.navigate(view).to(loc).go(); + navigationController.navigate(view).to(loc); } }); diff --git a/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/injection/InjectionTestActivity.java b/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/injection/InjectionTestActivity.java index af18bd9..98d760a 100644 --- a/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/injection/InjectionTestActivity.java +++ b/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/injection/InjectionTestActivity.java @@ -49,7 +49,7 @@ public static class HomeFragment extends DelegateFragment { @Override protected void onStartUp() { - navigationController.navigate(this).to(MvcTestActivityNavigation.Loc.A, null).go(); + navigationController.navigate(this).to(MvcTestActivityNavigation.Loc.A, null); } } diff --git a/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/lifecycle/MvcTestActivity.java b/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/lifecycle/MvcTestActivity.java index 6333909..a5a419a 100644 --- a/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/lifecycle/MvcTestActivity.java +++ b/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/lifecycle/MvcTestActivity.java @@ -39,7 +39,7 @@ public static class HomeFragment extends DelegateFragment { @Override protected void onStartUp() { - getNavigationController().navigate(this).to("TestFragment", null).go(); + getNavigationController().navigate(this).to("TestFragment", null); } @Override diff --git a/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/nav/MvcTestActivityNavigation.java b/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/nav/MvcTestActivityNavigation.java index a9f7566..9a63440 100644 --- a/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/nav/MvcTestActivityNavigation.java +++ b/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/nav/MvcTestActivityNavigation.java @@ -74,7 +74,7 @@ public static class HomeFragment extends DelegateFragment { @Override protected void onStartUp() { Log.d("MvcTesting", "navigate"); - navigationController.navigate(this).to(Loc.A).go(); + navigationController.navigate(this).to(Loc.A); } @Override diff --git a/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/nav/NavFragment.java b/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/nav/NavFragment.java index 8053bd6..bef0a4e 100644 --- a/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/nav/NavFragment.java +++ b/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/nav/NavFragment.java @@ -51,7 +51,7 @@ public void onViewReady(View view, Bundle savedInstanceState, Reason reason) { next.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - navigationController.navigate(v).to(getNextFragmentLocId()).go(); + navigationController.navigate(v).to(getNextFragmentLocId()); } }); @@ -59,7 +59,7 @@ public void onClick(View v) { clear.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { - navigationController.navigate(view).back(null).go(); + navigationController.navigate(view).back(null); } }); diff --git a/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/viewpager/TabFragmentA.java b/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/viewpager/TabFragmentA.java index eacdca1..98e2fc2 100644 --- a/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/viewpager/TabFragmentA.java +++ b/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/viewpager/TabFragmentA.java @@ -49,7 +49,7 @@ public void onViewReady(View view, Bundle savedInstanceState, Reason reason) { textView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - navigationController.navigate(v).to(SubFragment.class.getSimpleName()).go(); + navigationController.navigate(v).to(SubFragment.class.getSimpleName()); } }); } diff --git a/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/viewpager/ViewPagerTestActivity.java b/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/viewpager/ViewPagerTestActivity.java index bcb9900..1d963d6 100644 --- a/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/viewpager/ViewPagerTestActivity.java +++ b/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/viewpager/ViewPagerTestActivity.java @@ -39,7 +39,7 @@ protected Class getDelegateFragmentClass() { public static class HomeFragment extends DelegateFragment { @Override protected void onStartUp() { - getNavigationController().navigate(this).to(ViewPagerHomeFragment.class.getSimpleName(), null).go(); + getNavigationController().navigate(this).to(ViewPagerHomeFragment.class.getSimpleName(), null); } } diff --git a/library/android-mvc/src/main/java/com/shipdream/lib/android/mvc/view/MvcActivity.java b/library/android-mvc/src/main/java/com/shipdream/lib/android/mvc/view/MvcActivity.java index d2dee26..64b561a 100644 --- a/library/android-mvc/src/main/java/com/shipdream/lib/android/mvc/view/MvcActivity.java +++ b/library/android-mvc/src/main/java/com/shipdream/lib/android/mvc/view/MvcActivity.java @@ -252,7 +252,7 @@ public boolean onBackButtonPressed() { navigateBack = !topFragment.onBackButtonPressed(); } if (navigateBack) { - navigationController.navigate(this).back().go(); + navigationController.navigate(this).back(); } return true; } From 1606de3fe5396bbea3de655e26924da383fadc62 Mon Sep 17 00:00:00 2001 From: Kejun Xia Date: Tue, 1 Dec 2015 23:43:58 +1100 Subject: [PATCH 10/26] Add reference and dereference methods --- .../java/com/shipdream/lib/poke/Graph.java | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/library/poke/src/main/java/com/shipdream/lib/poke/Graph.java b/library/poke/src/main/java/com/shipdream/lib/poke/Graph.java index d1e62a7..63890c5 100644 --- a/library/poke/src/main/java/com/shipdream/lib/poke/Graph.java +++ b/library/poke/src/main/java/com/shipdream/lib/poke/Graph.java @@ -272,6 +272,22 @@ public void consume(Os instance) { public void use(Class type, Annotation qualifier, Class injectAnnotation, Consumer consumer) throws ProvideException, CircularDependenciesException, ProviderMissingException { + T instance = reference(type, qualifier, injectAnnotation); + consumer.consume(instance); + dereference(instance, type, qualifier, injectAnnotation); + } + + /** + * Reference an injectable object and retain it. Use + * {@link #dereference(Object, Class, Annotation, Class)} to dereference it when it's not used + * any more. + * @param type the type of the object + * @param qualifier the qualifier + * @param injectAnnotation the inject annotation + * @return + */ + public T reference(Class type, Annotation qualifier, Class injectAnnotation) + throws ProviderMissingException, ProvideException, CircularDependenciesException { T instance; Provider provider = getProvider(type, qualifier); @@ -290,14 +306,26 @@ public void use(Class type, Annotation qualifier, if (provider.getReferenceCount() == 1) { provider.notifyInjected(instance); } - consumer.consume(instance); //Clear visiting records visitedFields.clear(); visitedInjectNodes.clear(); + return instance; + } + + /** + * Dereference an injectable object. When it's not referenced by anything else after this + * dereferencing, release its cached instance if possible. + * @param type the type of the object + * @param qualifier the qualifier + * @param injectAnnotation the inject annotation + */ + public void dereference(T instance, Class type, Annotation qualifier, + Class injectAnnotation) throws ProviderMissingException { doRelease(instance, null, type, qualifier, injectAnnotation); + Provider provider = getProvider(type, qualifier); provider.release(); checkToFreeProvider(provider); } From 39737099de125b064715052387dec035366c0604 Mon Sep 17 00:00:00 2001 From: Kejun Xia Date: Tue, 1 Dec 2015 23:46:19 +1100 Subject: [PATCH 11/26] Adding prepared instances for navigation --- .../shipdream/lib/android/mvc/MvcGraph.java | 54 +++++--- .../mvc/controller/NavigationController.java | 19 ++- .../internal/NavigationControllerImpl.java | 131 +----------------- .../mvc/controller/internal/Navigator.java | 17 ++- .../lib/android/mvc/view/MvcActivity.java | 8 ++ 5 files changed, 72 insertions(+), 157 deletions(-) diff --git a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/MvcGraph.java b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/MvcGraph.java index f64a8df..251626a 100644 --- a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/MvcGraph.java +++ b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/MvcGraph.java @@ -188,16 +188,35 @@ public void clearOnProviderFreedListeners() { * @param consumer Consume to use the instance */ public void use(final Class type, final Consumer consumer) { -// uiThreadRunner.runOnUiThread(new Runnable() { -// @Override -// public void run() { - try { - graph.use(type, Inject.class, consumer); - } catch (PokeException e) { - throw new MvcGraphException(e.getMessage(), e); - } -// } -// }); + try { + graph.use(type, Inject.class, consumer); + } catch (PokeException e) { + throw new MvcGraphException(e.getMessage(), e); + } + } + + /** + * Reference an injectable object and retain it. Use + * {@link #dereference(Object, Class, Annotation)} to dereference it when it's not used + * any more. + * @param type the type of the object + * @param qualifier the qualifier + * @return + */ + public T reference(Class type, Annotation qualifier) + throws ProviderMissingException, ProvideException, CircularDependenciesException { + return graph.reference(type, qualifier, Inject.class); + } + + /** + * Dereference an injectable object. When it's not referenced by anything else after this + * dereferencing, release its cached instance if possible. + * @param type the type of the object + * @param qualifier the qualifier + */ + public void dereference(T instance, Class type, Annotation qualifier) + throws ProviderMissingException { + graph.dereference(instance, type, qualifier, Inject.class); } /** @@ -303,16 +322,11 @@ public void consume(Os instance) { * @throws MvcGraphException throw when there are exceptions during the consumption of the instance */ public void use(final Class type, final Annotation qualifier, final Consumer consumer) { -// uiThreadRunner.runOnUiThread(new Runnable() { -// @Override -// public void run() { - try { - graph.use(type, qualifier, Inject.class, consumer); - } catch (PokeException e) { - throw new MvcGraphException(e.getMessage(), e); - } -// } -// }); + try { + graph.use(type, qualifier, Inject.class, consumer); + } catch (PokeException e) { + throw new MvcGraphException(e.getMessage(), e); + } } /** diff --git a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/NavigationController.java b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/NavigationController.java index 4ff7c6b..5de3f57 100644 --- a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/NavigationController.java +++ b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/NavigationController.java @@ -127,8 +127,16 @@ public interface NavigationController extends BaseController { - public OnLocationChanged(Object sender, NavLocation lastValue, NavLocation currentValue) { + private final Navigator navigator; + + public OnLocationChanged(Object sender, NavLocation lastValue, NavLocation currentValue, + Navigator navigator) { super(sender, lastValue, currentValue); + this.navigator = navigator; + } + + public Navigator getNavigator() { + return navigator; } } @@ -148,8 +156,9 @@ class OnLocationForward extends OnLocationChanged { * @param locationWhereHistoryClearedUpTo If need to clear location, up to where */ public OnLocationForward(Object sender, NavLocation lastValue, NavLocation currentValue, - boolean clearHistory, NavLocation locationWhereHistoryClearedUpTo) { - super(sender, lastValue, currentValue); + boolean clearHistory, NavLocation locationWhereHistoryClearedUpTo, + Navigator navigator) { + super(sender, lastValue, currentValue, navigator); this.clearHistory = clearHistory; this.locationWhereHistoryClearedUpTo = locationWhereHistoryClearedUpTo; } @@ -179,8 +188,8 @@ class OnLocationBack extends OnLocationChanged { private boolean fastRewind; public OnLocationBack(Object sender, NavLocation lastValue, NavLocation currentValue, - boolean fastRewind) { - super(sender, lastValue, currentValue); + boolean fastRewind, Navigator navigator) { + super(sender, lastValue, currentValue, navigator); this.fastRewind = fastRewind; } diff --git a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/internal/NavigationControllerImpl.java b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/internal/NavigationControllerImpl.java index dddf2f2..6bba59d 100644 --- a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/internal/NavigationControllerImpl.java +++ b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/internal/NavigationControllerImpl.java @@ -38,155 +38,28 @@ public Navigator navigate(Object sender) { } @Override - public Navigator navigate(Object sender, Class... preparedControllers) { - Navigator navigator = new Navigator(sender, this); + public Navigator navigate(Object sender, Class... preparedObjects) { + Navigator navigator = new Navigator(sender, this, preparedObjects); return navigator; } @Override public void navigateTo(Object sender, String locationId) { -// doNavigateTo(sender, locationId, false, null); navigate(sender).to(locationId); } @Override public void navigateTo(Object sender, String locationId, String clearTopToLocationId) { -// doNavigateTo(sender, locationId, true, clearTopToLocationId); navigate(sender).to(locationId, clearTopToLocationId); } - private void doNavigateTo(Object sender, String locationId, boolean clearTop, - String clearTopToLocationId) { - NavLocation clearedTopToLocation = null; - if (clearTop) { - if (clearTopToLocationId != null) { - //find out the top most location in the history stack with clearTopToLocationId - NavLocation currentLoc = getModel().getCurrentLocation(); - while (currentLoc != null) { - if (clearTopToLocationId.equals(currentLoc.getLocationId())) { - //Reverse the history to this location - clearedTopToLocation = currentLoc; - break; - } - currentLoc = currentLoc.getPreviousLocation(); - } - if (clearedTopToLocation == null) { - //The location to clear up to is not found. Disable clear top. - clearTop = false; - } - } else { - clearedTopToLocation = null; - } - } - - NavLocation lastLoc = getModel().getCurrentLocation(); - boolean locationChanged = false; - - if (clearTop) { - locationChanged = true; - } else { - if (locationId != null) { - if(lastLoc == null) { - locationChanged = true; - } else if(!locationId.equals(lastLoc.getLocationId())) { - locationChanged = true; - } - } - } - - if (locationChanged) { - NavLocation currentLoc = new NavLocation(); - currentLoc._setLocationId(locationId); - if (!clearTop) { - //Remember last location as previous location - currentLoc._setPreviousLocation(lastLoc); - } else { - //Remember clear top location location as the previous location - currentLoc._setPreviousLocation(clearedTopToLocation); - } - - getModel().setCurrentLocation(currentLoc); - - String lastLocId = lastLoc == null ? null : lastLoc.getLocationId(); - - EventC2V.OnLocationForward navEvent = new EventC2V.OnLocationForward(sender, lastLoc, - currentLoc, clearTop, clearedTopToLocation); - - postC2VEvent(navEvent); - - logger.trace("Nav Controller: Forward: {} -> {}", lastLocId, currentLoc.getLocationId()); - } - - dumpHistory(); - } - @Override public void navigateBack(Object sender) { -// NavLocation currentLoc = getModel().getCurrentLocation(); -// if (currentLoc == null) { -// logger.warn("Current location should never be null before navigating backwards."); -// return; -// } -// -// NavLocation previousLoc = currentLoc.getPreviousLocation(); -// getModel().setCurrentLocation(previousLoc); -// -// EventC2V.OnLocationBack navEvent = new EventC2V.OnLocationBack(sender, currentLoc, previousLoc, false); -// -// postC2VEvent(navEvent); -// -// logger.trace("Nav Controller: Backward: {} -> {}", currentLoc.getLocationId(), -// previousLoc == null ? "null" : previousLoc.getLocationId()); -// -// checkAppExit(sender); -// -// dumpHistory(); navigate(sender).back(); } @Override public void navigateBack(Object sender, String toLocationId) { -// NavLocation currentLoc = getModel().getCurrentLocation(); -// if (currentLoc == null) { -// logger.warn("Current location should never be null before navigating backwards."); -// return; -// } -// -// if (currentLoc.getPreviousLocation() == null) { -// //Has already been the first location, don't do anything -// return; -// } -// -// boolean success = false; -// NavLocation previousLoc = currentLoc; -// -// if(toLocationId == null) { -// success = true; -// } -// while (currentLoc != null) { -// if(toLocationId != null) { -// if (toLocationId.equals(currentLoc.getLocationId())) { -// success = true; -// break; -// } -// } else { -// if(currentLoc.getPreviousLocation() == null) { -// break; -// } -// } -// currentLoc = currentLoc.getPreviousLocation(); -// } -// if(success) { -// getModel().setCurrentLocation(currentLoc); -// postC2VEvent(new EventC2V.OnLocationBack(sender, previousLoc, currentLoc, true)); -// logger.trace("Nav Controller: Backward: {} -> {}", currentLoc.getLocationId(), -// previousLoc.getLocationId()); -// -// checkAppExit(sender); -// -// dumpHistory(); -// } - navigate(sender).back(toLocationId); } diff --git a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/internal/Navigator.java b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/internal/Navigator.java index 0258d82..2e34407 100644 --- a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/internal/Navigator.java +++ b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/internal/Navigator.java @@ -1,5 +1,6 @@ package com.shipdream.lib.android.mvc.controller.internal; +import com.shipdream.lib.android.mvc.Injector; import com.shipdream.lib.android.mvc.NavLocation; import com.shipdream.lib.android.mvc.controller.NavigationController; @@ -18,6 +19,12 @@ public interface OnSettled { this.navigationController = navigationController; } + Navigator(Object sender, NavigationControllerImpl navigationController, Class... preparedObjects) { + this.sender = sender; + this.navigationController = navigationController; + + } + public Object getSender() { return sender; } @@ -26,6 +33,10 @@ public OnSettled getOnSettled() { return onSettled; } + public Navigator prepare() { + return this; + } + public void to(String locationId) { doNavigateTo(locationId, false, null); go(); @@ -89,7 +100,7 @@ private void doNavigateTo(String locationId, boolean clearTop, navigationController.getModel().setCurrentLocation(currentLoc); navigateEvent = new NavigationController.EventC2V.OnLocationForward(sender, lastLoc, - currentLoc, clearTop, clearedTopToLocation); + currentLoc, clearTop, clearedTopToLocation, this); } } @@ -103,7 +114,7 @@ public void back() { NavLocation previousLoc = currentLoc.getPreviousLocation(); navigationController.getModel().setCurrentLocation(previousLoc); - navigateEvent = new NavigationController.EventC2V.OnLocationBack(sender, currentLoc, previousLoc, false); + navigateEvent = new NavigationController.EventC2V.OnLocationBack(sender, currentLoc, previousLoc, false, this); go(); } @@ -140,7 +151,7 @@ public void back(String toLocationId) { } if(success) { navigationController.getModel().setCurrentLocation(currentLoc); - navigateEvent = new NavigationController.EventC2V.OnLocationBack(sender, previousLoc, currentLoc, true); + navigateEvent = new NavigationController.EventC2V.OnLocationBack(sender, previousLoc, currentLoc, true, this); } go(); } diff --git a/library/android-mvc/src/main/java/com/shipdream/lib/android/mvc/view/MvcActivity.java b/library/android-mvc/src/main/java/com/shipdream/lib/android/mvc/view/MvcActivity.java index 64b561a..3ced5eb 100644 --- a/library/android-mvc/src/main/java/com/shipdream/lib/android/mvc/view/MvcActivity.java +++ b/library/android-mvc/src/main/java/com/shipdream/lib/android/mvc/view/MvcActivity.java @@ -449,6 +449,10 @@ private void performForwardNav(final NavigationController.EventC2V.OnLocationFor fragment.registerOnViewReadyListener(new Runnable() { @Override public void run() { + if (event.getNavigator() != null && event.getNavigator().getOnSettled() != null) { + event.getNavigator().getOnSettled().run(); + } + if (finalLastFrag != null) { finalLastFrag.releaseDependencies(); } @@ -521,6 +525,10 @@ private void performBackNav(final NavigationController.EventC2V.OnLocationBack e currentFrag.registerOnViewReadyListener(new Runnable() { @Override public void run() { + if (event.getNavigator() != null && event.getNavigator().getOnSettled() != null) { + event.getNavigator().getOnSettled().run(); + } + if (finalLastFrag != null) { finalLastFrag.releaseDependencies(); finalLastFrag.selfRelease = true; From 728debd50b7c73cfb7026d19425326ead1880143 Mon Sep 17 00:00:00 2001 From: Kejun Xia Date: Wed, 2 Dec 2015 23:23:14 +1100 Subject: [PATCH 12/26] Use navigator for each navigation to hold and delay releasing of controllers --- .../lib/android/mvc/Constructable.java | 27 ++++++++ .../shipdream/lib/android/mvc/Disposable.java | 3 + .../shipdream/lib/android/mvc/MvcGraph.java | 1 - .../lib/android/mvc/MvcGraphException.java | 2 +- .../lib/android/mvc/UiThreadRunner.java | 5 -- .../mvc/controller/NavigationController.java | 3 +- .../internal/BaseControllerImpl.java | 3 +- .../internal/NavigationControllerImpl.java | 6 -- .../mvc/controller/internal/Navigator.java | 69 ++++++++++++++++--- .../nav/TestCaseNavigationFromController.java | 54 +++++++-------- .../lib/android/mvc/UiThreadRunnerImpl.java | 31 --------- .../internal/__MvcControllerHelper.java | 11 +++ .../lib/android/mvc/view/AndroidMvc.java | 2 - .../lib/android/mvc/view/MvcActivity.java | 9 +-- .../java/com/shipdream/lib/poke/Consumer.java | 4 ++ 15 files changed, 139 insertions(+), 91 deletions(-) create mode 100644 library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/Constructable.java delete mode 100644 library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/UiThreadRunner.java delete mode 100644 library/android-mvc/src/main/java/com/shipdream/lib/android/mvc/UiThreadRunnerImpl.java create mode 100644 library/android-mvc/src/main/java/com/shipdream/lib/android/mvc/controller/internal/__MvcControllerHelper.java diff --git a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/Constructable.java b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/Constructable.java new file mode 100644 index 0000000..a3c7f07 --- /dev/null +++ b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/Constructable.java @@ -0,0 +1,27 @@ +/* + * Copyright 2015 Kejun Xia + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.shipdream.lib.android.mvc; + +/** + * Object has callback on construction + */ +public interface Constructable { + /** + * Execute onConstruct logic of given object + */ + void onConstruct(); +} diff --git a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/Disposable.java b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/Disposable.java index ffe58f3..0c29a13 100644 --- a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/Disposable.java +++ b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/Disposable.java @@ -16,6 +16,9 @@ package com.shipdream.lib.android.mvc; +/** + * Object has callback on disposal + */ public interface Disposable { /** * Execute onDisposed logic of given object diff --git a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/MvcGraph.java b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/MvcGraph.java index 251626a..ea74296 100644 --- a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/MvcGraph.java +++ b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/MvcGraph.java @@ -80,7 +80,6 @@ */ public class MvcGraph { private Logger logger = LoggerFactory.getLogger(getClass()); - static UiThreadRunner uiThreadRunner; ScopeCache singletonScopeCache; DefaultProviderFinder defaultProviderFinder; List stateManagedObjects = new ArrayList<>(); diff --git a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/MvcGraphException.java b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/MvcGraphException.java index 32ccf82..2200c80 100644 --- a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/MvcGraphException.java +++ b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/MvcGraphException.java @@ -1,7 +1,7 @@ package com.shipdream.lib.android.mvc; public class MvcGraphException extends RuntimeException { - MvcGraphException(String message, Throwable cause) { + public MvcGraphException(String message, Throwable cause) { super(message, cause); } } \ No newline at end of file diff --git a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/UiThreadRunner.java b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/UiThreadRunner.java deleted file mode 100644 index 1eabfc1..0000000 --- a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/UiThreadRunner.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.shipdream.lib.android.mvc; - -interface UiThreadRunner { - void runOnUiThread(Runnable runnable); -} diff --git a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/NavigationController.java b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/NavigationController.java index 5de3f57..d04455c 100644 --- a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/NavigationController.java +++ b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/NavigationController.java @@ -27,9 +27,8 @@ * Controller to navigate among different fragments in the SAME activity. */ public interface NavigationController extends BaseController { - Navigator navigate(Object sender); - Navigator navigate(Object sender, Class... preparedControllers); + Navigator navigate(Object sender); /** * diff --git a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/internal/BaseControllerImpl.java b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/internal/BaseControllerImpl.java index f029107..a4262b3 100644 --- a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/internal/BaseControllerImpl.java +++ b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/internal/BaseControllerImpl.java @@ -17,6 +17,7 @@ package com.shipdream.lib.android.mvc.controller.internal; +import com.shipdream.lib.android.mvc.Constructable; import com.shipdream.lib.android.mvc.Disposable; import com.shipdream.lib.android.mvc.StateKeeper; import com.shipdream.lib.android.mvc.StateManaged; @@ -39,7 +40,7 @@ * */ public abstract class BaseControllerImpl implements BaseController, - StateManaged, Disposable { + StateManaged, Constructable, Disposable { interface AndroidPoster { void post(EventBus eventBusC2V, BaseEventC2V eventC2V); } diff --git a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/internal/NavigationControllerImpl.java b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/internal/NavigationControllerImpl.java index 6bba59d..574c471 100644 --- a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/internal/NavigationControllerImpl.java +++ b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/internal/NavigationControllerImpl.java @@ -37,12 +37,6 @@ public Navigator navigate(Object sender) { return navigator; } - @Override - public Navigator navigate(Object sender, Class... preparedObjects) { - Navigator navigator = new Navigator(sender, this, preparedObjects); - return navigator; - } - @Override public void navigateTo(Object sender, String locationId) { navigate(sender).to(locationId); diff --git a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/internal/Navigator.java b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/internal/Navigator.java index 2e34407..5d860ce 100644 --- a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/internal/Navigator.java +++ b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/internal/Navigator.java @@ -1,39 +1,69 @@ package com.shipdream.lib.android.mvc.controller.internal; +import com.shipdream.lib.android.mvc.Constructable; import com.shipdream.lib.android.mvc.Injector; +import com.shipdream.lib.android.mvc.MvcGraphException; import com.shipdream.lib.android.mvc.NavLocation; import com.shipdream.lib.android.mvc.controller.NavigationController; +import com.shipdream.lib.poke.Consumer; +import com.shipdream.lib.poke.exception.PokeException; +import com.shipdream.lib.poke.exception.ProviderMissingException; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.lang.annotation.Annotation; +import java.util.ArrayList; +import java.util.List; public class Navigator { public interface OnSettled { void run(); } + private static class PendingReleaseInstance { + private Class type; + private Annotation qualifier; + private T instance; + } + private final Object sender; private OnSettled onSettled; private NavigationControllerImpl navigationController; private NavigationController.EventC2V.OnLocationChanged navigateEvent; + private List pendingReleaseInstances; Navigator(Object sender, NavigationControllerImpl navigationController) { this.sender = sender; this.navigationController = navigationController; } - Navigator(Object sender, NavigationControllerImpl navigationController, Class... preparedObjects) { - this.sender = sender; - this.navigationController = navigationController; - - } - public Object getSender() { return sender; } - public OnSettled getOnSettled() { - return onSettled; + public Navigator prepare(Class type, Consumer consumer) throws MvcGraphException { + prepare(type, null, consumer); + return this; } - public Navigator prepare() { + public Navigator prepare(Class type, Annotation qualifier, Consumer consumer) throws MvcGraphException { + try { + T instance = Injector.getGraph().reference(type, qualifier); + + consumer.consume(instance); + + if (pendingReleaseInstances == null) { + pendingReleaseInstances = new ArrayList<>(); + } + PendingReleaseInstance pendingReleaseInstance = new PendingReleaseInstance(); + pendingReleaseInstance.instance = instance; + pendingReleaseInstance.type = type; + pendingReleaseInstance.qualifier = qualifier; + pendingReleaseInstances.add(pendingReleaseInstance); + } catch (PokeException e) { + throw new MvcGraphException(e.getMessage(), e); + } return this; } @@ -161,6 +191,27 @@ public Navigator onSettled(OnSettled onSettled) { return this; } + /** + * Internal use. Don't do it in your app. + */ + void destroy() { + if (onSettled != null) { + onSettled.run(); + } + + if (pendingReleaseInstances != null) { + for (PendingReleaseInstance i : pendingReleaseInstances) { + try { + Injector.getGraph().dereference(i.instance, i.type, i.qualifier); + } catch (ProviderMissingException e) { + //should not happen + //in case this happens just logs it + navigationController.logger.warn("Failed to auto release {} after navigation settled", i.type.getName()); + } + } + } + } + private void go() { if (navigateEvent != null) { navigationController.postC2VEvent(navigateEvent); diff --git a/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/nav/TestCaseNavigationFromController.java b/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/nav/TestCaseNavigationFromController.java index 4c2c060..987e77b 100644 --- a/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/nav/TestCaseNavigationFromController.java +++ b/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/nav/TestCaseNavigationFromController.java @@ -167,42 +167,38 @@ public void test_should_release_injected_object_by_chained_navigation_controller final String valF = "ValueF = " + new Random().nextInt(); final String valG = "ValueG = " + new Random().nextInt(); - getActivity().runOnUiThread(new Runnable() { + navigationController.navigate(this).prepare(ControllerE.class, new Consumer() { @Override - public void run() { - Injector.getGraph().use(ControllerE.class, new Consumer() { - @Override - public void consume(ControllerE instance) { - instance.setValue(valE); - navigationController.navigateTo(this, MvcTestActivityNavigation.Loc.E); - } - }); - - Injector.getGraph().use(ControllerF.class, new Consumer() { - @Override - public void consume(ControllerF instance) { - instance.setValue(valF); - navigationController.navigateTo(this, MvcTestActivityNavigation.Loc.F); - } - }); - - Injector.getGraph().use(ControllerG.class, new Consumer() { - @Override - public void consume(ControllerG instance) { - instance.setValue(valG); - navigationController.navigateTo(this, MvcTestActivityNavigation.Loc.G); - } - }); + public void consume(ControllerE instance) { + instance.setValue(valE); } - }); + }).to(MvcTestActivityNavigation.Loc.E); - waitTest(); + waitTest(1000); + + navigationController.navigate(this).prepare(ControllerF.class, new Consumer() { + @Override + public void consume(ControllerF instance) { + instance.setValue(valF); + } + }).to(MvcTestActivityNavigation.Loc.F); + + waitTest(1000); + + navigationController.navigate(this).prepare(ControllerG.class, new Consumer() { + @Override + public void consume(ControllerG instance) { + instance.setValue(valG); + } + }).to(MvcTestActivityNavigation.Loc.G); + + waitTest(1000); //The value set to controller e in Injector.getGraph().use should be retained during the //navigation onView(withText(valG)).check(matches(isDisplayed())); - verify(disposeCheckerEMock, times(0)).onDisposed(); - verify(disposeCheckerFMock, times(0)).onDisposed(); + verify(disposeCheckerEMock, times(1)).onDisposed(); + verify(disposeCheckerFMock, times(1)).onDisposed(); verify(disposeCheckerGMock, times(0)).onDisposed(); resetDisposeCheckers(); navigationController.navigateBack(this); diff --git a/library/android-mvc/src/main/java/com/shipdream/lib/android/mvc/UiThreadRunnerImpl.java b/library/android-mvc/src/main/java/com/shipdream/lib/android/mvc/UiThreadRunnerImpl.java deleted file mode 100644 index 5eedb75..0000000 --- a/library/android-mvc/src/main/java/com/shipdream/lib/android/mvc/UiThreadRunnerImpl.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.shipdream.lib.android.mvc; - -import android.os.Handler; -import android.os.Looper; - -/** - * Internal use. - */ -public class UiThreadRunnerImpl implements UiThreadRunner { - private Handler handler; - - /** - * Internal use. - */ - public static void init() { - MvcGraph.uiThreadRunner = new UiThreadRunnerImpl(); - } - - @Override - public void runOnUiThread(Runnable runnable) { - if (Looper.getMainLooper().getThread() == Thread.currentThread()) { - runnable.run(); - } else { - //Android handler is presented, posting to the main thread on Android. - if (handler == null) { - handler = new Handler(Looper.getMainLooper()); - } - handler.post(runnable); - } - } -} diff --git a/library/android-mvc/src/main/java/com/shipdream/lib/android/mvc/controller/internal/__MvcControllerHelper.java b/library/android-mvc/src/main/java/com/shipdream/lib/android/mvc/controller/internal/__MvcControllerHelper.java new file mode 100644 index 0000000..bc7a7eb --- /dev/null +++ b/library/android-mvc/src/main/java/com/shipdream/lib/android/mvc/controller/internal/__MvcControllerHelper.java @@ -0,0 +1,11 @@ +package com.shipdream.lib.android.mvc.controller.internal; + +/** + * Internal use. + */ +//This class is to help access package hidden methods in controller.internal +public class __MvcControllerHelper { + public static void destroyNavigator(Navigator navigator) { + navigator.destroy(); + } +} diff --git a/library/android-mvc/src/main/java/com/shipdream/lib/android/mvc/view/AndroidMvc.java b/library/android-mvc/src/main/java/com/shipdream/lib/android/mvc/view/AndroidMvc.java index b763fd6..d7d15b7 100644 --- a/library/android-mvc/src/main/java/com/shipdream/lib/android/mvc/view/AndroidMvc.java +++ b/library/android-mvc/src/main/java/com/shipdream/lib/android/mvc/view/AndroidMvc.java @@ -21,7 +21,6 @@ import com.shipdream.lib.android.mvc.Injector; import com.shipdream.lib.android.mvc.MvcGraph; -import com.shipdream.lib.android.mvc.UiThreadRunnerImpl; import com.shipdream.lib.android.mvc.controller.internal.AndroidPosterImpl; import com.shipdream.lib.android.mvc.event.bus.EventBus; import com.shipdream.lib.android.mvc.event.bus.internal.EventBusImpl; @@ -81,7 +80,6 @@ public EventBus providesIEventBusC2V() { Injector.getGraph().register(new ViewComponent()); AndroidPosterImpl.init(); - UiThreadRunnerImpl.init(); } private AndroidMvc() { diff --git a/library/android-mvc/src/main/java/com/shipdream/lib/android/mvc/view/MvcActivity.java b/library/android-mvc/src/main/java/com/shipdream/lib/android/mvc/view/MvcActivity.java index 3ced5eb..fd02cd3 100644 --- a/library/android-mvc/src/main/java/com/shipdream/lib/android/mvc/view/MvcActivity.java +++ b/library/android-mvc/src/main/java/com/shipdream/lib/android/mvc/view/MvcActivity.java @@ -29,6 +29,7 @@ import com.shipdream.lib.android.mvc.NavLocation; import com.shipdream.lib.android.mvc.StateManaged; import com.shipdream.lib.android.mvc.controller.NavigationController; +import com.shipdream.lib.android.mvc.controller.internal.__MvcControllerHelper; import com.shipdream.lib.poke.util.ReflectUtils; import org.slf4j.Logger; @@ -449,8 +450,8 @@ private void performForwardNav(final NavigationController.EventC2V.OnLocationFor fragment.registerOnViewReadyListener(new Runnable() { @Override public void run() { - if (event.getNavigator() != null && event.getNavigator().getOnSettled() != null) { - event.getNavigator().getOnSettled().run(); + if (event.getNavigator() != null) { + __MvcControllerHelper.destroyNavigator(event.getNavigator()); } if (finalLastFrag != null) { @@ -525,8 +526,8 @@ private void performBackNav(final NavigationController.EventC2V.OnLocationBack e currentFrag.registerOnViewReadyListener(new Runnable() { @Override public void run() { - if (event.getNavigator() != null && event.getNavigator().getOnSettled() != null) { - event.getNavigator().getOnSettled().run(); + if (event.getNavigator() != null) { + __MvcControllerHelper.destroyNavigator(event.getNavigator()); } if (finalLastFrag != null) { diff --git a/library/poke/src/main/java/com/shipdream/lib/poke/Consumer.java b/library/poke/src/main/java/com/shipdream/lib/poke/Consumer.java index 3e7c4c4..a1211ff 100644 --- a/library/poke/src/main/java/com/shipdream/lib/poke/Consumer.java +++ b/library/poke/src/main/java/com/shipdream/lib/poke/Consumer.java @@ -1,5 +1,9 @@ package com.shipdream.lib.poke; +/** + * Consumer to consume an injected object + * @param + */ public abstract class Consumer { public abstract void consume(T instance); } From d985983ffa80a65eefab2ab9b882d8c3ec307497 Mon Sep 17 00:00:00 2001 From: Kejun Xia Date: Wed, 2 Dec 2015 23:23:57 +1100 Subject: [PATCH 13/26] Update documents --- .../lib/android/mvc/controller/NavigationController.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/NavigationController.java b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/NavigationController.java index d04455c..68e269c 100644 --- a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/NavigationController.java +++ b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/NavigationController.java @@ -51,7 +51,7 @@ public interface NavigationController extends BaseController * - *

Deprecated: use {@link #navigate(Object)} or {@link #navigate(Object, Class[])} instead

+ *

Deprecated: use {@link #navigate(Object)} instead

* * @param sender Who wants to navigate * @param locationId The id of the location navigate to @@ -82,7 +82,7 @@ public interface NavigationController extends BaseController * - *

Deprecated: use {@link #navigate(Object)} or {@link #navigate(Object, Class[])} instead

+ *

Deprecated: use {@link #navigate(Object)} instead

* * @param sender Who wants to navigate * @param locationId The id of the location navigate to @@ -97,7 +97,7 @@ public interface NavigationController extends BaseControllerDeprecated: use {@link #navigate(Object)} or {@link #navigate(Object, Class[])} instead

+ *

Deprecated: use {@link #navigate(Object)} instead

* * @param sender Who wants to navigate back */ @@ -110,7 +110,7 @@ public interface NavigationController extends BaseControllerDeprecated: use {@link #navigate(Object)} or {@link #navigate(Object, Class[])} instead

+ *

Deprecated: use {@link #navigate(Object)} instead

* * @param sender Who wants to navigate * @param toLocationId Null when needs to navigate to the very first location and all history From e8023993521dd24b9f89c7d8d10ae63bad09e691 Mon Sep 17 00:00:00 2001 From: Kejun Xia Date: Thu, 3 Dec 2015 20:31:57 +1100 Subject: [PATCH 14/26] Fix issue that graph.reference() doesn't increment nested instances' reference count correctly --- .../java/com/shipdream/lib/poke/Graph.java | 17 +- .../poke/TestInjectionReferenceCount2.java | 122 ------ .../poke/TestNestedInjectionAndRelease.java | 353 ++++++++++++++++++ 3 files changed, 356 insertions(+), 136 deletions(-) delete mode 100644 library/poke/src/test/java/com/shipdream/lib/poke/TestInjectionReferenceCount2.java create mode 100644 library/poke/src/test/java/com/shipdream/lib/poke/TestNestedInjectionAndRelease.java diff --git a/library/poke/src/main/java/com/shipdream/lib/poke/Graph.java b/library/poke/src/main/java/com/shipdream/lib/poke/Graph.java index 63890c5..e808eda 100644 --- a/library/poke/src/main/java/com/shipdream/lib/poke/Graph.java +++ b/library/poke/src/main/java/com/shipdream/lib/poke/Graph.java @@ -288,20 +288,9 @@ public void use(Class type, Annotation qualifier, */ public T reference(Class type, Annotation qualifier, Class injectAnnotation) throws ProviderMissingException, ProvideException, CircularDependenciesException { - T instance; - - Provider provider = getProvider(type, qualifier); - T cachedInstance = provider.findCachedInstance(); - if (cachedInstance != null) { - instance = cachedInstance; - } else { - T newInstance = provider.get(); - - doInject(newInstance, null, type, qualifier, injectAnnotation); - - instance = newInstance; - } - + Provider provider = getProvider(type, qualifier); + T instance = provider.get(); + doInject(instance, null, type, qualifier, injectAnnotation); provider.retain(); if (provider.getReferenceCount() == 1) { provider.notifyInjected(instance); diff --git a/library/poke/src/test/java/com/shipdream/lib/poke/TestInjectionReferenceCount2.java b/library/poke/src/test/java/com/shipdream/lib/poke/TestInjectionReferenceCount2.java deleted file mode 100644 index 11f0624..0000000 --- a/library/poke/src/test/java/com/shipdream/lib/poke/TestInjectionReferenceCount2.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright 2015 Kejun Xia - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.shipdream.lib.poke; - -import com.shipdream.lib.poke.exception.CircularDependenciesException; -import com.shipdream.lib.poke.exception.ProvideException; -import com.shipdream.lib.poke.exception.ProviderConflictException; -import com.shipdream.lib.poke.exception.ProviderMissingException; - -import org.junit.Assert; -import org.junit.Test; - -import javax.inject.Singleton; - -@SuppressWarnings("unchecked") -public class TestInjectionReferenceCount2 extends BaseTestCases { - - interface ControllerA { - - } - - static class ControllerAImpl implements ControllerA { - @MyInject - ManageA manageA; - } - - interface ManageA { - - } - - static class ManageAImpl implements ManageA { - - } - - static class ViewA { - @MyInject - ControllerA controllerA; - } - - static class ViewB { - @MyInject - ControllerA controllerA; - } - - static class ViewC { - @MyInject - ControllerA controllerA; - } - - static class TestComp extends Component { - @Singleton - @Provides - public ControllerA providesController() { - return new ControllerAImpl(); - } - - @Singleton - @Provides - public ManageA providersManageA() { - return new ManageAImpl(); - } - } - - @Test - public void should_release_nested_injection_until_last_instance_dereferenced() throws - ProvideException, ProviderConflictException, CircularDependenciesException, - ProviderMissingException { - ViewA viewA = new ViewA(); - ViewB viewB = new ViewB(); - ViewC viewC = new ViewC(); - - SimpleGraph graph = new SimpleGraph(); - Component component = new TestComp(); - graph.register(component); - - graph.inject(viewA, MyInject.class); - ControllerA controllerAInViewA = viewA.controllerA; - ManageA manageAInViewA = ((ControllerAImpl)viewA.controllerA).manageA; - - graph.inject(viewB, MyInject.class); - ControllerA controllerAInViewB = viewB.controllerA; - ManageA manageAInViewB = ((ControllerAImpl)viewA.controllerA).manageA; - - graph.inject(viewC, MyInject.class); - ControllerA controllerAInViewC = viewB.controllerA; - ManageA manageAInViewC = ((ControllerAImpl)viewA.controllerA).manageA; - - Assert.assertTrue(manageAInViewA == manageAInViewB); - - //ControllerA released first time by viewB but still referenced by viewA - //So controllerA should not be freed so does the manageA held by ControllerA - graph.release(viewC, MyInject.class); - Assert.assertTrue(graph.getProvider(ControllerA.class, null).get() == controllerAInViewA); - Assert.assertTrue(graph.getProvider(ManageA.class, null).get() == manageAInViewA); - Assert.assertEquals(2, graph.getProvider(ManageA.class, null).getReferenceCount()); - - graph.release(viewB, MyInject.class); - Assert.assertTrue(graph.getProvider(ControllerA.class, null).get() == controllerAInViewA); - Assert.assertTrue(graph.getProvider(ManageA.class, null).get() == manageAInViewA); - Assert.assertEquals(1, graph.getProvider(ManageA.class, null).getReferenceCount()); - - graph.release(viewA, MyInject.class); - Assert.assertTrue(graph.getProvider(ControllerA.class, null).get() != controllerAInViewA); - Assert.assertTrue(graph.getProvider(ManageA.class, null).get() != manageAInViewA); - Assert.assertEquals(0, graph.getProvider(ManageA.class, null).getReferenceCount()); - } - -} diff --git a/library/poke/src/test/java/com/shipdream/lib/poke/TestNestedInjectionAndRelease.java b/library/poke/src/test/java/com/shipdream/lib/poke/TestNestedInjectionAndRelease.java new file mode 100644 index 0000000..5b2b576 --- /dev/null +++ b/library/poke/src/test/java/com/shipdream/lib/poke/TestNestedInjectionAndRelease.java @@ -0,0 +1,353 @@ +/* + * Copyright 2015 Kejun Xia + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.shipdream.lib.poke; + +import com.shipdream.lib.poke.exception.CircularDependenciesException; +import com.shipdream.lib.poke.exception.ProvideException; +import com.shipdream.lib.poke.exception.ProviderConflictException; +import com.shipdream.lib.poke.exception.ProviderMissingException; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import javax.inject.Singleton; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +public class TestNestedInjectionAndRelease extends BaseTestCases { + interface Service { + } + + interface Controller { + } + + class OnFree { + void onFreed() {} + } + + Component component; + + class ControllerImpl implements Controller { + @MyInject + Service service; + } + + class ViewA { + @MyInject + Controller controller; + } + + class ViewB { + @MyInject + Controller controller; + } + + class ViewC { + @MyInject + Controller controller; + } + + private OnFree serviceOnFreed; + private OnFree controllerOnFreed; + private Service serviceMock; + private SimpleGraph graph; + private ScopeCache cache; + + @Before + public void setUp() throws Exception { + serviceOnFreed = mock(OnFree.class); + controllerOnFreed = mock(OnFree.class); + serviceMock = mock(Service.class); + graph = new SimpleGraph(); + component = new Component() { + @Provides + @Singleton + public Controller provideController() { + return new ControllerImpl(); + } + + @Provides + @Singleton + public Service providesFood2() { + return serviceMock; + } + }; + graph.register(component); + cache = component.getScopeCache(); + graph.registerProviderFreedListener(new Provider.OnFreedListener() { + @Override + public void onFreed(Provider provider) { + if (provider.type() == Service.class) { + serviceOnFreed.onFreed(); + } else if (provider.type() == Controller.class) { + controllerOnFreed.onFreed(); + } + } + }); + } + + @Test + public void should_not_release_nested_instance_until_all_of_its_holders_are_disposed_by_instance_injection() + throws ProvideException, ProviderConflictException, CircularDependenciesException, ProviderMissingException { + ViewA viewA = new ViewA(); + ViewB viewB = new ViewB(); + ViewC viewC = new ViewC(); + + //Simulate to navigate to ViewA + graph.inject(viewA, MyInject.class); + Controller controllerInA = viewA.controller; + Service serviceInA = ((ControllerImpl) viewA.controller).service; + + verify(serviceOnFreed, times(0)).onFreed(); + verify(controllerOnFreed, times(0)).onFreed(); + + //Simulate to navigate to ViewB + graph.inject(viewB, MyInject.class); + Controller controllerInB = viewB.controller; + Service serviceInB = ((ControllerImpl) viewB.controller).service; + graph.release(viewA, MyInject.class); + + verify(controllerOnFreed, times(0)).onFreed(); + verify(serviceOnFreed, times(0)).onFreed(); + Assert.assertNotNull(cache.findCacheItem(Controller.class, null)); + Assert.assertNotNull(cache.findCacheItem(Service.class, null)); + Assert.assertTrue(graph.getProvider(Controller.class, null).get() == controllerInA); + Assert.assertTrue(graph.getProvider(Controller.class, null).get() == controllerInB); + Assert.assertTrue(graph.getProvider(Service.class, null).get() == serviceInA); + Assert.assertTrue(graph.getProvider(Service.class, null).get() == serviceInB); + Assert.assertEquals(1, graph.getProvider(Controller.class, null).getReferenceCount()); + Assert.assertEquals(1, graph.getProvider(Service.class, null).getReferenceCount()); + + //Simulate to navigate to ViewC + graph.inject(viewC, MyInject.class); + Controller controllerInC = viewB.controller; + Service serviceInC = ((ControllerImpl) viewC.controller).service; + graph.release(viewB, MyInject.class); + + verify(controllerOnFreed, times(0)).onFreed(); + verify(serviceOnFreed, times(0)).onFreed(); + Assert.assertNotNull(cache.findCacheItem(Controller.class, null)); + Assert.assertNotNull(cache.findCacheItem(Service.class, null)); + Assert.assertTrue(graph.getProvider(Controller.class, null).get() == controllerInA); + Assert.assertTrue(graph.getProvider(Controller.class, null).get() == controllerInB); + Assert.assertTrue(graph.getProvider(Controller.class, null).get() == controllerInC); + Assert.assertTrue(graph.getProvider(Service.class, null).get() == serviceInA); + Assert.assertTrue(graph.getProvider(Service.class, null).get() == serviceInB); + Assert.assertTrue(graph.getProvider(Service.class, null).get() == serviceInC); + Assert.assertEquals(1, graph.getProvider(Controller.class, null).getReferenceCount()); + Assert.assertEquals(1, graph.getProvider(Service.class, null).getReferenceCount()); + + Assert.assertTrue(controllerInA == controllerInB); + Assert.assertTrue(controllerInB == controllerInC); + + //Simulate to navigate back to ViewB + graph.inject(viewB, MyInject.class); + Assert.assertTrue(controllerInC == viewB.controller); + Assert.assertTrue(serviceInC == serviceInB); + graph.release(viewC, MyInject.class); + + verify(controllerOnFreed, times(0)).onFreed(); + verify(serviceOnFreed, times(0)).onFreed(); + Assert.assertNotNull(cache.findCacheItem(Controller.class, null)); + Assert.assertNotNull(cache.findCacheItem(Service.class, null)); + Assert.assertTrue(graph.getProvider(Controller.class, null).get() == controllerInA); + Assert.assertTrue(graph.getProvider(Controller.class, null).get() == controllerInB); + Assert.assertTrue(graph.getProvider(Service.class, null).get() == serviceInA); + Assert.assertTrue(graph.getProvider(Service.class, null).get() == serviceInB); + Assert.assertEquals(1, graph.getProvider(Controller.class, null).getReferenceCount()); + Assert.assertEquals(1, graph.getProvider(Service.class, null).getReferenceCount()); + + //Simulate to navigate back to ViewA + graph.inject(viewA, MyInject.class); + Assert.assertTrue(controllerInB == viewA.controller); + Assert.assertTrue(serviceInB == serviceInA); + graph.release(viewB, MyInject.class); + + verify(controllerOnFreed, times(0)).onFreed(); + verify(serviceOnFreed, times(0)).onFreed(); + Assert.assertNotNull(cache.findCacheItem(Controller.class, null)); + Assert.assertNotNull(cache.findCacheItem(Service.class, null)); + Assert.assertTrue(graph.getProvider(Controller.class, null).get() == controllerInA); + Assert.assertTrue(graph.getProvider(Controller.class, null).get() == controllerInB); + Assert.assertTrue(graph.getProvider(Service.class, null).get() == serviceInA); + Assert.assertTrue(graph.getProvider(Service.class, null).get() == serviceInB); + Assert.assertEquals(1, graph.getProvider(Controller.class, null).getReferenceCount()); + Assert.assertEquals(1, graph.getProvider(Service.class, null).getReferenceCount()); + + //Simulate to navigate back to exit + graph.release(viewA, MyInject.class); + + verify(controllerOnFreed, times(1)).onFreed(); + verify(serviceOnFreed, times(1)).onFreed(); + Assert.assertNull(cache.findCacheItem(Controller.class, null)); + Assert.assertNull(cache.findCacheItem(Service.class, null)); + Assert.assertEquals(0, graph.getProvider(Controller.class, null).getReferenceCount()); + Assert.assertEquals(0, graph.getProvider(Service.class, null).getReferenceCount()); + } + + @Test + public void should_count_reference_correctly_for_reference_dereference_methods() + throws ProvideException, CircularDependenciesException, ProviderMissingException { + ViewA viewA = new ViewA(); + ViewB viewB = new ViewB(); + + //Simulate to navigate to ViewA + graph.reference(Controller.class, null, MyInject.class); + Assert.assertEquals(1, graph.getProvider(Controller.class, null).getReferenceCount()); + Assert.assertEquals(1, graph.getProvider(Service.class, null).getReferenceCount()); + + graph.inject(viewA, MyInject.class); + Assert.assertEquals(2, graph.getProvider(Controller.class, null).getReferenceCount()); + Assert.assertEquals(2, graph.getProvider(Service.class, null).getReferenceCount()); + + graph.dereference(viewA.controller, Controller.class, null, MyInject.class); + Assert.assertEquals(1, graph.getProvider(Controller.class, null).getReferenceCount()); + Assert.assertEquals(1, graph.getProvider(Service.class, null).getReferenceCount()); + + verify(serviceOnFreed, times(0)).onFreed(); + verify(controllerOnFreed, times(0)).onFreed(); + + //Simulate to navigate to ViewB + graph.reference(Controller.class, null, MyInject.class); + Assert.assertEquals(2, graph.getProvider(Controller.class, null).getReferenceCount()); + Assert.assertEquals(2, graph.getProvider(Service.class, null).getReferenceCount()); + + graph.inject(viewB, MyInject.class); + Assert.assertEquals(3, graph.getProvider(Controller.class, null).getReferenceCount()); + Assert.assertEquals(3, graph.getProvider(Service.class, null).getReferenceCount()); + + graph.release(viewA, MyInject.class); + Assert.assertEquals(2, graph.getProvider(Controller.class, null).getReferenceCount()); + Assert.assertEquals(2, graph.getProvider(Service.class, null).getReferenceCount()); + + graph.dereference(viewB.controller, Controller.class, null, MyInject.class); + Assert.assertEquals(1, graph.getProvider(Controller.class, null).getReferenceCount()); + Assert.assertEquals(1, graph.getProvider(Service.class, null).getReferenceCount()); + } + + @Test + public void should_not_release_nested_instance_until_all_of_its_holders_are_disposed_by_reference_methods() + throws ProvideException, ProviderConflictException, CircularDependenciesException, ProviderMissingException { + ViewA viewA = new ViewA(); + ViewB viewB = new ViewB(); + ViewC viewC = new ViewC(); + + //Simulate to navigate to ViewA + graph.reference(Controller.class, null, MyInject.class); + graph.inject(viewA, MyInject.class); + Controller controllerInA = viewA.controller; + Service serviceInA = ((ControllerImpl)viewA.controller).service; + graph.dereference(controllerInA, Controller.class, null, MyInject.class); + + verify(serviceOnFreed, times(0)).onFreed(); + verify(controllerOnFreed, times(0)).onFreed(); + + //Simulate to navigate to ViewB + graph.reference(Controller.class, null, MyInject.class); + graph.inject(viewB, MyInject.class); + Controller controllerInB = viewB.controller; + Service serviceInB = ((ControllerImpl)viewB.controller).service; + graph.release(viewA, MyInject.class); + graph.dereference(controllerInB, Controller.class, null, MyInject.class); + + verify(controllerOnFreed, times(0)).onFreed(); + verify(serviceOnFreed, times(0)).onFreed(); + Assert.assertNotNull(cache.findCacheItem(Controller.class, null)); + Assert.assertNotNull(cache.findCacheItem(Service.class, null)); + Assert.assertTrue(graph.getProvider(Controller.class, null).get() == controllerInA); + Assert.assertTrue(graph.getProvider(Controller.class, null).get() == controllerInB); + Assert.assertTrue(graph.getProvider(Service.class, null).get() == serviceInA); + Assert.assertTrue(graph.getProvider(Service.class, null).get() == serviceInB); + Assert.assertEquals(1, graph.getProvider(Controller.class, null).getReferenceCount()); + Assert.assertEquals(1, graph.getProvider(Service.class, null).getReferenceCount()); + + //Simulate to navigate to ViewC + graph.reference(Controller.class, null, MyInject.class); + graph.inject(viewC, MyInject.class); + Controller controllerInC = viewB.controller; + Service serviceInC = ((ControllerImpl)viewC.controller).service; + graph.release(viewB, MyInject.class); + graph.dereference(controllerInC, Controller.class, null, MyInject.class); + + verify(controllerOnFreed, times(0)).onFreed(); + verify(serviceOnFreed, times(0)).onFreed(); + Assert.assertNotNull(cache.findCacheItem(Controller.class, null)); + Assert.assertNotNull(cache.findCacheItem(Service.class, null)); + Assert.assertTrue(graph.getProvider(Controller.class, null).get() == controllerInA); + Assert.assertTrue(graph.getProvider(Controller.class, null).get() == controllerInB); + Assert.assertTrue(graph.getProvider(Controller.class, null).get() == controllerInC); + Assert.assertTrue(graph.getProvider(Service.class, null).get() == serviceInA); + Assert.assertTrue(graph.getProvider(Service.class, null).get() == serviceInB); + Assert.assertTrue(graph.getProvider(Service.class, null).get() == serviceInC); + Assert.assertEquals(1, graph.getProvider(Controller.class, null).getReferenceCount()); + Assert.assertEquals(1, graph.getProvider(Service.class, null).getReferenceCount()); + + Assert.assertTrue(controllerInA == controllerInB); + Assert.assertTrue(controllerInB == controllerInC); + + //Simulate to navigate back to ViewB + graph.reference(Controller.class, null, MyInject.class); + graph.inject(viewB, MyInject.class); + Assert.assertTrue(controllerInC == viewB.controller); + Assert.assertTrue(serviceInC == serviceInB); + graph.release(viewC, MyInject.class); + graph.dereference(viewB.controller, Controller.class, null, MyInject.class); + + verify(controllerOnFreed, times(0)).onFreed(); + verify(serviceOnFreed, times(0)).onFreed(); + Assert.assertNotNull(cache.findCacheItem(Controller.class, null)); + Assert.assertNotNull(cache.findCacheItem(Service.class, null)); + Assert.assertTrue(graph.getProvider(Controller.class, null).get() == controllerInA); + Assert.assertTrue(graph.getProvider(Controller.class, null).get() == controllerInB); + Assert.assertTrue(graph.getProvider(Service.class, null).get() == serviceInA); + Assert.assertTrue(graph.getProvider(Service.class, null).get() == serviceInB); + Assert.assertEquals(1, graph.getProvider(Controller.class, null).getReferenceCount()); + Assert.assertEquals(1, graph.getProvider(Service.class, null).getReferenceCount()); + + //Simulate to navigate back to ViewA + graph.reference(Controller.class, null, MyInject.class); + graph.inject(viewA, MyInject.class); + Assert.assertTrue(controllerInB == viewA.controller); + Assert.assertTrue(serviceInB == serviceInA); + graph.release(viewB, MyInject.class); + graph.dereference(viewA.controller, Controller.class, null, MyInject.class); + + verify(controllerOnFreed, times(0)).onFreed(); + verify(serviceOnFreed, times(0)).onFreed(); + Assert.assertNotNull(cache.findCacheItem(Controller.class, null)); + Assert.assertNotNull(cache.findCacheItem(Service.class, null)); + Assert.assertTrue(graph.getProvider(Controller.class, null).get() == controllerInA); + Assert.assertTrue(graph.getProvider(Controller.class, null).get() == controllerInB); + Assert.assertTrue(graph.getProvider(Service.class, null).get() == serviceInA); + Assert.assertTrue(graph.getProvider(Service.class, null).get() == serviceInB); + Assert.assertEquals(1, graph.getProvider(Controller.class, null).getReferenceCount()); + Assert.assertEquals(1, graph.getProvider(Service.class, null).getReferenceCount()); + + //Simulate to navigate back to exit + graph.release(viewA, MyInject.class); + + verify(controllerOnFreed, times(1)).onFreed(); + verify(serviceOnFreed, times(1)).onFreed(); + Assert.assertNull(cache.findCacheItem(Controller.class, null)); + Assert.assertNull(cache.findCacheItem(Service.class, null)); + Assert.assertEquals(0, graph.getProvider(Controller.class, null).getReferenceCount()); + Assert.assertEquals(0, graph.getProvider(Service.class, null).getReferenceCount()); + } +} From fa82752d2cc268b489b39cb948a92900583e8ce8 Mon Sep 17 00:00:00 2001 From: Kejun Xia Date: Thu, 3 Dec 2015 21:09:59 +1100 Subject: [PATCH 15/26] Don't release fragment in forward navigation --- .../mvc/controller/internal/Navigator.java | 8 +- .../internal/TestNavigationController.java | 4 +- .../injection/TestInjectionAndLifeCycle.java | 7 +- .../nav/TestCaseNavigationAndInjection.java | 186 ++++++++++-------- .../mvc/view/nav/TestCaseNavigationBasic.java | 27 ++- .../nav/TestCaseNavigationFromController.java | 48 ++--- .../viewpager/TestFragmentsInViewPager.java | 33 ++-- .../lib/android/mvc/view/MvcActivity.java | 5 - 8 files changed, 166 insertions(+), 152 deletions(-) diff --git a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/internal/Navigator.java b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/internal/Navigator.java index 5d860ce..b907edd 100644 --- a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/internal/Navigator.java +++ b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/internal/Navigator.java @@ -1,6 +1,5 @@ package com.shipdream.lib.android.mvc.controller.internal; -import com.shipdream.lib.android.mvc.Constructable; import com.shipdream.lib.android.mvc.Injector; import com.shipdream.lib.android.mvc.MvcGraphException; import com.shipdream.lib.android.mvc.NavLocation; @@ -9,9 +8,6 @@ import com.shipdream.lib.poke.exception.PokeException; import com.shipdream.lib.poke.exception.ProviderMissingException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.List; @@ -232,10 +228,8 @@ private void go() { checkAppExit(sender); } - - dumpHistory(); } - + dumpHistory(); } private void checkAppExit(Object sender) { diff --git a/library/android-mvc-controller/src/test/java/com/shipdream/lib/android/mvc/controller/internal/TestNavigationController.java b/library/android-mvc-controller/src/test/java/com/shipdream/lib/android/mvc/controller/internal/TestNavigationController.java index 61591b8..4847829 100644 --- a/library/android-mvc-controller/src/test/java/com/shipdream/lib/android/mvc/controller/internal/TestNavigationController.java +++ b/library/android-mvc-controller/src/test/java/com/shipdream/lib/android/mvc/controller/internal/TestNavigationController.java @@ -325,8 +325,8 @@ public void should_not_raise_navigate_back_event_when_fast_back_navigate_from_nu public void should_be_able_to_log_navigation_history() throws Exception { // Arrange Logger logger = mock(Logger.class); - ((NavigationControllerImpl)navigationController).dumpHistoryOnLocationChange = true; - ((NavigationControllerImpl) navigationController).logger = logger; + navigationController.dumpHistoryOnLocationChange = true; + navigationController.logger = logger; // Act navigationController.navigateTo(this, "any location", "back to location"); diff --git a/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/injection/TestInjectionAndLifeCycle.java b/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/injection/TestInjectionAndLifeCycle.java index cfba244..1c93040 100644 --- a/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/injection/TestInjectionAndLifeCycle.java +++ b/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/injection/TestInjectionAndLifeCycle.java @@ -97,7 +97,9 @@ public void testShouldRetainInjectionsOfFragmentAAfterNavigatedToFragmentB() thr LifeCycle.onViewReadyFirstTime, LifeCycle.onViewReadyPopOut, LifeCycle.onPoppedOutToFront); - onView(withId(R.id.textA)).check(matches(withText("Added by FragmentB"))); + onView(withId(R.id.textA)).check(matches(withText("Added by FragmentA\n" + + "Added by FragmentB\n" + + "Added by FragmentB"))); onView(withId(R.id.textB)).check(matches(withText("Added by FragmentA\n" + "Added by FragmentB\n" + "Added by FragmentC\n" + @@ -117,7 +119,8 @@ public void testShouldRetainInjectionsOfFragmentAAfterNavigatedToFragmentB() thr LifeCycle.onViewReadyFirstTime, LifeCycle.onViewReadyPopOut, LifeCycle.onPoppedOutToFront); - onView(withId(R.id.textA)).check(matches(withText( + onView(withId(R.id.textA)).check(matches(withText("Added by FragmentA\n" + + "Added by FragmentB\n" + "Added by FragmentB\n" + "Added by FragmentA"))); onView(withId(R.id.textB)).check(matches(withText("Added by FragmentA\n" + diff --git a/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/nav/TestCaseNavigationAndInjection.java b/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/nav/TestCaseNavigationAndInjection.java index 37c1f9b..8837e92 100644 --- a/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/nav/TestCaseNavigationAndInjection.java +++ b/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/nav/TestCaseNavigationAndInjection.java @@ -87,7 +87,7 @@ public void onRelease(Object target) { @Test public void test_should_reinject_last_fragment_and_release_top_fragment_on_single_step_back_navigation() throws Throwable { - prepareAndCheckStack(); + prepareAndCheckStack(true); //->A->B->C->D FragmentManager fm = activity.getRootFragmentManager(); @@ -108,7 +108,7 @@ public void test_should_reinject_last_fragment_and_release_top_fragment_on_singl Assert.assertEquals(fragAReleaseCount, 0); Assert.assertEquals(fragBInjectCount, 0); Assert.assertEquals(fragBReleaseCount, 0); - Assert.assertEquals(fragCInjectCount, 1); + Assert.assertEquals(fragCInjectCount, 0); Assert.assertEquals(fragCReleaseCount, 0); Assert.assertEquals(fragDInjectCount, 0); Assert.assertEquals(fragDReleaseCount, 1); @@ -127,7 +127,7 @@ public void test_should_reinject_last_fragment_and_release_top_fragment_on_singl Assert.assertTrue(fm.getBackStackEntryAt(1).getName().contains(MvcTestActivityNavigation.Loc.B)); Assert.assertEquals(fragAInjectCount, 0); Assert.assertEquals(fragAReleaseCount, 0); - Assert.assertEquals(fragBInjectCount, 1); + Assert.assertEquals(fragBInjectCount, 0); Assert.assertEquals(fragBReleaseCount, 0); Assert.assertEquals(fragCInjectCount, 0); Assert.assertEquals(fragCReleaseCount, 1); @@ -145,7 +145,7 @@ public void test_should_reinject_last_fragment_and_release_top_fragment_on_singl Assert.assertNull(fm.getFragments().get(3)); Assert.assertEquals(fm.getBackStackEntryCount(), 1); Assert.assertTrue(fm.getBackStackEntryAt(0).getName().contains(MvcTestActivityNavigation.Loc.A)); - Assert.assertEquals(fragAInjectCount, 1); + Assert.assertEquals(fragAInjectCount, 0); Assert.assertEquals(fragAReleaseCount, 0); Assert.assertEquals(fragBInjectCount, 0); Assert.assertEquals(fragBReleaseCount, 1); @@ -181,6 +181,7 @@ public void test_should_release_top_fragment_and_inject_new_fragment_on_forward_ prepareAndCheckStack(); //->A->B->C->D + waitTest(200); resetGraphMonitorCounts(); //Now clear history up to A and put C on it. Then A should pop out without re navigationController.navigateTo(this, MvcTestActivityNavigation.Loc.C, MvcTestActivityNavigation.Loc.A); @@ -194,12 +195,12 @@ public void test_should_release_top_fragment_and_inject_new_fragment_on_forward_ Assert.assertEquals(fm.getBackStackEntryCount(), 2); Assert.assertTrue(fm.getBackStackEntryAt(0).getName().contains(MvcTestActivityNavigation.Loc.A)); Assert.assertTrue(fm.getBackStackEntryAt(1).getName().contains(MvcTestActivityNavigation.Loc.C)); - Assert.assertEquals(fragAInjectCount, 1); + Assert.assertEquals(fragAInjectCount, 0); Assert.assertEquals(fragAReleaseCount, 0); Assert.assertEquals(fragBInjectCount, 0); - Assert.assertEquals(fragBReleaseCount, 0); + Assert.assertEquals(fragBReleaseCount, 1); Assert.assertEquals(fragCInjectCount, 1); - Assert.assertEquals(fragCReleaseCount, 0); + Assert.assertEquals(fragCReleaseCount, 1); Assert.assertEquals(fragDInjectCount, 0); Assert.assertEquals(fragDReleaseCount, 1); } @@ -211,6 +212,7 @@ public void test_should_release_top_fragment_and_inject_new_fragment_on_forward_ prepareAndCheckStack(); //->A->B->C->D + waitTest(); resetGraphMonitorCounts(); //Now clear history up to A and put C on it. Then A should pop out without re navigationController.navigateTo(this, MvcTestActivityNavigation.Loc.B, null); @@ -224,11 +226,11 @@ public void test_should_release_top_fragment_and_inject_new_fragment_on_forward_ Assert.assertEquals(fm.getBackStackEntryCount(), 1); Assert.assertTrue(fm.getBackStackEntryAt(0).getName().contains(MvcTestActivityNavigation.Loc.B)); Assert.assertEquals(fragAInjectCount, 0); - Assert.assertEquals(fragAReleaseCount, 0); + Assert.assertEquals(fragAReleaseCount, 1); Assert.assertEquals(fragBInjectCount, 1); - Assert.assertEquals(fragBReleaseCount, 0); + Assert.assertEquals(fragBReleaseCount, 1); Assert.assertEquals(fragCInjectCount, 0); - Assert.assertEquals(fragCReleaseCount, 0); + Assert.assertEquals(fragCReleaseCount, 1); Assert.assertEquals(fragDInjectCount, 0); Assert.assertEquals(fragDReleaseCount, 1); } @@ -240,6 +242,7 @@ public void test_should_release_top_fragment_and_inject_home_fragment_with_fast_ prepareAndCheckStack(); //->A->B->C->D + waitTest(200); resetGraphMonitorCounts(); navigationController.navigateBack(this, MvcTestActivityNavigation.Loc.B); //->A->B @@ -254,10 +257,10 @@ public void test_should_release_top_fragment_and_inject_home_fragment_with_fast_ Assert.assertTrue(fm.getBackStackEntryAt(1).getName().contains(MvcTestActivityNavigation.Loc.B)); Assert.assertEquals(fragAInjectCount, 0); Assert.assertEquals(fragAReleaseCount, 0); - Assert.assertEquals(fragBInjectCount, 1); + Assert.assertEquals(fragBInjectCount, 0); Assert.assertEquals(fragBReleaseCount, 0); Assert.assertEquals(fragCInjectCount, 0); - Assert.assertEquals(fragCReleaseCount, 0); + Assert.assertEquals(fragCReleaseCount, 1); Assert.assertEquals(fragDInjectCount, 0); Assert.assertEquals(fragDReleaseCount, 1); } @@ -269,6 +272,7 @@ public void test_should_release_top_fragment_and_inject_home_fragment_when_clear prepareAndCheckStack(); //->A->B->C->D + waitTest(200); resetGraphMonitorCounts(); navigationController.navigateBack(this, null); //->A @@ -280,12 +284,12 @@ public void test_should_release_top_fragment_and_inject_home_fragment_when_clear Assert.assertNull(fm.getFragments().get(3)); Assert.assertEquals(fm.getBackStackEntryCount(), 1); Assert.assertTrue(fm.getBackStackEntryAt(0).getName().contains(MvcTestActivityNavigation.Loc.A)); - Assert.assertEquals(fragAInjectCount, 1); + Assert.assertEquals(fragAInjectCount, 0); Assert.assertEquals(fragAReleaseCount, 0); Assert.assertEquals(fragBInjectCount, 0); - Assert.assertEquals(fragBReleaseCount, 0); + Assert.assertEquals(fragBReleaseCount, 1); Assert.assertEquals(fragCInjectCount, 0); - Assert.assertEquals(fragCReleaseCount, 0); + Assert.assertEquals(fragCReleaseCount, 1); Assert.assertEquals(fragDInjectCount, 0); Assert.assertEquals(fragDReleaseCount, 1); } @@ -317,14 +321,14 @@ public void test_should_release_top_fragment_and_inject_home_fragment_when_clear Assert.assertNull(fm.getFragments().get(5)); Assert.assertEquals(fm.getBackStackEntryCount(), 1); Assert.assertTrue(fm.getBackStackEntryAt(0).getName().contains(MvcTestActivityNavigation.Loc.A)); - Assert.assertEquals(2, fragAInjectCount); - Assert.assertTrue(fragAInjectCount == fragAReleaseCount + 1); + Assert.assertEquals(0, fragAInjectCount); + Assert.assertEquals(1, fragAReleaseCount); + Assert.assertEquals(0, fragBInjectCount); Assert.assertEquals(1, fragBReleaseCount); - Assert.assertTrue(fragBInjectCount == fragBReleaseCount); - Assert.assertEquals(1, fragCInjectCount); - Assert.assertTrue(fragCReleaseCount == fragCInjectCount + 1); - Assert.assertEquals(1, fragDInjectCount); - Assert.assertTrue(fragDInjectCount == fragDReleaseCount); + Assert.assertEquals(0, fragCInjectCount); + Assert.assertEquals(2, fragCReleaseCount); + Assert.assertEquals(0, fragDInjectCount); + Assert.assertEquals(1, fragDReleaseCount); } @Test @@ -536,83 +540,97 @@ public void test_should_release_and_inject_properly_on_fast_rewind_forward_navig } private void prepareAndCheckStack() throws InterruptedException { - //The activity will navigate to fragment a on launch - Assert.assertEquals(fragAInjectCount, 1); - Assert.assertEquals(fragAReleaseCount, 0); - Assert.assertEquals(fragBInjectCount, 0); - Assert.assertEquals(fragBReleaseCount, 0); - Assert.assertEquals(fragCInjectCount, 0); - Assert.assertEquals(fragCReleaseCount, 0); - Assert.assertEquals(fragDInjectCount, 0); - Assert.assertEquals(fragDReleaseCount, 0); + prepareAndCheckStack(false); + } + + private void prepareAndCheckStack(boolean check) throws InterruptedException { + if (check) { + //The activity will navigate to fragment a on launch + Assert.assertEquals(fragAInjectCount, 1); + Assert.assertEquals(fragAReleaseCount, 0); + Assert.assertEquals(fragBInjectCount, 0); + Assert.assertEquals(fragBReleaseCount, 0); + Assert.assertEquals(fragCInjectCount, 0); + Assert.assertEquals(fragCReleaseCount, 0); + Assert.assertEquals(fragDInjectCount, 0); + Assert.assertEquals(fragDReleaseCount, 0); + } FragmentManager fm = activity.getRootFragmentManager(); //->A //should not take effect to navigate to the same location navigationController.navigateTo(this, MvcTestActivityNavigation.Loc.A); //->A - waitTest(); - Assert.assertEquals(fm.getFragments().size(), 1); - Assert.assertTrue(fm.getFragments().get(0) instanceof NavFragmentA); - Assert.assertEquals(fm.getBackStackEntryCount(), 1); - Assert.assertTrue(fm.getBackStackEntryAt(0).getName().contains(MvcTestActivityNavigation.Loc.A)); - - Assert.assertEquals(fragAInjectCount, 1); - Assert.assertEquals(fragAReleaseCount, 0); + if (check) { + waitTest(); + Assert.assertEquals(fm.getFragments().size(), 1); + Assert.assertTrue(fm.getFragments().get(0) instanceof NavFragmentA); + Assert.assertEquals(fm.getBackStackEntryCount(), 1); + Assert.assertTrue(fm.getBackStackEntryAt(0).getName().contains(MvcTestActivityNavigation.Loc.A)); + + Assert.assertEquals(fragAInjectCount, 1); + Assert.assertEquals(fragAReleaseCount, 0); + } navigationController.navigateTo(this, MvcTestActivityNavigation.Loc.B); //->A->B - waitTest(); - Assert.assertEquals(fm.getFragments().size(), 2); - Assert.assertTrue(fm.getFragments().get(0) instanceof NavFragmentA); - Assert.assertTrue(fm.getFragments().get(1) instanceof NavFragmentB); - Assert.assertEquals(fm.getBackStackEntryCount(), 2); - Assert.assertTrue(fm.getBackStackEntryAt(0).getName().contains(MvcTestActivityNavigation.Loc.A)); - Assert.assertTrue(fm.getBackStackEntryAt(1).getName().contains(MvcTestActivityNavigation.Loc.B)); - Assert.assertEquals(fragAInjectCount, 1); - Assert.assertEquals(fragAReleaseCount, 1); - Assert.assertEquals(fragBInjectCount, 1); - Assert.assertEquals(fragBReleaseCount, 0); + if (check) { + waitTest(); + Assert.assertEquals(fm.getFragments().size(), 2); + Assert.assertTrue(fm.getFragments().get(0) instanceof NavFragmentA); + Assert.assertTrue(fm.getFragments().get(1) instanceof NavFragmentB); + Assert.assertEquals(fm.getBackStackEntryCount(), 2); + Assert.assertTrue(fm.getBackStackEntryAt(0).getName().contains(MvcTestActivityNavigation.Loc.A)); + Assert.assertTrue(fm.getBackStackEntryAt(1).getName().contains(MvcTestActivityNavigation.Loc.B)); + Assert.assertEquals(fragAInjectCount, 1); + Assert.assertEquals(fragAReleaseCount, 0); + Assert.assertEquals(fragBInjectCount, 1); + Assert.assertEquals(fragBReleaseCount, 0); + } navigationController.navigateTo(this, MvcTestActivityNavigation.Loc.C); //->A->B->C - waitTest(); - Assert.assertEquals(fm.getFragments().size(), 3); - Assert.assertTrue(fm.getFragments().get(0) instanceof NavFragmentA); - Assert.assertTrue(fm.getFragments().get(1) instanceof NavFragmentB); - Assert.assertTrue(fm.getFragments().get(2) instanceof NavFragmentC); - Assert.assertEquals(fm.getBackStackEntryCount(), 3); - Assert.assertTrue(fm.getBackStackEntryAt(0).getName().contains(MvcTestActivityNavigation.Loc.A)); - Assert.assertTrue(fm.getBackStackEntryAt(1).getName().contains(MvcTestActivityNavigation.Loc.B)); - Assert.assertTrue(fm.getBackStackEntryAt(2).getName().contains(MvcTestActivityNavigation.Loc.C)); - Assert.assertEquals(fragAInjectCount, 1); - Assert.assertEquals(fragAReleaseCount, 1); - Assert.assertEquals(fragBInjectCount, 1); - Assert.assertEquals(fragBReleaseCount, 1); - Assert.assertEquals(fragCInjectCount, 1); - Assert.assertEquals(fragCReleaseCount, 0); + if (check) { + waitTest(); + Assert.assertEquals(fm.getFragments().size(), 3); + Assert.assertTrue(fm.getFragments().get(0) instanceof NavFragmentA); + Assert.assertTrue(fm.getFragments().get(1) instanceof NavFragmentB); + Assert.assertTrue(fm.getFragments().get(2) instanceof NavFragmentC); + Assert.assertEquals(fm.getBackStackEntryCount(), 3); + Assert.assertTrue(fm.getBackStackEntryAt(0).getName().contains(MvcTestActivityNavigation.Loc.A)); + Assert.assertTrue(fm.getBackStackEntryAt(1).getName().contains(MvcTestActivityNavigation.Loc.B)); + Assert.assertTrue(fm.getBackStackEntryAt(2).getName().contains(MvcTestActivityNavigation.Loc.C)); + Assert.assertEquals(fragAInjectCount, 1); + Assert.assertEquals(fragAReleaseCount, 0); + Assert.assertEquals(fragBInjectCount, 1); + Assert.assertEquals(fragBReleaseCount, 0); + Assert.assertEquals(fragCInjectCount, 1); + Assert.assertEquals(fragCReleaseCount, 0); + } navigationController.navigateTo(this, MvcTestActivityNavigation.Loc.D); //->A->B->C->D - waitTest(); - Assert.assertEquals(fm.getFragments().size(), 4); - Assert.assertTrue(fm.getFragments().get(0) instanceof NavFragmentA); - Assert.assertTrue(fm.getFragments().get(1) instanceof NavFragmentB); - Assert.assertTrue(fm.getFragments().get(2) instanceof NavFragmentC); - Assert.assertTrue(fm.getFragments().get(3) instanceof NavFragmentD); - Assert.assertEquals(fm.getBackStackEntryCount(), 4); - Assert.assertTrue(fm.getBackStackEntryAt(0).getName().contains(MvcTestActivityNavigation.Loc.A)); - Assert.assertTrue(fm.getBackStackEntryAt(1).getName().contains(MvcTestActivityNavigation.Loc.B)); - Assert.assertTrue(fm.getBackStackEntryAt(2).getName().contains(MvcTestActivityNavigation.Loc.C)); - Assert.assertTrue(fm.getBackStackEntryAt(3).getName().contains(MvcTestActivityNavigation.Loc.D)); - Assert.assertEquals(fragAInjectCount, 1); - Assert.assertEquals(fragAReleaseCount, 1); - Assert.assertEquals(fragBInjectCount, 1); - Assert.assertEquals(fragBReleaseCount, 1); - Assert.assertEquals(fragCInjectCount, 1); - Assert.assertEquals(fragCReleaseCount, 1); - Assert.assertEquals(fragDInjectCount, 1); - Assert.assertEquals(fragDReleaseCount, 0); + if (check) { + waitTest(); + Assert.assertEquals(fm.getFragments().size(), 4); + Assert.assertTrue(fm.getFragments().get(0) instanceof NavFragmentA); + Assert.assertTrue(fm.getFragments().get(1) instanceof NavFragmentB); + Assert.assertTrue(fm.getFragments().get(2) instanceof NavFragmentC); + Assert.assertTrue(fm.getFragments().get(3) instanceof NavFragmentD); + Assert.assertEquals(fm.getBackStackEntryCount(), 4); + Assert.assertTrue(fm.getBackStackEntryAt(0).getName().contains(MvcTestActivityNavigation.Loc.A)); + Assert.assertTrue(fm.getBackStackEntryAt(1).getName().contains(MvcTestActivityNavigation.Loc.B)); + Assert.assertTrue(fm.getBackStackEntryAt(2).getName().contains(MvcTestActivityNavigation.Loc.C)); + Assert.assertTrue(fm.getBackStackEntryAt(3).getName().contains(MvcTestActivityNavigation.Loc.D)); + Assert.assertEquals(fragAInjectCount, 1); + Assert.assertEquals(fragAReleaseCount, 0); + Assert.assertEquals(fragBInjectCount, 1); + Assert.assertEquals(fragBReleaseCount, 0); + Assert.assertEquals(fragCInjectCount, 1); + Assert.assertEquals(fragCReleaseCount, 0); + Assert.assertEquals(fragDInjectCount, 1); + Assert.assertEquals(fragDReleaseCount, 0); + } } private void resetGraphMonitorCounts () { diff --git a/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/nav/TestCaseNavigationBasic.java b/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/nav/TestCaseNavigationBasic.java index d5e2620..eccff96 100644 --- a/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/nav/TestCaseNavigationBasic.java +++ b/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/nav/TestCaseNavigationBasic.java @@ -45,7 +45,6 @@ import static org.mockito.Mockito.reset; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; public class TestCaseNavigationBasic extends BaseTestCase { private Logger logger = LoggerFactory.getLogger(getClass()); @@ -138,28 +137,28 @@ public void testShouldReleaseInjectionsAfterFragmentsArePoppedOut() throws Throw testNavigateToA(); testNavigateToB(); waitTest(1000); - verify(disposeCheckerAMock, times(1)).onDisposed(); + verify(disposeCheckerAMock, times(0)).onDisposed(); verify(disposeCheckerBMock, times(0)).onDisposed(); verify(disposeCheckerCMock, times(0)).onDisposed(); verify(disposeCheckerDMock, times(0)).onDisposed(); testNavigateToC(); waitTest(1000); - verify(disposeCheckerAMock, times(1)).onDisposed(); - verify(disposeCheckerBMock, times(1)).onDisposed(); + verify(disposeCheckerAMock, times(0)).onDisposed(); + verify(disposeCheckerBMock, times(0)).onDisposed(); verify(disposeCheckerCMock, times(0)).onDisposed(); verify(disposeCheckerDMock, times(0)).onDisposed(); testNavigateToD(); waitTest(1000); - verify(disposeCheckerAMock, times(1)).onDisposed(); - verify(disposeCheckerBMock, times(1)).onDisposed(); - verify(disposeCheckerCMock, times(1)).onDisposed(); + verify(disposeCheckerAMock, times(0)).onDisposed(); + verify(disposeCheckerBMock, times(0)).onDisposed(); + verify(disposeCheckerCMock, times(0)).onDisposed(); verify(disposeCheckerDMock, times(0)).onDisposed(); navigationController.navigateBack(this); waitTest(); waitTest(2000); - verify(disposeCheckerAMock, times(1)).onDisposed(); - verify(disposeCheckerBMock, times(1)).onDisposed(); - verify(disposeCheckerCMock, times(1)).onDisposed(); + verify(disposeCheckerAMock, times(0)).onDisposed(); + verify(disposeCheckerBMock, times(0)).onDisposed(); + verify(disposeCheckerCMock, times(0)).onDisposed(); verify(disposeCheckerDMock, times(1)).onDisposed(); //A->B->C @@ -171,7 +170,7 @@ public void testShouldReleaseInjectionsAfterFragmentsArePoppedOut() throws Throw waitTest(1000); verify(disposeCheckerAMock, times(0)).onDisposed(); verify(disposeCheckerBMock, times(0)).onDisposed(); - verify(disposeCheckerCMock, times(1)).onDisposed(); + verify(disposeCheckerCMock, times(0)).onDisposed(); verify(disposeCheckerDMock, times(0)).onDisposed(); //A->B->C->D @@ -182,8 +181,8 @@ public void testShouldReleaseInjectionsAfterFragmentsArePoppedOut() throws Throw navigationController.navigateBack(this, null); waitTest(1000); verify(disposeCheckerAMock, times(0)).onDisposed(); - verify(disposeCheckerBMock, times(0)).onDisposed(); - verify(disposeCheckerCMock, times(0)).onDisposed(); + verify(disposeCheckerBMock, times(1)).onDisposed(); + verify(disposeCheckerCMock, times(1)).onDisposed(); verify(disposeCheckerDMock, times(1)).onDisposed(); //A @@ -192,7 +191,7 @@ public void testShouldReleaseInjectionsAfterFragmentsArePoppedOut() throws Throw reset(disposeCheckerCMock); reset(disposeCheckerDMock); navigationController.navigateBack(this); - waitTest(1000); + waitTest(2000); verify(disposeCheckerAMock, times(1)).onDisposed(); verify(disposeCheckerBMock, times(0)).onDisposed(); verify(disposeCheckerCMock, times(0)).onDisposed(); diff --git a/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/nav/TestCaseNavigationFromController.java b/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/nav/TestCaseNavigationFromController.java index 987e77b..f786d40 100644 --- a/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/nav/TestCaseNavigationFromController.java +++ b/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/nav/TestCaseNavigationFromController.java @@ -16,7 +16,6 @@ package com.shipdream.lib.android.mvc.view.nav; -import com.shipdream.lib.android.mvc.Injector; import com.shipdream.lib.android.mvc.controller.NavigationController; import com.shipdream.lib.android.mvc.view.AndroidMvc; import com.shipdream.lib.android.mvc.view.BaseTestCase; @@ -137,13 +136,12 @@ public void test_should_release_injected_object_by_pure_navigation_controller_na final String val = "Value = " + new Random().nextInt(); - Injector.getGraph().use(ControllerE.class, new Consumer() { + navigationController.navigate(this).prepare(ControllerE.class, new Consumer() { @Override public void consume(ControllerE instance) { instance.setValue(val); - navigationController.navigateTo(this, MvcTestActivityNavigation.Loc.E); } - }); + }).to(MvcTestActivityNavigation.Loc.E); //The value set to controller e in Injector.getGraph().use should be retained during the //navigation @@ -167,61 +165,67 @@ public void test_should_release_injected_object_by_chained_navigation_controller final String valF = "ValueF = " + new Random().nextInt(); final String valG = "ValueG = " + new Random().nextInt(); + resetDisposeCheckers(); navigationController.navigate(this).prepare(ControllerE.class, new Consumer() { @Override public void consume(ControllerE instance) { instance.setValue(valE); } }).to(MvcTestActivityNavigation.Loc.E); + waitTest(); + onView(withText(valE)).check(matches(isDisplayed())); + verify(disposeCheckerEMock, times(0)).onDisposed(); + verify(disposeCheckerFMock, times(0)).onDisposed(); + verify(disposeCheckerGMock, times(0)).onDisposed(); - waitTest(1000); - + resetDisposeCheckers(); navigationController.navigate(this).prepare(ControllerF.class, new Consumer() { @Override public void consume(ControllerF instance) { instance.setValue(valF); } }).to(MvcTestActivityNavigation.Loc.F); + waitTest(); + onView(withText(valF)).check(matches(isDisplayed())); + verify(disposeCheckerEMock, times(0)).onDisposed(); + verify(disposeCheckerFMock, times(0)).onDisposed(); + verify(disposeCheckerGMock, times(0)).onDisposed(); - waitTest(1000); - + resetDisposeCheckers(); navigationController.navigate(this).prepare(ControllerG.class, new Consumer() { @Override public void consume(ControllerG instance) { instance.setValue(valG); } }).to(MvcTestActivityNavigation.Loc.G); - - waitTest(1000); - - //The value set to controller e in Injector.getGraph().use should be retained during the - //navigation + waitTest(); onView(withText(valG)).check(matches(isDisplayed())); - verify(disposeCheckerEMock, times(1)).onDisposed(); - verify(disposeCheckerFMock, times(1)).onDisposed(); + verify(disposeCheckerEMock, times(0)).onDisposed(); + verify(disposeCheckerFMock, times(0)).onDisposed(); verify(disposeCheckerGMock, times(0)).onDisposed(); + resetDisposeCheckers(); + //The value set to controller e in Injector.getGraph().use should be retained during the + //navigation navigationController.navigateBack(this); - - onView(withText(valF)).check(matches(isDisplayed())); waitTest(); + onView(withText(valF)).check(matches(isDisplayed())); verify(disposeCheckerEMock, times(0)).onDisposed(); verify(disposeCheckerFMock, times(0)).onDisposed(); verify(disposeCheckerGMock, times(0)).onDisposed(); + resetDisposeCheckers(); navigationController.navigateBack(this); - - onView(withText(valE)).check(matches(isDisplayed())); waitTest(); + onView(withText(valE)).check(matches(isDisplayed())); verify(disposeCheckerEMock, times(0)).onDisposed(); - LoggerFactory.getLogger(getClass()).debug("DisposeCheck, checking F"); verify(disposeCheckerFMock, times(1)).onDisposed(); //__MvcGraphHelper retaining all cache is dangerous. Try to only retain relevant injected instances. verify(disposeCheckerGMock, times(0)).onDisposed(); + resetDisposeCheckers(); - LoggerFactory.getLogger(getClass()).debug("DisposeCheck nav back"); navigationController.navigateBack(this); - + waitTest(); verify(disposeCheckerEMock, times(1)).onDisposed(); verify(disposeCheckerFMock, times(0)).onDisposed(); verify(disposeCheckerGMock, times(1)).onDisposed(); diff --git a/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/viewpager/TestFragmentsInViewPager.java b/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/viewpager/TestFragmentsInViewPager.java index f2e9f2b..7fa268d 100644 --- a/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/viewpager/TestFragmentsInViewPager.java +++ b/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/viewpager/TestFragmentsInViewPager.java @@ -39,6 +39,11 @@ public TestFragmentsInViewPager() { super(ViewPagerTestActivity.class); } + @Override + protected void waitTest() throws InterruptedException { + super.waitTest(200); + } + @Test public void test_should_call_onViewReady_in_tab_fragments_when_resumed_hosting_fragment_pops_out() throws Throwable { if (isDontKeepActivities()) { @@ -248,16 +253,16 @@ public void test_should_call_onViewReady_in_tab_fragments_when_comes_back_from_a //=============================> At Sub Fragment getActivity().launchAnotherActivity(); - waitTest(1200); + waitTest(); pressBack(); - waitTest(1200); + waitTest(); lifeCycleValidatorA.expect(LifeCycle.onReturnForeground); onView(withId(R.id.viewpager)).perform(swipeLeft()); onView(withText("Tab B")).check(matches(not(isDisplayed()))); onView(withText("Tab C")).check(matches(isDisplayed())); waitTest(1000); - lifeCycleValidatorA.expect(LifeCycle.onDestroyView, LifeCycle.onDestroy); + lifeCycleValidatorA.expect(LifeCycle.onDestroyView); onView(withId(R.id.viewpager)).perform(swipeRight()); onView(withText("Tab C")).check(matches(not(isDisplayed()))); @@ -265,14 +270,12 @@ public void test_should_call_onViewReady_in_tab_fragments_when_comes_back_from_a onView(withId(R.id.viewpager)).perform(swipeRight()); onView(withText("Tab B")).check(matches(not(isDisplayed()))); - onView(withText(TabFragmentA.RESTORE_TEXT)).check(matches(isDisplayed())); + onView(withText("Tab A")).check(matches(isDisplayed())); lifeCycleValidatorA.expect( - LifeCycle.onCreateNotNull, - LifeCycle.onCreateViewNotNull, - LifeCycle.onViewCreatedNotNull, - LifeCycle.onViewReadyNewInstance, - LifeCycle.onViewReadyRestore); + LifeCycle.onCreateViewNull, + LifeCycle.onViewCreatedNull, + LifeCycle.onViewReadyFirstTime); } @Test @@ -330,7 +333,7 @@ public void test_should_call_onViewReady_in_tab_fragments_when_comes_back_from_a onView(withText("Tab B")).check(matches(not(isDisplayed()))); onView(withText("Tab C")).check(matches(isDisplayed())); waitTest(1000); - lifeCycleValidatorA.expect(LifeCycle.onDestroyView, LifeCycle.onDestroy); + lifeCycleValidatorA.expect(LifeCycle.onDestroyView); onView(withId(R.id.viewpager)).perform(swipeRight()); onView(withText("Tab C")).check(matches(not(isDisplayed()))); @@ -338,14 +341,12 @@ public void test_should_call_onViewReady_in_tab_fragments_when_comes_back_from_a onView(withId(R.id.viewpager)).perform(swipeRight()); onView(withText("Tab B")).check(matches(not(isDisplayed()))); - onView(withText(TabFragmentA.RESTORE_TEXT)).check(matches(isDisplayed())); + onView(withText("Tab A")).check(matches(isDisplayed())); lifeCycleValidatorA.expect( - LifeCycle.onCreateNotNull, - LifeCycle.onCreateViewNotNull, - LifeCycle.onViewCreatedNotNull, - LifeCycle.onViewReadyNewInstance, - LifeCycle.onViewReadyRestore); + LifeCycle.onCreateViewNull, + LifeCycle.onViewCreatedNull, + LifeCycle.onViewReadyFirstTime); } @Test diff --git a/library/android-mvc/src/main/java/com/shipdream/lib/android/mvc/view/MvcActivity.java b/library/android-mvc/src/main/java/com/shipdream/lib/android/mvc/view/MvcActivity.java index fd02cd3..0483f7e 100644 --- a/library/android-mvc/src/main/java/com/shipdream/lib/android/mvc/view/MvcActivity.java +++ b/library/android-mvc/src/main/java/com/shipdream/lib/android/mvc/view/MvcActivity.java @@ -454,10 +454,6 @@ public void run() { __MvcControllerHelper.destroyNavigator(event.getNavigator()); } - if (finalLastFrag != null) { - finalLastFrag.releaseDependencies(); - } - logger.trace("Fragment ready: " + fragment.getClass().getSimpleName()); fragment.unregisterOnViewReadyListener(this); @@ -500,7 +496,6 @@ private void performBackNav(final NavigationController.EventC2V.OnLocationBack e String currentFragTag = getFragmentTag(currentLoc.getLocationId()); final MvcFragment currentFrag = (MvcFragment) fm.findFragmentByTag(currentFragTag); if (currentFrag != null) { - currentFrag.injectDependencies(); currentFrag.aboutToPopOut = true; List subFragments = currentFrag.getChildFragmentManager().getFragments(); From bfafc96f576870dc8739ba4f414ddd158b510e88 Mon Sep 17 00:00:00 2001 From: Kejun Xia Date: Fri, 4 Dec 2015 23:30:39 +1100 Subject: [PATCH 16/26] Fix testing to align with the change in MvcActivity for device kills activity immediately after it's paused. --- .../nav/TestCaseNavigationAndInjection.java | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/nav/TestCaseNavigationAndInjection.java b/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/nav/TestCaseNavigationAndInjection.java index 8837e92..b974ec5 100644 --- a/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/nav/TestCaseNavigationAndInjection.java +++ b/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/nav/TestCaseNavigationAndInjection.java @@ -341,6 +341,7 @@ public void test_should_release_and_inject_properly_on_single_step_back_navigati prepareAndCheckStack(); //->A->B->C->D + waitTest(200); resetGraphMonitorCounts(); pressHome(); waitTest(); @@ -348,11 +349,11 @@ public void test_should_release_and_inject_properly_on_single_step_back_navigati waitTest(2000); Assert.assertEquals(fragAInjectCount, 1); - Assert.assertEquals(fragAReleaseCount, 0); + Assert.assertEquals(fragAReleaseCount, 1); Assert.assertEquals(fragBInjectCount, 1); - Assert.assertEquals(fragBReleaseCount, 0); + Assert.assertEquals(fragBReleaseCount, 1); Assert.assertEquals(fragCInjectCount, 1); - Assert.assertEquals(fragCReleaseCount, 0); + Assert.assertEquals(fragCReleaseCount, 1); Assert.assertEquals(fragDInjectCount, 1); Assert.assertEquals(fragDReleaseCount, 1); @@ -419,6 +420,7 @@ public void test_should_release_and_inject_properly_on_fast_rewind_back_navigati prepareAndCheckStack(); //->A->B->C->D + waitTest(200); resetGraphMonitorCounts(); pressHome(); waitTest(1000); @@ -426,11 +428,11 @@ public void test_should_release_and_inject_properly_on_fast_rewind_back_navigati waitTest(1000); Assert.assertEquals(fragAInjectCount, 1); - Assert.assertEquals(fragAReleaseCount, 0); + Assert.assertEquals(fragAReleaseCount, 1); Assert.assertEquals(fragBInjectCount, 1); - Assert.assertEquals(fragBReleaseCount, 0); + Assert.assertEquals(fragBReleaseCount, 1); Assert.assertEquals(fragCInjectCount, 1); - Assert.assertEquals(fragCReleaseCount, 0); + Assert.assertEquals(fragCReleaseCount, 1); Assert.assertEquals(fragDInjectCount, 1); Assert.assertEquals(fragDReleaseCount, 1); @@ -470,7 +472,7 @@ public void test_should_release_and_inject_properly_on_fast_rewind_forward_navig prepareAndCheckStack(); //->A->B->C->D - + waitTest(200); resetGraphMonitorCounts(); pressHome(); waitTest(); @@ -478,11 +480,11 @@ public void test_should_release_and_inject_properly_on_fast_rewind_forward_navig waitTest(2000); Assert.assertEquals(fragAInjectCount, 1); - Assert.assertEquals(fragAReleaseCount, 0); + Assert.assertEquals(fragAReleaseCount, 1); Assert.assertEquals(fragBInjectCount, 1); - Assert.assertEquals(fragBReleaseCount, 0); + Assert.assertEquals(fragBReleaseCount, 1); Assert.assertEquals(fragCInjectCount, 1); - Assert.assertEquals(fragCReleaseCount, 0); + Assert.assertEquals(fragCReleaseCount, 1); Assert.assertEquals(fragDInjectCount, 1); Assert.assertEquals(fragDReleaseCount, 1); From 826f37555544c9a00bd9bc179ce705a060a749aa Mon Sep 17 00:00:00 2001 From: Kejun Xia Date: Sat, 5 Dec 2015 12:28:45 +1100 Subject: [PATCH 17/26] Add documents for new navigation related classes --- .../mvc/controller/BaseController.java | 2 +- .../mvc/controller/NavigationController.java | 5 + .../internal/BaseControllerImpl.java | 2 +- .../internal/NavigationControllerImpl.java | 5 +- .../mvc/controller/internal/Navigator.java | 140 ++++++++++++++++++ 5 files changed, 149 insertions(+), 5 deletions(-) diff --git a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/BaseController.java b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/BaseController.java index f4b88ac..5aa8808 100644 --- a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/BaseController.java +++ b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/BaseController.java @@ -17,7 +17,7 @@ package com.shipdream.lib.android.mvc.controller; /** - * + * Base controller interface. */ public interface BaseController { diff --git a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/NavigationController.java b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/NavigationController.java index 68e269c..395983f 100644 --- a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/NavigationController.java +++ b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/NavigationController.java @@ -28,6 +28,11 @@ */ public interface NavigationController extends BaseController { + /** + * Initiates a {@link Navigator} to start navigation. + * @param sender Who wants to navigate + * @return A new instance of {@link Navigator} + */ Navigator navigate(Object sender); /** diff --git a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/internal/BaseControllerImpl.java b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/internal/BaseControllerImpl.java index a4262b3..e5af715 100644 --- a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/internal/BaseControllerImpl.java +++ b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/internal/BaseControllerImpl.java @@ -37,7 +37,7 @@ import javax.inject.Inject; /** - * + * Base controller implementation. */ public abstract class BaseControllerImpl implements BaseController, StateManaged, Constructable, Disposable { diff --git a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/internal/NavigationControllerImpl.java b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/internal/NavigationControllerImpl.java index 574c471..16ac5a8 100644 --- a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/internal/NavigationControllerImpl.java +++ b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/internal/NavigationControllerImpl.java @@ -24,7 +24,7 @@ */ public class NavigationControllerImpl extends BaseControllerImpl implements NavigationController { - public static boolean dumpHistoryOnLocationChange = false; + public boolean dumpHistoryOnLocationChange = false; @Override public Class getModelClassType() { @@ -33,8 +33,7 @@ public Class getModelClassType() { @Override public Navigator navigate(Object sender) { - Navigator navigator = new Navigator(sender, this); - return navigator; + return new Navigator(sender, this); } @Override diff --git a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/internal/Navigator.java b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/internal/Navigator.java index b907edd..0969724 100644 --- a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/internal/Navigator.java +++ b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/internal/Navigator.java @@ -1,6 +1,7 @@ package com.shipdream.lib.android.mvc.controller.internal; import com.shipdream.lib.android.mvc.Injector; +import com.shipdream.lib.android.mvc.MvcGraph; import com.shipdream.lib.android.mvc.MvcGraphException; import com.shipdream.lib.android.mvc.NavLocation; import com.shipdream.lib.android.mvc.controller.NavigationController; @@ -13,6 +14,12 @@ import java.util.List; public class Navigator { + /** + * The callback when the navigation is settled. Since Android Fragment doesn't invoke its call + * back like onCreate, onCreateView and etc after a fragment manager commits fragment transaction, + * if something needs to be done after the fragment being navigated to is ready to show + * (MvcFragment.onViewReady is called), put the actions in here. + */ public interface OnSettled { void run(); } @@ -29,20 +36,97 @@ private static class PendingReleaseInstance { private NavigationController.EventC2V.OnLocationChanged navigateEvent; private List pendingReleaseInstances; + /** + * Construct a {@link Navigator} + * @param sender Who wants to navigate + * @param navigationController The navigation controller + */ Navigator(Object sender, NavigationControllerImpl navigationController) { this.sender = sender; this.navigationController = navigationController; } + /** + * Who wants to navigate + * @return the sender + */ public Object getSender() { return sender; } + /** + * Prepare the instance subject to being injected with no qualifier for the fragment being + * navigated to. It's an equivalent way to pass arguments to the next fragment.For example, when + * next fragment needs to have a pre set page title name, the controller referenced by the + * fragment can be prepared here and set the title in the controller's model. Then in the + * MvcFragment.onViewReady bind the value of the page title from the controller's model to the + * fragment. + * + *

Example:

+ * To initialize the timer of a TimerFragment which counts down seconds,sets the initial value + * of its controller by this prepare method. + *
+     class TimerFragment {
+    @Inject
+    TimerController timerController;
+    }
+
+     interface TimerController {
+     void setInitialValue(long howManySeconds);
+     }
+
+     navigationController.navigate(this).prepare(TimerController.class, new Consumer() {
+    @Override
+    public void consume(TimerController instance) {
+    long fiveMinutes = 60 * 5;
+    instance.setInitialValue(fiveMinutes);
+    }
+    }).to(TimerFragment.class.getName());
+     * 
+ * @param type The class type of the instance needs to be prepared + * @param consumer The consumer in which the injected instance will be prepared + * @return This navigator + * @throws MvcGraphException Raised when the required injectable object cannot be injected + */ public Navigator prepare(Class type, Consumer consumer) throws MvcGraphException { prepare(type, null, consumer); return this; } + /** + * Prepare the instance subject to being injected for the fragment being navigated to. It's an + * equivalent way to pass arguments to the next fragment.For example, when next fragment needs + * to have a pre set page title name, the controller referenced by the fragment can be prepared + * here and set the title in the controller's model. Then in the MvcFragment.onViewReady bind + * the value of the page title from the controller's model to the fragment. + * + *

Example:

+ * To initialize the timer of a TimerFragment which counts down seconds,sets the initial value + * of its controller by this prepare method. + *
+     class TimerFragment {
+        @Inject
+        TimerController timerController;
+     }
+
+     interface TimerController {
+        void setInitialValue(long howManySeconds);
+     }
+
+     navigationController.navigate(this).prepare(TimerController.class, null, new Consumer() {
+        @Override
+        public void consume(TimerController instance) {
+            long fiveMinutes = 60 * 5;
+            instance.setInitialValue(fiveMinutes);
+        }
+     }).to(TimerFragment.class.getName());
+     * 
+ * @param type The class type of the instance needs to be prepared + * @param qualifier The qualifier + * @param consumer The consumer in which the injected instance will be prepared + * @return This navigator + * @throws MvcGraphException Raised when the required injectable object cannot be injected + */ public Navigator prepare(Class type, Annotation qualifier, Consumer consumer) throws MvcGraphException { try { T instance = Injector.getGraph().reference(type, qualifier); @@ -68,6 +152,30 @@ public void to(String locationId) { go(); } + /** + * Navigates to a new location and exclusively clears history prior to the given + * clearTopToLocationId (clearTopToLocationId will be last location below given location). + * When clearTopToLocationId is null, it clears all history. In other words, the current given + * location will be the only location in the history stack and all other previous locations + * will be cleared. Navigation only takes effect when the given locationId is different from the + * current location and raises {@link NavigationController.EventC2V.OnLocationForward} + * + *

+ * To set argument for the next fragment navigating to, use {@link #prepare(Class, Annotation, Consumer)} + *

+ * + *

+ * Forward navigating will automatically manage continuity of state before and after the + * navigation is performed. The injected instance will not be released until the next fragment + * is settled. So when the current fragment and next fragment share same injected + * controller their instance will be same. + *

+ * + * @param locationId The id of the location navigate to + * @param clearTopToLocationId Null if all history locations want to be cleared otherwise, the + * id of the location the history will be exclusively cleared up to + * which will be the second last location after navigation. + */ public void to(String locationId, String clearTopToLocationId) { doNavigateTo(locationId, true, clearTopToLocationId); go(); @@ -130,6 +238,11 @@ private void doNavigateTo(String locationId, boolean clearTop, } } + /** + * Navigates one step back. If current location is null it doesn't take any effect otherwise + * raises a {@link NavigationController.EventC2V.OnLocationBack} event when there is a previous + * location. + */ public void back() { NavLocation currentLoc = navigationController.getModel().getCurrentLocation(); if (currentLoc == null) { @@ -144,6 +257,17 @@ public void back() { go(); } + /** + * Navigates back. If current location is null it doesn't take any effect. When toLocationId + * is null, navigate to the very first location and clear all history prior to it, otherwise + * navigate to location with given locationId and clear history prior to it. Then a + * {@link NavigationController.EventC2V.OnLocationBack} event will be raised. + * + * @param toLocationId Null when needs to navigate to the very first location and all history + * locations will be above it will be cleared. Otherwise, the id of the + * location where the history will be exclusively cleared up to. Then this + * location will be the second last one. + */ public void back(String toLocationId) { NavLocation currentLoc = navigationController.getModel().getCurrentLocation(); if (currentLoc == null) { @@ -182,6 +306,12 @@ public void back(String toLocationId) { go(); } + /** + * Sets the call back when fragment being navigated to is ready to show(MvcFragment.onViewReady + * is called). + * @param onSettled {@link OnSettled} call back + * @return The navigator itself + */ public Navigator onSettled(OnSettled onSettled) { this.onSettled = onSettled; return this; @@ -208,6 +338,9 @@ void destroy() { } } + /** + * Sends out the navigation event to execute the navigation + */ private void go() { if (navigateEvent != null) { navigationController.postC2VEvent(navigateEvent); @@ -232,6 +365,10 @@ private void go() { dumpHistory(); } + /** + * Check the app is exiting + * @param sender The sender + */ private void checkAppExit(Object sender) { NavLocation curLocation = navigationController.getModel().getCurrentLocation(); if (curLocation == null) { @@ -239,6 +376,9 @@ private void checkAppExit(Object sender) { } } + /** + * Prints navigation history + */ private void dumpHistory() { if (navigationController.dumpHistoryOnLocationChange) { navigationController.logger.trace(""); From 09e338f4af782b2750e22f2d555002cc9e95c8a4 Mon Sep 17 00:00:00 2001 From: Kejun Xia Date: Sat, 5 Dec 2015 12:29:19 +1100 Subject: [PATCH 18/26] Remove unused code --- .../internal/NavigationControllerImpl.java | 21 ------------------- 1 file changed, 21 deletions(-) diff --git a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/internal/NavigationControllerImpl.java b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/internal/NavigationControllerImpl.java index 16ac5a8..fadda8f 100644 --- a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/internal/NavigationControllerImpl.java +++ b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/internal/NavigationControllerImpl.java @@ -56,25 +56,4 @@ public void navigateBack(Object sender, String toLocationId) { navigate(sender).back(toLocationId); } - private void checkAppExit(Object sender) { - NavLocation curLocation = getModel().getCurrentLocation(); - if (curLocation == null) { - postC2CEvent(new EventC2C.OnAppExit(sender)); - } - } - - private void dumpHistory() { - if (dumpHistoryOnLocationChange) { - logger.trace(""); - logger.trace("Nav Controller: dump: begin ---------------------------------------------->"); - NavLocation curLoc = getModel().getCurrentLocation(); - while (curLoc != null) { - logger.trace("Nav Controller: dump: {}({})", curLoc.getLocationId()); - curLoc = curLoc.getPreviousLocation(); - } - logger.trace("Nav Controller: dump: end ---------------------------------------------->"); - logger.trace(""); - } - } - } From f1edec696d239ffa54976fc81bed587ee6f3e95c Mon Sep 17 00:00:00 2001 From: Kejun Xia Date: Sat, 5 Dec 2015 15:34:57 +1100 Subject: [PATCH 19/26] New interface to wrap preparation logic for navigation --- .../mvc/controller/internal/Navigator.java | 42 +++++++++++++------ .../mvc/controller/internal/Preparer.java | 12 ++++++ .../nav/TestCaseNavigationFromController.java | 17 ++++---- 3 files changed, 50 insertions(+), 21 deletions(-) create mode 100644 library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/internal/Preparer.java diff --git a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/internal/Navigator.java b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/internal/Navigator.java index 0969724..708343b 100644 --- a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/internal/Navigator.java +++ b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/internal/Navigator.java @@ -1,7 +1,6 @@ package com.shipdream.lib.android.mvc.controller.internal; import com.shipdream.lib.android.mvc.Injector; -import com.shipdream.lib.android.mvc.MvcGraph; import com.shipdream.lib.android.mvc.MvcGraphException; import com.shipdream.lib.android.mvc.NavLocation; import com.shipdream.lib.android.mvc.controller.NavigationController; @@ -64,7 +63,7 @@ public Object getSender() { * *

Example:

* To initialize the timer of a TimerFragment which counts down seconds,sets the initial value - * of its controller by this prepare method. + * of its controller by this with method. *
      class TimerFragment {
     @Inject
@@ -75,7 +74,7 @@ interface TimerController {
      void setInitialValue(long howManySeconds);
      }
 
-     navigationController.navigate(this).prepare(TimerController.class, new Consumer() {
+     navigationController.navigate(this).with(TimerController.class, new Consumer() {
     @Override
     public void consume(TimerController instance) {
     long fiveMinutes = 60 * 5;
@@ -84,12 +83,12 @@ public void consume(TimerController instance) {
     }).to(TimerFragment.class.getName());
      * 
* @param type The class type of the instance needs to be prepared - * @param consumer The consumer in which the injected instance will be prepared + * @param preparer The preparer in which the injected instance will be prepared * @return This navigator * @throws MvcGraphException Raised when the required injectable object cannot be injected */ - public Navigator prepare(Class type, Consumer consumer) throws MvcGraphException { - prepare(type, null, consumer); + public Navigator with(Class type, Preparer preparer) throws MvcGraphException { + with(type, null, preparer); return this; } @@ -102,7 +101,7 @@ public Navigator prepare(Class type, Consumer consumer) throws MvcGrap * *

Example:

* To initialize the timer of a TimerFragment which counts down seconds,sets the initial value - * of its controller by this prepare method. + * of its controller by this with method. *
      class TimerFragment {
         @Inject
@@ -113,7 +112,7 @@ interface TimerController {
         void setInitialValue(long howManySeconds);
      }
 
-     navigationController.navigate(this).prepare(TimerController.class, null, new Consumer() {
+     navigationController.navigate(this).with(TimerController.class, null, new Consumer() {
         @Override
         public void consume(TimerController instance) {
             long fiveMinutes = 60 * 5;
@@ -123,15 +122,15 @@ public void consume(TimerController instance) {
      * 
* @param type The class type of the instance needs to be prepared * @param qualifier The qualifier - * @param consumer The consumer in which the injected instance will be prepared + * @param preparer The preparer in which the injected instance will be prepared * @return This navigator * @throws MvcGraphException Raised when the required injectable object cannot be injected */ - public Navigator prepare(Class type, Annotation qualifier, Consumer consumer) throws MvcGraphException { + public Navigator with(Class type, Annotation qualifier, Preparer preparer) throws MvcGraphException { try { T instance = Injector.getGraph().reference(type, qualifier); - consumer.consume(instance); + preparer.prepare(instance); if (pendingReleaseInstances == null) { pendingReleaseInstances = new ArrayList<>(); @@ -147,6 +146,23 @@ public Navigator prepare(Class type, Annotation qualifier, Consumer co return this; } + /** + * Navigates to the specified location. Navigation only takes effect when the given locationId + * is different from the current location and raises {@link NavigationController.EventC2V.OnLocationForward} + * + *

+ * To set argument for the next fragment navigating to, use {@link #with(Class, Annotation, Consumer)} + *

+ * + *

+ * Navigation will automatically manage continuity of state before and after the + * navigation is performed. The injected instance will not be released until the next fragment + * is settled. So when the current fragment and next fragment share same injected + * controller their instance will be same. + *

+ * + * @param locationId The id of the location navigate to + */ public void to(String locationId) { doNavigateTo(locationId, false, null); go(); @@ -161,11 +177,11 @@ public void to(String locationId) { * current location and raises {@link NavigationController.EventC2V.OnLocationForward} * *

- * To set argument for the next fragment navigating to, use {@link #prepare(Class, Annotation, Consumer)} + * To set argument for the next fragment navigating to, use {@link #with(Class, Annotation, Preparer)} *

* *

- * Forward navigating will automatically manage continuity of state before and after the + * Navigation will automatically manage continuity of state before and after the * navigation is performed. The injected instance will not be released until the next fragment * is settled. So when the current fragment and next fragment share same injected * controller their instance will be same. diff --git a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/internal/Preparer.java b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/internal/Preparer.java new file mode 100644 index 0000000..e8eefb0 --- /dev/null +++ b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/internal/Preparer.java @@ -0,0 +1,12 @@ +package com.shipdream.lib.android.mvc.controller.internal; + +/** + * Preparer in which the injected instance will be configured before a navigation. + */ +public interface Preparer { + /** + * Prepare the state for a navigation + * @param instance The instance(usually a controller) that will be configured + */ + void prepare(T instance); +} diff --git a/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/nav/TestCaseNavigationFromController.java b/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/nav/TestCaseNavigationFromController.java index f786d40..fd98393 100644 --- a/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/nav/TestCaseNavigationFromController.java +++ b/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/nav/TestCaseNavigationFromController.java @@ -17,6 +17,7 @@ package com.shipdream.lib.android.mvc.view.nav; import com.shipdream.lib.android.mvc.controller.NavigationController; +import com.shipdream.lib.android.mvc.controller.internal.Preparer; import com.shipdream.lib.android.mvc.view.AndroidMvc; import com.shipdream.lib.android.mvc.view.BaseTestCase; import com.shipdream.lib.poke.Component; @@ -136,9 +137,9 @@ public void test_should_release_injected_object_by_pure_navigation_controller_na final String val = "Value = " + new Random().nextInt(); - navigationController.navigate(this).prepare(ControllerE.class, new Consumer() { + navigationController.navigate(this).with(ControllerE.class, new Preparer() { @Override - public void consume(ControllerE instance) { + public void prepare(ControllerE instance) { instance.setValue(val); } }).to(MvcTestActivityNavigation.Loc.E); @@ -166,9 +167,9 @@ public void test_should_release_injected_object_by_chained_navigation_controller final String valG = "ValueG = " + new Random().nextInt(); resetDisposeCheckers(); - navigationController.navigate(this).prepare(ControllerE.class, new Consumer() { + navigationController.navigate(this).with(ControllerE.class, new Preparer() { @Override - public void consume(ControllerE instance) { + public void prepare(ControllerE instance) { instance.setValue(valE); } }).to(MvcTestActivityNavigation.Loc.E); @@ -179,9 +180,9 @@ public void consume(ControllerE instance) { verify(disposeCheckerGMock, times(0)).onDisposed(); resetDisposeCheckers(); - navigationController.navigate(this).prepare(ControllerF.class, new Consumer() { + navigationController.navigate(this).with(ControllerF.class, new Preparer() { @Override - public void consume(ControllerF instance) { + public void prepare(ControllerF instance) { instance.setValue(valF); } }).to(MvcTestActivityNavigation.Loc.F); @@ -192,9 +193,9 @@ public void consume(ControllerF instance) { verify(disposeCheckerGMock, times(0)).onDisposed(); resetDisposeCheckers(); - navigationController.navigate(this).prepare(ControllerG.class, new Consumer() { + navigationController.navigate(this).with(ControllerG.class, new Preparer() { @Override - public void consume(ControllerG instance) { + public void prepare(ControllerG instance) { instance.setValue(valG); } }).to(MvcTestActivityNavigation.Loc.G); From c313ab8fd23d068601961e32e2972a32bfa2c5bf Mon Sep 17 00:00:00 2001 From: Kejun Xia Date: Sat, 5 Dec 2015 15:40:47 +1100 Subject: [PATCH 20/26] Rename navigator.destroy --- .../lib/android/mvc/controller/internal/Navigator.java | 2 +- .../android/mvc/controller/internal/__MvcControllerHelper.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/internal/Navigator.java b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/internal/Navigator.java index 708343b..c13d3f4 100644 --- a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/internal/Navigator.java +++ b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/internal/Navigator.java @@ -336,7 +336,7 @@ public Navigator onSettled(OnSettled onSettled) { /** * Internal use. Don't do it in your app. */ - void destroy() { + void __destroy() { if (onSettled != null) { onSettled.run(); } diff --git a/library/android-mvc/src/main/java/com/shipdream/lib/android/mvc/controller/internal/__MvcControllerHelper.java b/library/android-mvc/src/main/java/com/shipdream/lib/android/mvc/controller/internal/__MvcControllerHelper.java index bc7a7eb..31cb864 100644 --- a/library/android-mvc/src/main/java/com/shipdream/lib/android/mvc/controller/internal/__MvcControllerHelper.java +++ b/library/android-mvc/src/main/java/com/shipdream/lib/android/mvc/controller/internal/__MvcControllerHelper.java @@ -6,6 +6,6 @@ //This class is to help access package hidden methods in controller.internal public class __MvcControllerHelper { public static void destroyNavigator(Navigator navigator) { - navigator.destroy(); + navigator.__destroy(); } } From a32f12cc4ca2f899a498e36a9bb71ebc31ffc8a7 Mon Sep 17 00:00:00 2001 From: Kejun Xia Date: Sat, 5 Dec 2015 15:45:07 +1100 Subject: [PATCH 21/26] Bump up library version --- ChangeLog.md | 7 +++++++ build.gradle | 4 ++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index 38f7f60..9abf510 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,3 +1,10 @@ +Version: 1.5.0 +Add reference/dereference method to MvcGraph +Enhanced navigation controller: +* allow config controller easier +* allow call back after navigation is settled +Fix issue that controllers configured by navigationController is not released correctly + Version: 1.4.1 * Able to initialize the controller state referenced by next navigating fragment and retain the state until the navigation is fully performed when the navigation is requested directly via NavigationController in MvcGraph.use method. * Cached instances will be dereferenced and disposed when the instance is referenced by the fields with same variable name in both super and current class. diff --git a/build.gradle b/build.gradle index f265f70..85d1049 100644 --- a/build.gradle +++ b/build.gradle @@ -73,8 +73,8 @@ ext { gitUrl = 'https://github.com/kejunxia/AndroidMvc.git' // Git repository URL version = [ major: 1, - minor: 4, - patch : 1 + minor: 5, + patch : 0 ] libGroup = 'com.shipdream' libVersion = "${version.major}.${version.minor}.${version.patch}" From fa1977459c0d3c7017948d2a51df8de2f7082ce0 Mon Sep 17 00:00:00 2001 From: Kejun Xia Date: Sun, 6 Dec 2015 23:10:45 +1100 Subject: [PATCH 22/26] Remove unused code and destroy pending navigator when activity exits --- .../shipdream/lib/android/mvc/MvcGraph.java | 1 + .../lib/android/mvc/__MvcGraphHelper.java | 1 - .../mvc/controller/internal/Navigator.java | 46 ++++++++++--------- .../lib/android/mvc/view/MvcActivity.java | 5 +- 4 files changed, 29 insertions(+), 24 deletions(-) diff --git a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/MvcGraph.java b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/MvcGraph.java index ea74296..b94093c 100644 --- a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/MvcGraph.java +++ b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/MvcGraph.java @@ -79,6 +79,7 @@ *

*/ public class MvcGraph { + List cachedInstancesBeforeNavigation = new ArrayList<>(); private Logger logger = LoggerFactory.getLogger(getClass()); ScopeCache singletonScopeCache; DefaultProviderFinder defaultProviderFinder; diff --git a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/__MvcGraphHelper.java b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/__MvcGraphHelper.java index a78898e..09cd3eb 100644 --- a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/__MvcGraphHelper.java +++ b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/__MvcGraphHelper.java @@ -12,7 +12,6 @@ * Helper class to work with MvcGraph. Internal use only. Don't use it in your app. */ public class __MvcGraphHelper { - private static Map> retainedProviders = new HashMap<>(); /** * Internal use. Gets all cached items this cache still manages * @return The collection of cached times diff --git a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/internal/Navigator.java b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/internal/Navigator.java index c13d3f4..936e2f8 100644 --- a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/internal/Navigator.java +++ b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/internal/Navigator.java @@ -3,6 +3,7 @@ import com.shipdream.lib.android.mvc.Injector; import com.shipdream.lib.android.mvc.MvcGraphException; import com.shipdream.lib.android.mvc.NavLocation; +import com.shipdream.lib.android.mvc.__MvcGraphHelper; import com.shipdream.lib.android.mvc.controller.NavigationController; import com.shipdream.lib.poke.Consumer; import com.shipdream.lib.poke.exception.PokeException; @@ -151,7 +152,7 @@ public Navigator with(Class type, Annotation qualifier, Preparer prepa * is different from the current location and raises {@link NavigationController.EventC2V.OnLocationForward} * *

- * To set argument for the next fragment navigating to, use {@link #with(Class, Annotation, Consumer)} + * To set argument for the next fragment navigating to, use {@link #with(Class, Annotation, Preparer)} *

* *

@@ -333,32 +334,12 @@ public Navigator onSettled(OnSettled onSettled) { return this; } - /** - * Internal use. Don't do it in your app. - */ - void __destroy() { - if (onSettled != null) { - onSettled.run(); - } - - if (pendingReleaseInstances != null) { - for (PendingReleaseInstance i : pendingReleaseInstances) { - try { - Injector.getGraph().dereference(i.instance, i.type, i.qualifier); - } catch (ProviderMissingException e) { - //should not happen - //in case this happens just logs it - navigationController.logger.warn("Failed to auto release {} after navigation settled", i.type.getName()); - } - } - } - } - /** * Sends out the navigation event to execute the navigation */ private void go() { if (navigateEvent != null) { + navigationController.postC2VEvent(navigateEvent); if (navigateEvent instanceof NavigationController.EventC2V.OnLocationForward) { @@ -381,6 +362,27 @@ private void go() { dumpHistory(); } + /** + * Internal use. Don't do it in your app. + */ + void __destroy() { + if (onSettled != null) { + onSettled.run(); + } + + if (pendingReleaseInstances != null) { + for (PendingReleaseInstance i : pendingReleaseInstances) { + try { + Injector.getGraph().dereference(i.instance, i.type, i.qualifier); + } catch (ProviderMissingException e) { + //should not happen + //in case this happens just logs it + navigationController.logger.warn("Failed to auto release {} after navigation settled", i.type.getName()); + } + } + } + } + /** * Check the app is exiting * @param sender The sender diff --git a/library/android-mvc/src/main/java/com/shipdream/lib/android/mvc/view/MvcActivity.java b/library/android-mvc/src/main/java/com/shipdream/lib/android/mvc/view/MvcActivity.java index 0483f7e..a68ac49 100644 --- a/library/android-mvc/src/main/java/com/shipdream/lib/android/mvc/view/MvcActivity.java +++ b/library/android-mvc/src/main/java/com/shipdream/lib/android/mvc/view/MvcActivity.java @@ -446,7 +446,6 @@ private void performForwardNav(final NavigationController.EventC2V.OnLocationFor logger.trace("Cleared fragment back stack up to {}", tagPopTo); } - final MvcFragment finalLastFrag = lastFrag; fragment.registerOnViewReadyListener(new Runnable() { @Override public void run() { @@ -485,6 +484,10 @@ public void run() { private void performBackNav(final NavigationController.EventC2V.OnLocationBack event) { NavLocation currentLoc = event.getCurrentValue(); if (currentLoc == null) { + if (event.getNavigator() != null) { + __MvcControllerHelper.destroyNavigator(event.getNavigator()); + } + MvcActivity mvcActivity = ((MvcActivity) getActivity()); //Back to null which should finish the current activity mvcActivity.performSuperBackKeyPressed(); From 60de1f79720d9ead7fd654ec385a4d8d149d607a Mon Sep 17 00:00:00 2001 From: Kejun Xia Date: Sun, 6 Dec 2015 23:56:32 +1100 Subject: [PATCH 23/26] Add convenient method to hold injected instance until navigation is settled without configuring it. --- .../mvc/controller/internal/Navigator.java | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/internal/Navigator.java b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/internal/Navigator.java index 936e2f8..f449bf2 100644 --- a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/internal/Navigator.java +++ b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/internal/Navigator.java @@ -54,6 +54,20 @@ public Object getSender() { return sender; } + /** + * Prepare the instance subject to being injected with no qualifier for the fragment being + * navigated to. This instance will be not be released until the navigation is settled. To + * config the instance try {@link #with(Class, Preparer)} or {@link #with(Class, Annotation, Preparer)} + * + * @param type The class type of the instance needs to be prepared + * @return This navigator + * @throws MvcGraphException Raised when the required injectable object cannot be injected + */ + public Navigator with(Class type) throws MvcGraphException { + with(type, null, null); + return this; + } + /** * Prepare the instance subject to being injected with no qualifier for the fragment being * navigated to. It's an equivalent way to pass arguments to the next fragment.For example, when @@ -131,7 +145,9 @@ public Navigator with(Class type, Annotation qualifier, Preparer prepa try { T instance = Injector.getGraph().reference(type, qualifier); - preparer.prepare(instance); + if (preparer != null) { + preparer.prepare(instance); + } if (pendingReleaseInstances == null) { pendingReleaseInstances = new ArrayList<>(); From bf34e54ded26f5f2fe51d8143f07150e7b55aa95 Mon Sep 17 00:00:00 2001 From: Kejun Xia Date: Sun, 6 Dec 2015 23:58:29 +1100 Subject: [PATCH 24/26] update documents --- .../shipdream/lib/android/mvc/MvcGraph.java | 38 ++++++++++++------- .../mvc/controller/internal/Navigator.java | 34 +++++++++-------- 2 files changed, 44 insertions(+), 28 deletions(-) diff --git a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/MvcGraph.java b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/MvcGraph.java index b94093c..a7d3021 100644 --- a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/MvcGraph.java +++ b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/MvcGraph.java @@ -16,8 +16,11 @@ package com.shipdream.lib.android.mvc; +import com.shipdream.lib.android.mvc.controller.NavigationController; import com.shipdream.lib.android.mvc.controller.internal.AsyncTask; import com.shipdream.lib.android.mvc.controller.internal.BaseControllerImpl; +import com.shipdream.lib.android.mvc.controller.internal.Navigator; +import com.shipdream.lib.android.mvc.controller.internal.Preparer; import com.shipdream.lib.android.mvc.event.bus.EventBus; import com.shipdream.lib.android.mvc.event.bus.annotation.EventBusC2C; import com.shipdream.lib.android.mvc.event.bus.annotation.EventBusC2V; @@ -182,19 +185,6 @@ public void clearOnProviderFreedListeners() { graph.clearOnProviderFreedListeners(); } - /** - * Same as {@link #use(Class, Annotation, Consumer)} except using un-qualified injectable type. - * @param type The type of the injectable instance - * @param consumer Consume to use the instance - */ - public void use(final Class type, final Consumer consumer) { - try { - graph.use(type, Inject.class, consumer); - } catch (PokeException e) { - throw new MvcGraphException(e.getMessage(), e); - } - } - /** * Reference an injectable object and retain it. Use * {@link #dereference(Object, Class, Annotation)} to dereference it when it's not used @@ -219,6 +209,19 @@ public void dereference(T instance, Class type, Annotation qualifier) graph.dereference(instance, type, qualifier, Inject.class); } + /** + * Same as {@link #use(Class, Annotation, Consumer)} except using un-qualified injectable type. + * @param type The type of the injectable instance + * @param consumer Consume to use the instance + */ + public void use(final Class type, final Consumer consumer) { + try { + graph.use(type, Inject.class, consumer); + } catch (PokeException e) { + throw new MvcGraphException(e.getMessage(), e); + } + } + /** * Use an injectable instance in the scope of {@link Consumer#consume(Object)} without injecting * it as a field of an object. This method will automatically retain the instance before @@ -226,6 +229,7 @@ public void dereference(T instance, Class type, Annotation qualifier) * it doesn't hold the instance like the field marked by {@link Inject} that will retain the * reference of the instance until {@link #release(Object)} is called. However, in the * scope of {@link Consumer#consume(Object)} the instance will be held. + * *

For example,

*
         interface Os {
@@ -316,6 +320,14 @@ public void consume(Os instance) {
 
         mvcGraph.release(device);  //OsReferenceCount = 0
      * 
+ * + *

Note that, if navigation is involved in {@link Consumer#consume(Object)}, though the + * instance injected is still held until consume method returns, the injected instance may + * loose its state when the next fragment is loaded. This is because Android doesn't load + * fragment immediately by fragment manager, instead navigation will be done in the future main + * loop. Therefore, if the state of an injected instance needs to be carried to the next fragment + * navigated to, use {@link NavigationController#navigate(Object)}.{@link Navigator#with(Class, Annotation, Preparer)}

+ * * @param type The type of the injectable instance * @param qualifier Qualifier for the injectable instance * @param consumer Consume to use the instance diff --git a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/internal/Navigator.java b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/internal/Navigator.java index f449bf2..be1519a 100644 --- a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/internal/Navigator.java +++ b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/internal/Navigator.java @@ -3,9 +3,7 @@ import com.shipdream.lib.android.mvc.Injector; import com.shipdream.lib.android.mvc.MvcGraphException; import com.shipdream.lib.android.mvc.NavLocation; -import com.shipdream.lib.android.mvc.__MvcGraphHelper; import com.shipdream.lib.android.mvc.controller.NavigationController; -import com.shipdream.lib.poke.Consumer; import com.shipdream.lib.poke.exception.PokeException; import com.shipdream.lib.poke.exception.ProviderMissingException; @@ -81,21 +79,24 @@ public Navigator with(Class type) throws MvcGraphException { * of its controller by this with method. *
      class TimerFragment {
-    @Inject
-    TimerController timerController;
-    }
+        @Inject
+        TimerController timerController;
+     }
 
      interface TimerController {
-     void setInitialValue(long howManySeconds);
+        void setInitialValue(long howManySeconds);
      }
 
-     navigationController.navigate(this).with(TimerController.class, new Consumer() {
-    @Override
-    public void consume(TimerController instance) {
-    long fiveMinutes = 60 * 5;
-    instance.setInitialValue(fiveMinutes);
-    }
-    }).to(TimerFragment.class.getName());
+     navigationController.navigate(this).with(TimerController.class, new Preparer() {
+        @Override
+        public void prepare(TimerController instance) {
+            long fiveMinutes = 60 * 5;
+            instance.setInitialValue(fiveMinutes);
+
+            //Then the value set to the controller will be guaranteed to be retained when
+            //TimerFragment is ready to show
+        }
+     }).to(TimerFragment.class.getName());
      * 
* @param type The class type of the instance needs to be prepared * @param preparer The preparer in which the injected instance will be prepared @@ -127,11 +128,14 @@ interface TimerController { void setInitialValue(long howManySeconds); } - navigationController.navigate(this).with(TimerController.class, null, new Consumer() { + navigationController.navigate(this).with(TimerController.class, null, new Preparer() { @Override - public void consume(TimerController instance) { + public void prepare(TimerController instance) { long fiveMinutes = 60 * 5; instance.setInitialValue(fiveMinutes); + + //Then the value set to the controller will be guaranteed to be retained when + //TimerFragment is ready to show } }).to(TimerFragment.class.getName()); * From 3a2190d07247b39ddcbe36d97ab2968a5d9e7f79 Mon Sep 17 00:00:00 2001 From: Kejun Xia Date: Mon, 7 Dec 2015 00:02:24 +1100 Subject: [PATCH 25/26] Unit tests for new navigation methods --- .../internal/TestNavigationController.java | 231 ++++++++++++++++++ .../controller/TimerController.java | 24 ++ .../controller/TimerModel.java | 29 +++ .../internal/TimerControllerImpl.java | 22 ++ .../lib/poke/ProviderFinderByRegistry.java | 2 +- 5 files changed, 307 insertions(+), 1 deletion(-) create mode 100644 library/android-mvc-controller/src/test/java/com/shipdream/lib/android/mvc/inject/testNameMapping/controller/TimerController.java create mode 100644 library/android-mvc-controller/src/test/java/com/shipdream/lib/android/mvc/inject/testNameMapping/controller/TimerModel.java create mode 100644 library/android-mvc-controller/src/test/java/com/shipdream/lib/android/mvc/inject/testNameMapping/controller/internal/TimerControllerImpl.java diff --git a/library/android-mvc-controller/src/test/java/com/shipdream/lib/android/mvc/controller/internal/TestNavigationController.java b/library/android-mvc-controller/src/test/java/com/shipdream/lib/android/mvc/controller/internal/TestNavigationController.java index 4847829..b9c0686 100644 --- a/library/android-mvc-controller/src/test/java/com/shipdream/lib/android/mvc/controller/internal/TestNavigationController.java +++ b/library/android-mvc-controller/src/test/java/com/shipdream/lib/android/mvc/controller/internal/TestNavigationController.java @@ -16,15 +16,30 @@ package com.shipdream.lib.android.mvc.controller.internal; +import com.shipdream.lib.android.mvc.Injector; +import com.shipdream.lib.android.mvc.MvcGraphException; import com.shipdream.lib.android.mvc.NavLocation; import com.shipdream.lib.android.mvc.controller.BaseNavigationControllerTest; import com.shipdream.lib.android.mvc.controller.NavigationController; +import com.shipdream.lib.android.mvc.inject.testNameMapping.controller.TimerController; +import com.shipdream.lib.android.mvc.inject.testNameMapping.controller.internal.TimerControllerImpl; +import com.shipdream.lib.poke.Component; +import com.shipdream.lib.poke.Consumer; +import com.shipdream.lib.poke.Provides; import org.junit.Assert; import org.junit.Test; import org.mockito.ArgumentCaptor; import org.slf4j.Logger; +import java.lang.annotation.Annotation; +import java.lang.annotation.Retention; + +import javax.inject.Inject; +import javax.inject.Qualifier; +import javax.inject.Singleton; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; import static junit.framework.Assert.assertEquals; import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.atLeast; @@ -363,6 +378,222 @@ public void should_be_able_to_log_navigation_history() throws Exception { verify(logger, atLeast(1)).trace(anyString()); } + private static class TimerFragmentX2 { + @Inject + @Slower2 + private TimerController timerController; + } + + private static class TimerFragmentX3 { + @Inject + @Slower3 + private TimerController timerController; + } + + @Qualifier + @Retention(RUNTIME) + @interface Slower2 {} + + @Qualifier + @Retention(RUNTIME) + @interface Slower3 {} + + @Slower2 + @Slower3 + static class SlowXHolder { + + } + + @Test(expected = MvcGraphException.class) + public void should_catch_invocation_exception_when_NPE_detected_on_injection() throws Exception { + Component com = new Component() { + @Provides + @Singleton + @Slower2 + TimerController timerSlowerX2() { + return new TimerControllerImpl(){ + { + onConstruct(); + } + @Override + public void setInitialValue(long value) { + super.setInitialValue(value * 2); + } + }; + } + }; + Injector.getGraph().register(com); + + Annotation slower2Qualifier = SlowXHolder.class.getAnnotation(Slower2.class); + + Injector.getGraph().use(TimerController.class, slower2Qualifier, new Consumer() { + @Override + public void consume(TimerController instance) { + //Controller should have now been released + Assert.assertEquals(0, instance.getInitialValue()); + } + }); + } + + @Test + public void should_retain_prepared_instance_until_navigation_settled() throws Exception { + // Arrange + final long fiveMinutes = 60 * 5; + + Component com = new Component() { + @Provides + @Singleton + @Slower2 + TimerController timerSlowerX2() { + return new TimerControllerImpl(){ + { + try { + onConstruct(); + } catch (Exception e) {} + + } + @Override + public void setInitialValue(long value) { + super.setInitialValue(value * 2); + } + }; + } + + @Provides + @Singleton + @Slower3 + TimerController timerSlowerX3() { + return new TimerControllerImpl(){ + { + try { + onConstruct(); + } catch (Exception e) {} + } + @Override + public void setInitialValue(long value) { + super.setInitialValue(value * 3); + } + }; + } + }; + + Injector.getGraph().register(com); + + Annotation slower2Qualifier = SlowXHolder.class.getAnnotation(Slower2.class); + Annotation slower3Qualifier = SlowXHolder.class.getAnnotation(Slower3.class); + + Injector.getGraph().use(TimerController.class, slower2Qualifier, new Consumer() { + @Override + public void consume(TimerController instance) { + //Controller should have now been released + Assert.assertEquals(0, instance.getInitialValue()); + } + }); + + // Act + Navigator navigator = navigationController.navigate(this).with(TimerController.class, slower2Qualifier, new Preparer() { + @Override + public void prepare(TimerController instance) { + instance.setInitialValue(fiveMinutes); + } + }); + navigator.to(TimerFragmentX2.class.getName()); + + Injector.getGraph().use(TimerController.class, slower2Qualifier, new Consumer() { + @Override + public void consume(TimerController instance) { + //Controller should not have been released yet + Assert.assertEquals(fiveMinutes * 2, instance.getInitialValue()); + } + }); + + //destroy the navigator + navigator.__destroy(); + + Injector.getGraph().use(TimerController.class, slower2Qualifier, new Consumer() { + @Override + public void consume(TimerController instance) { + //Controller should have now been released + Assert.assertEquals(0, instance.getInitialValue()); + } + }); + + //Test fragment 3 + Injector.getGraph().use(TimerController.class, slower3Qualifier, new Consumer() { + @Override + public void consume(TimerController instance) { + //Controller should have now been released + Assert.assertEquals(0, instance.getInitialValue()); + } + }); + + navigator = navigationController.navigate(this).with(TimerController.class, slower3Qualifier, new Preparer() { + @Override + public void prepare(TimerController instance) { + instance.setInitialValue(fiveMinutes); + } + }); + navigator.to(TimerFragmentX3.class.getName()); + + Injector.getGraph().use(TimerController.class, slower3Qualifier, new Consumer() { + @Override + public void consume(TimerController instance) { + //Controller should not have been released yet + Assert.assertEquals(fiveMinutes * 3, instance.getInitialValue()); + } + }); + + //destroy the navigator + navigator.__destroy(); + + Injector.getGraph().use(TimerController.class, slower3Qualifier, new Consumer() { + @Override + public void consume(TimerController instance) { + //Controller should have now been released + Assert.assertEquals(0, instance.getInitialValue()); + } + }); + } + + private static class TimerFragment { + @Inject + private TimerController timerController; + } + + @Test + public void should_retain_prepared_instance_until_navigation_settled_without_qualifier() throws Exception { + // Arrange + final long fiveMinutes = 60 * 5; + + // Act + Navigator navigator = navigationController.navigate(this).with(TimerController.class, new Preparer() { + @Override + public void prepare(TimerController instance) { + instance.setInitialValue(fiveMinutes); + } + }); + navigator.to(TimerFragment.class.getName()); + + Injector.getGraph().use(TimerController.class, null, new Consumer() { + @Override + public void consume(TimerController instance) { + //Controller should not have been released yet + Assert.assertEquals(fiveMinutes, instance.getInitialValue()); + } + }); + + //destroy the navigator + navigator.__destroy(); + + Injector.getGraph().use(TimerController.class, new Consumer() { + @Override + public void consume(TimerController instance) { + //Controller should have now been released + Assert.assertEquals(0, instance.getInitialValue()); + } + }); + } + /** * Prepare 4 locations in the history */ diff --git a/library/android-mvc-controller/src/test/java/com/shipdream/lib/android/mvc/inject/testNameMapping/controller/TimerController.java b/library/android-mvc-controller/src/test/java/com/shipdream/lib/android/mvc/inject/testNameMapping/controller/TimerController.java new file mode 100644 index 0000000..1ee5f85 --- /dev/null +++ b/library/android-mvc-controller/src/test/java/com/shipdream/lib/android/mvc/inject/testNameMapping/controller/TimerController.java @@ -0,0 +1,24 @@ +/* + * Copyright 2015 Kejun Xia + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.shipdream.lib.android.mvc.inject.testNameMapping.controller; + +import com.shipdream.lib.android.mvc.controller.BaseController; + +public interface TimerController extends BaseController { + void setInitialValue(long value); + long getInitialValue(); +} diff --git a/library/android-mvc-controller/src/test/java/com/shipdream/lib/android/mvc/inject/testNameMapping/controller/TimerModel.java b/library/android-mvc-controller/src/test/java/com/shipdream/lib/android/mvc/inject/testNameMapping/controller/TimerModel.java new file mode 100644 index 0000000..c8b357b --- /dev/null +++ b/library/android-mvc-controller/src/test/java/com/shipdream/lib/android/mvc/inject/testNameMapping/controller/TimerModel.java @@ -0,0 +1,29 @@ +/* + * Copyright 2015 Kejun Xia + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.shipdream.lib.android.mvc.inject.testNameMapping.controller; + +public class TimerModel { + private long initialValue; + + public long getInitialValue() { + return initialValue; + } + + public void setInitialValue(long initialValue) { + this.initialValue = initialValue; + } +} diff --git a/library/android-mvc-controller/src/test/java/com/shipdream/lib/android/mvc/inject/testNameMapping/controller/internal/TimerControllerImpl.java b/library/android-mvc-controller/src/test/java/com/shipdream/lib/android/mvc/inject/testNameMapping/controller/internal/TimerControllerImpl.java new file mode 100644 index 0000000..ef86b39 --- /dev/null +++ b/library/android-mvc-controller/src/test/java/com/shipdream/lib/android/mvc/inject/testNameMapping/controller/internal/TimerControllerImpl.java @@ -0,0 +1,22 @@ +package com.shipdream.lib.android.mvc.inject.testNameMapping.controller.internal; + +import com.shipdream.lib.android.mvc.controller.internal.BaseControllerImpl; +import com.shipdream.lib.android.mvc.inject.testNameMapping.controller.TimerController; +import com.shipdream.lib.android.mvc.inject.testNameMapping.controller.TimerModel; + +public class TimerControllerImpl extends BaseControllerImpl implements TimerController{ + @Override + protected Class getModelClassType() { + return TimerModel.class; + } + + @Override + public void setInitialValue(long value) { + getModel().setInitialValue(value); + } + + @Override + public long getInitialValue() { + return getModel().getInitialValue(); + } +} diff --git a/library/poke/src/main/java/com/shipdream/lib/poke/ProviderFinderByRegistry.java b/library/poke/src/main/java/com/shipdream/lib/poke/ProviderFinderByRegistry.java index dc3781c..19e0030 100644 --- a/library/poke/src/main/java/com/shipdream/lib/poke/ProviderFinderByRegistry.java +++ b/library/poke/src/main/java/com/shipdream/lib/poke/ProviderFinderByRegistry.java @@ -425,7 +425,7 @@ protected Object createInstance() throws ProvideException { "be accessible.", method.getName()), e); // $COVERAGE-IGNORE$ } catch (InvocationTargetException e) { throw new ProvideException(String.format("Provides method %s is not able " + - "to be invoked against %s.", method.getName(), component.getClass().getName())); // $COVERAGE-IGNORE$ + "to be invoked against %s.", method.getName(), component.getClass().getName()), e); // $COVERAGE-IGNORE$ } } } From fe78d0440a1cea23576ebbe0d167570065be5e35 Mon Sep 17 00:00:00 2001 From: Kejun Xia Date: Mon, 7 Dec 2015 23:36:19 +1100 Subject: [PATCH 26/26] More unit tests for MvcGraph and navigation controller --- .../shipdream/lib/android/mvc/MvcGraph.java | 7 +--- .../lib/android/mvc/TestMvcGraph.java | 36 ++++++++++++++++ .../internal/TestNavigationController.java | 42 +++++++++++++++++++ 3 files changed, 79 insertions(+), 6 deletions(-) diff --git a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/MvcGraph.java b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/MvcGraph.java index a7d3021..0243a2e 100644 --- a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/MvcGraph.java +++ b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/MvcGraph.java @@ -82,7 +82,6 @@ *

*/ public class MvcGraph { - List cachedInstancesBeforeNavigation = new ArrayList<>(); private Logger logger = LoggerFactory.getLogger(getClass()); ScopeCache singletonScopeCache; DefaultProviderFinder defaultProviderFinder; @@ -350,11 +349,7 @@ public void use(final Class type, final Annotation qualifier, final Consu public void inject(Object target) { try { graph.inject(target, Inject.class); - } catch (ProvideException e) { - throw new MvcGraphException(e.getMessage(), e); - } catch (ProviderMissingException e) { - throw new MvcGraphException(e.getMessage(), e); - } catch (CircularDependenciesException e) { + } catch (PokeException e) { throw new MvcGraphException(e.getMessage(), e); } } diff --git a/library/android-mvc-controller/src/test/java/com/shipdream/lib/android/mvc/TestMvcGraph.java b/library/android-mvc-controller/src/test/java/com/shipdream/lib/android/mvc/TestMvcGraph.java index 77c4b5f..15c713a 100644 --- a/library/android-mvc-controller/src/test/java/com/shipdream/lib/android/mvc/TestMvcGraph.java +++ b/library/android-mvc-controller/src/test/java/com/shipdream/lib/android/mvc/TestMvcGraph.java @@ -410,4 +410,40 @@ public void should_be_able_save_and_restore_state_correctly() // Verify verify(stateManagedMock).restoreState(eq(stateMock)); } + + interface UnimplementedInterface{} + + @Test(expected = MvcGraphException.class) + public void should_raise_mvc_graph_exception_when_inject_on_poke_exception() { + class View { + @Inject + UnimplementedInterface unimplementedInterface; + } + mvcGraph.inject(new View()); + } + + @Test(expected = MvcGraphException.class) + public void should_raise_mvc_graph_exception_when_release_on_poke_exception() { + class View { + @Inject + UnimplementedInterface unimplementedInterface; + } + View view = new View(); + view.unimplementedInterface = new UnimplementedInterface() { + }; + mvcGraph.release(view); + } + + @Test(expected = MvcGraphException.class) + public void should_raise_mvc_graph_exception_when_use_on_poke_exception() { + class View { + @Inject + UnimplementedInterface unimplementedInterface; + } + mvcGraph.use(UnimplementedInterface.class, new Consumer() { + @Override + public void consume(UnimplementedInterface instance) { + } + }); + } } diff --git a/library/android-mvc-controller/src/test/java/com/shipdream/lib/android/mvc/controller/internal/TestNavigationController.java b/library/android-mvc-controller/src/test/java/com/shipdream/lib/android/mvc/controller/internal/TestNavigationController.java index b9c0686..3544540 100644 --- a/library/android-mvc-controller/src/test/java/com/shipdream/lib/android/mvc/controller/internal/TestNavigationController.java +++ b/library/android-mvc-controller/src/test/java/com/shipdream/lib/android/mvc/controller/internal/TestNavigationController.java @@ -30,6 +30,7 @@ import org.junit.Assert; import org.junit.Test; import org.mockito.ArgumentCaptor; +import org.mockito.internal.matchers.Null; import org.slf4j.Logger; import java.lang.annotation.Annotation; @@ -42,9 +43,12 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME; import static junit.framework.Assert.assertEquals; import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.eq; +import static org.mockito.Matchers.isNull; import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -594,6 +598,44 @@ public void consume(TimerController instance) { }); } + @Test + public void should_invoke_singular_argument_with_method_of_navigator_correct() throws Exception { + Navigator navigator = navigationController.navigate(this); + Navigator spiedNavigator = spy(navigator); + + verify(spiedNavigator, times(0)).with(eq(NavigationController.class), isNull(Annotation.class), isNull(Preparer.class)); + + spiedNavigator.with(NavigationController.class); + + verify(spiedNavigator).with(eq(NavigationController.class), isNull(Annotation.class), isNull(Preparer.class)); + } + + @Test + public void should_return_correct_sender_by_navigator() throws Exception { + // Act + Navigator navigator = navigationController.navigate(this); + + Assert.assertTrue(this == navigator.getSender()); + } + + @Test + public void should_invoke_on_settled_when_navigation_is_done() throws Exception { + // Arrange + Navigator.OnSettled onSettled = mock(Navigator.OnSettled.class); + + // Act + Navigator navigator = navigationController.navigate(this).with(TimerController.class) + .onSettled(onSettled); + navigator.to(TimerFragment.class.getName()); + + verify(onSettled, times(0)).run(); + + //destroy the navigator + navigator.__destroy(); + + verify(onSettled, times(1)).run(); + } + /** * Prepare 4 locations in the history */