Skip to content
This repository was archived by the owner on Oct 25, 2024. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 29 additions & 6 deletions flow/src/main/java/flow/Flow.java
Original file line number Diff line number Diff line change
Expand Up @@ -261,18 +261,39 @@ public void set(@NonNull final Object newTopKey) {
}

/**
* Go back one key.
* Go back one key. Typically called from {@link Activity#onBackPressed()}, with
* the return value determining whether or not to call super. E.g.
* <pre>
* public void onBackPressed() {
* if (!Flow.get(this).goBack()) {
* super.onBackPressed();
* }
* }
* </pre>
*
* @return false if going back is not possible or a traversal is in progress.
* @return false if going back is not possible.
*/
@CheckResult public boolean goBack() {
boolean canGoBack = history.size() > 1 || (pendingTraversal != null
&& pendingTraversal.state != TraversalState.FINISHED);
if (!canGoBack) return false;
History.Builder builder = history.buildUpon();
builder.pop();
final History newHistory = builder.build();
setHistory(newHistory, Direction.BACKWARD);

move(new PendingTraversal() {
@Override void doExecute() {
if (history.size() <= 1) {
// The history shrank while this op was pending. It happens, let's
// no-op. See lengthy discussions:
// https://github.com/square/flow/issues/195
// https://github.com/square/flow/pull/197
return;
}

History.Builder builder = history.buildUpon();
builder.pop();
final History newHistory = builder.build();
dispatch(newHistory, Direction.BACKWARD);
}
});
return true;
}

