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}"
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 379dca9..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
@@ -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;
@@ -79,7 +82,6 @@
*
*/
public class MvcGraph {
- List cachedInstancesBeforeNavigation = new ArrayList<>();
private Logger logger = LoggerFactory.getLogger(getClass());
ScopeCache singletonScopeCache;
DefaultProviderFinder defaultProviderFinder;
@@ -182,12 +184,36 @@ public void clearOnProviderFreedListeners() {
graph.clearOnProviderFreedListeners();
}
+ /**
+ * 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);
+ }
+
/**
* 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(Class type, Consumer consumer) {
+ public void use(final Class type, final Consumer consumer) {
try {
graph.use(type, Inject.class, consumer);
} catch (PokeException e) {
@@ -202,6 +228,7 @@ public void use(Class type, Consumer consumer) {
* 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 {
@@ -292,12 +319,20 @@ 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
* @throws MvcGraphException throw when there are exceptions during the consumption of the instance
*/
- public void use(Class type, Annotation qualifier, Consumer consumer) {
+ public void use(final Class type, final Annotation qualifier, final Consumer consumer) {
try {
graph.use(type, qualifier, Inject.class, consumer);
} catch (PokeException e) {
@@ -314,11 +349,7 @@ public void use(Class type, Annotation qualifier, Consumer consumer) {
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/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/__MvcGraphHelper.java b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/__MvcGraphHelper.java
index 30016be..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
@@ -1,43 +1,17 @@
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.Collection;
+import java.util.HashMap;
+import java.util.Map;
/**
* Helper class to work with MvcGraph. Internal use only. Don't use it in your app.
*/
public class __MvcGraphHelper {
- /**
- * Internal use. Don't call it in app directly.
- */
- public static void retainCachedObjectsBeforeNavigation(MvcGraph mvcGraph) {
- mvcGraph.cachedInstancesBeforeNavigation.clear();
- //Retain all cached items before navigation.
- Collection cachedItems = mvcGraph.singletonScopeCache.getCachedItems();
- for (ScopeCache.CachedItem cachedItem : cachedItems) {
- Provider provider = cachedItem.getProvider();
- if (provider != null) {
- mvcGraph.cachedInstancesBeforeNavigation.add(provider);
- provider.retain();
- }
- }
- }
-
- /**
- * Internal use. Don't call it in app directly.
- */
- public static void releaseCachedItemsAfterNavigation(MvcGraph mvcGraph) {
- //Release all cached items after the fragment navigated to is ready to show.
- for (Provider provider : mvcGraph.cachedInstancesBeforeNavigation) {
- if (provider != null) {
- provider.release();
- }
- }
- mvcGraph.cachedInstancesBeforeNavigation.clear();
- }
-
/**
* 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/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 6caa270..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
@@ -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,16 @@
* Controller to navigate among different fragments in the SAME activity.
*/
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);
+
/**
+ *
* 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 +56,13 @@ public interface NavigationController extends BaseController
*
+ * Deprecated: use {@link #navigate(Object)} 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 +87,26 @@ public interface NavigationController extends BaseController
*
+ * Deprecated: use {@link #navigate(Object)} 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)} instead
+ *
* @param sender Who wants to navigate back
*/
+ @Deprecated
void navigateBack(Object sender);
/**
@@ -95,19 +115,39 @@ public interface NavigationController extends BaseControllerDeprecated: 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
* 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 {
+ 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;
+ }
+ }
+
/**
* Event to notify views navigation will move forward.
*/
- class OnLocationForward extends ValueChangeEventC2V {
+ class OnLocationForward extends OnLocationChanged {
private boolean clearHistory;
private NavLocation locationWhereHistoryClearedUpTo;
@@ -120,8 +160,9 @@ class OnLocationForward extends ValueChangeEventC2V {
* @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;
}
@@ -147,12 +188,12 @@ 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,
- 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/BaseControllerImpl.java b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/internal/BaseControllerImpl.java
index f029107..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
@@ -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;
@@ -36,10 +37,10 @@
import javax.inject.Inject;
/**
- *
+ * Base controller implementation.
*/
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 3c25c2c..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
@@ -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;
@@ -26,7 +24,7 @@
*/
public class NavigationControllerImpl extends BaseControllerImpl
implements NavigationController {
- public static boolean dumpHistoryOnLocationChange = false;
+ public boolean dumpHistoryOnLocationChange = false;
@Override
public Class getModelClassType() {
@@ -34,175 +32,28 @@ public Class getModelClassType() {
}
@Override
- public void navigateTo(Object sender, String locationId) {
- doNavigateTo(sender, locationId, false, null);
+ public Navigator navigate(Object sender) {
+ return new Navigator(sender, this);
}
@Override
- public void navigateTo(Object sender, String locationId, String clearTopToLocationId) {
- doNavigateTo(sender, locationId, true, clearTopToLocationId);
+ public void navigateTo(Object sender, String locationId) {
+ navigate(sender).to(locationId);
}
- 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();
-
- /**
- * 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(Injector.getGraph());
-
- postC2VEvent(new EventC2V.OnLocationForward(sender, lastLoc, currentLoc, clearTop,
- clearedTopToLocation));
-
- logger.trace("Nav Controller: Forward: {} -> {}", lastLocId, currentLoc.getLocationId());
- }
-
- dumpHistory();
+ @Override
+ public void navigateTo(Object sender, String locationId, String clearTopToLocationId) {
+ navigate(sender).to(locationId, clearTopToLocationId);
}
@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);
- postC2VEvent(new EventC2V.OnLocationBack(sender, currentLoc, previousLoc, false));
-
- 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();
- }
- }
-
- 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("");
- }
+ 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
new file mode 100644
index 0000000..be1519a
--- /dev/null
+++ b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/controller/internal/Navigator.java
@@ -0,0 +1,433 @@
+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.NavigationController;
+import com.shipdream.lib.poke.exception.PokeException;
+import com.shipdream.lib.poke.exception.ProviderMissingException;
+
+import java.lang.annotation.Annotation;
+import java.util.ArrayList;
+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();
+ }
+
+ 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;
+
+ /**
+ * 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. 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
+ * 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 with method.
+ *
+ class TimerFragment {
+ @Inject
+ TimerController timerController;
+ }
+
+ interface TimerController {
+ void setInitialValue(long howManySeconds);
+ }
+
+ 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
+ * @return This navigator
+ * @throws MvcGraphException Raised when the required injectable object cannot be injected
+ */
+ public Navigator with(Class type, Preparer preparer) throws MvcGraphException {
+ with(type, null, preparer);
+ 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 with method.
+ *
+ class TimerFragment {
+ @Inject
+ TimerController timerController;
+ }
+
+ interface TimerController {
+ void setInitialValue(long howManySeconds);
+ }
+
+ navigationController.navigate(this).with(TimerController.class, null, 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 qualifier The qualifier
+ * @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 with(Class type, Annotation qualifier, Preparer preparer) throws MvcGraphException {
+ try {
+ T instance = Injector.getGraph().reference(type, qualifier);
+
+ if (preparer != null) {
+ preparer.prepare(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;
+ }
+
+ /**
+ * 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, Preparer)}
+ *
+ *
+ *
+ * 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();
+ }
+
+ /**
+ * 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 #with(Class, Annotation, Preparer)}
+ *
+ *
+ *
+ * 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
+ * @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();
+ }
+
+ 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, this);
+ }
+ }
+
+ /**
+ * 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) {
+ navigationController.logger.warn("Current location should never be null before navigating backwards.");
+ return;
+ }
+
+ NavLocation previousLoc = currentLoc.getPreviousLocation();
+ navigationController.getModel().setCurrentLocation(previousLoc);
+
+ navigateEvent = new NavigationController.EventC2V.OnLocationBack(sender, currentLoc, previousLoc, false, this);
+ 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) {
+ navigationController.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) {
+ navigationController.getModel().setCurrentLocation(currentLoc);
+ navigateEvent = new NavigationController.EventC2V.OnLocationBack(sender, previousLoc, currentLoc, true, this);
+ }
+ 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;
+ }
+
+ /**
+ * Sends out the navigation event to execute the navigation
+ */
+ private 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();
+ }
+
+ /**
+ * 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
+ */
+ private void checkAppExit(Object sender) {
+ NavLocation curLocation = navigationController.getModel().getCurrentLocation();
+ if (curLocation == null) {
+ navigationController.postC2CEvent(new NavigationController.EventC2C.OnAppExit(sender));
+ }
+ }
+
+ /**
+ * Prints navigation history
+ */
+ 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/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-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/TestMvcGraphHelper.java b/library/android-mvc-controller/src/test/java/com/shipdream/lib/android/mvc/TestMvcGraphHelper.java
deleted file mode 100644
index 36f9deb..0000000
--- a/library/android-mvc-controller/src/test/java/com/shipdream/lib/android/mvc/TestMvcGraphHelper.java
+++ /dev/null
@@ -1,50 +0,0 @@
-package com.shipdream.lib.android.mvc;
-
-import com.shipdream.lib.android.mvc.controller.NavigationController;
-
-import org.junit.After;
-import org.junit.Assert;
-import org.junit.Test;
-
-import java.util.concurrent.ExecutorService;
-
-import javax.inject.Inject;
-
-import static org.mockito.Mockito.mock;
-
-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();
-
- __MvcGraphHelper.retainCachedObjectsBeforeNavigation(Injector.getGraph());
- Assert.assertNotEquals(initSize, __MvcGraphHelper.getAllCachedInstances(Injector.getGraph()));
-
- __MvcGraphHelper.releaseCachedItemsAfterNavigation(Injector.getGraph());
- Assert.assertEquals(initSize, __MvcGraphHelper.getAllCachedInstances(Injector.getGraph()).size());
- }
-
-}
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..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
@@ -16,20 +16,39 @@
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.mockito.internal.matchers.Null;
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.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;
@@ -325,8 +344,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");
@@ -363,6 +382,260 @@ 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());
+ }
+ });
+ }
+
+ @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
*/
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/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/BaseTestCase.java b/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/BaseTestCase.java
index 5081576..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.navigateBack(this, null);
- navigationController.navigateBack(this);
+ 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/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/injection/TestInjectionAndLifeCycle.java b/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/injection/TestInjectionAndLifeCycle.java
index 96a92e3..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
@@ -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);
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);
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).back();
waitTest(1000);
//=============================> At B
lifeCycleValidatorC.expect(LifeCycle.onDestroyView, LifeCycle.onDestroy);
@@ -97,14 +97,16 @@ 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" +
"Added by FragmentB")));
onView(withId(R.id.textC)).check(matches(withText("")));
- navigationController.navigateBack(this);
+ navigationController.navigate(this).back();
waitTest(1000);
//=============================> At A
//onDestroy of previous Fragment(FragmentB) is not called until it's removed out from back stack
@@ -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" +
@@ -140,7 +143,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);
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..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
@@ -87,12 +87,12 @@ 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();
resetGraphMonitorCounts();
- navigationController.navigateBack(this);
+ navigationController.navigate(this).back();
//->A->B->C
waitTest(1000);
Assert.assertEquals(fm.getFragments().size(), 4);
@@ -108,13 +108,13 @@ 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);
resetGraphMonitorCounts();
- navigationController.navigateBack(this);
+ navigationController.navigate(this).back();
//->A->B
waitTest();
Assert.assertEquals(fm.getFragments().size(), 4);
@@ -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);
@@ -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).back();
//->A
waitTest();
Assert.assertEquals(fm.getFragments().size(), 4);
@@ -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);
@@ -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).back();
//quit
waitTest(2000);
Assert.assertEquals(fm.getFragments().size(), 4);
@@ -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
@@ -337,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();
@@ -344,16 +349,16 @@ 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);
resetGraphMonitorCounts();
- navigationController.navigateBack(this);
+ navigationController.navigate(this).back();
//->A->B->C
waitTest(1000);
Assert.assertEquals(fragAInjectCount, 0);
@@ -366,7 +371,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).back();
//->A->B
waitTest();
Assert.assertEquals(fragAInjectCount, 0);
@@ -379,7 +384,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).back();
//->A
waitTest();
Assert.assertEquals(fragAInjectCount, 0);
@@ -392,7 +397,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).back();
//quit
waitTest();
Assert.assertEquals(fragAInjectCount, 0);
@@ -415,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);
@@ -422,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);
@@ -444,7 +450,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).back();
//quit
waitTest();
Assert.assertEquals(fragAInjectCount, 0);
@@ -466,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();
@@ -474,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);
@@ -496,7 +502,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).back();
//->A->B
waitTest();
Assert.assertEquals(fragAInjectCount, 0);
@@ -509,7 +515,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).back();
//->A
waitTest();
Assert.assertEquals(fragAInjectCount, 0);
@@ -522,7 +528,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).back();
//quit
waitTest();
Assert.assertEquals(fragAInjectCount, 0);
@@ -536,83 +542,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 933d9da..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
@@ -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,15 @@
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 TestCaseNavigationBasic extends BaseTestCase {
+ private Logger logger = LoggerFactory.getLogger(getClass());
+
@Inject
private AnotherController anotherController;
@@ -97,6 +104,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);
@@ -123,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(1000);
- verify(disposeCheckerAMock, times(1)).onDisposed();
- verify(disposeCheckerBMock, times(1)).onDisposed();
- verify(disposeCheckerCMock, times(1)).onDisposed();
+ waitTest(2000);
+ verify(disposeCheckerAMock, times(0)).onDisposed();
+ verify(disposeCheckerBMock, times(0)).onDisposed();
+ verify(disposeCheckerCMock, times(0)).onDisposed();
verify(disposeCheckerDMock, times(1)).onDisposed();
//A->B->C
@@ -156,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
@@ -167,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
@@ -177,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 4d1dde0..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
@@ -16,8 +16,8 @@
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.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;
@@ -26,6 +26,10 @@
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;
@@ -36,16 +40,22 @@
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;
private Comp comp;
private DisposeCheckerE disposeCheckerEMock;
+ private DisposeCheckerF disposeCheckerFMock;
+ private DisposeCheckerG disposeCheckerGMock;
public TestCaseNavigationFromController() {
super(MvcTestActivityNavigation.class);
@@ -68,6 +78,18 @@ public static class Comp extends Component{
public DisposeCheckerE providesDisposeCheckerE() {
return testCaseNavigation.disposeCheckerEMock;
}
+
+ @Singleton
+ @Provides
+ public DisposeCheckerF providesDisposeCheckerF() {
+ return testCaseNavigation.disposeCheckerFMock;
+ }
+
+ @Singleton
+ @Provides
+ public DisposeCheckerG providesDisposeCheckerG() {
+ return testCaseNavigation.disposeCheckerGMock;
+ }
}
@Override
@@ -75,6 +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);
@@ -87,18 +132,17 @@ protected void cleanDependencies() {
}
@Test
- public void testShouldReleaseInjectionsAfterFragmentsArePoppedOut() 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();
- Injector.getGraph().use(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);
- 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
@@ -114,4 +158,84 @@ public void consume(ControllerE instance) {
verify(disposeCheckerEMock, times(1)).onDisposed();
}
+ @Test
+ 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();
+ final String valF = "ValueF = " + new Random().nextInt();
+ final String valG = "ValueG = " + new Random().nextInt();
+
+ resetDisposeCheckers();
+ navigationController.navigate(this).with(ControllerE.class, new Preparer() {
+ @Override
+ public void prepare(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();
+
+ resetDisposeCheckers();
+ navigationController.navigate(this).with(ControllerF.class, new Preparer() {
+ @Override
+ public void prepare(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();
+
+ resetDisposeCheckers();
+ navigationController.navigate(this).with(ControllerG.class, new Preparer() {
+ @Override
+ public void prepare(ControllerG instance) {
+ instance.setValue(valG);
+ }
+ }).to(MvcTestActivityNavigation.Loc.G);
+ waitTest();
+ onView(withText(valG)).check(matches(isDisplayed()));
+ 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);
+ 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);
+ waitTest();
+ onView(withText(valE)).check(matches(isDisplayed()));
+ verify(disposeCheckerEMock, times(0)).onDisposed();
+ verify(disposeCheckerFMock, times(1)).onDisposed();
+ //__MvcGraphHelper retaining all cache is dangerous. Try to only retain relevant injected instances.
+ verify(disposeCheckerGMock, times(0)).onDisposed();
+
+ resetDisposeCheckers();
+ navigationController.navigateBack(this);
+ waitTest();
+ 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/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..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,29 +39,13 @@ public TestFragmentsInViewPager() {
super(ViewPagerTestActivity.class);
}
- @Test
- public void 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()));
+ @Override
+ protected void waitTest() throws InterruptedException {
+ super.waitTest(200);
}
@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 +125,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 +215,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;
@@ -269,16 +253,16 @@ public void should_call_onViewReady_in_tab_fragments_when_comes_back_from_anothe
//=============================> 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())));
@@ -286,18 +270,16 @@ public void should_call_onViewReady_in_tab_fragments_when_comes_back_from_anothe
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
- 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;
@@ -351,7 +333,7 @@ public void should_call_onViewReady_in_tab_fragments_when_comes_back_from_anothe
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())));
@@ -359,18 +341,16 @@ public void should_call_onViewReady_in_tab_fragments_when_comes_back_from_anothe
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
- 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);
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..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().navigateTo(this, "TestFragment", null);
+ 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 a9839f3..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.navigateTo(view, loc);
+ 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 c0a30a3..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.navigateTo(this, MvcTestActivityNavigation.Loc.A, null);
+ 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 6cff31e..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().navigateTo(this, "TestFragment", null);
+ getNavigationController().navigate(this).to("TestFragment", null);
}
@Override
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..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
@@ -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 extends MvcFragment> 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;
}
@@ -68,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);
}
@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..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.navigateTo(v, getNextFragmentLocId());
+ 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.navigateBack(view, null);
+ navigationController.navigate(view).back(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..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
@@ -25,15 +25,12 @@
import javax.inject.Inject;
public class ControllerEImpl extends BaseControllerImpl implements ControllerE {
- private int value = 0;
-
@Inject
private DisposeCheckerE disposeCheckerE;
@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
new file mode 100644
index 0000000..8211db6
--- /dev/null
+++ b/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/nav/internal/ControllerFImpl.java
@@ -0,0 +1,51 @@
+/*
+ * 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", "Controller F disposed");
+ }
+
+ @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..6bbc378
--- /dev/null
+++ b/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/nav/internal/ControllerGImpl.java
@@ -0,0 +1,51 @@
+/*
+ * 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", "Controller G disposed");
+ }
+
+ @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..82c6f82
--- /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.DisposeCheckerF;
+
+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
new file mode 100644
index 0000000..248987d
--- /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.DisposeCheckerG;
+
+public class DisposeCheckerGImpl implements DisposeCheckerG {
+ @Override
+ public void onDisposed() {
+ Log.i("DisposeCheck", "Checker G disposed");
+ }
+}
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..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.navigateTo(v, SubFragment.class.getSimpleName());
+ 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 2890c92..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 extends DelegateFragment> 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);
}
}
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..31cb864
--- /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/MvcActivity.java b/library/android-mvc/src/main/java/com/shipdream/lib/android/mvc/view/MvcActivity.java
index 84ffa1b..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
@@ -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;
@@ -252,7 +253,7 @@ public boolean onBackButtonPressed() {
navigateBack = !topFragment.onBackButtonPressed();
}
if (navigateBack) {
- navigationController.navigateBack(this);
+ navigationController.navigate(this).back();
}
return true;
}
@@ -397,7 +398,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();
@@ -420,11 +421,11 @@ private void performForwardNav(NavigationController.EventC2V.OnLocationForward e
}
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();
}
@@ -445,18 +446,15 @@ private void performForwardNav(NavigationController.EventC2V.OnLocationForward e
logger.trace("Cleared fragment back stack up to {}", tagPopTo);
}
- final MvcFragment finalLastFrag = lastFrag;
fragment.registerOnViewReadyListener(new Runnable() {
@Override
public void run() {
- //Release reference count to pair the retaining by NavigationControllerImpl
- // with Injector.getGraph().retainCachedObjectsBeforeNavigation();
- __MvcGraphHelper.releaseCachedItemsAfterNavigation(Injector.getGraph());
-
- if (finalLastFrag != null) {
- finalLastFrag.releaseDependencies();
+ if (event.getNavigator() != null) {
+ __MvcControllerHelper.destroyNavigator(event.getNavigator());
}
+ logger.trace("Fragment ready: " + fragment.getClass().getSimpleName());
+
fragment.unregisterOnViewReadyListener(this);
}
});
@@ -483,9 +481,13 @@ 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) {
+ if (event.getNavigator() != null) {
+ __MvcControllerHelper.destroyNavigator(event.getNavigator());
+ }
+
MvcActivity mvcActivity = ((MvcActivity) getActivity());
//Back to null which should finish the current activity
mvcActivity.performSuperBackKeyPressed();
@@ -497,7 +499,6 @@ private void performBackNav(NavigationController.EventC2V.OnLocationBack event)
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();
@@ -508,6 +509,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() {
+ if (event.getNavigator() != null) {
+ __MvcControllerHelper.destroyNavigator(event.getNavigator());
+ }
+
+ if (finalLastFrag != null) {
+ finalLastFrag.releaseDependencies();
+ finalLastFrag.selfRelease = true;
+ }
+
+ currentFrag.unregisterOnViewReadyListener(this);
+ }
+ });
}
if (event.isFastRewind()) {
@@ -542,7 +570,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 e6bca32..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
@@ -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;
@@ -144,6 +146,7 @@ public String toString() {
private boolean dependenciesInjected = false;
boolean isStateManagedByRootDelegateFragment = false;
+ boolean selfRelease = true;
/**
* @return orientation before last orientation change.
@@ -180,6 +183,8 @@ void releaseDependencies() {
if (dependenciesInjected) {
AndroidMvc.graph().release(this);
dependenciesInjected = false;
+
+ LoggerFactory.getLogger(getClass()).trace("Fragment release: " + getClass().getSimpleName());
}
}
@@ -391,7 +396,9 @@ public void onDestroyView() {
@Override
public void onDestroy() {
super.onDestroy();
- releaseDependencies();
+ if (selfRelease) {
+ releaseDependencies();
+ }
eventRegister.onDestroy();
eventRegister = 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);
}
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..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
@@ -272,32 +272,49 @@ public void consume(Os instance) {
public void use(Class type, Annotation qualifier,
Class extends Annotation> injectAnnotation, Consumer consumer)
throws ProvideException, CircularDependenciesException, ProviderMissingException {
- 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;
- }
+ 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 extends Annotation> injectAnnotation)
+ throws ProviderMissingException, ProvideException, CircularDependenciesException {
+ Provider provider = getProvider(type, qualifier);
+ T instance = provider.get();
+ doInject(instance, null, type, qualifier, injectAnnotation);
provider.retain();
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 extends Annotation> injectAnnotation) throws ProviderMissingException {
doRelease(instance, null, type, qualifier, injectAnnotation);
+ Provider provider = getProvider(type, qualifier);
provider.release();
checkToFreeProvider(provider);
}
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$
}
}
}
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());
+ }
+}