From 185ccf5d6bc3a53293b188c3ad34b1f825761b0a Mon Sep 17 00:00:00 2001 From: Kejun Xia Date: Sun, 11 Oct 2015 20:49:18 +1100 Subject: [PATCH 01/14] Lower accessibility of navigation event handler in root fragment of MvcActivity --- .../shipdream/lib/android/mvc/view/MvcActivity.java | 12 ++++++++++-- .../mvc/samples/note/view/fragment/MainFragment.java | 4 ++-- 2 files changed, 12 insertions(+), 4 deletions(-) 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 b85ac15..311b5df 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 @@ -363,7 +363,11 @@ private void notifyAllSubMvcFragmentsTheirStateIsManagedByMe(MvcFragment fragmen } } - public void onEvent(final NavigationController.EventC2V.OnLocationForward event) { + /** + * Handle the forward navigation event call back + * @param event The forward navigation event + */ + protected void onEvent(final NavigationController.EventC2V.OnLocationForward event) { if (!canCommitFragmentTransaction) { pendingNavActions.add(new Runnable() { @Override @@ -432,7 +436,11 @@ private void performForwardNav(NavigationController.EventC2V.OnLocationForward e } } - public void onEvent(final NavigationController.EventC2V.OnLocationBack event) { + /** + * Handle the backward navigation event call back + * @param event The backward navigation event + */ + protected void onEvent(final NavigationController.EventC2V.OnLocationBack event) { if (!canCommitFragmentTransaction) { pendingNavActions.add(new Runnable() { @Override diff --git a/samples/note/android/src/main/java/com/shipdream/lib/android/mvc/samples/note/view/fragment/MainFragment.java b/samples/note/android/src/main/java/com/shipdream/lib/android/mvc/samples/note/view/fragment/MainFragment.java index 4cab9c6..4ecff72 100644 --- a/samples/note/android/src/main/java/com/shipdream/lib/android/mvc/samples/note/view/fragment/MainFragment.java +++ b/samples/note/android/src/main/java/com/shipdream/lib/android/mvc/samples/note/view/fragment/MainFragment.java @@ -124,13 +124,13 @@ private AppController.Orientation convertOrientation(int orientation) { } @Override - public void onEvent(NavigationController.EventC2V.OnLocationForward event) { + protected void onEvent(NavigationController.EventC2V.OnLocationForward event) { super.onEvent(event); updateNavigationUi(); } @Override - public void onEvent(NavigationController.EventC2V.OnLocationBack event) { + protected void onEvent(NavigationController.EventC2V.OnLocationBack event) { super.onEvent(event); updateNavigationUi(); } From 6e92517ab37d1697805aac5b39b3ec86c03b985e Mon Sep 17 00:00:00 2001 From: Kejun Xia Date: Sun, 11 Oct 2015 20:49:50 +1100 Subject: [PATCH 02/14] Add c2c event to handle app exit --- .../mvc/controller/NavigationController.java | 17 ++++++ .../internal/NavigationControllerImpl.java | 11 ++++ .../internal/TestNavigationController.java | 52 +++++++++++++++++++ 3 files changed, 80 insertions(+) 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 df1ecbd..15b7412 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 @@ -17,6 +17,7 @@ package com.shipdream.lib.android.mvc.controller; import com.shipdream.lib.android.mvc.NavLocation; +import com.shipdream.lib.android.mvc.event.BaseEventC2C; import com.shipdream.lib.android.mvc.event.ValueChangeEventC2V; /** @@ -132,6 +133,22 @@ public boolean isFastRewind() { return fastRewind; } } + + } + + interface EventC2C { + /** + * Event to notify the controllers the app exists, for example by back button. Be aware, this + * doesn't mean the process of the application is killed but only all navigable fragments + * and their containing activity are destroyed since there might be services still running. + * + *

This is a good point to notify controllers to clear the their state.

+ */ + class OnAppExit extends BaseEventC2C { + public OnAppExit(Object sender) { + super(sender); + } + } } class Model { 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 82d2127..1928e4e 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 @@ -118,6 +118,8 @@ public void navigateBack(Object sender) { logger.debug("Nav Controller: Backward: {} -> {}", currentLoc.getLocationId(), previousLoc == null ? "null" : previousLoc.getLocationId()); + checkAppExit(sender); + dumpHistory(); } @@ -159,10 +161,19 @@ public void navigateBack(Object sender, String toLocationId) { logger.debug("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(""); 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 67a1d77..61591b8 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 @@ -220,6 +220,58 @@ public void shouldBeAbleToNavigateBackToFirstLocation() throws Exception { Assert.assertTrue(event.getValue().isFastRewind()); } + @Test + public void should_post_app_exit_event_on_the_last_back_of_linear_back_navigation() { + //mock the subscriber + class AppExitListener { + public void onEvent(NavigationController.EventC2C.OnAppExit event) {} + } + + ArgumentCaptor event + = ArgumentCaptor.forClass(NavigationController.EventC2C.OnAppExit.class); + + AppExitListener exitListener = mock(AppExitListener.class); + eventBusC2C.register(exitListener); + + prepareLocationHistory(); + + reset(exitListener); + navigationController.navigateBack(this); + verify(exitListener, times(0)).onEvent(event.capture()); + + navigationController.navigateBack(this); + verify(exitListener, times(0)).onEvent(event.capture()); + + navigationController.navigateBack(this); + verify(exitListener, times(0)).onEvent(event.capture()); + + navigationController.navigateBack(this); + verify(exitListener, times(1)).onEvent(event.capture()); + } + + @Test + public void should_post_app_exit_event_on_the_last_back_of_fast_back_navigation() { + //mock the subscriber + class AppExitListener { + public void onEvent(NavigationController.EventC2C.OnAppExit event) {} + } + + ArgumentCaptor event + = ArgumentCaptor.forClass(NavigationController.EventC2C.OnAppExit.class); + + AppExitListener exitListener = mock(AppExitListener.class); + eventBusC2C.register(exitListener); + + prepareLocationHistory(); + + reset(exitListener); + navigationController.navigateBack(this, null); + verify(exitListener, times(0)).onEvent(event.capture()); + + navigationController.navigateBack(this); + verify(exitListener, times(1)).onEvent(event.capture()); + } + @Test public void should_not_raise_navigate_back_event_when_navigate_to_first_location_from_the_first_location() throws Exception { // Arrange From 9368f8c5a2af3d0850eb315ad9da67ffe5560c7a Mon Sep 17 00:00:00 2001 From: Kejun Xia Date: Tue, 13 Oct 2015 20:46:54 +1100 Subject: [PATCH 03/14] Refactor MvcFragment.Reason --- .../android/mvc/view/LifeCycleValidator.java | 52 ++++++++- .../help/internal/LifeCycleMonitorImpl.java | 2 +- .../mvc/view/lifecycle/MvcTestActivity.java | 2 +- .../mvc/view/viewpager/TabFragmentA.java | 4 +- .../view/viewpager/ViewPagerHomeFragment.java | 2 +- .../lib/android/mvc/view/MvcActivity.java | 2 +- .../lib/android/mvc/view/MvcFragment.java | 106 ++++++++++++++---- .../note/view/fragment/NoteListFragment.java | 2 +- .../view/fragment/WeatherListFragment.java | 2 +- .../mvc/samples/simple/view/FragmentA.java | 2 +- 10 files changed, 144 insertions(+), 32 deletions(-) diff --git a/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/LifeCycleValidator.java b/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/LifeCycleValidator.java index af06662..d9fcbe6 100644 --- a/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/LifeCycleValidator.java +++ b/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/LifeCycleValidator.java @@ -21,11 +21,12 @@ import com.shipdream.lib.android.mvc.view.help.LifeCycleMonitor; +import org.mockito.ArgumentMatcher; import org.mockito.Mockito; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyInt; -import static org.mockito.Matchers.eq; +import static org.mockito.Matchers.argThat; import static org.mockito.Matchers.isNotNull; import static org.mockito.Matchers.isNull; import static org.mockito.Mockito.times; @@ -114,9 +115,16 @@ public void expect(LifeCycle... lifeCycles) { verify(lifeCycleMonitorMock, times(onCreateViewCountNotNull)).onCreateView(any(View.class), isNotNull(Bundle.class)); verify(lifeCycleMonitorMock, times(onViewCreatedCountNull)).onViewCreated(any(View.class), isNull(Bundle.class)); verify(lifeCycleMonitorMock, times(onViewCreatedCountNotNull)).onViewCreated(any(View.class), isNotNull(Bundle.class)); - verify(lifeCycleMonitorMock, times(onViewReadyFirstTime)).onViewReady(any(View.class), any(Bundle.class), eq(MvcFragment.Reason.FIRST_TIME)); - verify(lifeCycleMonitorMock, times(onViewReadyRotation)).onViewReady(any(View.class), any(Bundle.class), eq(MvcFragment.Reason.ROTATE)); - verify(lifeCycleMonitorMock, times(onViewReadyRestore)).onViewReady(any(View.class), any(Bundle.class), eq(MvcFragment.Reason.RESTORE)); + + verify(lifeCycleMonitorMock, times(onViewReadyFirstTime)).onViewReady(any(View.class), + any(Bundle.class), argThat(new FirstTimeMatcher())); + + verify(lifeCycleMonitorMock, times(onViewReadyRotation)).onViewReady(any(View.class), + any(Bundle.class), argThat(new RotateMatcher())); + + verify(lifeCycleMonitorMock, times(onViewReadyRestore)).onViewReady(any(View.class), + any(Bundle.class), argThat(new RestoreMatcher())); + verify(lifeCycleMonitorMock, times(onViewCreatedCountNotNull)).onViewCreated(any(View.class), isNotNull(Bundle.class)); verify(lifeCycleMonitorMock, times(onPushingToBackStackCount)).onPushingToBackStack(); verify(lifeCycleMonitorMock, times(onPoppedOutToFrontCount)).onPoppedOutToFront(); @@ -128,6 +136,42 @@ public void expect(LifeCycle... lifeCycles) { reset(); } + private class FirstTimeMatcher extends ArgumentMatcher { + @Override + public boolean matches(Object argument) { + if (argument instanceof MvcFragment.Reason) { + if (((MvcFragment.Reason) argument).isFirstTime()) { + return true; + } + } + return false; + } + } + + private class RestoreMatcher extends ArgumentMatcher { + @Override + public boolean matches(Object argument) { + if (argument instanceof MvcFragment.Reason) { + if (((MvcFragment.Reason) argument).isRestored()) { + return true; + } + } + return false; + } + } + + private class RotateMatcher extends ArgumentMatcher { + @Override + public boolean matches(Object argument) { + if (argument instanceof MvcFragment.Reason) { + if (((MvcFragment.Reason) argument).isRotated()) { + return true; + } + } + return false; + } + } + public void reset() { onCreateCountNull = 0; onCreateCountNotNull = 0; diff --git a/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/help/internal/LifeCycleMonitorImpl.java b/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/help/internal/LifeCycleMonitorImpl.java index be08ec4..ca220e4 100644 --- a/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/help/internal/LifeCycleMonitorImpl.java +++ b/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/help/internal/LifeCycleMonitorImpl.java @@ -45,7 +45,7 @@ public void onViewCreated(View rootView, Bundle savedInstanceState) { @Override public void onViewReady(View rootView, Bundle savedInstanceState, MvcFragment.Reason reason) { - logger.info("Lifecycle method invoked: onViewReady, reason?: " + reason.name()); + logger.info("Lifecycle method invoked: onViewReady, reason?: " + reason.toString()); } @Override 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 378ea78..6cff31e 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 @@ -46,7 +46,7 @@ protected void onStartUp() { public void onViewReady(View view, Bundle savedInstanceState, Reason reason) { super.onViewReady(view, savedInstanceState, reason); - if (reason == Reason.RESTORE) { + if (reason.isRestored()) { if (!onViewStateRestoredCalled) { throw new IllegalStateException("When activity is restoring, onViewReady must be called after onViewStateRestored to guarantee all state of this fragment is ready to use."); } 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 decb076..87b1f79 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 @@ -39,10 +39,10 @@ public void onViewReady(View view, Bundle savedInstanceState, Reason reason) { super.onViewReady(view, savedInstanceState, reason); textView = (TextView) view.findViewById(R.id.fragment_view_pager_tab_text); - if (reason == Reason.FIRST_TIME) { + if (reason.isFirstTime()) { textView.setText(INIT_TEXT); tabController.setName(RESTORE_TEXT); - } else if (reason == Reason.RESTORE) { + } else if (reason.isRestored()) { textView.setText(tabController.getModel().getName()); } diff --git a/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/viewpager/ViewPagerHomeFragment.java b/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/viewpager/ViewPagerHomeFragment.java index cb2b54c..7b8f3a0 100644 --- a/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/viewpager/ViewPagerHomeFragment.java +++ b/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/viewpager/ViewPagerHomeFragment.java @@ -32,7 +32,7 @@ public void onViewReady(View view, Bundle savedInstanceState, Reason reason) { lifeCycleMonitor.onViewReady(view, savedInstanceState, reason); viewPager = (ViewPager) view.findViewById(R.id.viewpager); - if (reason == Reason.FIRST_TIME || reason == Reason.RESTORE) { + if (reason.isFirstTime() || reason.isRestored()) { pagerAdapter = new PagerAdapter(getChildFragmentManager()); } 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 311b5df..9c0bbc3 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 @@ -267,7 +267,7 @@ public void onCreate(Bundle savedInstanceState) { @Override public void onViewReady(View view, Bundle savedInstanceState, Reason reason) { super.onViewReady(view, savedInstanceState, reason); - if (reason == Reason.FIRST_TIME) { + if (reason.isFirstTime()) { firstTimeRun = true; } } 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 552db31..22c159c 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 @@ -52,24 +52,92 @@ *

*/ public abstract class MvcFragment extends Fragment { + static final int REASON_FIRST_TIME = 1; + static final int REASON_RESTORE = REASON_FIRST_TIME << 1; + static final int REASON_POP_OUT = REASON_RESTORE << 1; + static final int REASON_ROTATE = REASON_POP_OUT << 1; + /** * Reason of creating the view of the fragment */ - public enum Reason { - /** - * The view of the fragment is newly created for the first time - */ - FIRST_TIME, - /** - * The view of the fragment is recreated due to rotation - */ - ROTATE, - /** - * The view of the fragment is recreated on restoration after the activity of the fragment - * is killed and recreated by the OS. Note that even there is an orientation change along - * with the restoration only the reason will still be RESTORE. - */ - RESTORE + public static class Reason { +// /** +// * The view of the fragment is newly created for the first time +// */ +// FIRST_TIME, +// /** +// * The view of the fragment is recreated on restoration after the activity of the fragment +// * is killed and recreated by the OS. Note that RESTORE takes higher priority to be +// * considered as the reason of the creation of the fragment view than ROTATE when they occur +// * at the same time. +// */ +// RESTORE, +// /** +// * The view of the fragment is recreated because it's popped out by a back navigation. Note +// * that POP_OUT takes higher priority to be considered as the reason of the creation of the +// * fragment view than ROTATE when they occur at the same time. +// */ +// POP_OUT, +// /** +// * The view of the fragment is recreated due to rotation +// */ +// ROTATE; + + private int value; + + private void setValue(boolean trueOrFalse, int mask) { + if (trueOrFalse) { + value |= mask; + } else { + value &= ~mask; + } + } + + void setFirstTime(boolean isFirstTime) { + setValue(isFirstTime, REASON_FIRST_TIME); + } + + void setRestored(boolean isRestored) { + setValue(isRestored, REASON_RESTORE); + } + + void setPoppedOut(boolean isPoppedOut) { + setValue(isPoppedOut, REASON_POP_OUT); + } + + void setRotated(boolean isRotated) { + setValue(isRotated, REASON_ROTATE); + } + + private boolean getValue(int mask) { + return (value & mask) == mask; + } + + public boolean isFirstTime() { + return getValue(REASON_FIRST_TIME); + } + + public boolean isRestored() { + return getValue(REASON_RESTORE); + } + + public boolean isPoppedOut() { + return getValue(REASON_POP_OUT); + } + + public boolean isRotated() { + return getValue(REASON_ROTATE); + } + + @Override + public String toString() { + return "Reason: {" + + "firstTime: " + isFirstTime() + + "restore: " + isRestored() + + "popOut: " + isPoppedOut() + + "rotate: " + isRotated() + + '}'; + } } private final static String STATE_LAST_ORIENTATION = AndroidMvc.MVC_SATE_PREFIX + "LastOrientation--__"; @@ -194,14 +262,14 @@ public void run() { private void doOnViewCreatedCallBack(View view, Bundle savedInstanceState, boolean restoring) { int currentOrientation = getResources().getConfiguration().orientation; boolean orientationChanged = currentOrientation != lastOrientation; - Reason reason; + Reason reason = new Reason(); if (restoring) { - reason = Reason.RESTORE; + reason.setRestored(true); } else { if (orientationChanged) { - reason = Reason.ROTATE; + reason.setRotated(true); } else { - reason = Reason.FIRST_TIME; + reason.setFirstTime(true); } } diff --git a/samples/note/android/src/main/java/com/shipdream/lib/android/mvc/samples/note/view/fragment/NoteListFragment.java b/samples/note/android/src/main/java/com/shipdream/lib/android/mvc/samples/note/view/fragment/NoteListFragment.java index 57b63c6..f2a0689 100644 --- a/samples/note/android/src/main/java/com/shipdream/lib/android/mvc/samples/note/view/fragment/NoteListFragment.java +++ b/samples/note/android/src/main/java/com/shipdream/lib/android/mvc/samples/note/view/fragment/NoteListFragment.java @@ -75,7 +75,7 @@ public void onClick(View v) { layoutManager = new LinearLayoutManager(getActivity()); listView.setLayoutManager(layoutManager); - if (reason != Reason.ROTATE) { + if (!reason.isRotated()) { updateList(); } diff --git a/samples/note/android/src/main/java/com/shipdream/lib/android/mvc/samples/note/view/fragment/WeatherListFragment.java b/samples/note/android/src/main/java/com/shipdream/lib/android/mvc/samples/note/view/fragment/WeatherListFragment.java index 6b1ae19..31949d4 100644 --- a/samples/note/android/src/main/java/com/shipdream/lib/android/mvc/samples/note/view/fragment/WeatherListFragment.java +++ b/samples/note/android/src/main/java/com/shipdream/lib/android/mvc/samples/note/view/fragment/WeatherListFragment.java @@ -84,7 +84,7 @@ public void onClick(View v) { updateList(); //Automatically update weathers of all cities on first creation. - if (reason == Reason.FIRST_TIME) { + if (reason.isFirstTime()) { weatherController.updateAllCities(this); } } diff --git a/samples/simple/src/main/java/com/shipdream/lib/android/mvc/samples/simple/view/FragmentA.java b/samples/simple/src/main/java/com/shipdream/lib/android/mvc/samples/simple/view/FragmentA.java index 4656a63..92e9452 100644 --- a/samples/simple/src/main/java/com/shipdream/lib/android/mvc/samples/simple/view/FragmentA.java +++ b/samples/simple/src/main/java/com/shipdream/lib/android/mvc/samples/simple/view/FragmentA.java @@ -92,7 +92,7 @@ public void onClick(View v) { } }); - if (reason == Reason.FIRST_TIME) { + if (reason.isFirstTime()) { FragmentA_SubFragment f = new FragmentA_SubFragment(); getChildFragmentManager().beginTransaction() From 7a0dc5ee2a9087e0bcb3f089e8ba9b26156e6060 Mon Sep 17 00:00:00 2001 From: Kejun Xia Date: Tue, 13 Oct 2015 21:27:39 +1100 Subject: [PATCH 04/14] Change way to detect pop out navigation --- .../lib/android/mvc/view/MvcActivity.java | 8 +---- .../lib/android/mvc/view/MvcFragment.java | 30 ++++++++++++++----- 2 files changed, 24 insertions(+), 14 deletions(-) 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 9c0bbc3..40974c2 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 @@ -466,13 +466,7 @@ private void performBackNav(NavigationController.EventC2V.OnLocationBack event) final MvcFragment currentFrag = (MvcFragment) fm.findFragmentByTag(currentFragTag); if (currentFrag != null) { currentFrag.injectDependencies(); - currentFrag.registerOnViewReadyListener(new Runnable() { - @Override - public void run() { - currentFrag.onPoppedOutToFront(); - unregisterOnViewReadyListener(this); - } - }); + currentFrag.aboutToPopOut = true; } if (event.isFastRewind()) { 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 22c159c..9b86365 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 @@ -260,24 +260,32 @@ public void run() { } private void doOnViewCreatedCallBack(View view, Bundle savedInstanceState, boolean restoring) { + int currentOrientation = getResources().getConfiguration().orientation; boolean orientationChanged = currentOrientation != lastOrientation; Reason reason = new Reason(); + + if (orientationChanged) { + reason.setRotated(true); + } + if (restoring) { reason.setRestored(true); } else { - if (orientationChanged) { - reason.setRotated(true); + if (aboutToPopOut) { + reason.setPoppedOut(true); + aboutToPopOut = false; } else { - reason.setFirstTime(true); + if (!orientationChanged) { + reason.setFirstTime(true); + } } } onViewReady(view, savedInstanceState, reason); - if (onViewReadyListeners != null) { - for (Runnable r : onViewReadyListeners) { - r.run(); - } + + if (reason.isPoppedOut()) { + onPoppedOutToFront(); } if (orientationChanged) { @@ -285,6 +293,12 @@ private void doOnViewCreatedCallBack(View view, Bundle savedInstanceState, boole } lastOrientation = currentOrientation; + + if (onViewReadyListeners != null) { + for (Runnable r : onViewReadyListeners) { + r.run(); + } + } } /** @@ -332,6 +346,8 @@ protected void onReturnForeground() { protected void onPushingToBackStack() { } + boolean aboutToPopOut = false; + /** * Called when this fragment is popped out from fragment back stack. This callback will be * invoked after {@link #onViewReady(View, Bundle, Reason)}. From 6a3ba5da91eaefb07b551d6ede14fc981bdb3d45 Mon Sep 17 00:00:00 2001 From: Kejun Xia Date: Wed, 14 Oct 2015 20:25:22 +1100 Subject: [PATCH 05/14] Pass existing UI tests with new Reason.isPoppedOut in MvcFragment.onViewReady --- .../lib/android/mvc/view/LifeCycle.java | 1 + .../android/mvc/view/LifeCycleValidator.java | 20 +++++++++++++++++++ .../injection/TestInjectionAndLifeCycle.java | 4 ++-- .../NotKeepActivitiesLifeCycleTestCase.java | 2 ++ 4 files changed, 25 insertions(+), 2 deletions(-) diff --git a/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/LifeCycle.java b/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/LifeCycle.java index 5a00182..8569443 100644 --- a/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/LifeCycle.java +++ b/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/LifeCycle.java @@ -26,6 +26,7 @@ public enum LifeCycle { onViewReadyFirstTime, onViewReadyRotate, onViewReadyRestore, + onViewReadyPopOut, onPushingToBackStack, onPoppedOutToFront, onReturnForeground, diff --git a/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/LifeCycleValidator.java b/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/LifeCycleValidator.java index d9fcbe6..ad0983a 100644 --- a/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/LifeCycleValidator.java +++ b/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/LifeCycleValidator.java @@ -48,6 +48,7 @@ public LifeCycleValidator(LifeCycleMonitor lifeCycleMonitorMock) { protected int onViewReadyFirstTime; protected int onViewReadyRotation; protected int onViewReadyRestore; + protected int onViewReadyPopOut; protected int onPushingToBackStackCount; protected int onPoppedOutToFrontCount; protected int onReturnForegroundCount; @@ -87,6 +88,9 @@ public void expect(LifeCycle... lifeCycles) { case onViewReadyRestore: onViewReadyRestore++; break; + case onViewReadyPopOut: + onViewReadyPopOut++; + break; case onPushingToBackStack: onPushingToBackStackCount++; break; @@ -125,6 +129,9 @@ public void expect(LifeCycle... lifeCycles) { verify(lifeCycleMonitorMock, times(onViewReadyRestore)).onViewReady(any(View.class), any(Bundle.class), argThat(new RestoreMatcher())); + verify(lifeCycleMonitorMock, times(onViewReadyPopOut)).onViewReady(any(View.class), + any(Bundle.class), argThat(new PopOutMatcher())); + verify(lifeCycleMonitorMock, times(onViewCreatedCountNotNull)).onViewCreated(any(View.class), isNotNull(Bundle.class)); verify(lifeCycleMonitorMock, times(onPushingToBackStackCount)).onPushingToBackStack(); verify(lifeCycleMonitorMock, times(onPoppedOutToFrontCount)).onPoppedOutToFront(); @@ -172,6 +179,18 @@ public boolean matches(Object argument) { } } + private class PopOutMatcher extends ArgumentMatcher { + @Override + public boolean matches(Object argument) { + if (argument instanceof MvcFragment.Reason) { + if (((MvcFragment.Reason) argument).isPoppedOut()) { + return true; + } + } + return false; + } + } + public void reset() { onCreateCountNull = 0; onCreateCountNotNull = 0; @@ -182,6 +201,7 @@ public void reset() { onViewReadyFirstTime = 0; onViewReadyRotation = 0; onViewReadyRestore = 0; + onViewReadyPopOut = 0; onPushingToBackStackCount = 0; onPoppedOutToFrontCount = 0; onReturnForegroundCount = 0; 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 13eb91b..39ecfdb 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 @@ -89,7 +89,7 @@ public void testShouldRetainInjectionsOfFragmentAAfterNavigatedToFragmentB() thr //View is newly created again //onPoppedOutToFront is called when the fragment pops out from back stack lifeCycleValidatorB.expect(LifeCycle.onCreateViewNull, LifeCycle.onViewCreatedNull, - LifeCycle.onViewReadyFirstTime, LifeCycle.onPoppedOutToFront); + LifeCycle.onViewReadyPopOut, LifeCycle.onPoppedOutToFront); onView(withId(R.id.textA)).check(matches(withText("Added by FragmentB"))); onView(withId(R.id.textB)).check(matches(withText("Added by FragmentA\n" + "Added by FragmentB\n" + @@ -105,7 +105,7 @@ public void testShouldRetainInjectionsOfFragmentAAfterNavigatedToFragmentB() thr //View is newly created again //onPoppedOutToFront is called when the fragment pops out from back stack lifeCycleValidatorA.expect(LifeCycle.onCreateViewNull, LifeCycle.onViewCreatedNull, - LifeCycle.onViewReadyFirstTime, LifeCycle.onPoppedOutToFront); + LifeCycle.onViewReadyPopOut, LifeCycle.onPoppedOutToFront); onView(withId(R.id.textA)).check(matches(withText( "Added by FragmentB\n" + "Added by FragmentA"))); diff --git a/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/lifecycle/NotKeepActivitiesLifeCycleTestCase.java b/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/lifecycle/NotKeepActivitiesLifeCycleTestCase.java index 8c29113..67c04e0 100644 --- a/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/lifecycle/NotKeepActivitiesLifeCycleTestCase.java +++ b/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/lifecycle/NotKeepActivitiesLifeCycleTestCase.java @@ -149,6 +149,7 @@ public void testRotations() throws Throwable { lifeCycleValidator.expect(LifeCycle.onCreateNotNull, LifeCycle.onCreateViewNotNull, LifeCycle.onViewCreatedNotNull, LifeCycle.onViewReadyRestore, + LifeCycle.onViewReadyRotate, LifeCycle.onOrientationChanged); pressHome(); @@ -161,6 +162,7 @@ public void testRotations() throws Throwable { lifeCycleValidator.expect(LifeCycle.onCreateNotNull, LifeCycle.onCreateViewNotNull, LifeCycle.onViewCreatedNotNull, LifeCycle.onViewReadyRestore, + LifeCycle.onViewReadyRotate, LifeCycle.onOrientationChanged); onView(withText(R.string.mvc_fragment_text)).check(matches(isDisplayed())); From fd6919f0da50298ca8b82f253b047fb583f8b992 Mon Sep 17 00:00:00 2001 From: Kejun Xia Date: Wed, 14 Oct 2015 21:27:32 +1100 Subject: [PATCH 06/14] Improving tests --- .../mvc/view/viewpager/TestFragmentsInViewPager.java | 5 ++--- .../shipdream/lib/android/mvc/view/MvcActivity.java | 11 ++++++++++- .../shipdream/lib/android/mvc/view/MvcFragment.java | 6 +++--- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/viewpager/TestFragmentsInViewPager.java b/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/viewpager/TestFragmentsInViewPager.java index 8196386..edfc6a1 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 @@ -107,18 +107,17 @@ public void should_call_onViewReady_in_tab_fragments_when_resumed_hosting_fragme lifeCycleValidator.expect( LifeCycle.onCreateViewNull, LifeCycle.onViewCreatedNull, - LifeCycle.onViewReadyFirstTime, LifeCycle.onPoppedOutToFront); lifeCycleValidatorA.expect( LifeCycle.onCreateViewNull, LifeCycle.onViewCreatedNull, - LifeCycle.onViewReadyFirstTime); + LifeCycle.onPoppedOutToFront); lifeCycleValidatorB.expect( LifeCycle.onCreateViewNull, LifeCycle.onViewCreatedNull, - LifeCycle.onViewReadyFirstTime); + LifeCycle.onPoppedOutToFront); lifeCycleValidatorC.expect(); } 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 40974c2..af3b0f7 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 @@ -290,7 +290,7 @@ public void onViewStateRestored(Bundle savedInstanceState) { * navigate to the initial fragment in this callback. {@link NavigationController} can be * obtained by {@link #getNavigationController()} or even be injected again. This callback is * equivalent to override {@link #onViewReady(View, Bundle, Reason)} and perform action when - * reason of view ready of this {@link DelegateFragment} is {@link Reason#FIRST_TIME}. + * reason of view ready of this {@link DelegateFragment} is {@link Reason#isFirstTime()}. * *

* Note this callback will NOT be invoked on restoration after the app is killed by the OS from background. @@ -467,6 +467,15 @@ private void performBackNav(NavigationController.EventC2V.OnLocationBack event) if (currentFrag != null) { currentFrag.injectDependencies(); currentFrag.aboutToPopOut = true; + + List subFragments = currentFrag.getChildFragmentManager().getFragments(); + if (subFragments != null && !subFragments.isEmpty()) { + for (Fragment fragment : subFragments) { + if (fragment instanceof MvcFragment) { + ((MvcFragment)fragment).aboutToPopOut = true; + } + } + } } if (event.isFastRewind()) { 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 9b86365..e36c135 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 @@ -133,9 +133,9 @@ public boolean isRotated() { public String toString() { return "Reason: {" + "firstTime: " + isFirstTime() + - "restore: " + isRestored() + - "popOut: " + isPoppedOut() + - "rotate: " + isRotated() + + ", restore: " + isRestored() + + ", popOut: " + isPoppedOut() + + ", rotate: " + isRotated() + '}'; } } From c917bf05853da5d44bb64f83de719581f7a8723e Mon Sep 17 00:00:00 2001 From: Kejun Xia Date: Thu, 15 Oct 2015 20:45:35 +1100 Subject: [PATCH 07/14] Avoid unnecessary setting of fragment adapter in the test --- .../injection/TestInjectionAndLifeCycle.java | 16 ++++++++++++---- .../view/viewpager/TestFragmentsInViewPager.java | 15 +++++++++++++-- .../view/viewpager/ViewPagerHomeFragment.java | 4 +--- .../lib/android/mvc/view/MvcFragment.java | 16 +++++++--------- 4 files changed, 33 insertions(+), 18 deletions(-) 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 39ecfdb..7a46f49 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 @@ -88,8 +88,12 @@ public void testShouldRetainInjectionsOfFragmentAAfterNavigatedToFragmentB() thr lifeCycleValidatorC.expect(LifeCycle.onDestroyView, LifeCycle.onDestroy); //View is newly created again //onPoppedOutToFront is called when the fragment pops out from back stack - lifeCycleValidatorB.expect(LifeCycle.onCreateViewNull, LifeCycle.onViewCreatedNull, - LifeCycle.onViewReadyPopOut, LifeCycle.onPoppedOutToFront); + lifeCycleValidatorB.expect( + LifeCycle.onCreateViewNull, + LifeCycle.onViewCreatedNull, + LifeCycle.onViewReadyFirstTime, + LifeCycle.onViewReadyPopOut, + LifeCycle.onPoppedOutToFront); onView(withId(R.id.textA)).check(matches(withText("Added by FragmentB"))); onView(withId(R.id.textB)).check(matches(withText("Added by FragmentA\n" + "Added by FragmentB\n" + @@ -104,8 +108,12 @@ public void testShouldRetainInjectionsOfFragmentAAfterNavigatedToFragmentB() thr lifeCycleValidatorB.expect(LifeCycle.onDestroyView, LifeCycle.onDestroy); //View is newly created again //onPoppedOutToFront is called when the fragment pops out from back stack - lifeCycleValidatorA.expect(LifeCycle.onCreateViewNull, LifeCycle.onViewCreatedNull, - LifeCycle.onViewReadyPopOut, LifeCycle.onPoppedOutToFront); + lifeCycleValidatorA.expect( + LifeCycle.onCreateViewNull, + LifeCycle.onViewCreatedNull, + LifeCycle.onViewReadyFirstTime, + LifeCycle.onViewReadyPopOut, + LifeCycle.onPoppedOutToFront); onView(withId(R.id.textA)).check(matches(withText( "Added by FragmentB\n" + "Added by FragmentA"))); 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 edfc6a1..61fc511 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 @@ -107,16 +107,22 @@ public void should_call_onViewReady_in_tab_fragments_when_resumed_hosting_fragme lifeCycleValidator.expect( LifeCycle.onCreateViewNull, LifeCycle.onViewCreatedNull, + LifeCycle.onViewReadyFirstTime, + LifeCycle.onViewReadyPopOut, LifeCycle.onPoppedOutToFront); lifeCycleValidatorA.expect( LifeCycle.onCreateViewNull, LifeCycle.onViewCreatedNull, + LifeCycle.onViewReadyFirstTime, + LifeCycle.onViewReadyPopOut, LifeCycle.onPoppedOutToFront); lifeCycleValidatorB.expect( LifeCycle.onCreateViewNull, LifeCycle.onViewCreatedNull, + LifeCycle.onViewReadyFirstTime, + LifeCycle.onViewReadyPopOut, LifeCycle.onPoppedOutToFront); lifeCycleValidatorC.expect(); @@ -176,17 +182,22 @@ public void should_call_onViewReady_in_tab_fragments_when_restored_hosting_fragm LifeCycle.onCreateViewNotNull, LifeCycle.onViewCreatedNotNull, LifeCycle.onViewReadyRestore, + LifeCycle.onViewReadyPopOut, LifeCycle.onPoppedOutToFront); lifeCycleValidatorA.expect( LifeCycle.onCreateViewNotNull, LifeCycle.onViewCreatedNotNull, - LifeCycle.onViewReadyRestore); + LifeCycle.onViewReadyRestore, + LifeCycle.onViewReadyPopOut, + LifeCycle.onPoppedOutToFront); lifeCycleValidatorB.expect( LifeCycle.onCreateViewNotNull, LifeCycle.onViewCreatedNotNull, - LifeCycle.onViewReadyRestore); + LifeCycle.onViewReadyRestore, + LifeCycle.onViewReadyPopOut, + LifeCycle.onPoppedOutToFront); lifeCycleValidatorC.expect(); } diff --git a/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/viewpager/ViewPagerHomeFragment.java b/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/viewpager/ViewPagerHomeFragment.java index 7b8f3a0..8c53ce4 100644 --- a/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/viewpager/ViewPagerHomeFragment.java +++ b/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/viewpager/ViewPagerHomeFragment.java @@ -34,12 +34,10 @@ public void onViewReady(View view, Bundle savedInstanceState, Reason reason) { viewPager = (ViewPager) view.findViewById(R.id.viewpager); if (reason.isFirstTime() || reason.isRestored()) { pagerAdapter = new PagerAdapter(getChildFragmentManager()); + viewPager.setAdapter(pagerAdapter); } - - viewPager.setAdapter(pagerAdapter); } - @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); 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 e36c135..c7b6b68 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 @@ -269,17 +269,15 @@ private void doOnViewCreatedCallBack(View view, Bundle savedInstanceState, boole reason.setRotated(true); } + if (aboutToPopOut) { + reason.setPoppedOut(true); + aboutToPopOut = false; + } + if (restoring) { reason.setRestored(true); - } else { - if (aboutToPopOut) { - reason.setPoppedOut(true); - aboutToPopOut = false; - } else { - if (!orientationChanged) { - reason.setFirstTime(true); - } - } + } else if (!orientationChanged) { + reason.setFirstTime(true); } onViewReady(view, savedInstanceState, reason); From 316fa7552b580c3a19350ed13f2af7bb93f7638f Mon Sep 17 00:00:00 2001 From: Kejun Xia Date: Sat, 17 Oct 2015 21:58:05 +1100 Subject: [PATCH 08/14] Add documentation of reasons for creation of fragments --- .../lib/android/mvc/view/MvcFragment.java | 37 ++++++++----------- 1 file changed, 15 insertions(+), 22 deletions(-) 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 c7b6b68..846e540 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 @@ -61,28 +61,6 @@ public abstract class MvcFragment extends Fragment { * Reason of creating the view of the fragment */ public static class Reason { -// /** -// * The view of the fragment is newly created for the first time -// */ -// FIRST_TIME, -// /** -// * The view of the fragment is recreated on restoration after the activity of the fragment -// * is killed and recreated by the OS. Note that RESTORE takes higher priority to be -// * considered as the reason of the creation of the fragment view than ROTATE when they occur -// * at the same time. -// */ -// RESTORE, -// /** -// * The view of the fragment is recreated because it's popped out by a back navigation. Note -// * that POP_OUT takes higher priority to be considered as the reason of the creation of the -// * fragment view than ROTATE when they occur at the same time. -// */ -// POP_OUT, -// /** -// * The view of the fragment is recreated due to rotation -// */ -// ROTATE; - private int value; private void setValue(boolean trueOrFalse, int mask) { @@ -113,18 +91,33 @@ private boolean getValue(int mask) { return (value & mask) == mask; } + /** + * @return Indicates whether the fragment view is created when the fragment is created for + * the first time. + */ public boolean isFirstTime() { return getValue(REASON_FIRST_TIME); } + /** + * @return Indicates whether the fragment view is created after the activity is killed by + * OS and restored. + */ public boolean isRestored() { return getValue(REASON_RESTORE); } + /** + * @return Indicates whether the fragment view is created when the fragment was pushed to + * backstack and just popped out. + */ public boolean isPoppedOut() { return getValue(REASON_POP_OUT); } + /** + * @return Indicates whether the fragment view is created after its orientation changed. + */ public boolean isRotated() { return getValue(REASON_ROTATE); } From 95a0de3833eb77bcfb1e32d2713293811f8df66a Mon Sep 17 00:00:00 2001 From: Kejun Xia Date: Sat, 17 Oct 2015 22:00:31 +1100 Subject: [PATCH 09/14] Bump up library version to 1.3.0 --- build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 9fc8356..9d51bad 100644 --- a/build.gradle +++ b/build.gradle @@ -74,8 +74,8 @@ ext { gitUrl = 'https://github.com/kejunxia/AndroidMvc.git' // Git repository URL version = [ major: 1, - minor: 2, - patch : 1 + minor: 3, + patch : 0 ] libGroup = 'com.shipdream' libVersion = "${version.major}.${version.minor}.${version.patch}" From 3d78d09939d692c1053b0f8d0d09917d18413959 Mon Sep 17 00:00:00 2001 From: Kejun Xia Date: Mon, 19 Oct 2015 19:53:06 +1100 Subject: [PATCH 10/14] Add new reason flag isNewInstance and fix tests for viewPager activity --- .../lib/android/mvc/view/BaseTestCase.java | 6 + .../lib/android/mvc/view/LifeCycle.java | 1 + .../android/mvc/view/LifeCycleValidator.java | 20 +++ .../viewpager/TestFragmentsInViewPager.java | 147 ++++++++++++++---- .../view/viewpager/ViewPagerHomeFragment.java | 2 +- .../lib/android/mvc/view/MvcFragment.java | 79 ++++++---- 6 files changed, 194 insertions(+), 61 deletions(-) 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 d8c9c0c..5081576 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 @@ -238,6 +238,12 @@ public void run() { FragmentManager fm = activity.getSupportFragmentManager(); Fragment fragment = fm.findFragmentByTag(activity.getDelegateFragmentTag()); clearStateOfFragments(fragment); + + lifeCycleValidator.reset(); + lifeCycleValidatorA.reset(); + lifeCycleValidatorB.reset(); + lifeCycleValidatorC.reset(); + lifeCycleValidatorD.reset(); } }); super.tearDown(); diff --git a/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/LifeCycle.java b/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/LifeCycle.java index 8569443..9f35b5d 100644 --- a/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/LifeCycle.java +++ b/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/LifeCycle.java @@ -23,6 +23,7 @@ public enum LifeCycle { onCreateViewNotNull, onViewCreatedNull, onViewCreatedNotNull, + onViewReadyNewInstance, onViewReadyFirstTime, onViewReadyRotate, onViewReadyRestore, diff --git a/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/LifeCycleValidator.java b/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/LifeCycleValidator.java index ad0983a..004e699 100644 --- a/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/LifeCycleValidator.java +++ b/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/LifeCycleValidator.java @@ -45,6 +45,7 @@ public LifeCycleValidator(LifeCycleMonitor lifeCycleMonitorMock) { protected int onCreateViewCountNotNull; protected int onViewCreatedCountNull; protected int onViewCreatedCountNotNull; + protected int onViewReadyNewInstance; protected int onViewReadyFirstTime; protected int onViewReadyRotation; protected int onViewReadyRestore; @@ -79,6 +80,9 @@ public void expect(LifeCycle... lifeCycles) { case onViewCreatedNotNull: onViewCreatedCountNotNull++; break; + case onViewReadyNewInstance: + onViewReadyNewInstance++; + break; case onViewReadyFirstTime: onViewReadyFirstTime++; break; @@ -120,6 +124,9 @@ public void expect(LifeCycle... lifeCycles) { verify(lifeCycleMonitorMock, times(onViewCreatedCountNull)).onViewCreated(any(View.class), isNull(Bundle.class)); verify(lifeCycleMonitorMock, times(onViewCreatedCountNotNull)).onViewCreated(any(View.class), isNotNull(Bundle.class)); + verify(lifeCycleMonitorMock, times(onViewReadyNewInstance)).onViewReady(any(View.class), + any(Bundle.class), argThat(new NewInstanceMatcher())); + verify(lifeCycleMonitorMock, times(onViewReadyFirstTime)).onViewReady(any(View.class), any(Bundle.class), argThat(new FirstTimeMatcher())); @@ -143,6 +150,18 @@ public void expect(LifeCycle... lifeCycles) { reset(); } + private class NewInstanceMatcher extends ArgumentMatcher { + @Override + public boolean matches(Object argument) { + if (argument instanceof MvcFragment.Reason) { + if (((MvcFragment.Reason) argument).isNewInstance()) { + return true; + } + } + return false; + } + } + private class FirstTimeMatcher extends ArgumentMatcher { @Override public boolean matches(Object argument) { @@ -198,6 +217,7 @@ public void reset() { onCreateViewCountNotNull = 0; onViewCreatedCountNull = 0; onViewCreatedCountNotNull = 0; + onViewReadyNewInstance = 0; onViewReadyFirstTime = 0; onViewReadyRotation = 0; onViewReadyRestore = 0; 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 61fc511..645cc49 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 @@ -68,14 +68,26 @@ public void should_call_onViewReady_in_tab_fragments_when_resumed_hosting_fragme } //=============================> At Home - lifeCycleValidator.expect(LifeCycle.onCreateNull, LifeCycle.onCreateViewNull, - LifeCycle.onViewCreatedNull, LifeCycle.onViewReadyFirstTime); + lifeCycleValidator.expect( + LifeCycle.onCreateNull, + LifeCycle.onCreateViewNull, + LifeCycle.onViewCreatedNull, + LifeCycle.onViewReadyNewInstance, + LifeCycle.onViewReadyFirstTime); - lifeCycleValidatorA.expect(LifeCycle.onCreateNull, LifeCycle.onCreateViewNull, - LifeCycle.onViewCreatedNull, LifeCycle.onViewReadyFirstTime); + lifeCycleValidatorA.expect( + LifeCycle.onCreateNull, + LifeCycle.onCreateViewNull, + LifeCycle.onViewCreatedNull, + LifeCycle.onViewReadyNewInstance, + LifeCycle.onViewReadyFirstTime); - lifeCycleValidatorB.expect(LifeCycle.onCreateNull, LifeCycle.onCreateViewNull, - LifeCycle.onViewCreatedNull, LifeCycle.onViewReadyFirstTime); + lifeCycleValidatorB.expect( + LifeCycle.onCreateNull, + LifeCycle.onCreateViewNull, + LifeCycle.onViewCreatedNull, + LifeCycle.onViewReadyNewInstance, + LifeCycle.onViewReadyFirstTime); lifeCycleValidatorC.expect(); @@ -136,14 +148,26 @@ public void should_call_onViewReady_in_tab_fragments_when_restored_hosting_fragm } //=============================> At Home - lifeCycleValidator.expect(LifeCycle.onCreateNull, LifeCycle.onCreateViewNull, - LifeCycle.onViewCreatedNull, LifeCycle.onViewReadyFirstTime); + lifeCycleValidator.expect( + LifeCycle.onCreateNull, + LifeCycle.onCreateViewNull, + LifeCycle.onViewCreatedNull, + LifeCycle.onViewReadyNewInstance, + LifeCycle.onViewReadyFirstTime); - lifeCycleValidatorA.expect(LifeCycle.onCreateNull, LifeCycle.onCreateViewNull, - LifeCycle.onViewCreatedNull, LifeCycle.onViewReadyFirstTime); + lifeCycleValidatorA.expect( + LifeCycle.onCreateNull, + LifeCycle.onCreateViewNull, + LifeCycle.onViewCreatedNull, + LifeCycle.onViewReadyNewInstance, + LifeCycle.onViewReadyFirstTime); - lifeCycleValidatorB.expect(LifeCycle.onCreateNull, LifeCycle.onCreateViewNull, - LifeCycle.onViewCreatedNull, LifeCycle.onViewReadyFirstTime); + lifeCycleValidatorB.expect( + LifeCycle.onCreateNull, + LifeCycle.onCreateViewNull, + LifeCycle.onViewCreatedNull, + LifeCycle.onViewReadyNewInstance, + LifeCycle.onViewReadyFirstTime); lifeCycleValidatorC.expect(); @@ -178,9 +202,11 @@ public void should_call_onViewReady_in_tab_fragments_when_restored_hosting_fragm //=============================> At A navigationController.navigateBack(this); waitTest(1200); + lifeCycleValidator.expect( LifeCycle.onCreateViewNotNull, LifeCycle.onViewCreatedNotNull, + LifeCycle.onViewReadyNewInstance, LifeCycle.onViewReadyRestore, LifeCycle.onViewReadyPopOut, LifeCycle.onPoppedOutToFront); @@ -188,6 +214,7 @@ public void should_call_onViewReady_in_tab_fragments_when_restored_hosting_fragm lifeCycleValidatorA.expect( LifeCycle.onCreateViewNotNull, LifeCycle.onViewCreatedNotNull, + LifeCycle.onViewReadyNewInstance, LifeCycle.onViewReadyRestore, LifeCycle.onViewReadyPopOut, LifeCycle.onPoppedOutToFront); @@ -195,6 +222,7 @@ public void should_call_onViewReady_in_tab_fragments_when_restored_hosting_fragm lifeCycleValidatorB.expect( LifeCycle.onCreateViewNotNull, LifeCycle.onViewCreatedNotNull, + LifeCycle.onViewReadyNewInstance, LifeCycle.onViewReadyRestore, LifeCycle.onViewReadyPopOut, LifeCycle.onPoppedOutToFront); @@ -210,14 +238,26 @@ public void should_call_onViewReady_in_tab_fragments_when_comes_back_from_anothe } //=============================> At Home - lifeCycleValidator.expect(LifeCycle.onCreateNull, LifeCycle.onCreateViewNull, - LifeCycle.onViewCreatedNull, LifeCycle.onViewReadyFirstTime); + lifeCycleValidator.expect( + LifeCycle.onCreateNull, + LifeCycle.onCreateViewNull, + LifeCycle.onViewCreatedNull, + LifeCycle.onViewReadyNewInstance, + LifeCycle.onViewReadyFirstTime); - lifeCycleValidatorA.expect(LifeCycle.onCreateNull, LifeCycle.onCreateViewNull, - LifeCycle.onViewCreatedNull, LifeCycle.onViewReadyFirstTime); + lifeCycleValidatorA.expect( + LifeCycle.onCreateNull, + LifeCycle.onCreateViewNull, + LifeCycle.onViewCreatedNull, + LifeCycle.onViewReadyNewInstance, + LifeCycle.onViewReadyFirstTime); - lifeCycleValidatorB.expect(LifeCycle.onCreateNull, LifeCycle.onCreateViewNull, - LifeCycle.onViewCreatedNull, LifeCycle.onViewReadyFirstTime); + lifeCycleValidatorB.expect( + LifeCycle.onCreateNull, + LifeCycle.onCreateViewNull, + LifeCycle.onViewCreatedNull, + LifeCycle.onViewReadyNewInstance, + LifeCycle.onViewReadyFirstTime); lifeCycleValidatorC.expect(); @@ -248,8 +288,12 @@ public void should_call_onViewReady_in_tab_fragments_when_comes_back_from_anothe onView(withText("Tab B")).check(matches(not(isDisplayed()))); onView(withText(TabFragmentA.RESTORE_TEXT)).check(matches(isDisplayed())); - lifeCycleValidatorA.expect(LifeCycle.onCreateNotNull, LifeCycle.onCreateViewNotNull, - LifeCycle.onViewCreatedNotNull, LifeCycle.onViewReadyRestore); + lifeCycleValidatorA.expect( + LifeCycle.onCreateNotNull, + LifeCycle.onCreateViewNotNull, + LifeCycle.onViewCreatedNotNull, + LifeCycle.onViewReadyNewInstance, + LifeCycle.onViewReadyRestore); } @Test @@ -260,14 +304,26 @@ public void should_call_onViewReady_in_tab_fragments_when_comes_back_from_anothe } //=============================> At Home - lifeCycleValidator.expect(LifeCycle.onCreateNull, LifeCycle.onCreateViewNull, - LifeCycle.onViewCreatedNull, LifeCycle.onViewReadyFirstTime); + lifeCycleValidator.expect( + LifeCycle.onCreateNull, + LifeCycle.onCreateViewNull, + LifeCycle.onViewCreatedNull, + LifeCycle.onViewReadyNewInstance, + LifeCycle.onViewReadyFirstTime); - lifeCycleValidatorA.expect(LifeCycle.onCreateNull, LifeCycle.onCreateViewNull, - LifeCycle.onViewCreatedNull, LifeCycle.onViewReadyFirstTime); + lifeCycleValidatorA.expect( + LifeCycle.onCreateNull, + LifeCycle.onCreateViewNull, + LifeCycle.onViewCreatedNull, + LifeCycle.onViewReadyNewInstance, + LifeCycle.onViewReadyFirstTime); - lifeCycleValidatorB.expect(LifeCycle.onCreateNull, LifeCycle.onCreateViewNull, - LifeCycle.onViewCreatedNull, LifeCycle.onViewReadyFirstTime); + lifeCycleValidatorB.expect( + LifeCycle.onCreateNull, + LifeCycle.onCreateViewNull, + LifeCycle.onViewCreatedNull, + LifeCycle.onViewReadyNewInstance, + LifeCycle.onViewReadyFirstTime); lifeCycleValidatorC.expect(); @@ -282,9 +338,14 @@ public void should_call_onViewReady_in_tab_fragments_when_comes_back_from_anothe waitTest(1200); pressBack(); waitTest(1200); - lifeCycleValidatorA.expect(LifeCycle.onDestroyView, LifeCycle.onDestroy, - LifeCycle.onCreateNotNull, LifeCycle.onCreateViewNotNull, - LifeCycle.onViewCreatedNotNull, LifeCycle.onViewReadyRestore); + lifeCycleValidatorA.expect( + LifeCycle.onDestroyView, + LifeCycle.onDestroy, + LifeCycle.onCreateNotNull, + LifeCycle.onCreateViewNotNull, + LifeCycle.onViewCreatedNotNull, + LifeCycle.onViewReadyNewInstance, + LifeCycle.onViewReadyRestore); onView(withId(R.id.viewpager)).perform(swipeLeft()); onView(withText("Tab B")).check(matches(not(isDisplayed()))); @@ -300,8 +361,32 @@ public void should_call_onViewReady_in_tab_fragments_when_comes_back_from_anothe onView(withText("Tab B")).check(matches(not(isDisplayed()))); onView(withText(TabFragmentA.RESTORE_TEXT)).check(matches(isDisplayed())); - lifeCycleValidatorA.expect(LifeCycle.onCreateNotNull, LifeCycle.onCreateViewNotNull, - LifeCycle.onViewCreatedNotNull, LifeCycle.onViewReadyRestore); + lifeCycleValidatorA.expect( + LifeCycle.onCreateNotNull, + LifeCycle.onCreateViewNotNull, + LifeCycle.onViewCreatedNotNull, + LifeCycle.onViewReadyNewInstance, + LifeCycle.onViewReadyRestore); + } + + @Test + public void 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); + + lifeCycleValidator.reset(); + + //=============================> At A + navigationController.navigateBack(this); + waitTest(1200); + + lifeCycleValidator.expect( + LifeCycle.onCreateViewNull, + LifeCycle.onViewCreatedNull, + LifeCycle.onViewReadyFirstTime, + LifeCycle.onViewReadyPopOut, + LifeCycle.onPoppedOutToFront); } } diff --git a/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/viewpager/ViewPagerHomeFragment.java b/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/viewpager/ViewPagerHomeFragment.java index 8c53ce4..28d8791 100644 --- a/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/viewpager/ViewPagerHomeFragment.java +++ b/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/viewpager/ViewPagerHomeFragment.java @@ -32,7 +32,7 @@ public void onViewReady(View view, Bundle savedInstanceState, Reason reason) { lifeCycleMonitor.onViewReady(view, savedInstanceState, reason); viewPager = (ViewPager) view.findViewById(R.id.viewpager); - if (reason.isFirstTime() || reason.isRestored()) { + if (reason.isNewInstance()) { pagerAdapter = new PagerAdapter(getChildFragmentManager()); viewPager.setAdapter(pagerAdapter); } 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 846e540..68a423c 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 @@ -22,6 +22,7 @@ import android.view.View; import android.view.ViewGroup; +import com.shipdream.lib.android.mvc.StateManaged; import com.shipdream.lib.android.mvc.event.BaseEventV2V; import java.util.concurrent.CopyOnWriteArrayList; @@ -52,80 +53,94 @@ *

*/ public abstract class MvcFragment extends Fragment { - static final int REASON_FIRST_TIME = 1; - static final int REASON_RESTORE = REASON_FIRST_TIME << 1; - static final int REASON_POP_OUT = REASON_RESTORE << 1; - static final int REASON_ROTATE = REASON_POP_OUT << 1; + private Object newInstanceChecker; /** * Reason of creating the view of the fragment */ public static class Reason { - private int value; - - private void setValue(boolean trueOrFalse, int mask) { - if (trueOrFalse) { - value |= mask; - } else { - value &= ~mask; - } + private boolean isNewInstance; + private boolean isFirstTime; + private boolean isRestored; + private boolean isRotated; + private boolean isPoppedOut; + + void setNewInstance(boolean isNewInstance) { + this.isNewInstance = isNewInstance; } void setFirstTime(boolean isFirstTime) { - setValue(isFirstTime, REASON_FIRST_TIME); + this.isFirstTime = isFirstTime; } void setRestored(boolean isRestored) { - setValue(isRestored, REASON_RESTORE); + this.isRestored = isRestored; } - void setPoppedOut(boolean isPoppedOut) { - setValue(isPoppedOut, REASON_POP_OUT); + void setRotated(boolean isRotated) { + this.isRotated = isRotated; } - void setRotated(boolean isRotated) { - setValue(isRotated, REASON_ROTATE); + void setPoppedOut(boolean isPoppedOut) { + this.isPoppedOut = isPoppedOut; } - private boolean getValue(int mask) { - return (value & mask) == mask; + /** + * @return Indicates whether the fragment is a new instance that all its fields need to be + * reinitialized and configured. This could happen when a fragment is created for the first + * time (when {@link #isFirstTime()} = true) or the fragment is recreated on restoration + * after its holding activity was killed by OS (when {@link #isRestored()} = true). + */ + public boolean isNewInstance() { + return this.isNewInstance; } /** * @return Indicates whether the fragment view is created when the fragment is created for - * the first time. + * the first time. When this flag is true it's a good time to initialize the state fragment. */ public boolean isFirstTime() { - return getValue(REASON_FIRST_TIME); + return this.isFirstTime; } /** * @return Indicates whether the fragment view is created after the activity is killed by - * OS and restored. + * OS and restored.

+ * + *

Although when a fragment is restored all fields of the fragment will be recreated + * ({@link #isNewInstance()} = true), MVC framework will automatically restore the + * state(model) of injected controllers held by the fragment . So when a fragment is being + * restored, only re-instantiate its non-controller fields. All injected {@link StateManaged} + * including controllers will be restored by the framework itself.

*/ public boolean isRestored() { - return getValue(REASON_RESTORE); + return this.isRestored; } /** * @return Indicates whether the fragment view is created when the fragment was pushed to - * backstack and just popped out. + * back stack and just popped out. + * + *

Note that, when a fragment is popped out, it will reuses its previous instance and the + * fields of the instance, so {@link #isNewInstance()} won't be true in this case. This is + * because Android OS won't call onDestroy when a fragment is pushed into back stack.

*/ public boolean isPoppedOut() { - return getValue(REASON_POP_OUT); + return this.isPoppedOut; } /** * @return Indicates whether the fragment view is created after its orientation changed. */ public boolean isRotated() { - return getValue(REASON_ROTATE); + return this.isRotated; } @Override public String toString() { return "Reason: {" + - "firstTime: " + isFirstTime() + + "newInstance: " + isNewInstance() + + ", firstTime: " + isFirstTime() + ", restore: " + isRestored() + ", popOut: " + isPoppedOut() + ", rotate: " + isRotated() + @@ -253,11 +268,17 @@ public void run() { } private void doOnViewCreatedCallBack(View view, Bundle savedInstanceState, boolean restoring) { - int currentOrientation = getResources().getConfiguration().orientation; boolean orientationChanged = currentOrientation != lastOrientation; Reason reason = new Reason(); + if (newInstanceChecker == null) { + newInstanceChecker = new Object(); + reason.setNewInstance(true); + } else { + reason.setNewInstance(false); + } + if (orientationChanged) { reason.setRotated(true); } From c5c188d5ce3a097ae796101aa39ebbf1eed5a14a Mon Sep 17 00:00:00 2001 From: Kejun Xia Date: Mon, 19 Oct 2015 20:22:33 +1100 Subject: [PATCH 11/14] All other UI tests pass with checking the new lifecycle - onViewReady with reason.isNewInstance() --- .../mvc/view/injection/TestInjectionAndLifeCycle.java | 9 ++++++--- .../lifecycle/KeepActivitiesLifeCycleTestCase.java | 7 +++++-- .../lifecycle/NotKeepActivitiesLifeCycleTestCase.java | 11 +++++++---- 3 files changed, 18 insertions(+), 9 deletions(-) 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 7a46f49..96a92e3 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 @@ -51,7 +51,8 @@ public void tearDown() throws Exception { public void testShouldRetainInjectionsOfFragmentAAfterNavigatedToFragmentB() throws Throwable { //=============================> At A lifeCycleValidatorA.expect(LifeCycle.onCreateNull, - LifeCycle.onCreateViewNull, LifeCycle.onViewCreatedNull, LifeCycle.onViewReadyFirstTime); + LifeCycle.onCreateViewNull, LifeCycle.onViewCreatedNull, + LifeCycle.onViewReadyNewInstance, LifeCycle.onViewReadyFirstTime); onView(withId(R.id.textA)).check(matches(withText("Added by FragmentA"))); onView(withId(R.id.textB)).check(matches(withText("Added by FragmentA"))); onView(withId(R.id.textC)).check(matches(withText(""))); @@ -63,7 +64,8 @@ public void testShouldRetainInjectionsOfFragmentAAfterNavigatedToFragmentB() thr //BUT onDestroy of previous Fragment(FragmentA) is not called when it's pushed to back stack lifeCycleValidatorA.expect(LifeCycle.onPushingToBackStack, LifeCycle.onDestroyView); lifeCycleValidatorB.expect(LifeCycle.onCreateNull, - LifeCycle.onCreateViewNull, LifeCycle.onViewCreatedNull, LifeCycle.onViewReadyFirstTime); + LifeCycle.onCreateViewNull, LifeCycle.onViewCreatedNull, + LifeCycle.onViewReadyNewInstance, LifeCycle.onViewReadyFirstTime); onView(withId(R.id.textA)).check(matches(withText("Added by FragmentA\n" + "Added by FragmentB"))); onView(withId(R.id.textB)).check(matches(withText("Added by FragmentA\n" + @@ -75,7 +77,8 @@ public void testShouldRetainInjectionsOfFragmentAAfterNavigatedToFragmentB() thr //=============================> At C lifeCycleValidatorB.expect(LifeCycle.onPushingToBackStack, LifeCycle.onDestroyView); lifeCycleValidatorC.expect(LifeCycle.onCreateNull, LifeCycle.onCreateViewNull, - LifeCycle.onViewCreatedNull, LifeCycle.onViewReadyFirstTime); + LifeCycle.onViewCreatedNull, + LifeCycle.onViewReadyNewInstance, LifeCycle.onViewReadyFirstTime); onView(withId(R.id.textA)).check(matches(withText(""))); onView(withId(R.id.textB)).check(matches(withText("Added by FragmentA\n" + "Added by FragmentB\n" + diff --git a/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/lifecycle/KeepActivitiesLifeCycleTestCase.java b/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/lifecycle/KeepActivitiesLifeCycleTestCase.java index 3b21b65..d57a2fe 100644 --- a/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/lifecycle/KeepActivitiesLifeCycleTestCase.java +++ b/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/lifecycle/KeepActivitiesLifeCycleTestCase.java @@ -42,7 +42,8 @@ public void testGoBackgroundAndBroughtBack() throws Throwable { onView(withText("MvcTest")).check(matches(isDisplayed())); lifeCycleValidator.expect(LifeCycle.onCreateNull, - LifeCycle.onCreateViewNull, LifeCycle.onViewCreatedNull, LifeCycle.onViewReadyFirstTime); + LifeCycle.onCreateViewNull, LifeCycle.onViewCreatedNull, + LifeCycle.onViewReadyNewInstance, LifeCycle.onViewReadyFirstTime); pressHome(); waitTest(); @@ -72,7 +73,8 @@ public void testRotations() throws Throwable { onView(withText("MvcTest")).check(matches(isDisplayed())); lifeCycleValidator.expect(LifeCycle.onCreateNull, - LifeCycle.onCreateViewNull, LifeCycle.onViewCreatedNull, LifeCycle.onViewReadyFirstTime); + LifeCycle.onCreateViewNull, LifeCycle.onViewCreatedNull, + LifeCycle.onViewReadyNewInstance, LifeCycle.onViewReadyFirstTime); //If not on portrait mode rotate it to portrait int currentOrientation = activity.getResources().getConfiguration().orientation; @@ -165,4 +167,5 @@ public void testRotations() throws Throwable { activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED); } + } diff --git a/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/lifecycle/NotKeepActivitiesLifeCycleTestCase.java b/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/lifecycle/NotKeepActivitiesLifeCycleTestCase.java index 67c04e0..46458ca 100644 --- a/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/lifecycle/NotKeepActivitiesLifeCycleTestCase.java +++ b/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/lifecycle/NotKeepActivitiesLifeCycleTestCase.java @@ -43,7 +43,7 @@ public void testGoBackgroundAndBroughtBack() throws Throwable { lifeCycleValidator.expect(LifeCycle.onCreateNull, LifeCycle.onCreateViewNull, LifeCycle.onViewCreatedNull, - LifeCycle.onViewReadyFirstTime); + LifeCycle.onViewReadyNewInstance, LifeCycle.onViewReadyFirstTime); pressHome(); waitTest(1200); @@ -53,7 +53,7 @@ public void testGoBackgroundAndBroughtBack() throws Throwable { waitTest(); lifeCycleValidator.expect(LifeCycle.onCreateNotNull, LifeCycle.onCreateViewNotNull, LifeCycle.onViewCreatedNotNull, - LifeCycle.onViewReadyRestore); + LifeCycle.onViewReadyNewInstance, LifeCycle.onViewReadyRestore); pressHome(); waitTest(); @@ -64,7 +64,7 @@ public void testGoBackgroundAndBroughtBack() throws Throwable { waitTest(); lifeCycleValidator.expect(LifeCycle.onCreateNotNull, LifeCycle.onCreateViewNotNull, LifeCycle.onViewCreatedNotNull, - LifeCycle.onViewReadyRestore); + LifeCycle.onViewReadyNewInstance, LifeCycle.onViewReadyRestore); pressHome(); waitTest(); @@ -83,7 +83,8 @@ public void testRotations() throws Throwable { onView(withText("MvcTest")).check(matches(isDisplayed())); lifeCycleValidator.expect(LifeCycle.onCreateNull, - LifeCycle.onCreateViewNull, LifeCycle.onViewCreatedNull, LifeCycle.onViewReadyFirstTime); + LifeCycle.onCreateViewNull, LifeCycle.onViewCreatedNull, + LifeCycle.onViewReadyNewInstance, LifeCycle.onViewReadyFirstTime); //If not on portrait mode rotate it to portrait int currentOrientation = activity.getResources().getConfiguration().orientation; @@ -148,6 +149,7 @@ public void testRotations() throws Throwable { waitTest(); lifeCycleValidator.expect(LifeCycle.onCreateNotNull, LifeCycle.onCreateViewNotNull, LifeCycle.onViewCreatedNotNull, + LifeCycle.onViewReadyNewInstance, LifeCycle.onViewReadyRestore, LifeCycle.onViewReadyRotate, LifeCycle.onOrientationChanged); @@ -161,6 +163,7 @@ public void testRotations() throws Throwable { waitTest(); lifeCycleValidator.expect(LifeCycle.onCreateNotNull, LifeCycle.onCreateViewNotNull, LifeCycle.onViewCreatedNotNull, + LifeCycle.onViewReadyNewInstance, LifeCycle.onViewReadyRestore, LifeCycle.onViewReadyRotate, LifeCycle.onOrientationChanged); From 937cce1103bf1d96f35fef8fc84b74a35f0271dd Mon Sep 17 00:00:00 2001 From: Kejun Xia Date: Mon, 19 Oct 2015 20:30:55 +1100 Subject: [PATCH 12/14] Update document of Reason.isNewInstance() --- .../com/shipdream/lib/android/mvc/view/MvcFragment.java | 8 ++++++++ 1 file changed, 8 insertions(+) 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 68a423c..2a75b73 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 @@ -90,6 +90,14 @@ void setPoppedOut(boolean isPoppedOut) { * reinitialized and configured. This could happen when a fragment is created for the first * time (when {@link #isFirstTime()} = true) or the fragment is recreated on restoration * after its holding activity was killed by OS (when {@link #isRestored()} = true). + * + *

Note that even this flag is true, widgets of the view of the fragment still need to be + * reconfigured whenever {@link #onViewReady(View, Bundle, Reason)} is called. For example, + * onClickListener of a Button still need be set regardless inNewStance() is true or false. + * But a view pager adapter as a field of the fragment doesn't need to be re-instantiated + * because as a fragment instance field, it is still held by the fragment when + * isNewInstance() is false. This usually happens when the fragment is popped out on back + * navigation.

*/ public boolean isNewInstance() { return this.isNewInstance; From 2dce77b299e565a853612897745fae9496cd8484 Mon Sep 17 00:00:00 2001 From: Kejun Xia Date: Tue, 20 Oct 2015 23:01:08 +1100 Subject: [PATCH 13/14] Update documents for 1.3.0 --- ChangeLog.md | 3 +++ README.md | 6 +++--- documents/sites/Site-MarkDown.md | 6 +++--- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index 1926ac8..9790bbc 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,3 +1,6 @@ +Version: 1.3.0 +* Refactor the MvcFragment.Reason object. + Version: 1.2.1 * Fix issue that MvcFragment and MvcDialogFragment post V2V events to C2V event bus. diff --git a/README.md b/README.md index 572f4ed..4dea962 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ - Easy testing for controllers on JVM without Android dependency - [Dependency injection with Poke to make mock easy](https://github.com/kejunxia/AndroidMvc/tree/master/library/poke) - Manage navigation by NavigationController which is also testable - - Improved Fragment life cycles - e.g. Differentiate why view is created: 1. __FIRST_TIME__, 2. __ROTATE__, 3. __RESTORE__ + - Improved Fragment life cycles - e.g. Differentiate why view is created: 1. Reason.isNewInstance(), 2. Reason.isFirstTime(), 3. Reason.isRestored(), 4 Reason.isRotated() - Automatically save restore instance state ## Samples @@ -33,13 +33,13 @@ The library is currently release to jCenter and MavenCentral com.shipdream android-mvc - 1.2.1 + 1.3.0 ``` **Gradle:** ```groovy -compile "com.shipdream:android-mvc:1.2.1" +compile "com.shipdream:android-mvc:1.3.0" ``` ## Dependency injection with reference count diff --git a/documents/sites/Site-MarkDown.md b/documents/sites/Site-MarkDown.md index c5c4d40..581cff3 100644 --- a/documents/sites/Site-MarkDown.md +++ b/documents/sites/Site-MarkDown.md @@ -21,7 +21,7 @@ First of all, let's look at some problems of the Android development below: - Easy testing for controllers on JVM without Android dependency - Automatically save restore instance state - Improved Fragment lifecycle - - __onViewReady(View view, Bundle savedInstanceState, Reason reason):__ Where reason differentiates the cause of creation of view: 1. __FIRST_TIME__, 2. __ROTATE__, 3. __RESTORE__ + - __onViewReady(View view, Bundle savedInstanceState, Reason reason):__ Where reason differentiates the cause of creation of view: 1. Reason.isNewInstance(), 2. Reason.isFirstTime(), 3. Reason.isRestored(), 4 Reason.isRotated() - __onReturnForeground():__ When app resume from background - __onOrientationChanged(int lastOrientation, int currentOrientation):__ When app rotated - __onPushingToBackStack():__ When current page is pushed into back stack and navigate to next page @@ -40,13 +40,13 @@ The library is currently release to jCenter and MavenCentral com.shipdream android-mvc - 1.2.1 + 1.3.0 ``` **Gradle:** ```groovy -compile "com.shipdream:android-mvc:1.2.1" +compile "com.shipdream:android-mvc:1.3.0" ``` ## Samples From 8b7301911d3e2b17906bbfb36afab2d0d96ab326 Mon Sep 17 00:00:00 2001 From: Kejun Xia Date: Tue, 20 Oct 2015 23:07:01 +1100 Subject: [PATCH 14/14] Remove module controller-retrofit --- ChangeLog.md | 1 + extension/controller-retrofit/build.gradle | 110 -------- .../AbstractRetrofitController.java | 112 -------- ...tractRetrofitMultiEndpointsController.java | 62 ----- .../mvc/controller/OnNetworkErrorEvent.java | 37 --- .../mvc/controller/RetrofitErrorHandler.java | 30 --- .../controller/RetrofitServiceFactory.java | 247 ------------------ settings.gradle | 1 - 8 files changed, 1 insertion(+), 599 deletions(-) delete mode 100644 extension/controller-retrofit/build.gradle delete mode 100644 extension/controller-retrofit/src/main/java/com/shipdream/lib/android/mvc/controller/AbstractRetrofitController.java delete mode 100644 extension/controller-retrofit/src/main/java/com/shipdream/lib/android/mvc/controller/AbstractRetrofitMultiEndpointsController.java delete mode 100644 extension/controller-retrofit/src/main/java/com/shipdream/lib/android/mvc/controller/OnNetworkErrorEvent.java delete mode 100644 extension/controller-retrofit/src/main/java/com/shipdream/lib/android/mvc/controller/RetrofitErrorHandler.java delete mode 100644 extension/controller-retrofit/src/main/java/com/shipdream/lib/android/mvc/controller/RetrofitServiceFactory.java diff --git a/ChangeLog.md b/ChangeLog.md index 9790bbc..3f79557 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,5 +1,6 @@ Version: 1.3.0 * Refactor the MvcFragment.Reason object. +* Remove android-mvc-controller-retrofit Version: 1.2.1 * Fix issue that MvcFragment and MvcDialogFragment post V2V events to C2V event bus. diff --git a/extension/controller-retrofit/build.gradle b/extension/controller-retrofit/build.gradle deleted file mode 100644 index b84f9a6..0000000 --- a/extension/controller-retrofit/build.gradle +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright 2015 Kejun Xia - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -apply plugin: 'java' -apply plugin: 'maven' -apply plugin: 'com.jfrog.bintray' - -task sourceJar(type: Jar) { - from sourceSets.main.java.srcDirs - classifier = 'sources' -} - -task javadocJar(type: Jar, dependsOn: javadoc) { - classifier = 'javadoc' - from javadoc.destinationDir -} - -// Add the sourceJars to non-extractor modules -artifacts { - archives sourceJar - archives javadocJar -} - -sourceSets { - main { - java.srcDir 'src/main/java' - resources.srcDir 'src/main/resources' - } - - test { - java.srcDir 'src/test/java' - resources.srcDir 'src/test/resources' - } -} - -dependencies { - compile ("com.squareup.retrofit:retrofit:1.9.0") { - exclude group: 'com.google.guava' - exclude group: 'com.google.code.gson' - } - compile (project(':library:android-mvc-controller')) { - exclude group: 'com.google.guava' - } -} - -install { - repositories.mavenInstaller { - // This generates POM.xml with proper parameters - pom { - project { - packaging 'jar' - - // Add your description here - name 'android-mvc-controller-retrofit' - description 'Http controller implementations with Retrofit' - url rootProject.ext.siteUrl - - // Set your license - licenses { - license { - name 'The Apache Software License, Version 2.0' - url 'http://www.apache.org/licenses/LICENSE-2.0.txt' - } - } - developers { - developer { - id 'kejunxia' - name 'Kejun Xia' - email 'ideablast@gmail.com' - } - } - scm { - connection rootProject.ext.gitUrl - developerConnection rootProject.ext.gitUrl - url rootProject.ext.siteUrl - } - } - } - } -} - -bintray { - user = System.properties['bintray.user'] - key = System.properties["bintray.apiKey"] - configure(subprojects.findAll { new File(it.projectDir, 'src').directory }) { - apply plugin: 'java' - } - configurations = ['archives'] - pkg { - repo = "maven" - name = "android-mvc-controller-retrofit" - websiteUrl = rootProject.ext.siteUrl - vcsUrl = rootProject.ext.gitUrl - licenses = ["Apache-2.0"] - publish = rootProject.ext.shouldPublish - } -} \ No newline at end of file diff --git a/extension/controller-retrofit/src/main/java/com/shipdream/lib/android/mvc/controller/AbstractRetrofitController.java b/extension/controller-retrofit/src/main/java/com/shipdream/lib/android/mvc/controller/AbstractRetrofitController.java deleted file mode 100644 index 90c9499..0000000 --- a/extension/controller-retrofit/src/main/java/com/shipdream/lib/android/mvc/controller/AbstractRetrofitController.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright 2015 Kejun Xia - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.shipdream.lib.android.mvc.controller; - -import com.shipdream.lib.android.mvc.controller.internal.AsyncExceptionHandler; -import com.shipdream.lib.android.mvc.controller.internal.AsyncTask; -import com.shipdream.lib.android.mvc.controller.internal.BaseControllerImpl; - -import java.util.concurrent.ExecutorService; - -import retrofit.RetrofitError; - -/** - * Controller will execute http async tasks with retrofit. - */ -public abstract class AbstractRetrofitController extends BaseControllerImpl{ - - /** - * Run a http async task with retrofit services on the given ExecutorService. - *
    - *
  • {@link OnNetworkErrorEvent} will be fired when a retrofit network error is detected
  • - *
- * @param sender Who initiated this task - * @param httpTask A runnable task wrapping retrofit service - * @param retrofitErrorHandler Error handler to handle retrofit error - * @param nonRetrofitErrorHandler Error handler for errors other than retrofit error. - * Note: When null is given, all non-retrofit - * exceptions will be suppressed with a warning level log. - */ - protected void runHttpTask(final Object sender, final AsyncTask httpTask, - final RetrofitErrorHandler retrofitErrorHandler, - final AsyncExceptionHandler nonRetrofitErrorHandler){ - runAsyncTask(sender, - new AsyncTask(){ - @Override - public void execute() throws Exception{ - httpTask.execute(); - } - }, - new AsyncExceptionHandler(){ - @Override - public void handleException(Exception e){ - handleAsyncError(sender, e, retrofitErrorHandler, nonRetrofitErrorHandler); - } - }); - } - - /** - * Run a http async task with retrofit services on the given ExecutorService. - *
    - *
  • {@link OnNetworkErrorEvent} will be fired when a retrofit network error is detected
  • - *
- * - * @param sender Who initiated this task - * @param executorService The executor service provided to execute the async task - * @param httpTask A runnable task wrapping retrofit service - * @param retrofitErrorHandler Error handler to handle retrofit error - * @param nonRetrofitErrorHandler Error handler for errors other than retrofit error. - * Note: When null is given, all non-retrofit - * exceptions will be suppressed with a warning level log. - */ - protected void runHttpTask(final Object sender, ExecutorService executorService, - final AsyncTask httpTask, - final RetrofitErrorHandler retrofitErrorHandler, - final AsyncExceptionHandler nonRetrofitErrorHandler){ - runAsyncTask(sender, executorService, - new AsyncTask(){ - @Override - public void execute() throws Exception{ - httpTask.execute(); - } - }, - new AsyncExceptionHandler(){ - @Override - public void handleException(Exception e){ - handleAsyncError(sender, e, retrofitErrorHandler, nonRetrofitErrorHandler); - } - }); - } - - private void handleAsyncError(Object sender, Exception e, RetrofitErrorHandler retrofitErrorHandler, - AsyncExceptionHandler nonRetrofitErrorHandler) { - if (e instanceof RetrofitError){ - RetrofitError error = (RetrofitError) e; - boolean handUpNetworkErrorEvent = !retrofitErrorHandler.handleError(sender, error); - if (handUpNetworkErrorEvent && error.getKind() == RetrofitError.Kind.NETWORK){ - //the retrofit error handler passed on the error so we can post a network error event. - postC2VEvent(new OnNetworkErrorEvent(sender, error)); - } - } else{ - if (nonRetrofitErrorHandler == null){ - logger.warn("Unhandled exception detected: {}", e.getMessage(), e); - } else{ - nonRetrofitErrorHandler.handleException(e); - } - } - } -} diff --git a/extension/controller-retrofit/src/main/java/com/shipdream/lib/android/mvc/controller/AbstractRetrofitMultiEndpointsController.java b/extension/controller-retrofit/src/main/java/com/shipdream/lib/android/mvc/controller/AbstractRetrofitMultiEndpointsController.java deleted file mode 100644 index 266e61a..0000000 --- a/extension/controller-retrofit/src/main/java/com/shipdream/lib/android/mvc/controller/AbstractRetrofitMultiEndpointsController.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 2015 Kejun Xia - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.shipdream.lib.android.mvc.controller; - -import java.util.HashMap; -import java.util.Map; - -import javax.inject.Inject; - -import retrofit.client.Client; - -/** - * Controller will execute http async tasks with retrofit and support switching endpoints dynamically - */ -public abstract class AbstractRetrofitMultiEndpointsController extends AbstractRetrofitController { - @Inject - Client client; - - private Map httpServiceFactoryCache = new HashMap<>(); - - /** - * Get a service for a specific endpoint. A new service will be created if not cached yet, otherwise cached service - * will be returned. - * @param endpoint The string of the endpoint - * @param serviceTypeClass The service type - * @return The service - */ - protected T getService(String endpoint, Class serviceTypeClass) { - return getHttpServiceFactory(endpoint).createService(serviceTypeClass); - } - - /** - * Override to config newly created HttpRetrofitServiceFactory for specific endpoint - * @param factory The factory to generate service - * @param endpoint The string of the endpoint - */ - protected void onCreateNewHttpRetrofitServiceFactory(RetrofitServiceFactory factory, String endpoint) {} - - private RetrofitServiceFactory getHttpServiceFactory(String endpoint) { - //Cache service factory - if(httpServiceFactoryCache.get(endpoint) == null) { - RetrofitServiceFactory factory = new RetrofitServiceFactory(endpoint, client); - onCreateNewHttpRetrofitServiceFactory(factory, endpoint); - httpServiceFactoryCache.put(endpoint, factory); - } - return httpServiceFactoryCache.get(endpoint); - } -} diff --git a/extension/controller-retrofit/src/main/java/com/shipdream/lib/android/mvc/controller/OnNetworkErrorEvent.java b/extension/controller-retrofit/src/main/java/com/shipdream/lib/android/mvc/controller/OnNetworkErrorEvent.java deleted file mode 100644 index c85b02a..0000000 --- a/extension/controller-retrofit/src/main/java/com/shipdream/lib/android/mvc/controller/OnNetworkErrorEvent.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2015 Kejun Xia - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.shipdream.lib.android.mvc.controller; - -import com.shipdream.lib.android.mvc.event.BaseEventC2V; - -import retrofit.RetrofitError; - -/** - * C2VEvent for Http network error. This event will be raised only if the type of the http error is caused by network issue. - */ -public class OnNetworkErrorEvent extends BaseEventC2V { - private RetrofitError error; - - public OnNetworkErrorEvent(Object sender, RetrofitError error) { - super(sender); - this.error = error; - } - - public RetrofitError getError(){ - return error; - } -} \ No newline at end of file diff --git a/extension/controller-retrofit/src/main/java/com/shipdream/lib/android/mvc/controller/RetrofitErrorHandler.java b/extension/controller-retrofit/src/main/java/com/shipdream/lib/android/mvc/controller/RetrofitErrorHandler.java deleted file mode 100644 index e3b644a..0000000 --- a/extension/controller-retrofit/src/main/java/com/shipdream/lib/android/mvc/controller/RetrofitErrorHandler.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2015 Kejun Xia - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.shipdream.lib.android.mvc.controller; - -import retrofit.RetrofitError; - -public interface RetrofitErrorHandler { - /** - * @param sender Who initiated the http task - * @param retrofitError What http error - * @return Indicates whether or not the error handle will absorb http Network error. - * True: NO network error event would be handed to upper level exception handler. - * False: Network error event will be relayed and handed up to upper level exception handler. - */ - boolean handleError(Object sender, RetrofitError retrofitError); -} diff --git a/extension/controller-retrofit/src/main/java/com/shipdream/lib/android/mvc/controller/RetrofitServiceFactory.java b/extension/controller-retrofit/src/main/java/com/shipdream/lib/android/mvc/controller/RetrofitServiceFactory.java deleted file mode 100644 index 5e96e7e..0000000 --- a/extension/controller-retrofit/src/main/java/com/shipdream/lib/android/mvc/controller/RetrofitServiceFactory.java +++ /dev/null @@ -1,247 +0,0 @@ -/* - * Copyright 2015 Kejun Xia - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.shipdream.lib.android.mvc.controller; - -import com.google.gson.FieldNamingPolicy; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.JsonParseException; -import com.google.gson.internal.bind.DateTypeAdapter; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.Reader; -import java.lang.reflect.Type; -import java.nio.charset.Charset; -import java.util.Date; -import java.util.List; -import java.util.concurrent.CopyOnWriteArrayList; - -import retrofit.RequestInterceptor; -import retrofit.RestAdapter; -import retrofit.client.Client; -import retrofit.converter.ConversionException; -import retrofit.converter.GsonConverter; -import retrofit.mime.MimeUtil; -import retrofit.mime.TypedInput; - -/** - * - */ -public class RetrofitServiceFactory { - /** - * Listen to all request - */ - interface OnRequestListener { - void onRequest(RequestInterceptor.RequestFacade requestFacade); - } - - /** - * Listen to successful response which comes with status code in range [200, 300) - */ - interface OnSuccessListener { - void onSuccessfulResponse(); - } - - private String endpoint; - private RestAdapter restAdapter; - private Client client; - private Gson gson; - private FlexibleConverter jsonConverter; - private List onRequestListeners = new CopyOnWriteArrayList(); - private List onSuccessListeners = new CopyOnWriteArrayList(); - - public void registerOnRequestListener(OnRequestListener listener) { - onRequestListeners.add(listener); - } - - public void unregisterOnRequestListener(OnRequestListener listener) { - onRequestListeners.remove(listener); - } - - public void registerOnResponseListener(OnSuccessListener listener) { - onSuccessListeners.add(listener); - } - - public void unregisterOnResponseListener(OnSuccessListener listener) { - onSuccessListeners.remove(listener); - } - - public void setEndpoint(String endpoint) { - this.endpoint = endpoint; - recreateRestAdapter(); - } - - public String getEndpoint() { - return this.endpoint; - } - - public SERVICE createService(Class serviceClass) { - return restAdapter.create(serviceClass); - } - - public RetrofitServiceFactory(Client client) { - this(null, client); - } - - public RetrofitServiceFactory(String endpoint, Client client) { - this.endpoint = endpoint; - this.client = client; - - if(this.endpoint != null) { - gson = new GsonBuilder() - .setFieldNamingPolicy(FieldNamingPolicy.IDENTITY) - .registerTypeAdapter(Date.class, new DateTypeAdapter()) - .create(); - jsonConverter = new FlexibleConverter(gson, "UTF-8", onSuccessListeners); - recreateRestAdapter(); - } - } - - private void recreateRestAdapter() { - RequestInterceptor requestInterceptor = new RequestInterceptor() { - @Override - public void intercept(final RequestFacade request) { - for(OnRequestListener listener : onRequestListeners) { - listener.onRequest(new RequestFacade() { - @Override - public void addHeader(String name, String value) { - request.addHeader(name, value); - } - - @Override - public void addPathParam(String name, String value) { - request.addPathParam(name, value); - } - - @Override - public void addEncodedPathParam(String name, String value) { - request.addEncodedPathParam(name, value); - } - - @Override - public void addQueryParam(String name, String value) { - request.addQueryParam(name, value); - } - - @Override - public void addEncodedQueryParam(String name, String value) { - request.addEncodedQueryParam(name, value); - } - }); - } - } - }; - restAdapter = new RestAdapter.Builder() - .setEndpoint(endpoint) - .setRequestInterceptor(requestInterceptor) - .setClient(client) - .setConverter(jsonConverter) - .build(); - } - - public static class FlexibleConverter extends GsonConverter { - private Logger logger = LoggerFactory.getLogger(getClass()); - private Gson gson; - private List onSuccessListeners; - private String encoding = "UTF-8"; - public FlexibleConverter(Gson gson, String encoding, List onSuccessListeners) { - super(gson, encoding); - if(this.encoding != null) { - this.encoding = encoding; - } - this.onSuccessListeners = onSuccessListeners; - this.gson = gson; - } - - @Override - public Object fromBody(TypedInput body, Type type) throws ConversionException { - String charset = "UTF-8"; - if (type == String.class) { - //return raw response string - try { - String content = inputStreamToString(body.in(), charset); - logger.debug("Responding json to raw string : {}", content); - - for(OnSuccessListener listener : onSuccessListeners) { - listener.onSuccessfulResponse(); - } - - return content; - } catch (IOException e) { - throw new ConversionException("Reading body as string failed. Message: " + e.getMessage(), e); - } - } else { - //map the response to given type - - if (body.mimeType() != null) { - charset = MimeUtil.parseCharset(body.mimeType(), "UTF-8"); - } - InputStreamReader isr = null; - try { - Object response = null; - if(logger.isDebugEnabled()) { - String content = inputStreamToString(body.in(), charset); - logger.debug("Responding json to object: {}", content); - response = gson.fromJson(content, type); - } else { - isr = new InputStreamReader(body.in(), charset); - response = gson.fromJson(isr, type); - } - - for(OnSuccessListener listener : onSuccessListeners) { - listener.onSuccessfulResponse(); - } - - return response; - } catch (IOException e) { - throw new ConversionException(e); - } catch (JsonParseException e) { - throw new ConversionException(e); - } finally { - if (isr != null) { - try { - isr.close(); - } catch (IOException ignored) { - } - } - } - } - } - - private static String inputStreamToString(InputStream inputStream, String encoding) - throws IOException{ - StringBuilder textBuilder = new StringBuilder(); - Charset charset = Charset.forName(encoding); - Reader reader = new BufferedReader(new InputStreamReader(inputStream, charset)); - try{ - int c = 0; - while ((c = reader.read()) != -1) { - textBuilder.append((char) c); - } - }finally{ - inputStream.close(); - } - return textBuilder.toString(); - } - } -} diff --git a/settings.gradle b/settings.gradle index 43a258b..8249aad 100644 --- a/settings.gradle +++ b/settings.gradle @@ -19,7 +19,6 @@ include ':library:android-mvc' include ':library:android-mvc-controller' include ':library:poke' -include ':extension:controller-retrofit' include ':extension:service-core' include ':extension:service-mediastore'