Expand Down Expand Up @@ -316,11 +337,13 @@ private static History preserveEquivalentPrefix(History current, History propose
private enum TraversalState {
/** {@link PendingTraversal#execute} has not been called. */
ENQUEUED,

/**
* {@link PendingTraversal#execute} was called, waiting for {@link
* PendingTraversal#onTraversalCompleted}.
*/
DISPATCHED,

/**
* {@link PendingTraversal#onTraversalCompleted} was called.
*/
Expand Down
102 changes: 85 additions & 17 deletions flow/src/test/java/flow/ReentranceTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,15 @@

import android.support.annotation.NonNull;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.atomic.AtomicInteger;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;

import static flow.Direction.FORWARD;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Fail.fail;
import static org.mockito.MockitoAnnotations.initMocks;
Expand All @@ -43,7 +44,8 @@ public class ReentranceTest {

@Test public void reentrantGo() {
Dispatcher dispatcher = new Dispatcher() {
@Override public void dispatch(@NonNull Traversal navigation, @NonNull TraversalCallback callback) {
@Override
public void dispatch(@NonNull Traversal navigation, @NonNull TraversalCallback callback) {
lastStack = navigation.destination;
Object next = navigation.destination.top();
if (next instanceof Detail) {
Expand All @@ -64,7 +66,8 @@ public class ReentranceTest {
Dispatcher dispatcher = new Dispatcher() {
boolean loading = true;

@Override public void dispatch(@NonNull Traversal navigation, @NonNull TraversalCallback onComplete) {
@Override
public void dispatch(@NonNull Traversal navigation, @NonNull TraversalCallback onComplete) {
lastStack = navigation.destination;
Object next = navigation.destination.top();
if (loading) {
Expand Down Expand Up @@ -93,7 +96,8 @@ public class ReentranceTest {
@Test public void reentrantForwardThenGo() {
Flow flow = new Flow(keyManager, History.single(new Catalog()));
flow.setDispatcher(new Dispatcher() {
@Override public void dispatch(@NonNull Traversal traversal, @NonNull TraversalCallback callback) {
@Override
public void dispatch(@NonNull Traversal traversal, @NonNull TraversalCallback callback) {
lastStack = traversal.destination;
Object next = traversal.destination.top();
if (next instanceof Detail) {
Expand All @@ -110,9 +114,61 @@ public class ReentranceTest {
verifyHistory(lastStack, new Error(), new Loading(), new Detail());
}

@Test public void goBackQueuesUp() {
final Queue<TraversalCallback> callbacks = new LinkedList<>();

Flow flow = new Flow(keyManager, History.single(new Catalog()));
flow.setDispatcher(new Dispatcher() {
@Override
public void dispatch(@NonNull Traversal traversal, @NonNull TraversalCallback callback) {
callbacks.add(callback);
lastStack = traversal.destination;
}
});

flow.set(new Detail());
flow.set(new Error());
assertThat(flow.goBack()).isTrue();

while (!callbacks.isEmpty()) {
callbacks.poll().onTraversalCompleted();
}

verifyHistory(lastStack, new Detail(), new Catalog());
}

@Test public void overwflowQueuedBackupsNoOp() {
final Queue<TraversalCallback> callbacks = new LinkedList<>();

Flow flow = new Flow(keyManager, History.single(new Catalog()));
flow.setDispatcher(new Dispatcher() {
@Override
public void dispatch(@NonNull Traversal traversal, @NonNull TraversalCallback callback) {
callbacks.add(callback);
lastStack = traversal.destination;
}
});

flow.set(new Detail());

for (int i = 0; i < 20; i++) {
assertThat(flow.goBack()).isTrue();
}

int callbackCount = 0;
while (!callbacks.isEmpty()) {
callbackCount++;
callbacks.poll().onTraversalCompleted();
}

assertThat(callbackCount).isEqualTo(3);
verifyHistory(lastStack, new Catalog());
}

@Test public void reentranceWaitsForCallback() {
Dispatcher dispatcher = new Dispatcher() {
@Override public void dispatch(@NonNull Traversal traversal, @NonNull TraversalCallback callback) {
@Override
public void dispatch(@NonNull Traversal traversal, @NonNull TraversalCallback callback) {
lastStack = traversal.destination;
lastCallback = callback;
Object next = traversal.destination.top();
Expand Down Expand Up @@ -140,7 +196,8 @@ public class ReentranceTest {
@Test public void onCompleteThrowsIfCalledTwice() {
flow = new Flow(keyManager, History.single(new Catalog()));
flow.setDispatcher(new Dispatcher() {
@Override public void dispatch(@NonNull Traversal traversal, @NonNull TraversalCallback callback) {
@Override
public void dispatch(@NonNull Traversal traversal, @NonNull TraversalCallback callback) {
lastStack = traversal.destination;
lastCallback = callback;
}
Expand All @@ -159,7 +216,8 @@ public class ReentranceTest {
flow = new Flow(keyManager, History.single(new Catalog()));

flow.setDispatcher(new Dispatcher() {
@Override public void dispatch(@NonNull Traversal traversal, @NonNull TraversalCallback callback) {
@Override
public void dispatch(@NonNull Traversal traversal, @NonNull TraversalCallback callback) {
lastStack = traversal.destination;
callback.onTraversalCompleted();
}
Expand All @@ -174,7 +232,8 @@ public class ReentranceTest {
flow.set(new Detail());

flow.setDispatcher(new Dispatcher() {
@Override public void dispatch(@NonNull Traversal traversal, @NonNull TraversalCallback callback) {
@Override
public void dispatch(@NonNull Traversal traversal, @NonNull TraversalCallback callback) {
dispatchCount.incrementAndGet();
lastStack = traversal.destination;
callback.onTraversalCompleted();
Expand All @@ -192,7 +251,8 @@ public class ReentranceTest {
flow.set(new Error());

flow.setDispatcher(new Dispatcher() {
@Override public void dispatch(@NonNull Traversal traversal, @NonNull TraversalCallback callback) {
@Override
public void dispatch(@NonNull Traversal traversal, @NonNull TraversalCallback callback) {
lastCallback = callback;
}
});
Expand All @@ -208,7 +268,8 @@ public class ReentranceTest {
flow = new Flow(keyManager, History.single(new Catalog()));

flow.setDispatcher(new Dispatcher() {
@Override public void dispatch(@NonNull Traversal traversal, @NonNull TraversalCallback callback) {
@Override
public void dispatch(@NonNull Traversal traversal, @NonNull TraversalCallback callback) {
flow.set(new Loading());
flow.removeDispatcher(this);
callback.onTraversalCompleted();
Expand All @@ -218,7 +279,8 @@ public class ReentranceTest {
verifyHistory(flow.getHistory(), new Catalog());

flow.setDispatcher(new Dispatcher() {
@Override public void dispatch(@NonNull Traversal traversal, @NonNull TraversalCallback callback) {
@Override
public void dispatch(@NonNull Traversal traversal, @NonNull TraversalCallback callback) {
callback.onTraversalCompleted();
}
});
Expand All @@ -229,12 +291,14 @@ public class ReentranceTest {
@Test public void dispatcherSetInMidFlightWaitsForBootstrap() {
flow = new Flow(keyManager, History.single(new Catalog()));
flow.setDispatcher(new Dispatcher() {
@Override public void dispatch(@NonNull Traversal traversal, @NonNull TraversalCallback callback) {
@Override
public void dispatch(@NonNull Traversal traversal, @NonNull TraversalCallback callback) {
lastCallback = callback;
}
});
flow.setDispatcher(new Dispatcher() {
@Override public void dispatch(@NonNull Traversal traversal, @NonNull TraversalCallback callback) {
@Override
public void dispatch(@NonNull Traversal traversal, @NonNull TraversalCallback callback) {
lastStack = traversal.destination;
callback.onTraversalCompleted();
}
Expand All @@ -249,13 +313,15 @@ public class ReentranceTest {
final AtomicInteger secondDispatcherCount = new AtomicInteger(0);
flow = new Flow(keyManager, History.single(new Catalog()));
flow.setDispatcher(new Dispatcher() {
@Override public void dispatch(@NonNull Traversal traversal, @NonNull TraversalCallback callback) {
@Override
public void dispatch(@NonNull Traversal traversal, @NonNull TraversalCallback callback) {
flow.set(new Detail());
lastCallback = callback;
}
});
flow.setDispatcher(new Dispatcher() {
@Override public void dispatch(@NonNull Traversal traversal, @NonNull TraversalCallback callback) {
@Override
public void dispatch(@NonNull Traversal traversal, @NonNull TraversalCallback callback) {
secondDispatcherCount.incrementAndGet();
lastStack = traversal.destination;
callback.onTraversalCompleted();
Expand All @@ -273,7 +339,8 @@ public class ReentranceTest {
flow = new Flow(keyManager, History.single(new Catalog()));

flow.setDispatcher(new Dispatcher() {
@Override public void dispatch(@NonNull Traversal traversal, @NonNull TraversalCallback callback) {
@Override
public void dispatch(@NonNull Traversal traversal, @NonNull TraversalCallback callback) {
lastCallback = callback;
flow.removeDispatcher(this);
flow.set(new Loading());
Expand All @@ -283,7 +350,8 @@ public class ReentranceTest {
verifyHistory(flow.getHistory(), new Catalog());

flow.setDispatcher(new Dispatcher() {
@Override public void dispatch(@NonNull Traversal traversal, @NonNull TraversalCallback callback) {
@Override
public void dispatch(@NonNull Traversal traversal, @NonNull TraversalCallback callback) {
secondDispatcherCount.incrementAndGet();
callback.onTraversalCompleted();
}
Expand Down