From ebea99fd18d9a8e72b56fb5069b18df31cc05a66 Mon Sep 17 00:00:00 2001 From: Kejun Xia Date: Thu, 14 Jul 2016 16:10:20 +1000 Subject: [PATCH 01/15] Rename screen views to xxxScreen --- .../android/mvc/samples/simple/MainActivity.java | 2 +- .../simple/service/internal/PosterImpl.java | 14 -------------- .../mvc/samples/simple/view/AbstractFragment.java | 4 ---- ...terDetailView.java => CounterDetailScreen.java} | 2 +- ...icSubView.java => CounterMasterInsideView.java} | 8 ++++---- ...nterBasicView.java => CounterMasterScreen.java} | 12 ++++++------ .../simple/controller/AppDelegateController.java | 2 +- ...ontroller.java => CounterMasterController.java} | 4 ++-- ...ler.java => CounterMasterInsideController.java} | 2 +- .../controller/CounterServiceController.java | 6 +----- .../android/mvc/samples/simple/service/Poster.java | 5 ----- .../internal/TestCounterBasicController.java | 12 ++++++------ 12 files changed, 23 insertions(+), 50 deletions(-) delete mode 100644 samples/simple-mvc/app/src/main/java/com/shipdream/lib/android/mvc/samples/simple/service/internal/PosterImpl.java rename samples/simple-mvc/app/src/main/java/com/shipdream/lib/android/mvc/samples/simple/view/{CounterDetailView.java => CounterDetailScreen.java} (98%) rename samples/simple-mvc/app/src/main/java/com/shipdream/lib/android/mvc/samples/simple/view/{CounterBasicSubView.java => CounterMasterInsideView.java} (77%) rename samples/simple-mvc/app/src/main/java/com/shipdream/lib/android/mvc/samples/simple/view/{CounterBasicView.java => CounterMasterScreen.java} (92%) rename samples/simple-mvc/core/src/main/java/com/shipdream/lib/android/mvc/samples/simple/controller/{CounterBasicController.java => CounterMasterController.java} (95%) rename samples/simple-mvc/core/src/main/java/com/shipdream/lib/android/mvc/samples/simple/controller/{CounterBasicSubController.java => CounterMasterInsideController.java} (90%) delete mode 100644 samples/simple-mvc/core/src/main/java/com/shipdream/lib/android/mvc/samples/simple/service/Poster.java diff --git a/samples/simple-mvc/app/src/main/java/com/shipdream/lib/android/mvc/samples/simple/MainActivity.java b/samples/simple-mvc/app/src/main/java/com/shipdream/lib/android/mvc/samples/simple/MainActivity.java index 7e694cc..57f3fd8 100644 --- a/samples/simple-mvc/app/src/main/java/com/shipdream/lib/android/mvc/samples/simple/MainActivity.java +++ b/samples/simple-mvc/app/src/main/java/com/shipdream/lib/android/mvc/samples/simple/MainActivity.java @@ -13,7 +13,7 @@ protected Class mapControllerFragment( String presenterPkgName = presenterClass.getPackage().getName(); String viewPkgName = presenterPkgName.substring(0, presenterPkgName.lastIndexOf(".")) + ".view"; String fragmentClassName = viewPkgName + "." - + presenterClass.getSimpleName().replace("Controller", "View"); + + presenterClass.getSimpleName().replace("Controller", "Screen"); try { return (Class) Class.forName(fragmentClassName); } catch (ClassNotFoundException e) { diff --git a/samples/simple-mvc/app/src/main/java/com/shipdream/lib/android/mvc/samples/simple/service/internal/PosterImpl.java b/samples/simple-mvc/app/src/main/java/com/shipdream/lib/android/mvc/samples/simple/service/internal/PosterImpl.java deleted file mode 100644 index 503faaf..0000000 --- a/samples/simple-mvc/app/src/main/java/com/shipdream/lib/android/mvc/samples/simple/service/internal/PosterImpl.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.shipdream.lib.android.mvc.samples.simple.service.internal; - -import android.os.Handler; - -import com.shipdream.lib.android.mvc.samples.simple.service.Poster; - -public class PosterImpl implements Poster { - private Handler handler = new Handler(); - - @Override - public void postDelayed(Runnable runnable, long delayMs) { - handler.postDelayed(runnable, delayMs); - } -} diff --git a/samples/simple-mvc/app/src/main/java/com/shipdream/lib/android/mvc/samples/simple/view/AbstractFragment.java b/samples/simple-mvc/app/src/main/java/com/shipdream/lib/android/mvc/samples/simple/view/AbstractFragment.java index 6af11e4..7f8acae 100644 --- a/samples/simple-mvc/app/src/main/java/com/shipdream/lib/android/mvc/samples/simple/view/AbstractFragment.java +++ b/samples/simple-mvc/app/src/main/java/com/shipdream/lib/android/mvc/samples/simple/view/AbstractFragment.java @@ -7,10 +7,6 @@ import com.shipdream.lib.android.mvc.Reason; import com.shipdream.lib.android.mvc.samples.simple.controller.AbstractController; -/** - * Created by kejun on 20/06/2016. - */ - public abstract class AbstractFragment extends MvcFragment implements UiView { @Override diff --git a/samples/simple-mvc/app/src/main/java/com/shipdream/lib/android/mvc/samples/simple/view/CounterDetailView.java b/samples/simple-mvc/app/src/main/java/com/shipdream/lib/android/mvc/samples/simple/view/CounterDetailScreen.java similarity index 98% rename from samples/simple-mvc/app/src/main/java/com/shipdream/lib/android/mvc/samples/simple/view/CounterDetailView.java rename to samples/simple-mvc/app/src/main/java/com/shipdream/lib/android/mvc/samples/simple/view/CounterDetailScreen.java index eae75fb..58e6007 100644 --- a/samples/simple-mvc/app/src/main/java/com/shipdream/lib/android/mvc/samples/simple/view/CounterDetailView.java +++ b/samples/simple-mvc/app/src/main/java/com/shipdream/lib/android/mvc/samples/simple/view/CounterDetailScreen.java @@ -29,7 +29,7 @@ import com.shipdream.lib.android.mvc.samples.simple.controller.CounterDetailController; import com.shipdream.lib.android.mvc.samples.simple.view.service.CountService; -public class CounterDetailView extends AbstractFragment +public class CounterDetailScreen extends AbstractFragment implements CounterDetailController.View{ private class ContinuousCounter implements Runnable { private final boolean incrementing; diff --git a/samples/simple-mvc/app/src/main/java/com/shipdream/lib/android/mvc/samples/simple/view/CounterBasicSubView.java b/samples/simple-mvc/app/src/main/java/com/shipdream/lib/android/mvc/samples/simple/view/CounterMasterInsideView.java similarity index 77% rename from samples/simple-mvc/app/src/main/java/com/shipdream/lib/android/mvc/samples/simple/view/CounterBasicSubView.java rename to samples/simple-mvc/app/src/main/java/com/shipdream/lib/android/mvc/samples/simple/view/CounterMasterInsideView.java index f6c23ed..2241c3e 100644 --- a/samples/simple-mvc/app/src/main/java/com/shipdream/lib/android/mvc/samples/simple/view/CounterBasicSubView.java +++ b/samples/simple-mvc/app/src/main/java/com/shipdream/lib/android/mvc/samples/simple/view/CounterMasterInsideView.java @@ -6,14 +6,14 @@ import com.shipdream.lib.android.mvc.Reason; import com.shipdream.lib.android.mvc.samples.simple.R; -import com.shipdream.lib.android.mvc.samples.simple.controller.CounterBasicSubController; +import com.shipdream.lib.android.mvc.samples.simple.controller.CounterMasterInsideController; -public class CounterBasicSubView extends AbstractFragment { +public class CounterMasterInsideView extends AbstractFragment { private TextView txtCountInEnglish; @Override - protected Class getControllerClass() { - return CounterBasicSubController.class; + protected Class getControllerClass() { + return CounterMasterInsideController.class; } @Override diff --git a/samples/simple-mvc/app/src/main/java/com/shipdream/lib/android/mvc/samples/simple/view/CounterBasicView.java b/samples/simple-mvc/app/src/main/java/com/shipdream/lib/android/mvc/samples/simple/view/CounterMasterScreen.java similarity index 92% rename from samples/simple-mvc/app/src/main/java/com/shipdream/lib/android/mvc/samples/simple/view/CounterBasicView.java rename to samples/simple-mvc/app/src/main/java/com/shipdream/lib/android/mvc/samples/simple/view/CounterMasterScreen.java index ca2b1f5..d26eb8b 100644 --- a/samples/simple-mvc/app/src/main/java/com/shipdream/lib/android/mvc/samples/simple/view/CounterBasicView.java +++ b/samples/simple-mvc/app/src/main/java/com/shipdream/lib/android/mvc/samples/simple/view/CounterMasterScreen.java @@ -11,12 +11,12 @@ import com.shipdream.lib.android.mvc.NavigationManager; import com.shipdream.lib.android.mvc.Reason; import com.shipdream.lib.android.mvc.samples.simple.R; -import com.shipdream.lib.android.mvc.samples.simple.controller.CounterBasicController; +import com.shipdream.lib.android.mvc.samples.simple.controller.CounterMasterController; import javax.inject.Inject; -public class CounterBasicView extends AbstractFragment - implements CounterBasicController.View{ +public class CounterMasterScreen extends AbstractFragment + implements CounterMasterController.View{ @Inject private NavigationManager navigationManager; @@ -30,8 +30,8 @@ public class CounterBasicView extends AbstractFragment private Button buttonShowAdvancedView; @Override - protected Class getControllerClass() { - return CounterBasicController.class; + protected Class getControllerClass() { + return CounterMasterController.class; } /** @@ -90,7 +90,7 @@ public void onClick(View v) { }); if (reason.isFirstTime()) { - CounterBasicSubView f = new CounterBasicSubView(); + CounterMasterInsideView f = new CounterMasterInsideView(); getChildFragmentManager().beginTransaction() .replace(R.id.fragment_a_anotherFragmentContainer, f).commit(); diff --git a/samples/simple-mvc/core/src/main/java/com/shipdream/lib/android/mvc/samples/simple/controller/AppDelegateController.java b/samples/simple-mvc/core/src/main/java/com/shipdream/lib/android/mvc/samples/simple/controller/AppDelegateController.java index fa578cd..589b9f8 100644 --- a/samples/simple-mvc/core/src/main/java/com/shipdream/lib/android/mvc/samples/simple/controller/AppDelegateController.java +++ b/samples/simple-mvc/core/src/main/java/com/shipdream/lib/android/mvc/samples/simple/controller/AppDelegateController.java @@ -9,7 +9,7 @@ public class AppDelegateController extends AbstractController { private NavigationManager navigationManager; public void startApp(Object sender) { - navigationManager.navigate(sender).to(CounterBasicController.class); + navigationManager.navigate(sender).to(CounterMasterController.class); } @Override diff --git a/samples/simple-mvc/core/src/main/java/com/shipdream/lib/android/mvc/samples/simple/controller/CounterBasicController.java b/samples/simple-mvc/core/src/main/java/com/shipdream/lib/android/mvc/samples/simple/controller/CounterMasterController.java similarity index 95% rename from samples/simple-mvc/core/src/main/java/com/shipdream/lib/android/mvc/samples/simple/controller/CounterBasicController.java rename to samples/simple-mvc/core/src/main/java/com/shipdream/lib/android/mvc/samples/simple/controller/CounterMasterController.java index dded368..6bb7396 100644 --- a/samples/simple-mvc/core/src/main/java/com/shipdream/lib/android/mvc/samples/simple/controller/CounterBasicController.java +++ b/samples/simple-mvc/core/src/main/java/com/shipdream/lib/android/mvc/samples/simple/controller/CounterMasterController.java @@ -12,8 +12,8 @@ import retrofit2.Response; -public class CounterBasicController extends AbstractController { +public class CounterMasterController extends AbstractController { @Override public Class modelType() { return Model.class; diff --git a/samples/simple-mvc/core/src/main/java/com/shipdream/lib/android/mvc/samples/simple/controller/CounterBasicSubController.java b/samples/simple-mvc/core/src/main/java/com/shipdream/lib/android/mvc/samples/simple/controller/CounterMasterInsideController.java similarity index 90% rename from samples/simple-mvc/core/src/main/java/com/shipdream/lib/android/mvc/samples/simple/controller/CounterBasicSubController.java rename to samples/simple-mvc/core/src/main/java/com/shipdream/lib/android/mvc/samples/simple/controller/CounterMasterInsideController.java index 595aae5..fe7cf7d 100644 --- a/samples/simple-mvc/core/src/main/java/com/shipdream/lib/android/mvc/samples/simple/controller/CounterBasicSubController.java +++ b/samples/simple-mvc/core/src/main/java/com/shipdream/lib/android/mvc/samples/simple/controller/CounterMasterInsideController.java @@ -4,7 +4,7 @@ import javax.inject.Inject; -public class CounterBasicSubController extends AbstractController { +public class CounterMasterInsideController extends AbstractController { @Override public Class modelType() { return null; diff --git a/samples/simple-mvc/core/src/main/java/com/shipdream/lib/android/mvc/samples/simple/controller/CounterServiceController.java b/samples/simple-mvc/core/src/main/java/com/shipdream/lib/android/mvc/samples/simple/controller/CounterServiceController.java index 37d78c7..ca28d63 100644 --- a/samples/simple-mvc/core/src/main/java/com/shipdream/lib/android/mvc/samples/simple/controller/CounterServiceController.java +++ b/samples/simple-mvc/core/src/main/java/com/shipdream/lib/android/mvc/samples/simple/controller/CounterServiceController.java @@ -3,7 +3,6 @@ import com.shipdream.lib.android.mvc.NavigationManager; import com.shipdream.lib.android.mvc.UiView; import com.shipdream.lib.android.mvc.samples.simple.manager.CounterManager; -import com.shipdream.lib.android.mvc.samples.simple.service.Poster; import javax.inject.Inject; @@ -27,7 +26,7 @@ public void run() { if (count ++ <= AUTO_FINISH_COUNT) { counterManager.setCount(this, counterManager.getModel().getCount() + 1); - poster.postDelayed(this, 1000); + uiThreadRunner.postDelayed(this, 1000); } else { view.counterFinished(); } @@ -42,9 +41,6 @@ private void cancel() { private static final int AUTO_FINISH_COUNT = 10; private AutoCounter autoCounter; - @Inject - private Poster poster; - @Inject private NavigationManager navigationManager; diff --git a/samples/simple-mvc/core/src/main/java/com/shipdream/lib/android/mvc/samples/simple/service/Poster.java b/samples/simple-mvc/core/src/main/java/com/shipdream/lib/android/mvc/samples/simple/service/Poster.java deleted file mode 100644 index ee8e4a5..0000000 --- a/samples/simple-mvc/core/src/main/java/com/shipdream/lib/android/mvc/samples/simple/service/Poster.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.shipdream.lib.android.mvc.samples.simple.service; - -public interface Poster { - void postDelayed(Runnable runnable, long delayMs); -} diff --git a/samples/simple-mvc/core/src/test/java/com/shipdream/lib/android/mvc/samples/simple/controller/internal/TestCounterBasicController.java b/samples/simple-mvc/core/src/test/java/com/shipdream/lib/android/mvc/samples/simple/controller/internal/TestCounterBasicController.java index d2fe946..6f74bc7 100644 --- a/samples/simple-mvc/core/src/test/java/com/shipdream/lib/android/mvc/samples/simple/controller/internal/TestCounterBasicController.java +++ b/samples/simple-mvc/core/src/test/java/com/shipdream/lib/android/mvc/samples/simple/controller/internal/TestCounterBasicController.java @@ -19,7 +19,7 @@ import com.shipdream.lib.android.mvc.Mvc; import com.shipdream.lib.android.mvc.NavigationManager; import com.shipdream.lib.android.mvc.TestUtil; -import com.shipdream.lib.android.mvc.samples.simple.controller.CounterBasicController; +import com.shipdream.lib.android.mvc.samples.simple.controller.CounterMasterController; import com.shipdream.lib.android.mvc.samples.simple.controller.CounterDetailController; import com.shipdream.lib.android.mvc.samples.simple.manager.CounterManager; @@ -41,13 +41,13 @@ public class TestCounterBasicController extends BaseTest { @Inject private NavigationManager navigationManager; - private CounterBasicController controller; + private CounterMasterController controller; @Override public void setUp() throws Exception { super.setUp(); - controller = new CounterBasicController(); + controller = new CounterMasterController(); Mvc.graph().inject(controller); controller.onCreated(); } @@ -56,7 +56,7 @@ public void setUp() throws Exception { public void increment_should_post_counter_update_event_with_incremented_value() { //1. Prepare //prepare event monitor - CounterBasicController.View view = mock(CounterBasicController.View.class); + CounterMasterController.View view = mock(CounterMasterController.View.class); TestUtil.assignControllerView(controller, view); //mock controller model for count value @@ -79,9 +79,9 @@ public void increment_should_post_counter_update_event_with_incremented_value() public void should_navigate_to_locationB_when_go_to_advance_view_and_back_to_locationA_after_go_to_basic_view() { //Prepare //Simulate navigating to location - navigationManager.navigate(this).to(CounterBasicController.class); + navigationManager.navigate(this).to(CounterMasterController.class); //Verify: location should be changed to LocationA - Assert.assertEquals(CounterBasicController.class.getName(), + Assert.assertEquals(CounterMasterController.class.getName(), navigationManager.getModel().getCurrentLocation().getLocationId()); //Act: CounterController now goes to advanced view underlining logic is navigating to locationB From 13d632efb347f5befe196bb2263f2935c112335b Mon Sep 17 00:00:00 2001 From: Kejun Xia Date: Thu, 14 Jul 2016 17:04:00 +1000 Subject: [PATCH 02/15] Update document --- .../com/shipdream/lib/android/mvc/UiView.java | 37 +++++++++++++++++-- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/library/android-mvc-core/src/main/java/com/shipdream/lib/android/mvc/UiView.java b/library/android-mvc-core/src/main/java/com/shipdream/lib/android/mvc/UiView.java index a2ef891..bfc5c86 100644 --- a/library/android-mvc-core/src/main/java/com/shipdream/lib/android/mvc/UiView.java +++ b/library/android-mvc-core/src/main/java/com/shipdream/lib/android/mvc/UiView.java @@ -1,15 +1,46 @@ package com.shipdream.lib.android.mvc; /** - * UiView represents android views. Call it Ui-View not to conflict with android.view.View and - * simplify imports + *

+ * UiView represents android views. It has a single method {@link #update()}. Every {@link Controller} + * holds an instance of {@link UiView} which is implemented by the controller's corresponding + * concrete view. Whenever the {@link Controller} updates its state, usually it's model, the controller + * needs to call {@link UiView}.{@link #update()}. + *

+ * + *

+ * Usually this is enough for most Android views since as long as the concrete view implements + * {@link UiView}.{@link #update()} by binding the full controller model to the view, it guarantees the view's + * graphics is always in sync with the controller's model. However, if the model of a controller is + * large and sometimes only very limited part of the model changes, you may not always want to call + * {@link UiView}.{@link #update()} to rebind the entire model to the view if performance becomes a concern. + *

+ * + *

+ * In this case, define a custom view interface extending {@link UiView} with extra method to allow + * controllers updating specific part of the view. For example, + *

+ * + *
+ * public interface PageView extends UiView {
+ *      /**
+ *      * Let the controller of the page view just update the title instead calling {@link UiView}.{@link #update()}
+ *      * to update the whole page.
+ *      * @param title The title of the page.
+ *      * /
+ *      void updateTitle(String title);
+ * }
+ * 
*/ public interface UiView { /** * When a view is requested to update itself, it should read it's controller's model by * {@link Controller#getModel()} to bind the data to the view. * - *

Do NOT change values of model from view but only from controllers.

+ *

It will be automatically called when a view is ready to shown. Controllers should call this + * method when they updated their model.

+ * + *

Do NOT change values of model from view but only from controllers.

*/ void update(); } From 7e345b48e7c3b62e798255f3cd268554a8cabb36 Mon Sep 17 00:00:00 2001 From: Kejun Xia Date: Thu, 14 Jul 2016 22:42:01 +1000 Subject: [PATCH 03/15] Documents for controller and related classes --- .../shipdream/lib/android/mvc/Controller.java | 102 ++++++++++++++++-- .../lib/android/mvc/UiThreadRunner.java | 6 +- .../mvc/event/bus/annotation/EventBusC.java | 8 +- .../mvc/event/bus/annotation/EventBusV.java | 14 ++- 4 files changed, 115 insertions(+), 15 deletions(-) diff --git a/library/android-mvc-core/src/main/java/com/shipdream/lib/android/mvc/Controller.java b/library/android-mvc-core/src/main/java/com/shipdream/lib/android/mvc/Controller.java index 8462bfa..789f50c 100644 --- a/library/android-mvc-core/src/main/java/com/shipdream/lib/android/mvc/Controller.java +++ b/library/android-mvc-core/src/main/java/com/shipdream/lib/android/mvc/Controller.java @@ -14,7 +14,46 @@ import javax.inject.Inject; /** - * Abstract view controller. Presenter will subscribe to {@link EventBusC} + *

+ * A controller is a delegate of a Android view. It holds a {@link UiView} that needs to be implemented + * by the corresponding Android view. Whenever the controller changes its model, it calls {@link UiView#update()} + * to update the view. Be careful when you are calling {@link UiView#update()} on the non-UI thread, + * because in this case, it needs to use {@link #uiThreadRunner} to post a runnable wrapping + * {@link UiView#update()} to ensure the view is updated on UI thread. Use {@link #runTask(Task)} to + * run heavy actions, especially network calls, on non-UI thread. + *

+ * + *

+ * In the above way the controller works as a presenter in MVP pattern. If you prefer MVVM pattern. + * You can define events and {@link #postEvent(Object)} to the view. Since {@link #postEvent(Object)} + * guarantees the event is posted onto UI thread, you don't need to worry about on which thread the + * event is posted. + *

+ * + *

+ * When some code needs to run on non-UI thread, use {@link #runTask(Task)}, + * {@link #runTask(Task, Task.Callback)} to run it on a different thread. Make sure if + * {@link UiView#update()} needs to be called in scope of @link Task#execute(Task.Monitor)} use + * {@link #uiThreadRunner} to post back to UI thread. + *

+ * + *

+ * The controller has 4 injected fields. They can be replaced by providing special or mocking objects + * in unit tests. + *

    + *
  • a {@link EventBus} annotated by {@link EventBusC} to receive events from managers and other non ui components
  • + *
  • a {@link EventBus} annotated by {@link EventBusV} to send event to Android views
  • + *
  • a {@link ExecutorService} that runs {@link Task} via {@link #runTask(Task)} on non-UI thread. + * by default, it has a fixed thread tool at size 10. You can inject a mocked {@link ExecutorService} + * that runs everything on the main thread in your Unit Tests to avoid multi-thread complexity.
  • + *
  • a protected {@link UiThreadRunner} to post a {@link Runnable} onto UI thread. Make sure + * use it to {@link UiView#update()} view inside method block {@link Task#execute(Task.Monitor)} + * which is run on non-UI thread. It's NOT necessary to use it in callback methods in + * {@link Task.Callback} since the framework has already guaranteed it. + * In non-Android environment, typically in unit tests, the framework will also provide a default + * uiThreadRunner that runs everything on the same thread as the caller's.
  • + *
+ *

* @param The view model of the controller. */ public abstract class Controller extends Bean { @@ -22,14 +61,14 @@ public abstract class Controller extends Bean @Inject @EventBusC - private EventBus eventBus2C; + private EventBus eventBusC; @Inject @EventBusV - private EventBus eventBus2V; + private EventBus eventBusV; @Inject - protected ExecutorService executorService; + private ExecutorService executorService; @Inject protected UiThreadRunner uiThreadRunner; @@ -51,7 +90,7 @@ public void onCreated() { uiThreadRunner = Mvc.graph().uiThreadRunner; } - eventBus2C.register(this); + eventBusC.register(this); } /** @@ -61,7 +100,7 @@ public void onCreated() { @Override public void onDestroy() { super.onDestroy(); - eventBus2C.unregister(this); + eventBusC.unregister(this); } /** @@ -254,16 +293,63 @@ public void run() { } /** + *

* Post the event to views. It automatically guarantees the event will be received - * and run on UI thread of Android + * and run on UI thread of Android. + *

+ * + *

+ * The event will be captured by views or any objects registered to {@link EventBus} annotated + * by {@link EventBusV} and has corresponding method named onEvent() with single parameter with + * the same type of the event. For example + *

+ *
+     *  public class OnTextChangedEvent {
+     *      private String text;
+     *
+     *      public OnTextChangedEvent(String text) {
+     *          this.text = text;
+     *      }
+     *
+     *      public String getText() {
+     *          return text;
+     *      }
+     *  }
+     *
+     *  public class SomeView {
+     *      @ Inject
+     *      @ EventBusV
+     *      private EventBus eventBusV;
+     *
+     *      private TextView textView;
+     *
+     *      public class SomeView() {
+     *          //This is just needed when you have a view not inheriting MvcFragment, MvcService or etc.
+     *          //In MvcFragment or MvcService will register to the event bus in onCreate automatically.
+     *          eventBusV.register(this);
+     *      }
+     *
+     *      public void onEvent(OnTextChangedEvent onTextChangedEvent) {
+     *          textView.setText(onTextChangedEvent.getText());
+     *      }
+     *  }
+     *
+     *  public class SomeController{
+     *      private void func() {
+     *          postEvent(new OnTextChangedEvent("Controller Wants to change text"));
+     *      }
+     *  }
+     * 
+ * * @param eventV The event */ protected void postEvent(final Object eventV) { uiThreadRunner.post(new Runnable() { @Override public void run() { - eventBus2V.post(eventV); + eventBusV.post(eventV); } }); } + } diff --git a/library/android-mvc-core/src/main/java/com/shipdream/lib/android/mvc/UiThreadRunner.java b/library/android-mvc-core/src/main/java/com/shipdream/lib/android/mvc/UiThreadRunner.java index 8d0c620..0e1fb65 100644 --- a/library/android-mvc-core/src/main/java/com/shipdream/lib/android/mvc/UiThreadRunner.java +++ b/library/android-mvc-core/src/main/java/com/shipdream/lib/android/mvc/UiThreadRunner.java @@ -8,13 +8,15 @@ public interface UiThreadRunner { boolean isOnUiThread(); /** - * Post the runnable to run on Android UI thread + * Post the runnable to run on Android UI thread. In Android app, when the caller is not + * currently on UI thread, it will be posted to the UI thread to be run in next main loop. + * Otherwise, it will be run immediately. * @param runnable */ void post(Runnable runnable); /** - * Post the runnable to run on Android UI thread + * Post the runnable to run on Android UI thread with the given delay. * @param runnable */ void postDelayed(Runnable runnable, long delayMs); diff --git a/library/android-mvc-core/src/main/java/com/shipdream/lib/android/mvc/event/bus/annotation/EventBusC.java b/library/android-mvc-core/src/main/java/com/shipdream/lib/android/mvc/event/bus/annotation/EventBusC.java index 2869172..29daaa7 100644 --- a/library/android-mvc-core/src/main/java/com/shipdream/lib/android/mvc/event/bus/annotation/EventBusC.java +++ b/library/android-mvc-core/src/main/java/com/shipdream/lib/android/mvc/event/bus/annotation/EventBusC.java @@ -16,6 +16,8 @@ package com.shipdream.lib.android.mvc.event.bus.annotation; +import com.shipdream.lib.android.mvc.event.bus.EventBus; + import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -23,8 +25,10 @@ import javax.inject.Qualifier; /** - * Indicates the annotated event bus is for events to controllers. Events through the event bus - * annotated by this annotation will be received on the same thread as the caller who posts them. + * Indicates the annotated event bus is for events to core components such as controllers, managers + * or core services. To receive or send events to Android views use {@link EventBusV} to qualify the + * injecting {@link EventBus}. Events through the event bus annotated by this annotation will be + * received on the same thread as the caller who posts them. */ @Qualifier @Documented diff --git a/library/android-mvc-core/src/main/java/com/shipdream/lib/android/mvc/event/bus/annotation/EventBusV.java b/library/android-mvc-core/src/main/java/com/shipdream/lib/android/mvc/event/bus/annotation/EventBusV.java index 0501854..fecf462 100644 --- a/library/android-mvc-core/src/main/java/com/shipdream/lib/android/mvc/event/bus/annotation/EventBusV.java +++ b/library/android-mvc-core/src/main/java/com/shipdream/lib/android/mvc/event/bus/annotation/EventBusV.java @@ -16,6 +16,8 @@ package com.shipdream.lib.android.mvc.event.bus.annotation; +import com.shipdream.lib.android.mvc.event.bus.EventBus; + import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -23,9 +25,15 @@ import javax.inject.Qualifier; /** - * Indicates the annotated event bus is for events sent to views. On Android - * events through the event bus annotated by this annotation will be guaranteed to be received on - * Android's UI thread automatically. + * Indicates the annotated event bus is for events sent to Android views. To send or receive events + * for core components such as controllers, managers, core services, use {@link EventBusC} to annotate + * the injecting {@link EventBus}. + * + *

+ * In Android app events through the event bus annotated by this annotation will be guaranteed to be + * received on Android's UI thread automatically. + *

+ * */ @Qualifier @Documented From bd2dbf04fb871a33453a85fd4c0aef8c0808fc46 Mon Sep 17 00:00:00 2001 From: Kejun Xia Date: Thu, 14 Jul 2016 23:32:38 +1000 Subject: [PATCH 04/15] Fix issue that back navigation through interim locations not working properly --- ChangeLog.md | 6 +- build.gradle | 2 +- .../shipdream/lib/android/mvc/Navigator.java | 21 +++- .../lib/android/mvc/BaseTestCase.java | 3 +- .../nav/TestCaseNavigationFromController.java | 106 +++++++++++++++++- .../lib/android/mvc/TestActivity.java | 4 + .../lib/android/mvc/view/nav/NavFragment.java | 8 +- .../main/res/layout/fragment_mvc_test_nav.xml | 6 + .../lib/android/mvc/MvcActivity.java | 25 ++--- samples/simple-mvc/README.md | 24 ---- 10 files changed, 149 insertions(+), 56 deletions(-) delete mode 100644 samples/simple-mvc/README.md diff --git a/ChangeLog.md b/ChangeLog.md index b1542d9..4d376af 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,4 +1,8 @@ -Version: 3.0 +Version: 3.0.1 +* Fix issue that interim navigation pages are not popped out on back navgation correctly +* Uplift target compile SDK to 24. + +Version: 3.0.0 * View and Controller are one to one mapped * FragmentController introduced that is bound with the same lifecycle as MvcFragment. It is easier to act as a presenter of fragment * Able to post actions to ui thread from controllers by protected uiThreadRunner field diff --git a/build.gradle b/build.gradle index 3569d40..50df32e 100644 --- a/build.gradle +++ b/build.gradle @@ -81,7 +81,7 @@ ext { shouldPublish = false androidMinSdkVersion = 14 - androidCompileSdkVersion = 23 + androidCompileSdkVersion = 24 androidBuildToolVersion = "23.0.3" supportLibVersion = "24.0.0" androidTargetSdkVersion = androidCompileSdkVersion diff --git a/library/android-mvc-core/src/main/java/com/shipdream/lib/android/mvc/Navigator.java b/library/android-mvc-core/src/main/java/com/shipdream/lib/android/mvc/Navigator.java index 3b02e1a..2069095 100644 --- a/library/android-mvc-core/src/main/java/com/shipdream/lib/android/mvc/Navigator.java +++ b/library/android-mvc-core/src/main/java/com/shipdream/lib/android/mvc/Navigator.java @@ -309,14 +309,20 @@ public void back() { } NavLocation previousLoc = currentLoc.getPreviousLocation(); + + boolean needFastRewind = false; while (previousLoc != null && previousLoc.isInterim()) { previousLoc = previousLoc.getPreviousLocation(); + needFastRewind = true; } - navigationManager.getModel().setCurrentLocation(previousLoc); - - navigateEvent = new NavigationManager.Event.OnLocationBack(sender, currentLoc, previousLoc, false, this); - go(); + if (needFastRewind) { + navigateBackToLoc(previousLoc == null ? null : previousLoc.getLocationId()); + } else { + navigationManager.getModel().setCurrentLocation(previousLoc); + navigateEvent = new NavigationManager.Event.OnLocationBack(sender, currentLoc, previousLoc, false, this); + go(); + } } /** @@ -328,6 +334,11 @@ public void back() { * @param controllerClass the controller class type */ public void back(Class controllerClass) { + String toLocationId = controllerClass == null ? null : controllerClass.getName(); + navigateBackToLoc(toLocationId); + } + + private void navigateBackToLoc(String toLocationId) { NavLocation currentLoc = navigationManager.getModel().getCurrentLocation(); if (currentLoc == null) { navigationManager.logger.warn("Current location should never be null before navigating backwards."); @@ -342,8 +353,6 @@ public void back(Class controllerClass) { boolean success = false; NavLocation previousLoc = currentLoc; - String toLocationId = controllerClass == null ? null : controllerClass.getName(); - if (toLocationId == null) { success = true; } diff --git a/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/BaseTestCase.java b/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/BaseTestCase.java index ba8b802..c03a3d1 100644 --- a/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/BaseTestCase.java +++ b/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/BaseTestCase.java @@ -242,6 +242,7 @@ public void tearDown() throws Exception { public void run() { Mvc.graph().release(BaseTestCase.this); try { + Mvc.graph().getRootComponent().getCache().clear(); Mvc.graph().getRootComponent().detach(component); } catch (Component.MismatchDetachException e) { e.printStackTrace(); @@ -251,8 +252,6 @@ public void run() { }); super.tearDown(); - Mvc.graph().getRootComponent().getCache().clear(); - eventBusV.unregister(this); } diff --git a/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/nav/TestCaseNavigationFromController.java b/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/nav/TestCaseNavigationFromController.java index d55788b..eb42813 100644 --- a/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/nav/TestCaseNavigationFromController.java +++ b/library/android-mvc-test/src/androidTest/java/com/shipdream/lib/android/mvc/view/nav/TestCaseNavigationFromController.java @@ -16,14 +16,22 @@ package com.shipdream.lib.android.mvc.view.nav; +import android.support.v4.app.FragmentManager; + import com.shipdream.lib.android.mvc.BaseTestCase; +import com.shipdream.lib.android.mvc.Forwarder; import com.shipdream.lib.android.mvc.Mvc; import com.shipdream.lib.android.mvc.MvcComponent; import com.shipdream.lib.android.mvc.NavigationManager; import com.shipdream.lib.android.mvc.Preparer; import com.shipdream.lib.android.mvc.view.injection.controller.ControllerA; +import com.shipdream.lib.android.mvc.view.injection.controller.ControllerB; +import com.shipdream.lib.android.mvc.view.injection.controller.ControllerC; +import com.shipdream.lib.android.mvc.view.injection.controller.ControllerD; import com.shipdream.lib.poke.Provides; +import junit.framework.Assert; + import org.junit.Test; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; @@ -36,6 +44,7 @@ import javax.inject.Singleton; import static android.support.test.espresso.Espresso.onView; +import static android.support.test.espresso.assertion.ViewAssertions.doesNotExist; import static android.support.test.espresso.assertion.ViewAssertions.matches; import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; import static android.support.test.espresso.matcher.ViewMatchers.withText; @@ -124,10 +133,99 @@ public void setUp() throws Exception { navTo(ControllerA.class); } - @Override - public void tearDown() throws Exception { - Mvc.graph().getRootComponent().unregister(comp); - super.tearDown(); + @Test + public void test_back_navigation_should_skip_interim_location() throws Throwable { + onView(withText(NavFragmentA.class.getSimpleName())).check(matches(isDisplayed())); + assertFragmentsCount(1); + + navigationManager.navigate(this).to(ControllerB.class, new Forwarder().setInterim(true)); + waitTest(); + onView(withText(NavFragmentB.class.getSimpleName())).check(matches(isDisplayed())); + assertFragmentsCount(2); + + navigationManager.navigate(this).to(ControllerC.class, new Forwarder().setInterim(true)); + waitTest(); + onView(withText(NavFragmentC.class.getSimpleName())).check(matches(isDisplayed())); + assertFragmentsCount(3); + + navigationManager.navigate(this).to(ControllerD.class); + waitTest(); + onView(withText(NavFragmentD.class.getSimpleName())).check(matches(isDisplayed())); + assertFragmentsCount(4); + + navigationManager.navigate(this).back(); + waitTest(); + onView(withText(NavFragmentA.class.getSimpleName())).check(matches(isDisplayed())); + onView(withText(NavFragmentB.class.getSimpleName())).check(doesNotExist()); + onView(withText(NavFragmentC.class.getSimpleName())).check(doesNotExist()); + onView(withText(NavFragmentD.class.getSimpleName())).check(doesNotExist()); + assertFragmentsCount(1); + + navigationManager.navigate(this).to(ControllerB.class, new Forwarder().setInterim(true)); + waitTest(); + onView(withText(NavFragmentB.class.getSimpleName())).check(matches(isDisplayed())); + assertFragmentsCount(2); + + navigationManager.navigate(this).to(ControllerC.class, new Forwarder().setInterim(true)); + waitTest(); + onView(withText(NavFragmentC.class.getSimpleName())).check(matches(isDisplayed())); + assertFragmentsCount(3); + + navigationManager.navigate(this).back(); + waitTest(); + onView(withText(NavFragmentA.class.getSimpleName())).check(matches(isDisplayed())); + onView(withText(NavFragmentB.class.getSimpleName())).check(doesNotExist()); + onView(withText(NavFragmentC.class.getSimpleName())).check(doesNotExist()); + onView(withText(NavFragmentD.class.getSimpleName())).check(doesNotExist()); + assertFragmentsCount(1); + + navigationManager.navigate(this).to(ControllerB.class); + waitTest(); + onView(withText(NavFragmentB.class.getSimpleName())).check(matches(isDisplayed())); + assertFragmentsCount(2); + + navigationManager.navigate(this).to(ControllerC.class, new Forwarder().setInterim(true)); + waitTest(); + onView(withText(NavFragmentC.class.getSimpleName())).check(matches(isDisplayed())); + assertFragmentsCount(3); + + navigationManager.navigate(this).to(ControllerD.class); + waitTest(); + onView(withText(NavFragmentD.class.getSimpleName())).check(matches(isDisplayed())); + assertFragmentsCount(4); + + navigationManager.navigate(this).back(); + waitTest(); + onView(withText(NavFragmentA.class.getSimpleName())).check(doesNotExist()); + onView(withText(NavFragmentB.class.getSimpleName())).check(matches(isDisplayed())); + onView(withText(NavFragmentC.class.getSimpleName())).check(doesNotExist()); + onView(withText(NavFragmentD.class.getSimpleName())).check(doesNotExist()); + assertFragmentsCount(2); + + navigationManager.navigate(this).back(); + waitTest(); + onView(withText(NavFragmentA.class.getSimpleName())).check(matches(isDisplayed())); + onView(withText(NavFragmentB.class.getSimpleName())).check(doesNotExist()); + onView(withText(NavFragmentC.class.getSimpleName())).check(doesNotExist()); + onView(withText(NavFragmentD.class.getSimpleName())).check(doesNotExist()); + assertFragmentsCount(1); + } + + private void assertFragmentsCount(int count) { + int actualFrags = 0; + int actualStackCount = 0; + FragmentManager fm = activity.getDelegateFragment().getChildFragmentManager(); + if (fm.getFragments() != null) { + for (int i = 0; i < fm.getFragments().size(); ++i) { + if (fm.getFragments().get(i) != null) { + ++actualFrags; + } + } + + actualStackCount = fm.getBackStackEntryCount(); + } + Assert.assertEquals(count, actualFrags); + Assert.assertEquals(count, actualStackCount); } @Test diff --git a/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/TestActivity.java b/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/TestActivity.java index b42c0a4..639ea9e 100644 --- a/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/TestActivity.java +++ b/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/TestActivity.java @@ -57,6 +57,10 @@ public State getState() { private List proxies = new CopyOnWriteArrayList<>();; + public DelegateFragment getDelegateFragment() { + return delegateFragment; + } + public void addProxy(Proxy proxy) { synchronized (proxies) { proxies.add(proxy); diff --git a/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/nav/NavFragment.java b/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/nav/NavFragment.java index 27d3fe2..0d479f1 100644 --- a/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/nav/NavFragment.java +++ b/library/android-mvc-test/src/main/java/com/shipdream/lib/android/mvc/view/nav/NavFragment.java @@ -20,6 +20,7 @@ import android.support.v7.widget.Toolbar; import android.view.View; import android.widget.Button; +import android.widget.TextView; import com.shipdream.lib.android.mvc.Controller; import com.shipdream.lib.android.mvc.Forwarder; @@ -33,6 +34,8 @@ public abstract class NavFragment extends MvcFragment { private Button next; private Button clear; + private TextView textView; + @Inject private NavigationManager navigationManager; @@ -49,6 +52,9 @@ protected int getLayoutResId() { public void onViewReady(View view, Bundle savedInstanceState, Reason reason) { super.onViewReady(view, savedInstanceState, reason); + textView = (TextView) view.findViewById(R.id.text); + textView.setText(getClass().getSimpleName()); + next = (Button) view.findViewById(R.id.button_next); next.setOnClickListener(new View.OnClickListener() { @Override @@ -73,7 +79,7 @@ public void onClick(View view) { public void onResume() { super.onResume(); Toolbar toolbar = (Toolbar) getView().findViewById(R.id.toolbar); - toolbar.setTitle(getClass().getSimpleName()); + toolbar.setTitle("Page: " + getClass().getSimpleName()); } @Override diff --git a/library/android-mvc-test/src/main/res/layout/fragment_mvc_test_nav.xml b/library/android-mvc-test/src/main/res/layout/fragment_mvc_test_nav.xml index 731a03e..f3319e7 100644 --- a/library/android-mvc-test/src/main/res/layout/fragment_mvc_test_nav.xml +++ b/library/android-mvc-test/src/main/res/layout/fragment_mvc_test_nav.xml @@ -37,6 +37,12 @@ android:layout_height="match_parent" android:orientation="vertical"> + +