Skip to content
This repository has been archived by the owner on May 23, 2020. It is now read-only.

Commit

Permalink
Merge pull request #20 from trello/dlew/all-listener
Browse files Browse the repository at this point in the history
Added Event.ALL which automatically emits all events
  • Loading branch information
dlew committed Nov 25, 2015
2 parents 48b8d50 + a24ab85 commit fd4eb20
Show file tree
Hide file tree
Showing 5 changed files with 230 additions and 8 deletions.
12 changes: 9 additions & 3 deletions navi/src/main/java/com/trello/navi/Event.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
*/
public final class Event<T> {

public static final Event<Type> ALL = new Event<>(Type.ALL, Type.class);

public static final Event<Bundle> CREATE = new Event<>(Type.CREATE, Bundle.class);
public static final Event<BundleBundle> CREATE_PERSISTABLE =
new Event<>(Type.CREATE, BundleBundle.class);
Expand Down Expand Up @@ -67,6 +69,10 @@ private Event(Type eventType, Class<T> callbackType) {
this.callbackType = callbackType;
}

public Type type() {
return eventType;
}

@Override public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Expand All @@ -90,9 +96,9 @@ private Event(Type eventType, Class<T> callbackType) {
'}';
}

// Allows us to distinguish between listeners that have the same callback but occur
// for different events. Private, since no one else needs to know.
private enum Type {
public enum Type {
ALL,

// Shared
CREATE,
START,
Expand Down
31 changes: 26 additions & 5 deletions navi/src/main/java/com/trello/navi/internal/BaseNaviComponent.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import android.os.PersistableBundle;
import android.support.annotation.NonNull;
import com.trello.navi.Event;
import com.trello.navi.Event.Type;
import com.trello.navi.Listener;
import com.trello.navi.NaviComponent;
import com.trello.navi.model.ActivityResult;
Expand All @@ -18,6 +19,7 @@
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
Expand Down Expand Up @@ -93,6 +95,11 @@ public static BaseNaviComponent createFragmentComponent() {
}

@Override public <T> boolean hasEvent(Event<T> event) {
// ALL always works
if (event == Event.ALL) {
return true;
}

return handledEvents.contains(event);
}

Expand Down Expand Up @@ -124,13 +131,27 @@ private void emitEvent(Event<Void> event) {
}

private <T> void emitEvent(Event<T> event, T data) {
if (!listenerMap.containsKey(event)) {
return;
// We gather listener iterators all at once so adding/removing listeners during emission
// doesn't change the listener list.
final List<Listener> listeners = listenerMap.get(event);
final Iterator<Listener> listenersIterator =
listeners != null ? listeners.listIterator() : null;

final List<Listener> allListeners = listenerMap.get(Event.ALL);
final Iterator<Listener> allListenersIterator =
allListeners != null ? allListeners.iterator() : null;

if (allListenersIterator != null) {
final Type type = event.type();
while (allListenersIterator.hasNext()) {
allListenersIterator.next().call(type);
}
}

List<Listener> listeners = listenerMap.get(event);
for (Listener listener : listeners) {
listener.call(data);
if (listeners != null) {
while (listenersIterator.hasNext()) {
listenersIterator.next().call(data);
}
}
}

Expand Down
59 changes: 59 additions & 0 deletions navi/src/test/java/com/trello/navi/AllEventTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package com.trello.navi;

import android.os.Bundle;
import android.os.PersistableBundle;
import com.trello.navi.Event.Type;
import com.trello.navi.internal.BaseNaviComponent;
import org.junit.Test;

import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;

public final class AllEventTest {

private final BaseNaviComponent activity = BaseNaviComponent.createActivityComponent();

// Test event without listener params works
@Test public void startAllListener() {
Listener<Type> listener = mock(Listener.class);
activity.addListener(Event.ALL, listener);

activity.onStart();
verify(listener).call(Type.START);

activity.removeListener(Event.ALL, listener);
activity.onStart();
verifyNoMoreInteractions(listener);
}

// Test event with listener params works
@Test public void createAllListener() {
Listener<Type> listener = mock(Listener.class);
activity.addListener(Event.ALL, listener);

Bundle bundle = new Bundle();
activity.onCreate(bundle);
verify(listener).call(Type.CREATE);

activity.removeListener(Event.ALL, listener);
activity.onCreate(bundle);
verifyNoMoreInteractions(listener);
}

// Test persistable Activities
@Test public void createPersistableListener() {
Listener<Type> listener = mock(Listener.class);
activity.addListener(Event.ALL, listener);

Bundle bundle = new Bundle();
PersistableBundle persistableBundle = mock(PersistableBundle.class);
activity.onCreate(bundle, persistableBundle);
verify(listener, times(2)).call(Type.CREATE);

activity.removeListener(Event.ALL, listener);
activity.onCreate(bundle, persistableBundle);
verifyNoMoreInteractions(listener);
}
}
71 changes: 71 additions & 0 deletions navi/src/test/java/com/trello/navi/ConcurrencyTest.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.trello.navi;

import com.trello.navi.Event.Type;
import com.trello.navi.internal.BaseNaviComponent;
import org.junit.Test;

Expand Down Expand Up @@ -49,6 +50,40 @@ public final class ConcurrencyTest {
verifyZeroInteractions(addedDuringEmit);
}

// Verify that adding an ALL listener during emission
// doesn't cause it to get the current emission
@Test public void addAllDuringEmit() {
final Listener<Type> addedDuringEmit = mock(Listener.class);
final Listener<Void> listener = spy(new Listener<Void>() {
@Override public void call(Void aVoid) {
activity.addListener(Event.ALL, addedDuringEmit);
}
});

activity.addListener(Event.RESUME, listener);
activity.onResume();

verify(listener).call(null);
verifyZeroInteractions(addedDuringEmit);
}

// Verify that adding a listener during an ALL emission
// doesn't cause it to get the current emission
@Test public void addDuringEmitAll() {
final Listener<Void> addedDuringEmit = mock(Listener.class);
final Listener<Type> listener = spy(new Listener<Type>() {
@Override public void call(Type type) {
activity.addListener(Event.RESUME, addedDuringEmit);
}
});

activity.addListener(Event.ALL, listener);
activity.onResume();

verify(listener).call(Type.RESUME);
verifyZeroInteractions(addedDuringEmit);
}

// Verify that listeners removed while emitting an event still receive it (since they were
// registered at the time of the event).
@Test public void removeDuringEmit() {
Expand All @@ -66,4 +101,40 @@ public final class ConcurrencyTest {
verify(listener).call(null);
verify(removedDuringEmit).call(null);
}

// Verify that removing an ALL listener during emission
// doesn't cause it to lose the current emission
@Test public void removeAllDuringEmit() {
final Listener<Type> removedDuringEmit = mock(Listener.class);
final Listener<Void> listener = spy(new Listener<Void>() {
@Override public void call(Void __) {
activity.removeListener(Event.ALL, removedDuringEmit);
}
});

activity.addListener(Event.RESUME, listener);
activity.addListener(Event.ALL, removedDuringEmit);
activity.onResume();

verify(listener).call(null);
verify(removedDuringEmit).call(Type.RESUME);
}

// Verify that removing a listener during an ALL emission
// doesn't cause it to lose the current emission
@Test public void removeDuringEmitAll() {
final Listener<Void> removedDuringEmit = mock(Listener.class);
final Listener<Type> listener = spy(new Listener<Type>() {
@Override public void call(Type type) {
activity.removeListener(Event.RESUME, removedDuringEmit);
}
});

activity.addListener(Event.ALL, listener);
activity.addListener(Event.RESUME, removedDuringEmit);
activity.onResume();

verify(listener).call(Type.RESUME);
verify(removedDuringEmit).call(null);
}
}
65 changes: 65 additions & 0 deletions navi/src/test/java/com/trello/navi/rx/RxNaviAllEventTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package com.trello.navi.rx;

import android.os.Bundle;
import android.os.PersistableBundle;
import com.trello.navi.Event;
import com.trello.navi.Event.Type;
import com.trello.navi.internal.BaseNaviComponent;
import org.junit.Test;
import rx.Subscription;
import rx.observers.TestSubscriber;

import static org.mockito.Mockito.mock;

public final class RxNaviAllEventTest {

private final BaseNaviComponent activity = BaseNaviComponent.createActivityComponent();

// Test event without listener params works
@Test public void observeAllStart() {
TestSubscriber<Type> testSubscriber = new TestSubscriber<>();
Subscription subscription = RxNavi.observe(activity, Event.ALL).subscribe(testSubscriber);
testSubscriber.assertNoValues();

activity.onStart();
subscription.unsubscribe();
activity.onStart();

testSubscriber.assertValue(Type.START);
testSubscriber.assertNoTerminalEvent();
testSubscriber.assertUnsubscribed();
}

// Test event with listener params works
@Test public void observeAllCreate() {
TestSubscriber<Type> testSubscriber = new TestSubscriber<>();
Subscription subscription = RxNavi.observe(activity, Event.ALL).subscribe(testSubscriber);
testSubscriber.assertNoValues();

Bundle bundle = new Bundle();
activity.onCreate(bundle);
subscription.unsubscribe();
activity.onCreate(bundle);

testSubscriber.assertValue(Type.CREATE);
testSubscriber.assertNoTerminalEvent();
testSubscriber.assertUnsubscribed();
}

// Test persistable Activities
@Test public void observeAllCreatePersistable() {
TestSubscriber<Type> testSubscriber = new TestSubscriber<>();
Subscription subscription = RxNavi.observe(activity, Event.ALL).subscribe(testSubscriber);
testSubscriber.assertNoValues();

Bundle bundle = new Bundle();
PersistableBundle persistableBundle = mock(PersistableBundle.class);
activity.onCreate(bundle, persistableBundle);
subscription.unsubscribe();
activity.onCreate(bundle, persistableBundle);

testSubscriber.assertValues(Type.CREATE, Type.CREATE);
testSubscriber.assertNoTerminalEvent();
testSubscriber.assertUnsubscribed();
}
}

0 comments on commit fd4eb20

Please sign in to comment.