diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseIndexArray.java b/database/src/main/java/com/firebase/ui/database/FirebaseIndexArray.java index 008470303..c599dacd2 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseIndexArray.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseIndexArray.java @@ -14,6 +14,7 @@ package com.firebase.ui.database; +import android.support.annotation.CallSuper; import android.support.annotation.NonNull; import android.support.v7.widget.RecyclerView; import android.util.Log; @@ -36,6 +37,7 @@ public class FirebaseIndexArray extends CachingObservableSnapshotArray imp private Map mRefs = new HashMap<>(); private FirebaseArray mKeySnapshots; + private JoinResolver mJoinResolver; private List mDataSnapshots = new ArrayList<>(); /** @@ -52,13 +54,44 @@ public class FirebaseIndexArray extends CachingObservableSnapshotArray imp private boolean mHasPendingMoveOrDelete; /** - * Create a new FirebaseIndexArray that parses snapshots as members of a given class. + * Create a new FirebaseIndexArray with a custom {@link SnapshotParser} and {@link + * JoinResolver}. * * @param keyQuery The Firebase location containing the list of keys to be found in {@code * dataRef}. Can also be a slice of a location, using some combination of {@code * limit()}, {@code startAt()}, and {@code endAt()}. * @param dataRef The Firebase location to watch for data changes. Each key key found at {@code * keyQuery}'s location represents a list item in the {@link RecyclerView}. + * @see ObservableSnapshotArray#ObservableSnapshotArray(SnapshotParser) + */ + public FirebaseIndexArray(Query keyQuery, + DatabaseReference dataRef, + SnapshotParser parser, + JoinResolver resolver) { + super(parser); + init(keyQuery, dataRef, resolver); + } + + /** + * Create a new FirebaseIndexArray that parses snapshots as members of a given class and joins + * refs together with a custom {@link JoinResolver}. + * + * @see FirebaseIndexArray#FirebaseIndexArray(Query, DatabaseReference, SnapshotParser, + * JoinResolver) + * @see ObservableSnapshotArray#ObservableSnapshotArray(Class) + */ + public FirebaseIndexArray(Query keyQuery, + DatabaseReference dataRef, + Class tClass, + JoinResolver resolver) { + super(tClass); + init(keyQuery, dataRef, resolver); + } + + /** + * Create a new FirebaseIndexArray that parses snapshots as members of a given class. + * + * @see FirebaseIndexArray#FirebaseIndexArray(Query, DatabaseReference, Class, JoinResolver) * @see ObservableSnapshotArray#ObservableSnapshotArray(Class) */ public FirebaseIndexArray(Query keyQuery, DatabaseReference dataRef, Class tClass) { @@ -69,16 +102,24 @@ public FirebaseIndexArray(Query keyQuery, DatabaseReference dataRef, Class tC /** * Create a new FirebaseIndexArray with a custom {@link SnapshotParser}. * + * @see FirebaseIndexArray#FirebaseIndexArray(Query, DatabaseReference, SnapshotParser, + * JoinResolver) * @see ObservableSnapshotArray#ObservableSnapshotArray(SnapshotParser) - * @see FirebaseIndexArray#FirebaseIndexArray(Query, DatabaseReference, Class) */ public FirebaseIndexArray(Query keyQuery, DatabaseReference dataRef, SnapshotParser parser) { super(parser); init(keyQuery, dataRef); } - private void init(Query keyQuery, DatabaseReference dataRef) { + @CallSuper + protected void init(Query keyQuery, DatabaseReference dataRef) { + init(keyQuery, dataRef, new DefaultJoinResolver()); + } + + @CallSuper + protected void init(Query keyQuery, DatabaseReference dataRef, JoinResolver resolver) { mDataRef = dataRef; + mJoinResolver = resolver; mKeySnapshots = new FirebaseArray<>(keyQuery, new SnapshotParser() { @Override public String parseSnapshot(DataSnapshot snapshot) { @@ -166,10 +207,9 @@ private boolean isKeyAtIndex(String key, int index) { } protected void onKeyAdded(DataSnapshot data) { - String key = data.getKey(); - DatabaseReference ref = mDataRef.child(key); + DatabaseReference ref = mJoinResolver.onJoin(data); - mKeysWithPendingData.add(key); + mKeysWithPendingData.add(data.getKey()); // Start listening mRefs.put(ref, ref.addValueEventListener(new DataRefListener())); } @@ -186,11 +226,11 @@ protected void onKeyMoved(DataSnapshot data, int index, int oldIndex) { } protected void onKeyRemoved(DataSnapshot data, int index) { - String key = data.getKey(); - ValueEventListener listener = mRefs.remove(mDataRef.getRef().child(key)); - if (listener != null) mDataRef.child(key).removeEventListener(listener); + DatabaseReference removeRef = mJoinResolver.onDisjoin(data); + ValueEventListener listener = mRefs.remove(removeRef); + if (listener != null) removeRef.removeEventListener(listener); - if (isKeyAtIndex(key, index)) { + if (isKeyAtIndex(data.getKey(), index)) { DataSnapshot snapshot = removeData(index); mHasPendingMoveOrDelete = true; notifyChangeEventListeners(EventType.REMOVED, snapshot, index); @@ -256,7 +296,7 @@ public void onDataChange(DataSnapshot snapshot) { notifyListenersOnDataChanged(); } else { // Data does not exist - Log.w(TAG, "Key not found at ref: " + snapshot.getRef()); + mJoinResolver.onJoinFailed(snapshot, index); } } } @@ -266,4 +306,23 @@ public void onCancelled(DatabaseError error) { notifyListenersOnCancelled(error); } } + + protected class DefaultJoinResolver implements JoinResolver { + @NonNull + @Override + public DatabaseReference onJoin(DataSnapshot keySnapshot) { + return mDataRef.child(keySnapshot.getKey()); + } + + @NonNull + @Override + public DatabaseReference onDisjoin(DataSnapshot keySnapshot) { + return onJoin(keySnapshot); // Match the join/disjoin pair + } + + @Override + public void onJoinFailed(DataSnapshot snapshot, int index) { + Log.w(TAG, "Key not found at ref " + snapshot.getRef() + " for index " + index); + } + } } diff --git a/database/src/main/java/com/firebase/ui/database/JoinResolver.java b/database/src/main/java/com/firebase/ui/database/JoinResolver.java new file mode 100644 index 000000000..18f399f88 --- /dev/null +++ b/database/src/main/java/com/firebase/ui/database/JoinResolver.java @@ -0,0 +1,43 @@ +package com.firebase.ui.database; + +import android.support.annotation.NonNull; + +import com.google.firebase.database.ChildEventListener; +import com.google.firebase.database.DataSnapshot; +import com.google.firebase.database.DatabaseReference; + +/** + * Handles joining two queries together. + */ +public interface JoinResolver { + /** + * Called after an {@link ChildEventListener#onChildAdded(DataSnapshot, String)} event from + * {@code keyRef}. + * + * @param keySnapshot the snapshot supplied in {@code onChildAdded} + * @return A query containing the joined data from the {@code keyRef}'s indexed snapshot. + *

Without any customization, a query on the child from your {@code dataRef} with the key + * found in {@code keySnapshot} will be returned. + */ + @NonNull + DatabaseReference onJoin(DataSnapshot keySnapshot); + + /** + * Called after an {@link ChildEventListener#onChildRemoved(DataSnapshot)} event from {@code + * keyRef}. + * + * @param keySnapshot the snapshot supplied in {@code onChildRemoved} + * @return The same query supplied in {@code onJoin} for the given {@code keySnapshot}. + */ + @NonNull + DatabaseReference onDisjoin(DataSnapshot keySnapshot); + + /** + * Called when a key in {@code keyRef} could not be found in {@code dataRef}. + * + * @param snapshot the snapshot who's key could not be found in {@code dataRef} + * @param index index of a {@link DataSnapshot} in {@code keyRef} that could not be found in + * {@code dataRef} + */ + void onJoinFailed(DataSnapshot snapshot, int index); +}