From b290824844fecb76a0d17d67a424f43e2ecc3e53 Mon Sep 17 00:00:00 2001 From: James Barr Date: Tue, 23 Feb 2021 12:47:26 -0800 Subject: [PATCH] Align rib-base with internal version (#402) * Remove Component type parameter from Router * Add RibEvents and related classes for tooling * Minor differences to align with internal version of rib-base * Extended WorkerBinder functionality to allow for binding a worker to a presenter's lifecycle * Minor differences & new tests to align with internal version of rib-base --- .../rib/root/logged_in/LoggedInRouter.java | 2 +- .../java/com/uber/rib/core/ViewRouter.java | 2 +- .../java/com/uber/rib/core/ViewRouter.java | 2 +- android/libraries/rib-base/build.gradle | 1 + .../java/com/uber/rib/core/BasicRouter.java | 2 +- .../main/java/com/uber/rib/core/Bundle.java | 25 ++- .../java/com/uber/rib/core/Presenter.java | 12 +- .../java/com/uber/rib/core/RibBuilder.java | 2 +- .../main/java/com/uber/rib/core/RibEvent.java | 52 +++++++ .../java/com/uber/rib/core/RibEventType.java | 21 +++ .../java/com/uber/rib/core/RibEvents.java | 49 ++++++ .../main/java/com/uber/rib/core/Router.java | 25 +-- .../main/java/com/uber/rib/core/Worker.java | 4 +- .../java/com/uber/rib/core/WorkerBinder.java | 146 ++++++++++++------ .../uber/rib/core/WorkerScopeProvider.java | 2 - .../src/test/java/android/os/Bundle.java | 2 - .../src/test/java/android/os/Parcelable.java | 1 - .../rib/core/InteractorAndRouterTest.java | 120 +++----------- .../com/uber/rib/core/RecordingObserver.java | 10 +- .../com/uber/rib/core/RibRefWatcherTest.java | 8 +- .../java/com/uber/rib/core/RouterTest.java | 9 +- .../com/uber/rib/core/WorkerBinderTest.java | 85 +++++++--- .../com/uber/rib/core/RouterDebugUtils.java | 4 +- .../uber/rib/core/RouterNavigatorFactory.java | 4 +- .../uber/rib/core/StackRouterNavigator.java | 4 +- .../templates/java/RibRouter.java.template | 2 +- .../rib/root/logged_in/LoggedInRouter.java | 2 +- .../rib/root/logged_in/LoggedInRouter.java | 2 +- .../rib/root/logged_in/LoggedInRouter.java | 2 +- 29 files changed, 380 insertions(+), 222 deletions(-) create mode 100644 android/libraries/rib-base/src/main/java/com/uber/rib/core/RibEvent.java create mode 100644 android/libraries/rib-base/src/main/java/com/uber/rib/core/RibEventType.java create mode 100644 android/libraries/rib-base/src/main/java/com/uber/rib/core/RibEvents.java diff --git a/android/demos/memory-leaks/src/main/java/com/uber/rib/root/logged_in/LoggedInRouter.java b/android/demos/memory-leaks/src/main/java/com/uber/rib/root/logged_in/LoggedInRouter.java index 9c111b9f..3aec3391 100644 --- a/android/demos/memory-leaks/src/main/java/com/uber/rib/root/logged_in/LoggedInRouter.java +++ b/android/demos/memory-leaks/src/main/java/com/uber/rib/root/logged_in/LoggedInRouter.java @@ -21,7 +21,7 @@ import com.uber.rib.core.Router; /** * Adds and removes children of {@link LoggedInBuilder.LoggedInScope}. */ -public class LoggedInRouter extends Router { +public class LoggedInRouter extends Router { LoggedInRouter( LoggedInInteractor interactor, LoggedInBuilder.Component component) { diff --git a/android/libraries/rib-android-test/src/main/java/com/uber/rib/core/ViewRouter.java b/android/libraries/rib-android-test/src/main/java/com/uber/rib/core/ViewRouter.java index 927aa83f..149d4983 100644 --- a/android/libraries/rib-android-test/src/main/java/com/uber/rib/core/ViewRouter.java +++ b/android/libraries/rib-android-test/src/main/java/com/uber/rib/core/ViewRouter.java @@ -26,7 +26,7 @@ import android.view.View; */ public abstract class ViewRouter< V extends View, I extends Interactor, C extends InteractorBaseComponent> - extends Router { + extends Router { private final V view; diff --git a/android/libraries/rib-android/src/main/java/com/uber/rib/core/ViewRouter.java b/android/libraries/rib-android/src/main/java/com/uber/rib/core/ViewRouter.java index 050c2b92..a0cc9951 100644 --- a/android/libraries/rib-android/src/main/java/com/uber/rib/core/ViewRouter.java +++ b/android/libraries/rib-android/src/main/java/com/uber/rib/core/ViewRouter.java @@ -26,7 +26,7 @@ import android.view.View; */ public abstract class ViewRouter< V extends View, I extends Interactor, C extends InteractorBaseComponent> - extends Router { + extends Router { private final V view; diff --git a/android/libraries/rib-base/build.gradle b/android/libraries/rib-base/build.gradle index 6a41ddb3..9552079a 100644 --- a/android/libraries/rib-base/build.gradle +++ b/android/libraries/rib-base/build.gradle @@ -42,6 +42,7 @@ dependencies { compileOnly deps.apt.daggerCompiler compileOnly deps.androidx.annotations compileOnly deps.apt.androidApi + compileOnly deps.external.checkerQual testCompile deps.androidx.annotations testCompile deps.apt.androidApi diff --git a/android/libraries/rib-base/src/main/java/com/uber/rib/core/BasicRouter.java b/android/libraries/rib-base/src/main/java/com/uber/rib/core/BasicRouter.java index ab665453..d945b726 100644 --- a/android/libraries/rib-base/src/main/java/com/uber/rib/core/BasicRouter.java +++ b/android/libraries/rib-base/src/main/java/com/uber/rib/core/BasicRouter.java @@ -20,7 +20,7 @@ package com.uber.rib.core; * * @param type of interactor. */ -public abstract class BasicRouter extends Router { +public abstract class BasicRouter extends Router { public BasicRouter(I interactor) { super(interactor); diff --git a/android/libraries/rib-base/src/main/java/com/uber/rib/core/Bundle.java b/android/libraries/rib-base/src/main/java/com/uber/rib/core/Bundle.java index bae28eab..89299267 100644 --- a/android/libraries/rib-base/src/main/java/com/uber/rib/core/Bundle.java +++ b/android/libraries/rib-base/src/main/java/com/uber/rib/core/Bundle.java @@ -30,7 +30,7 @@ public class Bundle { /** * Creates a new Uber bundle that wraps an Android bundle. * - * @param androidBundle Andorid bundle to convert. + * @param androidBundle Android bundle to convert. */ public Bundle(@Nullable android.os.Bundle androidBundle) { if (androidBundle == null) { @@ -142,6 +142,29 @@ public class Bundle { androidBundle.putString(key, value); } + /** + * Inserts an Int value into the mapping of this Bundle, replacing any existing value for the + * given key. + * + * @param key to insert. + * @param value to insert. + */ + public void putInt(String key, int value) { + androidBundle.putInt(key, value); + } + + /** + * Returns the value associated with the given key, or defaultValue if no mapping of the desired + * type exists for the given key or if a null value is explicitly associated with the given key. + * + * @param key to fetch. + * @return the int value associated with the given key or defaultValue if there is no int value in + * the bundle. + */ + public int getInt(String key, int defaultValue) { + return androidBundle.getInt(key, defaultValue); + } + android.os.Bundle getWrappedBundle() { return androidBundle; } diff --git a/android/libraries/rib-base/src/main/java/com/uber/rib/core/Presenter.java b/android/libraries/rib-base/src/main/java/com/uber/rib/core/Presenter.java index 44796547..f1382583 100644 --- a/android/libraries/rib-base/src/main/java/com/uber/rib/core/Presenter.java +++ b/android/libraries/rib-base/src/main/java/com/uber/rib/core/Presenter.java @@ -15,20 +15,17 @@ */ package com.uber.rib.core; -import androidx.annotation.CallSuper; +import static com.uber.rib.core.lifecycle.PresenterEvent.LOADED; +import static com.uber.rib.core.lifecycle.PresenterEvent.UNLOADED; +import androidx.annotation.CallSuper; import com.jakewharton.rxrelay2.BehaviorRelay; import com.jakewharton.rxrelay2.Relay; import com.uber.autodispose.ScopeProvider; import com.uber.rib.core.lifecycle.PresenterEvent; - -import io.reactivex.Completable; import io.reactivex.CompletableSource; -import io.reactivex.Maybe; import io.reactivex.Observable; - -import static com.uber.rib.core.lifecycle.PresenterEvent.LOADED; -import static com.uber.rib.core.lifecycle.PresenterEvent.UNLOADED; +import org.checkerframework.checker.guieffect.qual.UIEffect; /** Contains presentation logic. This class exists mainly for legacy reasons. In the past * we believed it was useful to have a class between interactors and views to facilitate model @@ -69,6 +66,7 @@ public abstract class Presenter implements ScopeProvider { * Tells the presenter that it will be destroyed. Presenter subclasses should perform any required * cleanup here. */ + @UIEffect @CallSuper protected void willUnload() {} diff --git a/android/libraries/rib-base/src/main/java/com/uber/rib/core/RibBuilder.java b/android/libraries/rib-base/src/main/java/com/uber/rib/core/RibBuilder.java index efe81e74..cdc636b6 100644 --- a/android/libraries/rib-base/src/main/java/com/uber/rib/core/RibBuilder.java +++ b/android/libraries/rib-base/src/main/java/com/uber/rib/core/RibBuilder.java @@ -23,4 +23,4 @@ import java.lang.annotation.Target; /** The annotation to mark that some object is an Builder. */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.CLASS) -public @interface RibBuilder { } +public @interface RibBuilder {} diff --git a/android/libraries/rib-base/src/main/java/com/uber/rib/core/RibEvent.java b/android/libraries/rib-base/src/main/java/com/uber/rib/core/RibEvent.java new file mode 100644 index 00000000..27cce98d --- /dev/null +++ b/android/libraries/rib-base/src/main/java/com/uber/rib/core/RibEvent.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2017. Uber Technologies + * + * 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.uber.rib.core; + +import androidx.annotation.Nullable; + +public class RibEvent { + + private final RibEventType eventType; + private final Router router; + /** Null for the root ribs that are directly attached to RibActivity/Fragment */ + @Nullable private final Router parentRouter; + + public RibEventType getEventType() { + return eventType; + } + + public Router getRouter() { + return router; + } + + /** @return null for the root ribs that are directly attached to RibActivity/Fragment */ + @Nullable + public Router getParentRouter() { + return parentRouter; + } + + /** + * @param eventType {@link RibEventType} + * @param router {@link Router} + * @param parentRouter {@link Router} and null for the root ribs that are directly attached to + * RibActivity/Fragment + */ + public RibEvent(RibEventType eventType, Router router, @Nullable Router parentRouter) { + this.eventType = eventType; + this.router = router; + this.parentRouter = parentRouter; + } +} diff --git a/android/libraries/rib-base/src/main/java/com/uber/rib/core/RibEventType.java b/android/libraries/rib-base/src/main/java/com/uber/rib/core/RibEventType.java new file mode 100644 index 00000000..2c779c49 --- /dev/null +++ b/android/libraries/rib-base/src/main/java/com/uber/rib/core/RibEventType.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2017. Uber Technologies + * + * 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.uber.rib.core; + +public enum RibEventType { + ATTACHED, + DETACHED +} diff --git a/android/libraries/rib-base/src/main/java/com/uber/rib/core/RibEvents.java b/android/libraries/rib-base/src/main/java/com/uber/rib/core/RibEvents.java new file mode 100644 index 00000000..7da815f3 --- /dev/null +++ b/android/libraries/rib-base/src/main/java/com/uber/rib/core/RibEvents.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2017. Uber Technologies + * + * 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.uber.rib.core; + +import androidx.annotation.Nullable; +import com.jakewharton.rxrelay2.PublishRelay; +import io.reactivex.Observable; + +public final class RibEvents { + + private static final RibEvents INSTANCE = new RibEvents(); + + private final PublishRelay events; + + private RibEvents() { + this.events = PublishRelay.create(); + } + + public static RibEvents getInstance() { + return INSTANCE; + } + + public Observable getEvents() { + return events.hide(); + } + + /** + * @param eventType {@link RibEventType} + * @param child {@link Router} + * @param parent {@link Router} and null for the root ribs that are directly attached to + * RibActivity/Fragment + */ + public void emitEvent(RibEventType eventType, Router child, @Nullable Router parent) { + events.accept(new RibEvent(eventType, child, parent)); + } +} diff --git a/android/libraries/rib-base/src/main/java/com/uber/rib/core/Router.java b/android/libraries/rib-base/src/main/java/com/uber/rib/core/Router.java index 279d6847..9c96e899 100644 --- a/android/libraries/rib-base/src/main/java/com/uber/rib/core/Router.java +++ b/android/libraries/rib-base/src/main/java/com/uber/rib/core/Router.java @@ -15,6 +15,8 @@ */ package com.uber.rib.core; +import static com.google.common.base.Preconditions.checkNotNull; + import android.os.Looper; import androidx.annotation.CallSuper; import androidx.annotation.MainThread; @@ -25,15 +27,12 @@ import java.util.List; import java.util.Locale; import java.util.concurrent.CopyOnWriteArrayList; -import static com.google.common.base.Preconditions.*; - /** * Responsible for handling the addition and removal of children routers. * * @param type of interactor this router routes. - * @param type of dependency held by this router. */ -public class Router { +public class Router { @VisibleForTesting static final String KEY_CHILD_ROUTERS = "Router.childRouters"; @VisibleForTesting static final String KEY_INTERACTOR = "Router.interactor"; @@ -49,7 +48,7 @@ public class Router childRouter) { + protected void attachChild(Router childRouter) { attachChild(childRouter, childRouter.getClass().getName()); } @@ -134,7 +133,7 @@ public class Router childRouter, String tag) { + protected void attachChild(Router childRouter, String tag) { for (Router child : children) { if (tag.equals(child.tag)) { Rib.getConfiguration() @@ -148,6 +147,7 @@ public class Router interactor, Worker worker) { - return bind(interactor.lifecycle(), worker); + return bind(mapInteractorLifecycleToWorker(interactor.lifecycle()), worker); } /** @@ -62,52 +58,106 @@ public final class WorkerBinder { } } + /** + * Bind a worker (ie. a manager or any other class that needs an presenter's lifecycle) to an + * presenter's lifecycle events. Inject this class into your presenter and call this method on any + * + * @param presenter The presenter that provides the lifecycle. + * @param worker The class that wants to be informed when to start and stop doing work. + * @return {@link WorkerUnbinder} to unbind {@link Worker Worker's} lifecycle. + */ + public static WorkerUnbinder bind(Presenter presenter, Worker worker) { + return bind(mapPresenterLifecycleToWorker(presenter.lifecycle()), worker); + } + + /** + * Bind a list of workers (ie. a manager or any other class that needs an presenter's lifecycle) + * to an presenter's lifecycle events. Use this class into your presenter and call this method on + * attach. + * + * @param presenter The presenter that provides the lifecycle. + * @param workers A list of classes that want to be informed when to start and stop doing work. + */ + public static void bind(Presenter presenter, List workers) { + for (Worker worker : workers) { + bind(presenter, worker); + } + } + @VisibleForTesting - static WorkerUnbinder bind(Observable lifecycle, final Worker worker) { + static WorkerUnbinder bind(Observable mappedLifecycle, final Worker worker) { final PublishRelay unbindSubject = PublishRelay.create(); final Observable workerLifecycle = - lifecycle - .map( - new Function() { - @Override - public WorkerEvent apply(InteractorEvent interactorEvent) { - switch (interactorEvent) { - case ACTIVE: - return WorkerEvent.START; - default: - return WorkerEvent.STOP; - } - } - }) + mappedLifecycle .mergeWith(unbindSubject) - .takeUntil( - new Predicate() { - @Override - public boolean test(WorkerEvent workerEvent) throws Exception { - return workerEvent == WorkerEvent.STOP; - } - }); + .takeUntil(workerEvent -> workerEvent == WorkerEvent.STOP); - workerLifecycle.subscribe(new Consumer() { - @Override - public void accept(WorkerEvent workerEvent) throws Exception { - switch (workerEvent) { - case START: - worker.onStart(new WorkerScopeProvider(workerLifecycle.hide())); - break; - default: - worker.onStop(); - break; - } - } - }); + bindToWorkerLifecycle(workerLifecycle, worker); - return new WorkerUnbinder() { - @Override - public void unbind() { - unbindSubject.accept(WorkerEvent.STOP); - } - }; + return () -> unbindSubject.accept(WorkerEvent.STOP); + } + + static Observable mapInteractorLifecycleToWorker( + Observable interactorEventObservable) { + return interactorEventObservable.map( + interactorEvent -> { + switch (interactorEvent) { + case ACTIVE: + return WorkerEvent.START; + default: + return WorkerEvent.STOP; + } + }); + } + + static Observable mapPresenterLifecycleToWorker( + Observable presenterEventObservable) { + return presenterEventObservable.map( + presenterEvent -> { + switch (presenterEvent) { + case LOADED: + return WorkerEvent.START; + default: + return WorkerEvent.STOP; + } + }); + } + + /** + * Bind a worker (ie. a manager or any other class that needs an interactor's lifecycle) to an + * interactor's lifecycle events. + * + * @param lifecycle The interactor's {@link LifecycleScopeProvider}. + * @param worker The class that wants to be informed when to start and stop doing work. + * @deprecated this method uses {@code LifecycleScopeProvider} for purposes other than + * AutoDispose. Usage is strongly discouraged as this method may be removed in the future. + */ + @Deprecated + public static void bindTo( + LifecycleScopeProvider lifecycle, final Worker worker) { + bind(mapInteractorLifecycleToWorker(lifecycle.lifecycle()), worker); + } + + /** + * Bind a worker to a {@link WorkerEvent} lifecycle provider. + * + * @param workerLifecycle the worker lifecycle event provider + * @param worker the class that wants to be informed when to start and stop doing work + */ + @SuppressWarnings("CheckReturnValue") + public static void bindToWorkerLifecycle( + Observable workerLifecycle, final Worker worker) { + workerLifecycle.subscribe( + workerEvent -> { + switch (workerEvent) { + case START: + worker.onStart(new WorkerScopeProvider(workerLifecycle.hide())); + break; + default: + worker.onStop(); + break; + } + }); } } diff --git a/android/libraries/rib-base/src/main/java/com/uber/rib/core/WorkerScopeProvider.java b/android/libraries/rib-base/src/main/java/com/uber/rib/core/WorkerScopeProvider.java index c9edb95d..4766864a 100644 --- a/android/libraries/rib-base/src/main/java/com/uber/rib/core/WorkerScopeProvider.java +++ b/android/libraries/rib-base/src/main/java/com/uber/rib/core/WorkerScopeProvider.java @@ -17,9 +17,7 @@ package com.uber.rib.core; import com.uber.autodispose.ScopeProvider; import com.uber.rib.core.lifecycle.WorkerEvent; - import io.reactivex.CompletableSource; -import io.reactivex.Maybe; import io.reactivex.Observable; /** {@link ScopeProvider} for {@link Worker} instances. */ diff --git a/android/libraries/rib-base/src/test/java/android/os/Bundle.java b/android/libraries/rib-base/src/test/java/android/os/Bundle.java index 3bf438d1..54bc9d21 100644 --- a/android/libraries/rib-base/src/test/java/android/os/Bundle.java +++ b/android/libraries/rib-base/src/test/java/android/os/Bundle.java @@ -13,14 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package android.os; import java.util.HashMap; import java.util.Map; /** Stub class to have pure Java unit tests. */ -@SuppressWarnings("ParcelCreator") public class Bundle implements Parcelable { private final Map testData = new HashMap<>(); diff --git a/android/libraries/rib-base/src/test/java/android/os/Parcelable.java b/android/libraries/rib-base/src/test/java/android/os/Parcelable.java index e3ebbe9a..29b467e9 100644 --- a/android/libraries/rib-base/src/test/java/android/os/Parcelable.java +++ b/android/libraries/rib-base/src/test/java/android/os/Parcelable.java @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package android.os; /** Stub class to have pure Java unit tests. */ diff --git a/android/libraries/rib-base/src/test/java/com/uber/rib/core/InteractorAndRouterTest.java b/android/libraries/rib-base/src/test/java/com/uber/rib/core/InteractorAndRouterTest.java index c33bee22..ea9b9bf8 100644 --- a/android/libraries/rib-base/src/test/java/com/uber/rib/core/InteractorAndRouterTest.java +++ b/android/libraries/rib-base/src/test/java/com/uber/rib/core/InteractorAndRouterTest.java @@ -15,20 +15,7 @@ */ package com.uber.rib.core; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.uber.autodispose.lifecycle.LifecycleEndedException; -import com.uber.rib.core.lifecycle.InteractorEvent; - -import org.junit.Before; -import org.junit.Ignore; -import org.junit.Test; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - import static com.google.common.truth.Truth.assertThat; -import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyObject; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; @@ -37,6 +24,15 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import com.uber.autodispose.lifecycle.LifecycleEndedException; +import com.uber.rib.core.lifecycle.InteractorEvent; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + public class InteractorAndRouterTest { private static final String TEST_KEY = "test_key"; @@ -80,41 +76,6 @@ public class InteractorAndRouterTest { verify(childInteractor).dispatchDetach(); } - @Ignore - @Test - public void saveInstanceState_whenAttached_shouldSaveChildControllerState() { - // Given. - TestRouterA childRouter = - new TestRouterA( - new TestInteractorA(), - new InteractorComponent() { - @Override - public void inject(TestInteractorA interactor) {} - - @Override - public TestPresenter presenter() { - return new TestPresenter(); - } - }); - router.dispatchAttach(null); - router.attachChild(childRouter); - - // When. - Bundle outState = new Bundle(); - router.saveInstanceState(outState); - - // Then. - verify(childInteractor).onSaveInstanceState(any(Bundle.class)); - - Bundle childrenBundle = outState.getBundleExtra(Router.KEY_CHILD_ROUTERS); - assertThat(childrenBundle).isNotNull(); - Bundle childBundle = childrenBundle.getBundleExtra(childRouter.getClass().getName()); - assertThat(childBundle).isNotNull(); - - Bundle interactorBundle = outState.getBundleExtra(Router.KEY_INTERACTOR); - assertThat(interactorBundle.getString(TEST_KEY)).isEqualTo(TEST_VALUE); - } - @Test public void correspondingEvents_whenActive_shouldReturnInactive() throws Exception { assertThat(interactor.correspondingEvents().apply(InteractorEvent.ACTIVE)) @@ -126,35 +87,6 @@ public class InteractorAndRouterTest { interactor.correspondingEvents().apply(InteractorEvent.INACTIVE); } - @Ignore - @Test - public void childRouter_whenDetachedAfterReattached_shouldClearOutChildsSavedInstanceState() { - // Given. - TestRouterA childRouter = - new TestRouterA( - new TestInteractorA(), - new InteractorComponent() { - @Override - public void inject(TestInteractorA interactor) {} - - @Override - public TestPresenter presenter() { - return new TestPresenter(); - } - }); - router.dispatchAttach(null); - router.attachChild(childRouter); - - // When. - Bundle outState = new Bundle(); - router.saveInstanceState(outState); - router.detachChild(childRouter); - router.attachChild(childRouter); - - // Then. - assertThat(childRouter.savedInstanceState).isNull(); - } - @Test public void saveInstanceState_whenDetached_shouldNotSaveChildControllerState() { // When. @@ -266,7 +198,7 @@ public class InteractorAndRouterTest { TestRouterB rootRouter = new TestRouterB(component, new TestInteractorB(), ribRefWatcher); - Router child = addTwoNestedChildInteractors(); + Router child = addTwoNestedChildInteractors(); verify(ribRefWatcher, never()).watchDeletedObject(anyObject()); // Action: Detach all child interactors. @@ -276,7 +208,7 @@ public class InteractorAndRouterTest { verify(ribRefWatcher, times(2)).watchDeletedObject(anyObject()); } - private Router addTwoNestedChildInteractors() { + private Router addTwoNestedChildInteractors() { InteractorComponent component = new InteractorComponent() { @Override @@ -299,10 +231,7 @@ public class InteractorAndRouterTest { return childRouter1; } - private static class TestInteractor - extends Interactor< - TestPresenter, - Router>> { + private static class TestInteractor extends Interactor> { @NonNull private final com.uber.rib.core.Interactor mChildInteractor; @@ -330,8 +259,7 @@ public class InteractorAndRouterTest { } } - private static class TestRouter - extends Router> { + private static class TestRouter extends Router { TestRouter( @NonNull TestInteractor interactor, @@ -343,8 +271,7 @@ public class InteractorAndRouterTest { private static class TestPresenter extends com.uber.rib.core.Presenter {} - private static class TestRouterA - extends Router> { + private static class TestRouterA extends Router { @Nullable private Bundle savedInstanceState; @@ -364,17 +291,11 @@ public class InteractorAndRouterTest { private static class TestInteractorA extends com.uber.rib.core.Interactor< - TestPresenter, - com.uber.rib.core.Router< - TestInteractorA, InteractorComponent>> {} + TestPresenter, com.uber.rib.core.Router> {} - private static class TestInteractorB - extends Interactor< - TestPresenter, - Router>> {} + private static class TestInteractorB extends Interactor> {} - private static class TestRouterB - extends Router> { + private static class TestRouterB extends Router { TestRouterB( @NonNull TestInteractorB interactor, @@ -393,12 +314,9 @@ public class InteractorAndRouterTest { } private static class TestChildInteractor - extends Interactor< - TestPresenter, - Router>> {} + extends Interactor> {} - private static class TestChildRouter - extends Router> { + private static class TestChildRouter extends Router { TestChildRouter( @NonNull TestChildInteractor interactor, diff --git a/android/libraries/rib-base/src/test/java/com/uber/rib/core/RecordingObserver.java b/android/libraries/rib-base/src/test/java/com/uber/rib/core/RecordingObserver.java index 49b929ed..dce1ac27 100644 --- a/android/libraries/rib-base/src/test/java/com/uber/rib/core/RecordingObserver.java +++ b/android/libraries/rib-base/src/test/java/com/uber/rib/core/RecordingObserver.java @@ -15,18 +15,16 @@ */ package com.uber.rib.core; -import androidx.annotation.NonNull; +import static com.google.common.truth.Truth.assertThat; +import androidx.annotation.NonNull; +import io.reactivex.Observer; +import io.reactivex.disposables.Disposable; import java.util.NoSuchElementException; import java.util.concurrent.BlockingDeque; import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.TimeUnit; -import io.reactivex.Observer; -import io.reactivex.disposables.Disposable; - -import static com.google.common.truth.Truth.assertThat; - /** * RecordingObserver implementation from RxBinding. * diff --git a/android/libraries/rib-base/src/test/java/com/uber/rib/core/RibRefWatcherTest.java b/android/libraries/rib-base/src/test/java/com/uber/rib/core/RibRefWatcherTest.java index b4f8d3fb..3d54af53 100644 --- a/android/libraries/rib-base/src/test/java/com/uber/rib/core/RibRefWatcherTest.java +++ b/android/libraries/rib-base/src/test/java/com/uber/rib/core/RibRefWatcherTest.java @@ -15,16 +15,16 @@ */ package com.uber.rib.core; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; + import org.junit.After; import org.junit.Before; import org.junit.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; - public class RibRefWatcherTest { @Mock private RibRefWatcher.ReferenceWatcher referenceWatcher; diff --git a/android/libraries/rib-base/src/test/java/com/uber/rib/core/RouterTest.java b/android/libraries/rib-base/src/test/java/com/uber/rib/core/RouterTest.java index aa5da794..432d8e77 100644 --- a/android/libraries/rib-base/src/test/java/com/uber/rib/core/RouterTest.java +++ b/android/libraries/rib-base/src/test/java/com/uber/rib/core/RouterTest.java @@ -15,20 +15,19 @@ */ package com.uber.rib.core; -import org.junit.Test; - -import java.util.concurrent.atomic.AtomicBoolean; - import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.mock; +import java.util.concurrent.atomic.AtomicBoolean; +import org.junit.Test; + public class RouterTest { @Test public void didLoad_shouldBeCalledAfterInstantiation() { final AtomicBoolean didLoad = new AtomicBoolean(false); Router router = - new Router( + new Router( mock(InteractorComponent.class), mock(Interactor.class), mock(RibRefWatcher.class), diff --git a/android/libraries/rib-base/src/test/java/com/uber/rib/core/WorkerBinderTest.java b/android/libraries/rib-base/src/test/java/com/uber/rib/core/WorkerBinderTest.java index 8ce5782a..baf476f1 100644 --- a/android/libraries/rib-base/src/test/java/com/uber/rib/core/WorkerBinderTest.java +++ b/android/libraries/rib-base/src/test/java/com/uber/rib/core/WorkerBinderTest.java @@ -15,32 +15,29 @@ */ package com.uber.rib.core; +import static com.uber.rib.core.WorkerBinder.bind; +import static com.uber.rib.core.WorkerBinder.bindToWorkerLifecycle; +import static com.uber.rib.core.WorkerBinder.mapInteractorLifecycleToWorker; +import static com.uber.rib.core.WorkerBinder.mapPresenterLifecycleToWorker; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + import com.jakewharton.rxrelay2.BehaviorRelay; import com.uber.rib.core.lifecycle.InteractorEvent; +import com.uber.rib.core.lifecycle.PresenterEvent; import com.uber.rib.core.lifecycle.WorkerEvent; - import org.junit.Before; import org.junit.Test; -import org.mockito.ArgumentCaptor; -import org.mockito.Captor; import org.mockito.Matchers; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import io.reactivex.CompletableSource; -import io.reactivex.Maybe; -import io.reactivex.functions.Consumer; - -import static com.uber.rib.core.WorkerBinder.bind; -import static com.google.common.truth.Truth.assertThat; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; - public class WorkerBinderTest { @Mock private Worker worker; - @Captor private ArgumentCaptor argumentCaptor; + @SuppressWarnings("NullAway.Init") @Before public void setUp() { MockitoAnnotations.initMocks(this); @@ -49,14 +46,14 @@ public class WorkerBinderTest { @Test public void bind_whenInteractorAttached_shouldStartWorker() { BehaviorRelay lifecycle = BehaviorRelay.createDefault(InteractorEvent.ACTIVE); - bind(lifecycle, worker); + bind(mapInteractorLifecycleToWorker(lifecycle), worker); verify(worker).onStart(Matchers.any()); } @Test public void bind_whenInteractorDetached_shouldStopWorker() { BehaviorRelay lifecycle = BehaviorRelay.createDefault(InteractorEvent.ACTIVE); - bind(lifecycle, worker); + bind(mapInteractorLifecycleToWorker(lifecycle), worker); lifecycle.accept(InteractorEvent.INACTIVE); verify(worker).onStop(); } @@ -64,7 +61,7 @@ public class WorkerBinderTest { @Test public void unbind_whenInteractorAttached_shouldStopWorker() { BehaviorRelay lifecycle = BehaviorRelay.createDefault(InteractorEvent.ACTIVE); - WorkerUnbinder unbinder = bind(lifecycle, worker); + WorkerUnbinder unbinder = bind(mapInteractorLifecycleToWorker(lifecycle), worker); unbinder.unbind(); @@ -75,7 +72,7 @@ public class WorkerBinderTest { public void unbind_whenOutsideInteractorLifecycle_shouldNotCallStopAgain() { BehaviorRelay lifecycle = BehaviorRelay.createDefault(InteractorEvent.INACTIVE); - WorkerUnbinder unbinder = bind(lifecycle, worker); + WorkerUnbinder unbinder = bind(mapInteractorLifecycleToWorker(lifecycle), worker); verify(worker, times(1)).onStop(); @@ -87,7 +84,7 @@ public class WorkerBinderTest { @Test public void onInactive_whenAfterUnbind_shouldNotCallStopAgain() { BehaviorRelay lifecycle = BehaviorRelay.createDefault(InteractorEvent.ACTIVE); - WorkerUnbinder unbinder = bind(lifecycle, worker); + WorkerUnbinder unbinder = bind(mapInteractorLifecycleToWorker(lifecycle), worker); unbinder.unbind(); @@ -97,4 +94,56 @@ public class WorkerBinderTest { verify(worker, times(1)).onStop(); } + + @Test + public void bindToWorkerLifecycle_whenStartEventEmitted_shouldStartWorker() { + BehaviorRelay lifecycle = BehaviorRelay.createDefault(WorkerEvent.START); + bindToWorkerLifecycle(lifecycle, worker); + verify(worker).onStart(Matchers.any()); + } + + @Test + public void bindToWorkerLifecycle_whenStopEventEmitted_shouldStopWorker() { + BehaviorRelay lifecycle = BehaviorRelay.createDefault(WorkerEvent.START); + bindToWorkerLifecycle(lifecycle, worker); + lifecycle.accept(WorkerEvent.STOP); + verify(worker).onStop(); + } + + @Test + public void bind_whenPresenterAttached_shouldStartWorker() { + BehaviorRelay lifecycle = BehaviorRelay.createDefault(PresenterEvent.LOADED); + bind(mapPresenterLifecycleToWorker(lifecycle), worker); + verify(worker).onStart(any()); + } + + @Test + public void bind_whenPresenterDetached_shouldStopWorker() { + BehaviorRelay lifecycle = BehaviorRelay.createDefault(PresenterEvent.LOADED); + bind(mapPresenterLifecycleToWorker(lifecycle), worker); + lifecycle.accept(PresenterEvent.UNLOADED); + verify(worker).onStop(); + } + + @Test + public void unbind_whenPresenterAttached_shouldStopWorker() { + BehaviorRelay lifecycle = BehaviorRelay.createDefault(PresenterEvent.LOADED); + WorkerUnbinder unbinder = bind(mapPresenterLifecycleToWorker(lifecycle), worker); + + unbinder.unbind(); + + verify(worker).onStop(); + } + + @Test + public void unbind_whenOutsidePresenterLifecycle_shouldNotCallStopAgain() { + BehaviorRelay lifecycle = BehaviorRelay.createDefault(PresenterEvent.UNLOADED); + WorkerUnbinder unbinder = bind(mapPresenterLifecycleToWorker(lifecycle), worker); + + verify(worker, times(1)).onStop(); + + unbinder.unbind(); + + verify(worker, times(1)).onStop(); + } } diff --git a/android/libraries/rib-debug-utils/src/main/java/com/uber/rib/core/RouterDebugUtils.java b/android/libraries/rib-debug-utils/src/main/java/com/uber/rib/core/RouterDebugUtils.java index 54dfa474..194ffdfa 100644 --- a/android/libraries/rib-debug-utils/src/main/java/com/uber/rib/core/RouterDebugUtils.java +++ b/android/libraries/rib-debug-utils/src/main/java/com/uber/rib/core/RouterDebugUtils.java @@ -31,12 +31,12 @@ public final class RouterDebugUtils { * * @param router {@link Router} */ - public static void printRouterSubtree(final Router router) { + public static void printRouterSubtree(final Router router) { printRouterSubtree(router, "", true); } private static void printRouterSubtree( - final Router router, final String prefix, final boolean isTail) { + final Router router, final String prefix, final boolean isTail) { Rib.getConfiguration() .handleDebugMessage(prefix + (isTail ? ARM_RIGHT : INTERSECTION) + router.getTag()); diff --git a/android/libraries/rib-router-navigator/src/main/java/com/uber/rib/core/RouterNavigatorFactory.java b/android/libraries/rib-router-navigator/src/main/java/com/uber/rib/core/RouterNavigatorFactory.java index 845ab175..4d066155 100644 --- a/android/libraries/rib-router-navigator/src/main/java/com/uber/rib/core/RouterNavigatorFactory.java +++ b/android/libraries/rib-router-navigator/src/main/java/com/uber/rib/core/RouterNavigatorFactory.java @@ -41,7 +41,7 @@ public final class RouterNavigatorFactory { * @return A new {@link RouterNavigator} */ public RouterNavigator create( - final Router hostRouter) { + final Router hostRouter) { if (creationStrategy != null) { return creationStrategy.create(hostRouter); } else { @@ -59,6 +59,6 @@ public final class RouterNavigatorFactory { * @return A new {@link RouterNavigator} */ RouterNavigator create( - final Router hostRouter); + final Router hostRouter); } } diff --git a/android/libraries/rib-router-navigator/src/main/java/com/uber/rib/core/StackRouterNavigator.java b/android/libraries/rib-router-navigator/src/main/java/com/uber/rib/core/StackRouterNavigator.java index 1e4d0f51..3c2c1fdd 100644 --- a/android/libraries/rib-router-navigator/src/main/java/com/uber/rib/core/StackRouterNavigator.java +++ b/android/libraries/rib-router-navigator/src/main/java/com/uber/rib/core/StackRouterNavigator.java @@ -31,7 +31,7 @@ public class StackRouterNavigator private final ArrayDeque> navigationStack = new ArrayDeque<>(); - private final Router hostRouter; + private final Router hostRouter; private final String hostRouterName; @Nullable private RouterAndState currentTransientRouterAndState; @@ -41,7 +41,7 @@ public class StackRouterNavigator * * @param hostRouter to add and remove children to. */ - public StackRouterNavigator(Router hostRouter) { + public StackRouterNavigator(Router hostRouter) { this.hostRouter = hostRouter; this.hostRouterName = hostRouter.getClass().getSimpleName(); log( diff --git a/android/tooling/rib-intellij-plugin/src/main/resources/templates/java/RibRouter.java.template b/android/tooling/rib-intellij-plugin/src/main/resources/templates/java/RibRouter.java.template index e7746874..d184ba26 100644 --- a/android/tooling/rib-intellij-plugin/src/main/resources/templates/java/RibRouter.java.template +++ b/android/tooling/rib-intellij-plugin/src/main/resources/templates/java/RibRouter.java.template @@ -11,7 +11,7 @@ import com.uber.rib.core.Router; * TODO describe the possible child configurations of this scope. */ public class ${rib_name}Router - extends Router<${rib_name}Interactor, ${rib_name}Builder.Component> { + extends Router<${rib_name}Interactor> { public ${rib_name}Router( ${rib_name}Interactor interactor, diff --git a/android/tutorials/tutorial3-completed/src/main/java/com/uber/rib/root/logged_in/LoggedInRouter.java b/android/tutorials/tutorial3-completed/src/main/java/com/uber/rib/root/logged_in/LoggedInRouter.java index 30d15b5d..eb455816 100644 --- a/android/tutorials/tutorial3-completed/src/main/java/com/uber/rib/root/logged_in/LoggedInRouter.java +++ b/android/tutorials/tutorial3-completed/src/main/java/com/uber/rib/root/logged_in/LoggedInRouter.java @@ -27,7 +27,7 @@ import com.uber.rib.root.logged_in.tic_tac_toe.TicTacToeRouter; * Adds and removes children of {@link LoggedInBuilder.LoggedInScope}. */ public class LoggedInRouter - extends Router { + extends Router { private final ViewGroup parentView; private final OffGameBuilder offGameBuilder; diff --git a/android/tutorials/tutorial3/src/main/java/com/uber/rib/root/logged_in/LoggedInRouter.java b/android/tutorials/tutorial3/src/main/java/com/uber/rib/root/logged_in/LoggedInRouter.java index 30d15b5d..eb455816 100644 --- a/android/tutorials/tutorial3/src/main/java/com/uber/rib/root/logged_in/LoggedInRouter.java +++ b/android/tutorials/tutorial3/src/main/java/com/uber/rib/root/logged_in/LoggedInRouter.java @@ -27,7 +27,7 @@ import com.uber.rib.root.logged_in.tic_tac_toe.TicTacToeRouter; * Adds and removes children of {@link LoggedInBuilder.LoggedInScope}. */ public class LoggedInRouter - extends Router { + extends Router { private final ViewGroup parentView; private final OffGameBuilder offGameBuilder; diff --git a/android/tutorials/tutorial4/src/main/java/com/uber/rib/root/logged_in/LoggedInRouter.java b/android/tutorials/tutorial4/src/main/java/com/uber/rib/root/logged_in/LoggedInRouter.java index a0f20c5f..53b30836 100644 --- a/android/tutorials/tutorial4/src/main/java/com/uber/rib/root/logged_in/LoggedInRouter.java +++ b/android/tutorials/tutorial4/src/main/java/com/uber/rib/root/logged_in/LoggedInRouter.java @@ -27,7 +27,7 @@ import com.uber.rib.root.logged_in.tic_tac_toe.TicTacToeBuilder; * Adds and removes children of {@link LoggedInBuilder.LoggedInScope}. */ public class LoggedInRouter - extends Router { + extends Router { private final ViewGroup parentView; private final OffGameBuilder offGameBuilder;