From 28c366cdb43b5aa18c56ee29736adcd68afdb3cd Mon Sep 17 00:00:00 2001 From: Alex Saveau Date: Thu, 8 Dec 2016 19:48:47 -0800 Subject: [PATCH 01/93] Add a callback in FirebaseIndexArray when a Key doesn't exist in a ref Signed-off-by: Alex Saveau --- .../ui/database/FirebaseIndexArray.java | 18 +++++++++++++++--- .../ui/database/FirebaseIndexListAdapter.java | 18 +++++++++++++++++- .../database/FirebaseIndexRecyclerAdapter.java | 17 ++++++++++++++++- .../ui/database/FirebaseListAdapter.java | 2 +- .../ui/database/FirebaseRecyclerAdapter.java | 2 +- .../ui/database/OnIndexMismatchListener.java | 7 +++++++ 6 files changed, 57 insertions(+), 7 deletions(-) create mode 100644 database/src/main/java/com/firebase/ui/database/OnIndexMismatchListener.java 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 6044426a0..948c2494b 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseIndexArray.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseIndexArray.java @@ -29,10 +29,11 @@ import java.util.Set; class FirebaseIndexArray extends FirebaseArray { - private static final String TAG = FirebaseIndexArray.class.getSimpleName(); + private static final String TAG = "FirebaseIndexArray"; private Query mQuery; private OnChangedListener mListener; + private OnIndexMismatchListener mIndexMismatchListener; private Map mRefs = new HashMap<>(); private List mDataSnapshots = new ArrayList<>(); @@ -130,7 +131,8 @@ public void onChildMoved(DataSnapshot keySnapshot, String previousChildKey) { @Override public void onCancelled(DatabaseError error) { - Log.e(TAG, "A fatal error occurred retrieving the necessary keys to populate your adapter."); + Log.e(TAG, + "A fatal error occurred retrieving the necessary keys to populate your adapter."); super.onCancelled(error); } @@ -140,6 +142,16 @@ public void setOnChangedListener(OnChangedListener listener) { mListener = listener; } + public void setOnIndexMismatchListener(OnIndexMismatchListener listener) { + mIndexMismatchListener = listener; + } + + protected void notifyIndexMismatchListeners(int index, DataSnapshot snapshot) { + if (mIndexMismatchListener != null) { + mIndexMismatchListener.onIndexMismatch(index, snapshot); + } + } + private class DataRefListener implements ValueEventListener { @Override public void onDataChange(DataSnapshot snapshot) { @@ -159,7 +171,7 @@ public void onDataChange(DataSnapshot snapshot) { mDataSnapshots.remove(index); notifyChangedListeners(OnChangedListener.EventType.REMOVED, index); } else { - Log.w(TAG, "Key not found at ref: " + snapshot.getRef()); + notifyIndexMismatchListeners(index, snapshot); } } } diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseIndexListAdapter.java b/database/src/main/java/com/firebase/ui/database/FirebaseIndexListAdapter.java index a5878d632..44177a1be 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseIndexListAdapter.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseIndexListAdapter.java @@ -2,7 +2,9 @@ import android.app.Activity; import android.support.annotation.LayoutRes; +import android.util.Log; +import com.google.firebase.database.DataSnapshot; import com.google.firebase.database.Query; /** @@ -34,7 +36,10 @@ * @param The class type to use as a model for the data * contained in the children of the given Firebase location */ -public abstract class FirebaseIndexListAdapter extends FirebaseListAdapter { +public abstract class FirebaseIndexListAdapter extends FirebaseListAdapter + implements OnIndexMismatchListener { + private static final String TAG = "IndexListAdapter"; + /** * @param activity The activity containing the ListView * @param modelClass Firebase will marshall the data at a location into @@ -55,5 +60,16 @@ public FirebaseIndexListAdapter(Activity activity, Query keyRef, Query dataRef) { super(activity, modelClass, modelLayout, new FirebaseIndexArray(keyRef, dataRef)); + ((FirebaseIndexArray) mSnapshots).setOnIndexMismatchListener(this); + } + + /** + * + * @param index The index of a {@code snapshot} in {@code keyRef} that could not be found in {@code dataRef}. + * @param snapshot The snapshot who's key could not be found in {@code dataRef}. + */ + @Override + public void onIndexMismatch(int index, DataSnapshot snapshot) { + Log.w(TAG, "Key not found at ref " + snapshot.getRef() + " for index " + index + "."); } } diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseIndexRecyclerAdapter.java b/database/src/main/java/com/firebase/ui/database/FirebaseIndexRecyclerAdapter.java index 19e45d401..f9804f125 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseIndexRecyclerAdapter.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseIndexRecyclerAdapter.java @@ -16,7 +16,9 @@ import android.support.annotation.LayoutRes; import android.support.v7.widget.RecyclerView; +import android.util.Log; +import com.google.firebase.database.DataSnapshot; import com.google.firebase.database.Query; /** @@ -62,7 +64,9 @@ * @param The ViewHolder class that contains the Views in the layout that is shown for each object. */ public abstract class FirebaseIndexRecyclerAdapter - extends FirebaseRecyclerAdapter { + extends FirebaseRecyclerAdapter implements OnIndexMismatchListener { + private static final String TAG = "IndexRecyclerAdapter"; + /** * @param modelClass Firebase will marshall the data at a location into an instance * of a class that you provide @@ -83,5 +87,16 @@ public FirebaseIndexRecyclerAdapter(Class modelClass, Query keyRef, Query dataRef) { super(modelClass, modelLayout, viewHolderClass, new FirebaseIndexArray(keyRef, dataRef)); + ((FirebaseIndexArray) mSnapshots).setOnIndexMismatchListener(this); + } + + /** + * + * @param index The index of a {@code snapshot} in {@code keyRef} that could not be found in {@code dataRef}. + * @param snapshot The snapshot who's key could not be found in {@code dataRef}. + */ + @Override + public void onIndexMismatch(int index, DataSnapshot snapshot) { + Log.w(TAG, "Key not found at ref " + snapshot.getRef() + " for index " + index + "."); } } diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseListAdapter.java b/database/src/main/java/com/firebase/ui/database/FirebaseListAdapter.java index 4cce0df7c..33d4e4806 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseListAdapter.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseListAdapter.java @@ -53,7 +53,7 @@ public abstract class FirebaseListAdapter extends BaseAdapter { private static final String TAG = FirebaseListAdapter.class.getSimpleName(); - private FirebaseArray mSnapshots; + FirebaseArray mSnapshots; private final Class mModelClass; protected Activity mActivity; protected int mLayout; diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseRecyclerAdapter.java b/database/src/main/java/com/firebase/ui/database/FirebaseRecyclerAdapter.java index 5661a8563..0bbf53cb3 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseRecyclerAdapter.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseRecyclerAdapter.java @@ -75,7 +75,7 @@ public abstract class FirebaseRecyclerAdapter { private static final String TAG = FirebaseRecyclerAdapter.class.getSimpleName(); - private FirebaseArray mSnapshots; + FirebaseArray mSnapshots; private Class mModelClass; protected Class mViewHolderClass; protected int mModelLayout; diff --git a/database/src/main/java/com/firebase/ui/database/OnIndexMismatchListener.java b/database/src/main/java/com/firebase/ui/database/OnIndexMismatchListener.java new file mode 100644 index 000000000..ee6654cc2 --- /dev/null +++ b/database/src/main/java/com/firebase/ui/database/OnIndexMismatchListener.java @@ -0,0 +1,7 @@ +package com.firebase.ui.database; + +import com.google.firebase.database.DataSnapshot; + +interface OnIndexMismatchListener { + void onIndexMismatch(int index, DataSnapshot snapshot); +} From 641297c48a4d8fc35de66d488269409368eb7681 Mon Sep 17 00:00:00 2001 From: Alex Saveau Date: Thu, 8 Dec 2016 20:49:10 -0800 Subject: [PATCH 02/93] Add documentation Signed-off-by: Alex Saveau --- .../java/com/firebase/ui/database/FirebaseIndexListAdapter.java | 1 + .../com/firebase/ui/database/FirebaseIndexRecyclerAdapter.java | 1 + 2 files changed, 2 insertions(+) diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseIndexListAdapter.java b/database/src/main/java/com/firebase/ui/database/FirebaseIndexListAdapter.java index 44177a1be..1a949b309 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseIndexListAdapter.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseIndexListAdapter.java @@ -64,6 +64,7 @@ public FirebaseIndexListAdapter(Activity activity, } /** + * Called when a key in {@code keyRef} could not be found in {@code dataRef}. * * @param index The index of a {@code snapshot} in {@code keyRef} that could not be found in {@code dataRef}. * @param snapshot The snapshot who's key could not be found in {@code dataRef}. diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseIndexRecyclerAdapter.java b/database/src/main/java/com/firebase/ui/database/FirebaseIndexRecyclerAdapter.java index f9804f125..6f4572207 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseIndexRecyclerAdapter.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseIndexRecyclerAdapter.java @@ -91,6 +91,7 @@ public FirebaseIndexRecyclerAdapter(Class modelClass, } /** + * Called when a key in {@code keyRef} could not be found in {@code dataRef}. * * @param index The index of a {@code snapshot} in {@code keyRef} that could not be found in {@code dataRef}. * @param snapshot The snapshot who's key could not be found in {@code dataRef}. From a62a025100619175245d510b16e7ceff8f24ab5d Mon Sep 17 00:00:00 2001 From: Alex Saveau Date: Tue, 13 Dec 2016 17:32:31 -0800 Subject: [PATCH 03/93] Rename listeners Signed-off-by: Alex Saveau --- .../firebase/ui/database/FirebaseArray.java | 18 +++++++++--------- .../ui/database/FirebaseIndexArray.java | 18 +++++++++--------- .../ui/database/FirebaseIndexListAdapter.java | 2 +- .../database/FirebaseIndexRecyclerAdapter.java | 2 +- .../ui/database/FirebaseListAdapter.java | 2 +- .../ui/database/FirebaseRecyclerAdapter.java | 2 +- ...istener.java => IndexMismatchListener.java} | 2 +- 7 files changed, 23 insertions(+), 23 deletions(-) rename database/src/main/java/com/firebase/ui/database/{OnIndexMismatchListener.java => IndexMismatchListener.java} (80%) diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseArray.java b/database/src/main/java/com/firebase/ui/database/FirebaseArray.java index 1bb5fa4fd..7acba6d5b 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseArray.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseArray.java @@ -26,7 +26,7 @@ * This class implements an array-like collection on top of a Firebase location. */ class FirebaseArray implements ChildEventListener { - public interface OnChangedListener { + public interface ChangedListener { enum EventType {ADDED, CHANGED, REMOVED, MOVED} void onChanged(EventType type, int index, int oldIndex); @@ -35,7 +35,7 @@ enum EventType {ADDED, CHANGED, REMOVED, MOVED} } private Query mQuery; - private OnChangedListener mListener; + private ChangedListener mListener; private List mSnapshots = new ArrayList<>(); public FirebaseArray(Query ref) { @@ -74,21 +74,21 @@ public void onChildAdded(DataSnapshot snapshot, String previousChildKey) { index = getIndexForKey(previousChildKey) + 1; } mSnapshots.add(index, snapshot); - notifyChangedListeners(OnChangedListener.EventType.ADDED, index); + notifyChangedListeners(ChangedListener.EventType.ADDED, index); } @Override public void onChildChanged(DataSnapshot snapshot, String previousChildKey) { int index = getIndexForKey(snapshot.getKey()); mSnapshots.set(index, snapshot); - notifyChangedListeners(OnChangedListener.EventType.CHANGED, index); + notifyChangedListeners(ChangedListener.EventType.CHANGED, index); } @Override public void onChildRemoved(DataSnapshot snapshot) { int index = getIndexForKey(snapshot.getKey()); mSnapshots.remove(index); - notifyChangedListeners(OnChangedListener.EventType.REMOVED, index); + notifyChangedListeners(ChangedListener.EventType.REMOVED, index); } @Override @@ -97,7 +97,7 @@ public void onChildMoved(DataSnapshot snapshot, String previousChildKey) { mSnapshots.remove(oldIndex); int newIndex = previousChildKey == null ? 0 : (getIndexForKey(previousChildKey) + 1); mSnapshots.add(newIndex, snapshot); - notifyChangedListeners(OnChangedListener.EventType.MOVED, newIndex, oldIndex); + notifyChangedListeners(ChangedListener.EventType.MOVED, newIndex, oldIndex); } @Override @@ -105,15 +105,15 @@ public void onCancelled(DatabaseError error) { notifyCancelledListeners(error); } - public void setOnChangedListener(OnChangedListener listener) { + public void setOnChangedListener(ChangedListener listener) { mListener = listener; } - protected void notifyChangedListeners(OnChangedListener.EventType type, int index) { + protected void notifyChangedListeners(ChangedListener.EventType type, int index) { notifyChangedListeners(type, index, -1); } - protected void notifyChangedListeners(OnChangedListener.EventType type, int index, int oldIndex) { + protected void notifyChangedListeners(ChangedListener.EventType type, int index, int oldIndex) { if (mListener != null) { mListener.onChanged(type, index, oldIndex); } 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 948c2494b..e2ea7075c 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseIndexArray.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseIndexArray.java @@ -32,8 +32,8 @@ class FirebaseIndexArray extends FirebaseArray { private static final String TAG = "FirebaseIndexArray"; private Query mQuery; - private OnChangedListener mListener; - private OnIndexMismatchListener mIndexMismatchListener; + private ChangedListener mListener; + private IndexMismatchListener mIndexMismatchListener; private Map mRefs = new HashMap<>(); private List mDataSnapshots = new ArrayList<>(); @@ -108,7 +108,7 @@ public void onChildRemoved(DataSnapshot keySnapshot) { if (isMatch(index, key)) { mDataSnapshots.remove(index); - notifyChangedListeners(OnChangedListener.EventType.REMOVED, index); + notifyChangedListeners(ChangedListener.EventType.REMOVED, index); } } @@ -125,7 +125,7 @@ public void onChildMoved(DataSnapshot keySnapshot, String previousChildKey) { DataSnapshot snapshot = mDataSnapshots.remove(oldIndex); int newIndex = getIndexForKey(key); mDataSnapshots.add(newIndex, snapshot); - notifyChangedListeners(OnChangedListener.EventType.MOVED, newIndex, oldIndex); + notifyChangedListeners(ChangedListener.EventType.MOVED, newIndex, oldIndex); } } @@ -137,12 +137,12 @@ public void onCancelled(DatabaseError error) { } @Override - public void setOnChangedListener(OnChangedListener listener) { + public void setOnChangedListener(ChangedListener listener) { super.setOnChangedListener(listener); mListener = listener; } - public void setOnIndexMismatchListener(OnIndexMismatchListener listener) { + public void setOnIndexMismatchListener(IndexMismatchListener listener) { mIndexMismatchListener = listener; } @@ -161,15 +161,15 @@ public void onDataChange(DataSnapshot snapshot) { if (snapshot.getValue() != null) { if (!isMatch(index, key)) { mDataSnapshots.add(index, snapshot); - notifyChangedListeners(OnChangedListener.EventType.ADDED, index); + notifyChangedListeners(ChangedListener.EventType.ADDED, index); } else { mDataSnapshots.set(index, snapshot); - notifyChangedListeners(OnChangedListener.EventType.CHANGED, index); + notifyChangedListeners(ChangedListener.EventType.CHANGED, index); } } else { if (isMatch(index, key)) { mDataSnapshots.remove(index); - notifyChangedListeners(OnChangedListener.EventType.REMOVED, index); + notifyChangedListeners(ChangedListener.EventType.REMOVED, index); } else { notifyIndexMismatchListeners(index, snapshot); } diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseIndexListAdapter.java b/database/src/main/java/com/firebase/ui/database/FirebaseIndexListAdapter.java index 1a949b309..a6595209c 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseIndexListAdapter.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseIndexListAdapter.java @@ -37,7 +37,7 @@ * contained in the children of the given Firebase location */ public abstract class FirebaseIndexListAdapter extends FirebaseListAdapter - implements OnIndexMismatchListener { + implements IndexMismatchListener { private static final String TAG = "IndexListAdapter"; /** diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseIndexRecyclerAdapter.java b/database/src/main/java/com/firebase/ui/database/FirebaseIndexRecyclerAdapter.java index 6f4572207..448b1f75b 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseIndexRecyclerAdapter.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseIndexRecyclerAdapter.java @@ -64,7 +64,7 @@ * @param The ViewHolder class that contains the Views in the layout that is shown for each object. */ public abstract class FirebaseIndexRecyclerAdapter - extends FirebaseRecyclerAdapter implements OnIndexMismatchListener { + extends FirebaseRecyclerAdapter implements IndexMismatchListener { private static final String TAG = "IndexRecyclerAdapter"; /** diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseListAdapter.java b/database/src/main/java/com/firebase/ui/database/FirebaseListAdapter.java index 33d4e4806..1b80d46ee 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseListAdapter.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseListAdapter.java @@ -67,7 +67,7 @@ public abstract class FirebaseListAdapter extends BaseAdapter { mLayout = modelLayout; mSnapshots = snapshots; - mSnapshots.setOnChangedListener(new FirebaseArray.OnChangedListener() { + mSnapshots.setOnChangedListener(new FirebaseArray.ChangedListener() { @Override public void onChanged(EventType type, int index, int oldIndex) { notifyDataSetChanged(); diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseRecyclerAdapter.java b/database/src/main/java/com/firebase/ui/database/FirebaseRecyclerAdapter.java index 0bbf53cb3..bc1925ffc 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseRecyclerAdapter.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseRecyclerAdapter.java @@ -89,7 +89,7 @@ public abstract class FirebaseRecyclerAdapter Date: Wed, 14 Dec 2016 18:25:46 -0800 Subject: [PATCH 04/93] Add JoinResolver.java interface Signed-off-by: Alex Saveau --- .../com/firebase/uidemo/database/Chat.java | 41 ++++ .../uidemo/database/ChatActivity.java | 188 +++++------------- .../firebase/uidemo/database/ChatHolder.java | 67 +++++++ app/src/main/res/values/strings.xml | 2 + .../database/FirebaseArrayOfObjectsTest.java | 21 +- .../ui/database/FirebaseArrayTest.java | 23 +-- .../FirebaseIndexArrayOfObjectsTest.java | 26 +-- .../ui/database/FirebaseIndexArrayTest.java | 27 +-- .../firebase/ui/database/utils/TestUtils.java | 40 +++- .../firebase/ui/database/FirebaseArray.java | 77 +++---- .../ui/database/FirebaseIndexArray.java | 126 ++++++------ .../ui/database/FirebaseIndexListAdapter.java | 28 ++- .../FirebaseIndexRecyclerAdapter.java | 28 ++- .../ui/database/FirebaseListAdapter.java | 22 +- .../ui/database/FirebaseRecyclerAdapter.java | 22 +- .../ui/database/IndexMismatchListener.java | 7 - .../firebase/ui/database/JoinResolver.java | 33 +++ 17 files changed, 443 insertions(+), 335 deletions(-) create mode 100644 app/src/main/java/com/firebase/uidemo/database/Chat.java create mode 100644 app/src/main/java/com/firebase/uidemo/database/ChatHolder.java delete mode 100644 database/src/main/java/com/firebase/ui/database/IndexMismatchListener.java create mode 100644 database/src/main/java/com/firebase/ui/database/JoinResolver.java diff --git a/app/src/main/java/com/firebase/uidemo/database/Chat.java b/app/src/main/java/com/firebase/uidemo/database/Chat.java new file mode 100644 index 000000000..c4fc6efdb --- /dev/null +++ b/app/src/main/java/com/firebase/uidemo/database/Chat.java @@ -0,0 +1,41 @@ +package com.firebase.uidemo.database; + +public class Chat { + private String mName; + private String mMessage; + private String mUid; + + public Chat() { + // Needed for Firebase + } + + public Chat(String name, String message, String uid) { + mName = name; + mMessage = message; + mUid = uid; + } + + public String getName() { + return mName; + } + + public void setName(String name) { + mName = name; + } + + public String getMessage() { + return mMessage; + } + + public void setMessage(String message) { + mMessage = message; + } + + public String getUid() { + return mUid; + } + + public void setUid(String uid) { + mUid = uid; + } +} diff --git a/app/src/main/java/com/firebase/uidemo/database/ChatActivity.java b/app/src/main/java/com/firebase/uidemo/database/ChatActivity.java index 73b33a162..2856182c8 100644 --- a/app/src/main/java/com/firebase/uidemo/database/ChatActivity.java +++ b/app/src/main/java/com/firebase/uidemo/database/ChatActivity.java @@ -14,29 +14,22 @@ package com.firebase.uidemo.database; -import android.graphics.PorterDuff; -import android.graphics.drawable.GradientDrawable; -import android.graphics.drawable.RotateDrawable; import android.os.Bundle; import android.support.annotation.NonNull; -import android.support.v4.content.ContextCompat; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.util.Log; -import android.view.Gravity; import android.view.View; import android.widget.Button; import android.widget.EditText; -import android.widget.FrameLayout; -import android.widget.LinearLayout; -import android.widget.RelativeLayout; -import android.widget.TextView; import android.widget.Toast; +import com.firebase.ui.database.FirebaseIndexRecyclerAdapter; import com.firebase.ui.database.FirebaseRecyclerAdapter; import com.firebase.uidemo.R; import com.google.android.gms.tasks.OnCompleteListener; +import com.google.android.gms.tasks.OnSuccessListener; import com.google.android.gms.tasks.Task; import com.google.firebase.auth.AuthResult; import com.google.firebase.auth.FirebaseAuth; @@ -50,6 +43,7 @@ public class ChatActivity extends AppCompatActivity implements FirebaseAuth.Auth private static final String TAG = "RecyclerViewDemo"; private FirebaseAuth mAuth; + private DatabaseReference mChatIndicesRef; private DatabaseReference mChatRef; private Button mSendButton; private EditText mMessageEdit; @@ -69,7 +63,9 @@ protected void onCreate(Bundle savedInstanceState) { mSendButton = (Button) findViewById(R.id.sendButton); mMessageEdit = (EditText) findViewById(R.id.messageEdit); - mChatRef = FirebaseDatabase.getInstance().getReference().child("chats"); + DatabaseReference ref = FirebaseDatabase.getInstance().getReference(); + mChatIndicesRef = ref.child("chatIndices"); + mChatRef = ref.child("chats"); mSendButton.setOnClickListener(new View.OnClickListener() { @Override @@ -78,11 +74,13 @@ public void onClick(View v) { String name = "User " + uid.substring(0, 6); Chat chat = new Chat(name, mMessageEdit.getText().toString(), uid); - mChatRef.push().setValue(chat, new DatabaseReference.CompletionListener() { + DatabaseReference chatRef = mChatRef.push(); + mChatIndicesRef.child(chatRef.getKey()).setValue(true); + chatRef.setValue(chat, new DatabaseReference.CompletionListener() { @Override - public void onComplete(DatabaseError databaseError, DatabaseReference reference) { - if (databaseError != null) { - Log.e(TAG, "Failed to write message", databaseError.toException()); + public void onComplete(DatabaseError error, DatabaseReference reference) { + if (error != null) { + Log.e(TAG, "Failed to write message", error.toException()); } } }); @@ -92,11 +90,7 @@ public void onComplete(DatabaseError databaseError, DatabaseReference reference) }); mMessages = (RecyclerView) findViewById(R.id.messagesList); - mManager = new LinearLayoutManager(this); - mManager.setReverseLayout(false); - - mMessages.setHasFixedSize(false); mMessages.setLayoutManager(mManager); } @@ -107,10 +101,10 @@ public void onStart() { // Default Database rules do not allow unauthenticated reads, so we need to // sign in before attaching the RecyclerView adapter otherwise the Adapter will // not be able to read any data from the Database. - if (!isSignedIn()) { - signInAnonymously(); - } else { + if (isSignedIn()) { attachRecyclerViewAdapter(); + } else { + signInAnonymously(); } } @@ -137,28 +131,34 @@ public void onAuthStateChanged(@NonNull FirebaseAuth firebaseAuth) { private void attachRecyclerViewAdapter() { Query lastFifty = mChatRef.limitToLast(50); - mRecyclerViewAdapter = new FirebaseRecyclerAdapter( - Chat.class, R.layout.message, ChatHolder.class, lastFifty) { - - @Override - public void populateViewHolder(ChatHolder chatView, Chat chat, int position) { - chatView.setName(chat.getName()); - chatView.setText(chat.getMessage()); + mRecyclerViewAdapter = + new FirebaseIndexRecyclerAdapter( + Chat.class, + R.layout.message, + ChatHolder.class, + mChatIndicesRef, + lastFifty) { + @Override + public void populateViewHolder(ChatHolder chatView, Chat chat, int position) { + chatView.setName(chat.getName()); + chatView.setText(chat.getMessage()); - FirebaseUser currentUser = mAuth.getCurrentUser(); - if (currentUser != null && chat.getUid().equals(currentUser.getUid())) { - chatView.setIsSender(true); - } else { - chatView.setIsSender(false); - } - } - }; + FirebaseUser currentUser = mAuth.getCurrentUser(); + if (currentUser != null && chat.getUid().equals(currentUser.getUid())) { + chatView.setIsSender(true); + } else { + chatView.setIsSender(false); + } + } + }; // Scroll to bottom on new messages mRecyclerViewAdapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() { @Override public void onItemRangeInserted(int positionStart, int itemCount) { - mManager.smoothScrollToPosition(mMessages, null, mRecyclerViewAdapter.getItemCount()); + mManager.smoothScrollToPosition(mMessages, + null, + mRecyclerViewAdapter.getItemCount()); } }); @@ -168,17 +168,25 @@ public void onItemRangeInserted(int positionStart, int itemCount) { private void signInAnonymously() { Toast.makeText(this, "Signing in...", Toast.LENGTH_SHORT).show(); mAuth.signInAnonymously() - .addOnCompleteListener(this, new OnCompleteListener() { + .addOnSuccessListener(this, new OnSuccessListener() { + @Override + public void onSuccess(AuthResult result) { + attachRecyclerViewAdapter(); + } + }) + .addOnCompleteListener(new OnCompleteListener() { @Override public void onComplete(@NonNull Task task) { - Log.d(TAG, "signInAnonymously:onComplete:" + task.isSuccessful()); if (task.isSuccessful()) { - Toast.makeText(ChatActivity.this, "Signed In", - Toast.LENGTH_SHORT).show(); - attachRecyclerViewAdapter(); + Toast.makeText(getApplicationContext(), + R.string.signed_in, + Toast.LENGTH_SHORT) + .show(); } else { - Toast.makeText(ChatActivity.this, "Sign In Failed", - Toast.LENGTH_SHORT).show(); + Toast.makeText(getApplicationContext(), + R.string.sign_in_failed, + Toast.LENGTH_SHORT) + .show(); } } }); @@ -193,96 +201,4 @@ public void updateUI() { mSendButton.setEnabled(isSignedIn()); mMessageEdit.setEnabled(isSignedIn()); } - - public static class Chat { - private String mName; - private String mMessage; - private String mUid; - - public Chat() { - // Needed for Firebase - } - - public Chat(String name, String message, String uid) { - mName = name; - mMessage = message; - mUid = uid; - } - - public String getName() { - return mName; - } - - public void setName(String name) { - mName = name; - } - - public String getMessage() { - return mMessage; - } - - public void setMessage(String message) { - mMessage = message; - } - - public String getUid() { - return mUid; - } - - public void setUid(String uid) { - mUid = uid; - } - } - - public static class ChatHolder extends RecyclerView.ViewHolder { - private final TextView mNameField; - private final TextView mTextField; - private final FrameLayout mLeftArrow; - private final FrameLayout mRightArrow; - private final RelativeLayout mMessageContainer; - private final LinearLayout mMessage; - private final int mGreen300; - private final int mGray300; - - public ChatHolder(View itemView) { - super(itemView); - mNameField = (TextView) itemView.findViewById(R.id.name_text); - mTextField = (TextView) itemView.findViewById(R.id.message_text); - mLeftArrow = (FrameLayout) itemView.findViewById(R.id.left_arrow); - mRightArrow = (FrameLayout) itemView.findViewById(R.id.right_arrow); - mMessageContainer = (RelativeLayout) itemView.findViewById(R.id.message_container); - mMessage = (LinearLayout) itemView.findViewById(R.id.message); - mGreen300 = ContextCompat.getColor(itemView.getContext(), R.color.material_green_300); - mGray300 = ContextCompat.getColor(itemView.getContext(), R.color.material_gray_300); - } - - public void setIsSender(boolean isSender) { - final int color; - if (isSender) { - color = mGreen300; - mLeftArrow.setVisibility(View.GONE); - mRightArrow.setVisibility(View.VISIBLE); - mMessageContainer.setGravity(Gravity.END); - } else { - color = mGray300; - mLeftArrow.setVisibility(View.VISIBLE); - mRightArrow.setVisibility(View.GONE); - mMessageContainer.setGravity(Gravity.START); - } - - ((GradientDrawable) mMessage.getBackground()).setColor(color); - ((RotateDrawable) mLeftArrow.getBackground()).getDrawable() - .setColorFilter(color, PorterDuff.Mode.SRC); - ((RotateDrawable) mRightArrow.getBackground()).getDrawable() - .setColorFilter(color, PorterDuff.Mode.SRC); - } - - public void setName(String name) { - mNameField.setText(name); - } - - public void setText(String text) { - mTextField.setText(text); - } - } } diff --git a/app/src/main/java/com/firebase/uidemo/database/ChatHolder.java b/app/src/main/java/com/firebase/uidemo/database/ChatHolder.java new file mode 100644 index 000000000..7c95dccdb --- /dev/null +++ b/app/src/main/java/com/firebase/uidemo/database/ChatHolder.java @@ -0,0 +1,67 @@ +package com.firebase.uidemo.database; + +import android.graphics.PorterDuff; +import android.graphics.drawable.GradientDrawable; +import android.graphics.drawable.RotateDrawable; +import android.support.v4.content.ContextCompat; +import android.support.v7.widget.RecyclerView; +import android.view.Gravity; +import android.view.View; +import android.widget.FrameLayout; +import android.widget.LinearLayout; +import android.widget.RelativeLayout; +import android.widget.TextView; + +import com.firebase.uidemo.R; + +public class ChatHolder extends RecyclerView.ViewHolder { + private final TextView mNameField; + private final TextView mTextField; + private final FrameLayout mLeftArrow; + private final FrameLayout mRightArrow; + private final RelativeLayout mMessageContainer; + private final LinearLayout mMessage; + private final int mGreen300; + private final int mGray300; + + public ChatHolder(View itemView) { + super(itemView); + mNameField = (TextView) itemView.findViewById(R.id.name_text); + mTextField = (TextView) itemView.findViewById(R.id.message_text); + mLeftArrow = (FrameLayout) itemView.findViewById(R.id.left_arrow); + mRightArrow = (FrameLayout) itemView.findViewById(R.id.right_arrow); + mMessageContainer = (RelativeLayout) itemView.findViewById(R.id.message_container); + mMessage = (LinearLayout) itemView.findViewById(R.id.message); + mGreen300 = ContextCompat.getColor(itemView.getContext(), R.color.material_green_300); + mGray300 = ContextCompat.getColor(itemView.getContext(), R.color.material_gray_300); + } + + public void setIsSender(boolean isSender) { + final int color; + if (isSender) { + color = mGreen300; + mLeftArrow.setVisibility(View.GONE); + mRightArrow.setVisibility(View.VISIBLE); + mMessageContainer.setGravity(Gravity.END); + } else { + color = mGray300; + mLeftArrow.setVisibility(View.VISIBLE); + mRightArrow.setVisibility(View.GONE); + mMessageContainer.setGravity(Gravity.START); + } + + ((GradientDrawable) mMessage.getBackground()).setColor(color); + ((RotateDrawable) mLeftArrow.getBackground()).getDrawable() + .setColorFilter(color, PorterDuff.Mode.SRC); + ((RotateDrawable) mRightArrow.getBackground()).getDrawable() + .setColorFilter(color, PorterDuff.Mode.SRC); + } + + public void setName(String name) { + mNameField.setText(name); + } + + public void setText(String text) { + mTextField.setText(text); + } +} diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2fe2b9f42..6a0645fd7 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -38,6 +38,8 @@ No internet connection You are signed in! Sign out + Signed In + Sign In Failed Delete account Sign out failed Delete account failed diff --git a/database/src/androidTest/java/com/firebase/ui/database/FirebaseArrayOfObjectsTest.java b/database/src/androidTest/java/com/firebase/ui/database/FirebaseArrayOfObjectsTest.java index f7420eee7..589a703c5 100644 --- a/database/src/androidTest/java/com/firebase/ui/database/FirebaseArrayOfObjectsTest.java +++ b/database/src/androidTest/java/com/firebase/ui/database/FirebaseArrayOfObjectsTest.java @@ -42,6 +42,7 @@ public class FirebaseArrayOfObjectsTest extends InstrumentationTestCase { @Before public void setUp() throws Exception { + super.setUp(); FirebaseApp app = getAppInstance(getInstrumentation().getContext()); mRef = FirebaseDatabase.getInstance(app).getReference() .child("firebasearray").child("objects"); @@ -50,6 +51,7 @@ public void setUp() throws Exception { runAndWaitUntil(mArray, new Runnable() { @Override public void run() { + mArray.startListening(); for (int i = 1; i <= 3; i++) { mRef.push().setValue(new Bean(i, "Text " + i, i % 2 == 0), i); } @@ -57,7 +59,7 @@ public void run() { }, new Callable() { @Override public Boolean call() throws Exception { - return mArray.getCount() == 3; + return mArray.size() == 3; } } ); @@ -68,20 +70,17 @@ public void tearDown() throws Exception { if (mRef != null) { mRef.getRoot().removeValue(); } - - if (mArray != null) { - mArray.cleanup(); - } + super.tearDown(); } @Test public void testSize() throws Exception { - assertEquals(3, mArray.getCount()); + assertEquals(3, mArray.size()); } @Test public void testPushIncreasesSize() throws Exception { - assertEquals(3, mArray.getCount()); + assertEquals(3, mArray.size()); runAndWaitUntil(mArray, new Runnable() { public void run() { mRef.push().setValue(new Bean(4)); @@ -89,7 +88,7 @@ public void run() { }, new Callable() { @Override public Boolean call() throws Exception { - return mArray.getCount() == 4; + return mArray.size() == 4; } }); } @@ -103,7 +102,7 @@ public void run() { }, new Callable() { @Override public Boolean call() throws Exception { - return mArray.getItem(3).getValue(Bean.class).getNumber() == 4; + return mArray.get(3).getValue(Bean.class).getNumber() == 4; } }); } @@ -116,7 +115,7 @@ public void run() { } }, new Callable() { public Boolean call() throws Exception { - return mArray.getItem(3).getValue(Bean.class).getNumber() == 3 && mArray.getItem(0) + return mArray.get(3).getValue(Bean.class).getNumber() == 3 && mArray.get(0) .getValue(Bean.class) .getNumber() == 4; } @@ -127,7 +126,7 @@ public Boolean call() throws Exception { public void testChangePriorities() throws Exception { runAndWaitUntil(mArray, new Runnable() { public void run() { - mArray.getItem(2).getRef().setPriority(0.5); + mArray.get(2).getRef().setPriority(0.5); } }, new Callable() { public Boolean call() throws Exception { diff --git a/database/src/androidTest/java/com/firebase/ui/database/FirebaseArrayTest.java b/database/src/androidTest/java/com/firebase/ui/database/FirebaseArrayTest.java index b79a4fcf3..629374515 100644 --- a/database/src/androidTest/java/com/firebase/ui/database/FirebaseArrayTest.java +++ b/database/src/androidTest/java/com/firebase/ui/database/FirebaseArrayTest.java @@ -41,19 +41,21 @@ public class FirebaseArrayTest extends InstrumentationTestCase { @Before public void setUp() throws Exception { + super.setUp(); FirebaseApp app = getAppInstance(getInstrumentation().getContext()); mRef = FirebaseDatabase.getInstance(app).getReference().child("firebasearray"); mArray = new FirebaseArray(mRef); mRef.removeValue(); runAndWaitUntil(mArray, new Runnable() { public void run() { + mArray.startListening(); for (int i = 1; i <= 3; i++) { mRef.push().setValue(i, i); } } }, new Callable() { public Boolean call() throws Exception { - return mArray.getCount() == 3; + return mArray.size() == 3; } }); } @@ -63,20 +65,17 @@ public void tearDown() throws Exception { if (mRef != null) { mRef.getRoot().removeValue(); } - - if (mArray != null) { - mArray.cleanup(); - } + super.tearDown(); } @Test public void testSize() throws Exception { - assertEquals(3, mArray.getCount()); + assertEquals(3, mArray.size()); } @Test public void testPushIncreasesSize() throws Exception { - assertEquals(3, mArray.getCount()); + assertEquals(3, mArray.size()); runAndWaitUntil(mArray, new Runnable() { public void run() { mRef.push().setValue(4); @@ -84,7 +83,7 @@ public void run() { }, new Callable() { @Override public Boolean call() throws Exception { - return mArray.getCount() == 4; + return mArray.size() == 4; } }); } @@ -98,7 +97,7 @@ public void run() { }, new Callable() { @Override public Boolean call() throws Exception { - return mArray.getItem(3).getValue(Integer.class).equals(4); + return mArray.get(3).getValue(Integer.class).equals(4); } }); } @@ -111,8 +110,8 @@ public void run() { } }, new Callable() { public Boolean call() throws Exception { - return mArray.getItem(3).getValue(Integer.class).equals(3) - && mArray.getItem(0).getValue(Integer.class).equals(4); + return mArray.get(3).getValue(Integer.class).equals(3) + && mArray.get(0).getValue(Integer.class).equals(4); } }); } @@ -121,7 +120,7 @@ public Boolean call() throws Exception { public void testChangePriorities() throws Exception { runAndWaitUntil(mArray, new Runnable() { public void run() { - mArray.getItem(2).getRef().setPriority(0.5); + mArray.get(2).getRef().setPriority(0.5); } }, new Callable() { public Boolean call() throws Exception { diff --git a/database/src/androidTest/java/com/firebase/ui/database/FirebaseIndexArrayOfObjectsTest.java b/database/src/androidTest/java/com/firebase/ui/database/FirebaseIndexArrayOfObjectsTest.java index ce833f74b..074a12e99 100644 --- a/database/src/androidTest/java/com/firebase/ui/database/FirebaseIndexArrayOfObjectsTest.java +++ b/database/src/androidTest/java/com/firebase/ui/database/FirebaseIndexArrayOfObjectsTest.java @@ -32,13 +32,14 @@ import static com.firebase.ui.database.TestUtils.getAppInstance; import static com.firebase.ui.database.TestUtils.getBean; import static com.firebase.ui.database.TestUtils.runAndWaitUntil; +import static com.firebase.ui.database.TestUtils.setJoinResolver; @RunWith(AndroidJUnit4.class) @SmallTest public class FirebaseIndexArrayOfObjectsTest extends InstrumentationTestCase { private DatabaseReference mRef; private DatabaseReference mKeyRef; - private FirebaseArray mArray; + private FirebaseIndexArray mArray; @Before public void setUp() throws Exception { @@ -47,13 +48,15 @@ public void setUp() throws Exception { mRef = databaseInstance.getReference().child("firebasearray").child("objects"); mKeyRef = databaseInstance.getReference().child("firebaseindexarray").child("objects"); - mArray = new FirebaseIndexArray(mKeyRef, mRef); + mArray = new FirebaseIndexArray(mKeyRef); + setJoinResolver(mArray, mRef); mRef.removeValue(); mKeyRef.removeValue(); runAndWaitUntil(mArray, new Runnable() { @Override public void run() { + mArray.startListening(); for (int i = 1; i <= 3; i++) { setValue(new Bean(i, "Text " + i, i % 2 == 0), i); } @@ -61,7 +64,7 @@ public void run() { }, new Callable() { @Override public Boolean call() throws Exception { - return mArray.getCount() == 3; + return mArray.size() == 3; } } ); @@ -72,20 +75,17 @@ public void tearDown() throws Exception { if (mRef != null) { mRef.getRoot().removeValue(); } - - if (mArray != null) { - mArray.cleanup(); - } + super.tearDown(); } @Test public void testSize() throws Exception { - assertEquals(3, mArray.getCount()); + assertEquals(3, mArray.size()); } @Test public void testPushIncreasesSize() throws Exception { - assertEquals(3, mArray.getCount()); + assertEquals(3, mArray.size()); runAndWaitUntil(mArray, new Runnable() { public void run() { setValue(new Bean(4), null); @@ -93,7 +93,7 @@ public void run() { }, new Callable() { @Override public Boolean call() throws Exception { - return mArray.getCount() == 4; + return mArray.size() == 4; } }); } @@ -107,7 +107,7 @@ public void run() { }, new Callable() { @Override public Boolean call() throws Exception { - return mArray.getItem(3).getValue(Bean.class).getNumber() == 4; + return mArray.get(3).getValue(Bean.class).getNumber() == 4; } }); } @@ -120,7 +120,7 @@ public void run() { } }, new Callable() { public Boolean call() throws Exception { - return mArray.getItem(3).getValue(Bean.class).getNumber() == 3 && mArray.getItem(0) + return mArray.get(3).getValue(Bean.class).getNumber() == 3 && mArray.get(0) .getValue(Bean.class) .getNumber() == 4; } @@ -131,7 +131,7 @@ public Boolean call() throws Exception { public void testChangePriorities() throws Exception { runAndWaitUntil(mArray, new Runnable() { public void run() { - mKeyRef.child(mArray.getItem(2).getKey()).setPriority(0.5); + mKeyRef.child(mArray.get(2).getKey()).setPriority(0.5); } }, new Callable() { public Boolean call() throws Exception { diff --git a/database/src/androidTest/java/com/firebase/ui/database/FirebaseIndexArrayTest.java b/database/src/androidTest/java/com/firebase/ui/database/FirebaseIndexArrayTest.java index 71d62f96e..ab2f0a3c4 100644 --- a/database/src/androidTest/java/com/firebase/ui/database/FirebaseIndexArrayTest.java +++ b/database/src/androidTest/java/com/firebase/ui/database/FirebaseIndexArrayTest.java @@ -31,6 +31,7 @@ import static com.firebase.ui.database.TestUtils.getAppInstance; import static com.firebase.ui.database.TestUtils.isValuesEqual; import static com.firebase.ui.database.TestUtils.runAndWaitUntil; +import static com.firebase.ui.database.TestUtils.setJoinResolver; @RunWith(AndroidJUnit4.class) @SmallTest @@ -41,18 +42,21 @@ public class FirebaseIndexArrayTest extends InstrumentationTestCase { @Before public void setUp() throws Exception { + super.setUp(); FirebaseDatabase databaseInstance = FirebaseDatabase.getInstance(getAppInstance(getInstrumentation().getContext())); mRef = databaseInstance.getReference().child("firebasearray"); mKeyRef = databaseInstance.getReference().child("firebaseindexarray"); - mArray = new FirebaseIndexArray(mKeyRef, mRef); + mArray = new FirebaseIndexArray(mKeyRef); + setJoinResolver(mArray, mRef); mRef.removeValue(); mKeyRef.removeValue(); runAndWaitUntil(mArray, new Runnable() { @Override public void run() { + mArray.startListening(); for (int i = 1; i <= 3; i++) { setValue(i, i); } @@ -60,7 +64,7 @@ public void run() { }, new Callable() { @Override public Boolean call() throws Exception { - return mArray.getCount() == 3; + return mArray.size() == 3; } }); } @@ -70,20 +74,17 @@ public void tearDown() throws Exception { if (mRef != null) { mRef.getRoot().removeValue(); } - - if (mArray != null) { - mArray.cleanup(); - } + super.tearDown(); } @Test public void testSize() throws Exception { - assertEquals(3, mArray.getCount()); + assertEquals(3, mArray.size()); } @Test public void testPushIncreasesSize() throws Exception { - assertEquals(3, mArray.getCount()); + assertEquals(3, mArray.size()); runAndWaitUntil(mArray, new Runnable() { public void run() { setValue(4, null); @@ -91,7 +92,7 @@ public void run() { }, new Callable() { @Override public Boolean call() throws Exception { - return mArray.getCount() == 4; + return mArray.size() == 4; } }); } @@ -105,7 +106,7 @@ public void run() { }, new Callable() { @Override public Boolean call() throws Exception { - return mArray.getItem(3).getValue(Integer.class).equals(4); + return mArray.get(3).getValue(Integer.class).equals(4); } }); } @@ -118,8 +119,8 @@ public void run() { } }, new Callable() { public Boolean call() throws Exception { - return mArray.getItem(3).getValue(Integer.class).equals(3) - && mArray.getItem(0).getValue(Integer.class).equals(4); + return mArray.get(3).getValue(Integer.class).equals(3) + && mArray.get(0).getValue(Integer.class).equals(4); } }); } @@ -128,7 +129,7 @@ public Boolean call() throws Exception { public void testChangePriorities() throws Exception { runAndWaitUntil(mArray, new Runnable() { public void run() { - mKeyRef.child(mArray.getItem(2).getKey()).setPriority(0.5); + mKeyRef.child(mArray.get(2).getKey()).setPriority(0.5); } }, new Callable() { public Boolean call() throws Exception { diff --git a/database/src/androidTest/java/com/firebase/ui/database/utils/TestUtils.java b/database/src/androidTest/java/com/firebase/ui/database/utils/TestUtils.java index 7e920561f..3703f29c1 100644 --- a/database/src/androidTest/java/com/firebase/ui/database/utils/TestUtils.java +++ b/database/src/androidTest/java/com/firebase/ui/database/utils/TestUtils.java @@ -5,7 +5,10 @@ import com.firebase.ui.database.utils.Bean; import com.google.firebase.FirebaseApp; import com.google.firebase.FirebaseOptions; +import com.google.firebase.database.DataSnapshot; import com.google.firebase.database.DatabaseError; +import com.google.firebase.database.DatabaseReference; +import com.google.firebase.database.Query; import junit.framework.AssertionFailedError; @@ -26,25 +29,45 @@ public static FirebaseApp getAppInstance(Context context) { } } - public static FirebaseApp initializeApp(Context context) { + private static FirebaseApp initializeApp(Context context) { return FirebaseApp.initializeApp(context, new FirebaseOptions.Builder() .setApplicationId("fir-ui-tests") .setDatabaseUrl("https://fir-ui-tests.firebaseio.com/") .build(), APP_NAME); } + public static void setJoinResolver(FirebaseIndexArray array, final DatabaseReference ref) { + array.setJoinResolver(new JoinResolver() { + @Override + public Query onJoin(DataSnapshot keySnapshot, String previousChildKey) { + return ref.child(keySnapshot.getKey()); + } + + @Override + public Query onDisjoin(DataSnapshot keySnapshot) { + return ref.child(keySnapshot.getKey()); + } + + @Override + public void onJoinFailed(int index, DataSnapshot snapshot) { + throw new IllegalStateException(index + ": " + snapshot); + } + }); + } + public static void runAndWaitUntil(FirebaseArray array, Runnable task, Callable done) throws InterruptedException { final Semaphore semaphore = new Semaphore(0); - array.setOnChangedListener(new FirebaseArray.OnChangedListener() { + array.setChangeListener(new FirebaseArray.ChangeListener() { + @Override public void onChanged(EventType type, int index, int oldIndex) { semaphore.release(); } @Override - public void onCancelled(DatabaseError databaseError) { - throw new IllegalStateException(databaseError.toException()); + public void onCancelled(DatabaseError error) { + throw new IllegalStateException(error.toException()); } }); task.run(); @@ -62,13 +85,12 @@ public void onCancelled(DatabaseError databaseError) { if (!isDone) { throw new AssertionFailedError(); } - array.setOnChangedListener(null); } public static boolean isValuesEqual(FirebaseArray array, int[] expected) { - if (array.getCount() != expected.length) return false; - for (int i = 0; i < array.getCount(); i++) { - if (!array.getItem(i).getValue(Integer.class).equals(expected[i])) { + if (array.size() != expected.length) return false; + for (int i = 0; i < array.size(); i++) { + if (!array.get(i).getValue(Integer.class).equals(expected[i])) { return false; } } @@ -76,6 +98,6 @@ public static boolean isValuesEqual(FirebaseArray array, int[] expected) { } public static Bean getBean(FirebaseArray array, int index) { - return array.getItem(index).getValue(Bean.class); + return array.get(index).getValue(Bean.class); } } diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseArray.java b/database/src/main/java/com/firebase/ui/database/FirebaseArray.java index 7acba6d5b..0fe3a4139 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseArray.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseArray.java @@ -14,6 +14,8 @@ 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.DatabaseError; @@ -26,47 +28,50 @@ * This class implements an array-like collection on top of a Firebase location. */ class FirebaseArray implements ChildEventListener { - public interface ChangedListener { + public interface ChangeListener { enum EventType {ADDED, CHANGED, REMOVED, MOVED} void onChanged(EventType type, int index, int oldIndex); - void onCancelled(DatabaseError databaseError); + void onCancelled(DatabaseError error); } + protected ChangeListener mListener; + protected boolean mIsListening; private Query mQuery; - private ChangedListener mListener; private List mSnapshots = new ArrayList<>(); public FirebaseArray(Query ref) { mQuery = ref; + } + + public void setChangeListener(@NonNull ChangeListener listener) { + if (mIsListening && listener == null) { + throw new IllegalStateException("Listener cannot be null."); + } + mListener = listener; + } + + public void startListening() { + if (mListener == null) throw new IllegalStateException("Listener cannot be null."); mQuery.addChildEventListener(this); + mIsListening = true; } - public void cleanup() { + public void stopListening() { mQuery.removeEventListener(this); + mSnapshots.clear(); + mIsListening = false; } - public int getCount() { + public int size() { return mSnapshots.size(); } - public DataSnapshot getItem(int index) { + public DataSnapshot get(int index) { return mSnapshots.get(index); } - private int getIndexForKey(String key) { - int index = 0; - for (DataSnapshot snapshot : mSnapshots) { - if (snapshot.getKey().equals(key)) { - return index; - } else { - index++; - } - } - throw new IllegalArgumentException("Key not found"); - } - @Override public void onChildAdded(DataSnapshot snapshot, String previousChildKey) { int index = 0; @@ -74,21 +79,21 @@ public void onChildAdded(DataSnapshot snapshot, String previousChildKey) { index = getIndexForKey(previousChildKey) + 1; } mSnapshots.add(index, snapshot); - notifyChangedListeners(ChangedListener.EventType.ADDED, index); + notifyChangeListener(ChangeListener.EventType.ADDED, index); } @Override public void onChildChanged(DataSnapshot snapshot, String previousChildKey) { int index = getIndexForKey(snapshot.getKey()); mSnapshots.set(index, snapshot); - notifyChangedListeners(ChangedListener.EventType.CHANGED, index); + notifyChangeListener(ChangeListener.EventType.CHANGED, index); } @Override public void onChildRemoved(DataSnapshot snapshot) { int index = getIndexForKey(snapshot.getKey()); mSnapshots.remove(index); - notifyChangedListeners(ChangedListener.EventType.REMOVED, index); + notifyChangeListener(ChangeListener.EventType.REMOVED, index); } @Override @@ -97,31 +102,27 @@ public void onChildMoved(DataSnapshot snapshot, String previousChildKey) { mSnapshots.remove(oldIndex); int newIndex = previousChildKey == null ? 0 : (getIndexForKey(previousChildKey) + 1); mSnapshots.add(newIndex, snapshot); - notifyChangedListeners(ChangedListener.EventType.MOVED, newIndex, oldIndex); + mListener.onChanged(ChangeListener.EventType.MOVED, newIndex, oldIndex); } @Override public void onCancelled(DatabaseError error) { - notifyCancelledListeners(error); - } - - public void setOnChangedListener(ChangedListener listener) { - mListener = listener; + mListener.onCancelled(error); } - protected void notifyChangedListeners(ChangedListener.EventType type, int index) { - notifyChangedListeners(type, index, -1); - } - - protected void notifyChangedListeners(ChangedListener.EventType type, int index, int oldIndex) { - if (mListener != null) { - mListener.onChanged(type, index, oldIndex); + private int getIndexForKey(String key) { + int index = 0; + for (DataSnapshot snapshot : mSnapshots) { + if (snapshot.getKey().equals(key)) { + return index; + } else { + index++; + } } + throw new IllegalArgumentException("Key not found"); } - protected void notifyCancelledListeners(DatabaseError databaseError) { - if (mListener != null) { - mListener.onCancelled(databaseError); - } + protected void notifyChangeListener(ChangeListener.EventType type, int index) { + mListener.onChanged(type, index, -1); } } 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 e2ea7075c..8b0bde362 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.NonNull; import android.util.Log; import com.google.firebase.database.DataSnapshot; @@ -30,85 +31,96 @@ class FirebaseIndexArray extends FirebaseArray { private static final String TAG = "FirebaseIndexArray"; + private static final ChangeListener NOOP_CHANGE_LISTENER = new ChangeListener() { + @Override + public void onChanged(EventType type, int index, int oldIndex) { + } + + @Override + public void onCancelled(DatabaseError error) { + } + }; - private Query mQuery; - private ChangedListener mListener; - private IndexMismatchListener mIndexMismatchListener; + private ChangeListener mListenerCopy; + private JoinResolver mJoinResolver; private Map mRefs = new HashMap<>(); private List mDataSnapshots = new ArrayList<>(); - public FirebaseIndexArray(Query keyRef, Query dataRef) { + public FirebaseIndexArray(Query keyRef) { super(keyRef); - mQuery = dataRef; } @Override - public void cleanup() { - super.cleanup(); + public void setChangeListener(@NonNull ChangeListener listener) { + super.setChangeListener(listener); + mListenerCopy = listener; + } + + public void setJoinResolver(@NonNull JoinResolver joinResolver) { + if (mIsListening && joinResolver == null) { + throw new IllegalStateException("Join resolver cannot be null."); + } + mJoinResolver = joinResolver; + } + + @Override + public void startListening() { + if (mJoinResolver == null) throw new IllegalStateException("Join resolver cannot be null."); + super.startListening(); + } + + @Override + public void stopListening() { + super.stopListening(); Set refs = new HashSet<>(mRefs.keySet()); for (Query ref : refs) { ref.removeEventListener(mRefs.remove(ref)); } + mDataSnapshots.clear(); } @Override - public int getCount() { + public int size() { return mDataSnapshots.size(); } @Override - public DataSnapshot getItem(int index) { + public DataSnapshot get(int index) { return mDataSnapshots.get(index); } - private int getIndexForKey(String key) { - int dataCount = getCount(); - int index = 0; - for (int keyIndex = 0; index < dataCount; keyIndex++) { - String superKey = super.getItem(keyIndex).getKey(); - if (key.equals(superKey)) { - break; - } else if (mDataSnapshots.get(index).getKey().equals(superKey)) { - index++; - } - } - return index; - } - - private boolean isMatch(int index, String key) { - return index >= 0 && index < getCount() && mDataSnapshots.get(index).getKey().equals(key); - } - @Override public void onChildAdded(DataSnapshot keySnapshot, String previousChildKey) { - super.setOnChangedListener(null); + super.setChangeListener(NOOP_CHANGE_LISTENER); super.onChildAdded(keySnapshot, previousChildKey); - super.setOnChangedListener(mListener); + super.setChangeListener(mListenerCopy); - Query ref = mQuery.getRef().child(keySnapshot.getKey()); + Query ref = mJoinResolver.onJoin(keySnapshot, previousChildKey); mRefs.put(ref, ref.addValueEventListener(new DataRefListener())); } @Override public void onChildChanged(DataSnapshot snapshot, String previousChildKey) { - super.setOnChangedListener(null); + super.setChangeListener(NOOP_CHANGE_LISTENER); super.onChildChanged(snapshot, previousChildKey); - super.setOnChangedListener(mListener); + super.setChangeListener(mListenerCopy); } @Override public void onChildRemoved(DataSnapshot keySnapshot) { String key = keySnapshot.getKey(); int index = getIndexForKey(key); - mQuery.getRef().child(key).removeEventListener(mRefs.remove(mQuery.getRef().child(key))); - super.setOnChangedListener(null); + Query removeQuery = mJoinResolver.onDisjoin(keySnapshot); + removeQuery.removeEventListener(mRefs.remove(removeQuery)); + + super.setChangeListener(NOOP_CHANGE_LISTENER); super.onChildRemoved(keySnapshot); - super.setOnChangedListener(mListener); + super.setChangeListener(mListenerCopy); if (isMatch(index, key)) { mDataSnapshots.remove(index); - notifyChangedListeners(ChangedListener.EventType.REMOVED, index); + notifyChangeListener(ChangeListener.EventType.REMOVED, index); } } @@ -117,15 +129,15 @@ public void onChildMoved(DataSnapshot keySnapshot, String previousChildKey) { String key = keySnapshot.getKey(); int oldIndex = getIndexForKey(key); - super.setOnChangedListener(null); + super.setChangeListener(NOOP_CHANGE_LISTENER); super.onChildMoved(keySnapshot, previousChildKey); - super.setOnChangedListener(mListener); + super.setChangeListener(mListenerCopy); if (isMatch(oldIndex, key)) { DataSnapshot snapshot = mDataSnapshots.remove(oldIndex); int newIndex = getIndexForKey(key); mDataSnapshots.add(newIndex, snapshot); - notifyChangedListeners(ChangedListener.EventType.MOVED, newIndex, oldIndex); + mListener.onChanged(ChangeListener.EventType.MOVED, newIndex, oldIndex); } } @@ -136,20 +148,22 @@ public void onCancelled(DatabaseError error) { super.onCancelled(error); } - @Override - public void setOnChangedListener(ChangedListener listener) { - super.setOnChangedListener(listener); - mListener = listener; - } - - public void setOnIndexMismatchListener(IndexMismatchListener listener) { - mIndexMismatchListener = listener; + private int getIndexForKey(String key) { + int dataCount = size(); + int index = 0; + for (int keyIndex = 0; index < dataCount; keyIndex++) { + String superKey = super.get(keyIndex).getKey(); + if (key.equals(superKey)) { + break; + } else if (mDataSnapshots.get(index).getKey().equals(superKey)) { + index++; + } + } + return index; } - protected void notifyIndexMismatchListeners(int index, DataSnapshot snapshot) { - if (mIndexMismatchListener != null) { - mIndexMismatchListener.onIndexMismatch(index, snapshot); - } + private boolean isMatch(int index, String key) { + return index >= 0 && index < size() && mDataSnapshots.get(index).getKey().equals(key); } private class DataRefListener implements ValueEventListener { @@ -161,24 +175,24 @@ public void onDataChange(DataSnapshot snapshot) { if (snapshot.getValue() != null) { if (!isMatch(index, key)) { mDataSnapshots.add(index, snapshot); - notifyChangedListeners(ChangedListener.EventType.ADDED, index); + notifyChangeListener(ChangeListener.EventType.ADDED, index); } else { mDataSnapshots.set(index, snapshot); - notifyChangedListeners(ChangedListener.EventType.CHANGED, index); + notifyChangeListener(ChangeListener.EventType.CHANGED, index); } } else { if (isMatch(index, key)) { mDataSnapshots.remove(index); - notifyChangedListeners(ChangedListener.EventType.REMOVED, index); + notifyChangeListener(ChangeListener.EventType.REMOVED, index); } else { - notifyIndexMismatchListeners(index, snapshot); + mJoinResolver.onJoinFailed(index, snapshot); } } } @Override public void onCancelled(DatabaseError error) { - notifyCancelledListeners(error); + mListener.onCancelled(error); } } } diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseIndexListAdapter.java b/database/src/main/java/com/firebase/ui/database/FirebaseIndexListAdapter.java index a6595209c..214928a62 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseIndexListAdapter.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseIndexListAdapter.java @@ -37,9 +37,11 @@ * contained in the children of the given Firebase location */ public abstract class FirebaseIndexListAdapter extends FirebaseListAdapter - implements IndexMismatchListener { + implements JoinResolver { private static final String TAG = "IndexListAdapter"; + protected Query mDataQuery; + /** * @param activity The activity containing the ListView * @param modelClass Firebase will marshall the data at a location into @@ -59,18 +61,24 @@ public FirebaseIndexListAdapter(Activity activity, @LayoutRes int modelLayout, Query keyRef, Query dataRef) { - super(activity, modelClass, modelLayout, new FirebaseIndexArray(keyRef, dataRef)); - ((FirebaseIndexArray) mSnapshots).setOnIndexMismatchListener(this); + super(activity, modelClass, modelLayout, new FirebaseIndexArray(keyRef), false); + mDataQuery = dataRef; + ((FirebaseIndexArray) mSnapshots).setJoinResolver(this); + mSnapshots.startListening(); + } + + @Override + public Query onJoin(DataSnapshot keySnapshot, String previousChildKey) { + return mDataQuery.getRef().child(keySnapshot.getKey()); + } + + @Override + public Query onDisjoin(DataSnapshot keySnapshot) { + return mDataQuery.getRef().child(keySnapshot.getKey()); } - /** - * Called when a key in {@code keyRef} could not be found in {@code dataRef}. - * - * @param index The index of a {@code snapshot} in {@code keyRef} that could not be found in {@code dataRef}. - * @param snapshot The snapshot who's key could not be found in {@code dataRef}. - */ @Override - public void onIndexMismatch(int index, DataSnapshot snapshot) { + public void onJoinFailed(int index, DataSnapshot snapshot) { Log.w(TAG, "Key not found at ref " + snapshot.getRef() + " for index " + index + "."); } } diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseIndexRecyclerAdapter.java b/database/src/main/java/com/firebase/ui/database/FirebaseIndexRecyclerAdapter.java index 448b1f75b..692f810c6 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseIndexRecyclerAdapter.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseIndexRecyclerAdapter.java @@ -64,9 +64,11 @@ * @param The ViewHolder class that contains the Views in the layout that is shown for each object. */ public abstract class FirebaseIndexRecyclerAdapter - extends FirebaseRecyclerAdapter implements IndexMismatchListener { + extends FirebaseRecyclerAdapter implements JoinResolver { private static final String TAG = "IndexRecyclerAdapter"; + protected Query mDataQuery; + /** * @param modelClass Firebase will marshall the data at a location into an instance * of a class that you provide @@ -86,18 +88,24 @@ public FirebaseIndexRecyclerAdapter(Class modelClass, Class viewHolderClass, Query keyRef, Query dataRef) { - super(modelClass, modelLayout, viewHolderClass, new FirebaseIndexArray(keyRef, dataRef)); - ((FirebaseIndexArray) mSnapshots).setOnIndexMismatchListener(this); + super(modelClass, modelLayout, viewHolderClass, new FirebaseIndexArray(keyRef), false); + mDataQuery = dataRef; + ((FirebaseIndexArray) mSnapshots).setJoinResolver(this); + mSnapshots.startListening(); + } + + @Override + public Query onJoin(DataSnapshot keySnapshot, String previousChildKey) { + return mDataQuery.getRef().child(keySnapshot.getKey()); + } + + @Override + public Query onDisjoin(DataSnapshot keySnapshot) { + return mDataQuery.getRef().child(keySnapshot.getKey()); } - /** - * Called when a key in {@code keyRef} could not be found in {@code dataRef}. - * - * @param index The index of a {@code snapshot} in {@code keyRef} that could not be found in {@code dataRef}. - * @param snapshot The snapshot who's key could not be found in {@code dataRef}. - */ @Override - public void onIndexMismatch(int index, DataSnapshot snapshot) { + public void onJoinFailed(int index, DataSnapshot snapshot) { Log.w(TAG, "Key not found at ref " + snapshot.getRef() + " for index " + index + "."); } } diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseListAdapter.java b/database/src/main/java/com/firebase/ui/database/FirebaseListAdapter.java index 1b80d46ee..87b3d439f 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseListAdapter.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseListAdapter.java @@ -61,23 +61,25 @@ public abstract class FirebaseListAdapter extends BaseAdapter { FirebaseListAdapter(Activity activity, Class modelClass, @LayoutRes int modelLayout, - FirebaseArray snapshots) { + FirebaseArray snapshots, + boolean shouldStartListening) { mActivity = activity; mModelClass = modelClass; mLayout = modelLayout; mSnapshots = snapshots; - mSnapshots.setOnChangedListener(new FirebaseArray.ChangedListener() { + mSnapshots.setChangeListener(new FirebaseArray.ChangeListener() { @Override public void onChanged(EventType type, int index, int oldIndex) { notifyDataSetChanged(); } @Override - public void onCancelled(DatabaseError databaseError) { - FirebaseListAdapter.this.onCancelled(databaseError); + public void onCancelled(DatabaseError error) { + FirebaseListAdapter.this.onCancelled(error); } }); + if (shouldStartListening) mSnapshots.startListening(); } /** @@ -94,21 +96,21 @@ public FirebaseListAdapter(Activity activity, Class modelClass, int modelLayout, Query ref) { - this(activity, modelClass, modelLayout, new FirebaseArray(ref)); + this(activity, modelClass, modelLayout, new FirebaseArray(ref), true); } public void cleanup() { - mSnapshots.cleanup(); + mSnapshots.stopListening(); } @Override public int getCount() { - return mSnapshots.getCount(); + return mSnapshots.size(); } @Override public T getItem(int position) { - return parseSnapshot(mSnapshots.getItem(position)); + return parseSnapshot(mSnapshots.get(position)); } /** @@ -123,13 +125,13 @@ protected T parseSnapshot(DataSnapshot snapshot) { } public DatabaseReference getRef(int position) { - return mSnapshots.getItem(position).getRef(); + return mSnapshots.get(position).getRef(); } @Override public long getItemId(int i) { // http://stackoverflow.com/questions/5100071/whats-the-purpose-of-item-ids-in-android-listview-adapter - return mSnapshots.getItem(i).getKey().hashCode(); + return mSnapshots.get(i).getKey().hashCode(); } @Override diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseRecyclerAdapter.java b/database/src/main/java/com/firebase/ui/database/FirebaseRecyclerAdapter.java index bc1925ffc..e73a050fb 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseRecyclerAdapter.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseRecyclerAdapter.java @@ -83,13 +83,14 @@ public abstract class FirebaseRecyclerAdapter modelClass, @LayoutRes int modelLayout, Class viewHolderClass, - FirebaseArray snapshots) { + FirebaseArray snapshots, + boolean shouldStartListening) { mModelClass = modelClass; mModelLayout = modelLayout; mViewHolderClass = viewHolderClass; mSnapshots = snapshots; - mSnapshots.setOnChangedListener(new FirebaseArray.ChangedListener() { + mSnapshots.setChangeListener(new FirebaseArray.ChangeListener() { @Override public void onChanged(EventType type, int index, int oldIndex) { switch (type) { @@ -111,10 +112,11 @@ public void onChanged(EventType type, int index, int oldIndex) { } @Override - public void onCancelled(DatabaseError databaseError) { - FirebaseRecyclerAdapter.this.onCancelled(databaseError); + public void onCancelled(DatabaseError error) { + FirebaseRecyclerAdapter.this.onCancelled(error); } }); + if (shouldStartListening) mSnapshots.startListening(); } /** @@ -131,20 +133,20 @@ public FirebaseRecyclerAdapter(Class modelClass, int modelLayout, Class viewHolderClass, Query ref) { - this(modelClass, modelLayout, viewHolderClass, new FirebaseArray(ref)); + this(modelClass, modelLayout, viewHolderClass, new FirebaseArray(ref), true); } public void cleanup() { - mSnapshots.cleanup(); + mSnapshots.stopListening(); } @Override public int getItemCount() { - return mSnapshots.getCount(); + return mSnapshots.size(); } public T getItem(int position) { - return parseSnapshot(mSnapshots.getItem(position)); + return parseSnapshot(mSnapshots.get(position)); } /** @@ -159,13 +161,13 @@ protected T parseSnapshot(DataSnapshot snapshot) { } public DatabaseReference getRef(int position) { - return mSnapshots.getItem(position).getRef(); + return mSnapshots.get(position).getRef(); } @Override public long getItemId(int position) { // http://stackoverflow.com/questions/5100071/whats-the-purpose-of-item-ids-in-android-listview-adapter - return mSnapshots.getItem(position).getKey().hashCode(); + return mSnapshots.get(position).getKey().hashCode(); } @Override diff --git a/database/src/main/java/com/firebase/ui/database/IndexMismatchListener.java b/database/src/main/java/com/firebase/ui/database/IndexMismatchListener.java deleted file mode 100644 index bfd2e1482..000000000 --- a/database/src/main/java/com/firebase/ui/database/IndexMismatchListener.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.firebase.ui.database; - -import com.google.firebase.database.DataSnapshot; - -interface IndexMismatchListener { - void onIndexMismatch(int index, DataSnapshot snapshot); -} 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..da2c6895b --- /dev/null +++ b/database/src/main/java/com/firebase/ui/database/JoinResolver.java @@ -0,0 +1,33 @@ +package com.firebase.ui.database; + +import com.google.firebase.database.DataSnapshot; +import com.google.firebase.database.Query; + +interface JoinResolver { + /** + * Called after an {@code onChildAdded} event from {@code keyRef}. + * + * @param keySnapshot The snapshot supplied in {@code onChildAdded}. + * @param previousChildKey The previous child's key 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. + */ + Query onJoin(DataSnapshot keySnapshot, String previousChildKey); + + /** + * Called after an {@code onChildRemoved} 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}. + */ + Query onDisjoin(DataSnapshot keySnapshot); + + /** + * Called when a key in {@code keyRef} could not be found in {@code dataRef}. + * + * @param index The index of a {@code snapshot} in {@code keyRef} that could not be found in {@code dataRef}. + * @param snapshot The snapshot who's key could not be found in {@code dataRef}. + */ + void onJoinFailed(int index, DataSnapshot snapshot); +} From af39a1e47c433ae9f09205cb9fbbbb5849bcacd1 Mon Sep 17 00:00:00 2001 From: Alex Saveau Date: Wed, 14 Dec 2016 18:32:17 -0800 Subject: [PATCH 05/93] Cleanup Signed-off-by: Alex Saveau --- constants.gradle | 2 +- .../com/firebase/ui/database/FirebaseArrayOfObjectsTest.java | 5 ++--- .../java/com/firebase/ui/database/FirebaseArrayTest.java | 5 ++--- .../ui/database/FirebaseIndexArrayOfObjectsTest.java | 5 ++--- .../com/firebase/ui/database/FirebaseIndexArrayTest.java | 5 ++--- 5 files changed, 9 insertions(+), 13 deletions(-) diff --git a/constants.gradle b/constants.gradle index 8340dcade..717e2fcc7 100644 --- a/constants.gradle +++ b/constants.gradle @@ -1,5 +1,5 @@ project.ext.firebase_version = '10.0.1' -project.ext.support_library_version = '25.0.1' +project.ext.support_library_version = '25.1.0' project.ext.submodules = ['database', 'auth', 'storage'] project.ext.group = 'com.firebaseui' diff --git a/database/src/androidTest/java/com/firebase/ui/database/FirebaseArrayOfObjectsTest.java b/database/src/androidTest/java/com/firebase/ui/database/FirebaseArrayOfObjectsTest.java index 589a703c5..0ba5abffb 100644 --- a/database/src/androidTest/java/com/firebase/ui/database/FirebaseArrayOfObjectsTest.java +++ b/database/src/androidTest/java/com/firebase/ui/database/FirebaseArrayOfObjectsTest.java @@ -67,9 +67,8 @@ public Boolean call() throws Exception { @After public void tearDown() throws Exception { - if (mRef != null) { - mRef.getRoot().removeValue(); - } + mRef.getRoot().removeValue(); + mArray.stopListening(); super.tearDown(); } diff --git a/database/src/androidTest/java/com/firebase/ui/database/FirebaseArrayTest.java b/database/src/androidTest/java/com/firebase/ui/database/FirebaseArrayTest.java index 629374515..b664f4bad 100644 --- a/database/src/androidTest/java/com/firebase/ui/database/FirebaseArrayTest.java +++ b/database/src/androidTest/java/com/firebase/ui/database/FirebaseArrayTest.java @@ -62,9 +62,8 @@ public Boolean call() throws Exception { @After public void tearDown() throws Exception { - if (mRef != null) { - mRef.getRoot().removeValue(); - } + mRef.getRoot().removeValue(); + mArray.stopListening(); super.tearDown(); } diff --git a/database/src/androidTest/java/com/firebase/ui/database/FirebaseIndexArrayOfObjectsTest.java b/database/src/androidTest/java/com/firebase/ui/database/FirebaseIndexArrayOfObjectsTest.java index 074a12e99..d795b3c89 100644 --- a/database/src/androidTest/java/com/firebase/ui/database/FirebaseIndexArrayOfObjectsTest.java +++ b/database/src/androidTest/java/com/firebase/ui/database/FirebaseIndexArrayOfObjectsTest.java @@ -72,9 +72,8 @@ public Boolean call() throws Exception { @After public void tearDown() throws Exception { - if (mRef != null) { - mRef.getRoot().removeValue(); - } + mRef.getRoot().removeValue(); + mArray.stopListening(); super.tearDown(); } diff --git a/database/src/androidTest/java/com/firebase/ui/database/FirebaseIndexArrayTest.java b/database/src/androidTest/java/com/firebase/ui/database/FirebaseIndexArrayTest.java index ab2f0a3c4..58365e4ed 100644 --- a/database/src/androidTest/java/com/firebase/ui/database/FirebaseIndexArrayTest.java +++ b/database/src/androidTest/java/com/firebase/ui/database/FirebaseIndexArrayTest.java @@ -71,9 +71,8 @@ public Boolean call() throws Exception { @After public void tearDown() throws Exception { - if (mRef != null) { - mRef.getRoot().removeValue(); - } + mRef.getRoot().removeValue(); + mArray.stopListening(); super.tearDown(); } From 25fc4df0fc8f655b99f2b80de2903de42ca97cf5 Mon Sep 17 00:00:00 2001 From: Alex Saveau Date: Wed, 14 Dec 2016 18:35:14 -0800 Subject: [PATCH 06/93] Rename ChangeListener to ChangeEventListener Signed-off-by: Alex Saveau --- .../firebase/ui/database/utils/TestUtils.java | 2 +- .../firebase/ui/database/FirebaseArray.java | 16 ++++----- .../ui/database/FirebaseIndexArray.java | 34 +++++++++---------- .../ui/database/FirebaseListAdapter.java | 2 +- .../ui/database/FirebaseRecyclerAdapter.java | 2 +- 5 files changed, 28 insertions(+), 28 deletions(-) diff --git a/database/src/androidTest/java/com/firebase/ui/database/utils/TestUtils.java b/database/src/androidTest/java/com/firebase/ui/database/utils/TestUtils.java index 3703f29c1..d7a6fd5c9 100644 --- a/database/src/androidTest/java/com/firebase/ui/database/utils/TestUtils.java +++ b/database/src/androidTest/java/com/firebase/ui/database/utils/TestUtils.java @@ -59,7 +59,7 @@ public static void runAndWaitUntil(FirebaseArray array, Runnable task, Callable done) throws InterruptedException { final Semaphore semaphore = new Semaphore(0); - array.setChangeListener(new FirebaseArray.ChangeListener() { + array.setChangeEventListener(new FirebaseArray.ChangeEventListener() { @Override public void onChanged(EventType type, int index, int oldIndex) { semaphore.release(); diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseArray.java b/database/src/main/java/com/firebase/ui/database/FirebaseArray.java index 0fe3a4139..30b438ed5 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseArray.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseArray.java @@ -28,7 +28,7 @@ * This class implements an array-like collection on top of a Firebase location. */ class FirebaseArray implements ChildEventListener { - public interface ChangeListener { + public interface ChangeEventListener { enum EventType {ADDED, CHANGED, REMOVED, MOVED} void onChanged(EventType type, int index, int oldIndex); @@ -36,7 +36,7 @@ enum EventType {ADDED, CHANGED, REMOVED, MOVED} void onCancelled(DatabaseError error); } - protected ChangeListener mListener; + protected ChangeEventListener mListener; protected boolean mIsListening; private Query mQuery; private List mSnapshots = new ArrayList<>(); @@ -45,7 +45,7 @@ public FirebaseArray(Query ref) { mQuery = ref; } - public void setChangeListener(@NonNull ChangeListener listener) { + public void setChangeEventListener(@NonNull ChangeEventListener listener) { if (mIsListening && listener == null) { throw new IllegalStateException("Listener cannot be null."); } @@ -79,21 +79,21 @@ public void onChildAdded(DataSnapshot snapshot, String previousChildKey) { index = getIndexForKey(previousChildKey) + 1; } mSnapshots.add(index, snapshot); - notifyChangeListener(ChangeListener.EventType.ADDED, index); + notifyChangeListener(ChangeEventListener.EventType.ADDED, index); } @Override public void onChildChanged(DataSnapshot snapshot, String previousChildKey) { int index = getIndexForKey(snapshot.getKey()); mSnapshots.set(index, snapshot); - notifyChangeListener(ChangeListener.EventType.CHANGED, index); + notifyChangeListener(ChangeEventListener.EventType.CHANGED, index); } @Override public void onChildRemoved(DataSnapshot snapshot) { int index = getIndexForKey(snapshot.getKey()); mSnapshots.remove(index); - notifyChangeListener(ChangeListener.EventType.REMOVED, index); + notifyChangeListener(ChangeEventListener.EventType.REMOVED, index); } @Override @@ -102,7 +102,7 @@ public void onChildMoved(DataSnapshot snapshot, String previousChildKey) { mSnapshots.remove(oldIndex); int newIndex = previousChildKey == null ? 0 : (getIndexForKey(previousChildKey) + 1); mSnapshots.add(newIndex, snapshot); - mListener.onChanged(ChangeListener.EventType.MOVED, newIndex, oldIndex); + mListener.onChanged(ChangeEventListener.EventType.MOVED, newIndex, oldIndex); } @Override @@ -122,7 +122,7 @@ private int getIndexForKey(String key) { throw new IllegalArgumentException("Key not found"); } - protected void notifyChangeListener(ChangeListener.EventType type, int index) { + protected void notifyChangeListener(ChangeEventListener.EventType type, int index) { mListener.onChanged(type, index, -1); } } 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 8b0bde362..ed10eb7b6 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseIndexArray.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseIndexArray.java @@ -31,7 +31,7 @@ class FirebaseIndexArray extends FirebaseArray { private static final String TAG = "FirebaseIndexArray"; - private static final ChangeListener NOOP_CHANGE_LISTENER = new ChangeListener() { + private static final ChangeEventListener NOOP_CHANGE_LISTENER = new ChangeEventListener() { @Override public void onChanged(EventType type, int index, int oldIndex) { } @@ -41,7 +41,7 @@ public void onCancelled(DatabaseError error) { } }; - private ChangeListener mListenerCopy; + private ChangeEventListener mListenerCopy; private JoinResolver mJoinResolver; private Map mRefs = new HashMap<>(); private List mDataSnapshots = new ArrayList<>(); @@ -51,8 +51,8 @@ public FirebaseIndexArray(Query keyRef) { } @Override - public void setChangeListener(@NonNull ChangeListener listener) { - super.setChangeListener(listener); + public void setChangeEventListener(@NonNull ChangeEventListener listener) { + super.setChangeEventListener(listener); mListenerCopy = listener; } @@ -91,9 +91,9 @@ public DataSnapshot get(int index) { @Override public void onChildAdded(DataSnapshot keySnapshot, String previousChildKey) { - super.setChangeListener(NOOP_CHANGE_LISTENER); + super.setChangeEventListener(NOOP_CHANGE_LISTENER); super.onChildAdded(keySnapshot, previousChildKey); - super.setChangeListener(mListenerCopy); + super.setChangeEventListener(mListenerCopy); Query ref = mJoinResolver.onJoin(keySnapshot, previousChildKey); mRefs.put(ref, ref.addValueEventListener(new DataRefListener())); @@ -101,9 +101,9 @@ public void onChildAdded(DataSnapshot keySnapshot, String previousChildKey) { @Override public void onChildChanged(DataSnapshot snapshot, String previousChildKey) { - super.setChangeListener(NOOP_CHANGE_LISTENER); + super.setChangeEventListener(NOOP_CHANGE_LISTENER); super.onChildChanged(snapshot, previousChildKey); - super.setChangeListener(mListenerCopy); + super.setChangeEventListener(mListenerCopy); } @Override @@ -114,13 +114,13 @@ public void onChildRemoved(DataSnapshot keySnapshot) { Query removeQuery = mJoinResolver.onDisjoin(keySnapshot); removeQuery.removeEventListener(mRefs.remove(removeQuery)); - super.setChangeListener(NOOP_CHANGE_LISTENER); + super.setChangeEventListener(NOOP_CHANGE_LISTENER); super.onChildRemoved(keySnapshot); - super.setChangeListener(mListenerCopy); + super.setChangeEventListener(mListenerCopy); if (isMatch(index, key)) { mDataSnapshots.remove(index); - notifyChangeListener(ChangeListener.EventType.REMOVED, index); + notifyChangeListener(ChangeEventListener.EventType.REMOVED, index); } } @@ -129,15 +129,15 @@ public void onChildMoved(DataSnapshot keySnapshot, String previousChildKey) { String key = keySnapshot.getKey(); int oldIndex = getIndexForKey(key); - super.setChangeListener(NOOP_CHANGE_LISTENER); + super.setChangeEventListener(NOOP_CHANGE_LISTENER); super.onChildMoved(keySnapshot, previousChildKey); - super.setChangeListener(mListenerCopy); + super.setChangeEventListener(mListenerCopy); if (isMatch(oldIndex, key)) { DataSnapshot snapshot = mDataSnapshots.remove(oldIndex); int newIndex = getIndexForKey(key); mDataSnapshots.add(newIndex, snapshot); - mListener.onChanged(ChangeListener.EventType.MOVED, newIndex, oldIndex); + mListener.onChanged(ChangeEventListener.EventType.MOVED, newIndex, oldIndex); } } @@ -175,15 +175,15 @@ public void onDataChange(DataSnapshot snapshot) { if (snapshot.getValue() != null) { if (!isMatch(index, key)) { mDataSnapshots.add(index, snapshot); - notifyChangeListener(ChangeListener.EventType.ADDED, index); + notifyChangeListener(ChangeEventListener.EventType.ADDED, index); } else { mDataSnapshots.set(index, snapshot); - notifyChangeListener(ChangeListener.EventType.CHANGED, index); + notifyChangeListener(ChangeEventListener.EventType.CHANGED, index); } } else { if (isMatch(index, key)) { mDataSnapshots.remove(index); - notifyChangeListener(ChangeListener.EventType.REMOVED, index); + notifyChangeListener(ChangeEventListener.EventType.REMOVED, index); } else { mJoinResolver.onJoinFailed(index, snapshot); } diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseListAdapter.java b/database/src/main/java/com/firebase/ui/database/FirebaseListAdapter.java index 87b3d439f..204d8e4d8 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseListAdapter.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseListAdapter.java @@ -68,7 +68,7 @@ public abstract class FirebaseListAdapter extends BaseAdapter { mLayout = modelLayout; mSnapshots = snapshots; - mSnapshots.setChangeListener(new FirebaseArray.ChangeListener() { + mSnapshots.setChangeEventListener(new FirebaseArray.ChangeEventListener() { @Override public void onChanged(EventType type, int index, int oldIndex) { notifyDataSetChanged(); diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseRecyclerAdapter.java b/database/src/main/java/com/firebase/ui/database/FirebaseRecyclerAdapter.java index e73a050fb..c2ce1a04c 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseRecyclerAdapter.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseRecyclerAdapter.java @@ -90,7 +90,7 @@ public abstract class FirebaseRecyclerAdapter Date: Wed, 14 Dec 2016 18:43:28 -0800 Subject: [PATCH 07/93] Cleanup Signed-off-by: Alex Saveau --- .../main/java/com/firebase/ui/database/FirebaseIndexArray.java | 3 +-- .../java/com/firebase/ui/database/FirebaseListAdapter.java | 2 +- .../java/com/firebase/ui/database/FirebaseRecyclerAdapter.java | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) 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 ed10eb7b6..44188b338 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseIndexArray.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseIndexArray.java @@ -143,8 +143,7 @@ public void onChildMoved(DataSnapshot keySnapshot, String previousChildKey) { @Override public void onCancelled(DatabaseError error) { - Log.e(TAG, - "A fatal error occurred retrieving the necessary keys to populate your adapter."); + Log.e(TAG, "A fatal error occurred retrieving the necessary keys to populate your adapter."); super.onCancelled(error); } diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseListAdapter.java b/database/src/main/java/com/firebase/ui/database/FirebaseListAdapter.java index 204d8e4d8..632eace5c 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseListAdapter.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseListAdapter.java @@ -51,7 +51,7 @@ * contained in the children of the given Firebase location */ public abstract class FirebaseListAdapter extends BaseAdapter { - private static final String TAG = FirebaseListAdapter.class.getSimpleName(); + private static final String TAG = "FirebaseListAdapter"; FirebaseArray mSnapshots; private final Class mModelClass; diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseRecyclerAdapter.java b/database/src/main/java/com/firebase/ui/database/FirebaseRecyclerAdapter.java index c2ce1a04c..581a98b14 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseRecyclerAdapter.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseRecyclerAdapter.java @@ -73,7 +73,7 @@ */ public abstract class FirebaseRecyclerAdapter extends RecyclerView.Adapter { - private static final String TAG = FirebaseRecyclerAdapter.class.getSimpleName(); + private static final String TAG = "FirebaseRecyclerAdapter"; FirebaseArray mSnapshots; private Class mModelClass; From 23806d7c5eead18184ac339eff3c7b90ea22ca98 Mon Sep 17 00:00:00 2001 From: Alex Saveau Date: Wed, 14 Dec 2016 19:04:29 -0800 Subject: [PATCH 08/93] Make JoinResolver.java public Signed-off-by: Alex Saveau --- .../src/main/java/com/firebase/ui/database/JoinResolver.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/database/src/main/java/com/firebase/ui/database/JoinResolver.java b/database/src/main/java/com/firebase/ui/database/JoinResolver.java index da2c6895b..631cdc8b4 100644 --- a/database/src/main/java/com/firebase/ui/database/JoinResolver.java +++ b/database/src/main/java/com/firebase/ui/database/JoinResolver.java @@ -3,7 +3,7 @@ import com.google.firebase.database.DataSnapshot; import com.google.firebase.database.Query; -interface JoinResolver { +public interface JoinResolver { /** * Called after an {@code onChildAdded} event from {@code keyRef}. * From 331b4bfaafeec9650530cc72aa15adc1cd2cdac6 Mon Sep 17 00:00:00 2001 From: Alex Saveau Date: Mon, 2 Jan 2017 17:36:23 -0800 Subject: [PATCH 09/93] Rename Signed-off-by: Alex Saveau --- .../java/com/firebase/ui/database/utils/TestUtils.java | 2 +- .../main/java/com/firebase/ui/database/FirebaseArray.java | 6 +++--- .../java/com/firebase/ui/database/FirebaseIndexArray.java | 2 +- .../java/com/firebase/ui/database/FirebaseListAdapter.java | 2 +- .../com/firebase/ui/database/FirebaseRecyclerAdapter.java | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/database/src/androidTest/java/com/firebase/ui/database/utils/TestUtils.java b/database/src/androidTest/java/com/firebase/ui/database/utils/TestUtils.java index d7a6fd5c9..761b63439 100644 --- a/database/src/androidTest/java/com/firebase/ui/database/utils/TestUtils.java +++ b/database/src/androidTest/java/com/firebase/ui/database/utils/TestUtils.java @@ -61,7 +61,7 @@ public static void runAndWaitUntil(FirebaseArray array, final Semaphore semaphore = new Semaphore(0); array.setChangeEventListener(new FirebaseArray.ChangeEventListener() { @Override - public void onChanged(EventType type, int index, int oldIndex) { + public void onChange(EventType type, int index, int oldIndex) { semaphore.release(); } diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseArray.java b/database/src/main/java/com/firebase/ui/database/FirebaseArray.java index 30b438ed5..45571d2de 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseArray.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseArray.java @@ -31,7 +31,7 @@ class FirebaseArray implements ChildEventListener { public interface ChangeEventListener { enum EventType {ADDED, CHANGED, REMOVED, MOVED} - void onChanged(EventType type, int index, int oldIndex); + void onChange(EventType type, int index, int oldIndex); void onCancelled(DatabaseError error); } @@ -102,7 +102,7 @@ public void onChildMoved(DataSnapshot snapshot, String previousChildKey) { mSnapshots.remove(oldIndex); int newIndex = previousChildKey == null ? 0 : (getIndexForKey(previousChildKey) + 1); mSnapshots.add(newIndex, snapshot); - mListener.onChanged(ChangeEventListener.EventType.MOVED, newIndex, oldIndex); + mListener.onChange(ChangeEventListener.EventType.MOVED, newIndex, oldIndex); } @Override @@ -123,6 +123,6 @@ private int getIndexForKey(String key) { } protected void notifyChangeListener(ChangeEventListener.EventType type, int index) { - mListener.onChanged(type, index, -1); + mListener.onChange(type, index, -1); } } 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 44188b338..63a5020eb 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseIndexArray.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseIndexArray.java @@ -33,7 +33,7 @@ class FirebaseIndexArray extends FirebaseArray { private static final String TAG = "FirebaseIndexArray"; private static final ChangeEventListener NOOP_CHANGE_LISTENER = new ChangeEventListener() { @Override - public void onChanged(EventType type, int index, int oldIndex) { + public void onChange(EventType type, int index, int oldIndex) { } @Override diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseListAdapter.java b/database/src/main/java/com/firebase/ui/database/FirebaseListAdapter.java index 632eace5c..a75478baa 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseListAdapter.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseListAdapter.java @@ -70,7 +70,7 @@ public abstract class FirebaseListAdapter extends BaseAdapter { mSnapshots.setChangeEventListener(new FirebaseArray.ChangeEventListener() { @Override - public void onChanged(EventType type, int index, int oldIndex) { + public void onChange(EventType type, int index, int oldIndex) { notifyDataSetChanged(); } diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseRecyclerAdapter.java b/database/src/main/java/com/firebase/ui/database/FirebaseRecyclerAdapter.java index 581a98b14..80bd922cf 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseRecyclerAdapter.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseRecyclerAdapter.java @@ -92,7 +92,7 @@ public abstract class FirebaseRecyclerAdapter Date: Tue, 3 Jan 2017 13:55:35 -0800 Subject: [PATCH 10/93] Fix compile error Signed-off-by: Alex Saveau --- .../main/java/com/firebase/ui/database/FirebaseIndexArray.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 63a5020eb..491d0222f 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseIndexArray.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseIndexArray.java @@ -137,7 +137,7 @@ public void onChildMoved(DataSnapshot keySnapshot, String previousChildKey) { DataSnapshot snapshot = mDataSnapshots.remove(oldIndex); int newIndex = getIndexForKey(key); mDataSnapshots.add(newIndex, snapshot); - mListener.onChanged(ChangeEventListener.EventType.MOVED, newIndex, oldIndex); + mListener.onChange(ChangeEventListener.EventType.MOVED, newIndex, oldIndex); } } From 2b65f11296b9068ca74a0e1ac8aa4eb2130dd1ce Mon Sep 17 00:00:00 2001 From: Alex Saveau Date: Mon, 9 Jan 2017 11:27:08 -0800 Subject: [PATCH 11/93] Make FirebaseArray.java fully decked out and implementing List Signed-off-by: Alex Saveau --- .../firebase/ui/database/FirebaseArray.java | 334 +++++++++++++++++- .../ui/database/FirebaseIndexArray.java | 144 ++++++-- 2 files changed, 440 insertions(+), 38 deletions(-) diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseArray.java b/database/src/main/java/com/firebase/ui/database/FirebaseArray.java index 45571d2de..6e606ac6a 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseArray.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseArray.java @@ -22,12 +22,15 @@ import com.google.firebase.database.Query; import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; import java.util.List; +import java.util.ListIterator; /** - * This class implements an array-like collection on top of a Firebase location. + * This class implements a collection on top of a Firebase location. */ -class FirebaseArray implements ChildEventListener { +class FirebaseArray implements ChildEventListener, List { public interface ChangeEventListener { enum EventType {ADDED, CHANGED, REMOVED, MOVED} @@ -37,7 +40,7 @@ enum EventType {ADDED, CHANGED, REMOVED, MOVED} } protected ChangeEventListener mListener; - protected boolean mIsListening; + private boolean mIsListening; private Query mQuery; private List mSnapshots = new ArrayList<>(); @@ -64,12 +67,12 @@ public void stopListening() { mIsListening = false; } - public int size() { - return mSnapshots.size(); + public boolean isListening() { + return mIsListening; } - public DataSnapshot get(int index) { - return mSnapshots.get(index); + protected void notifyChangeListener(ChangeEventListener.EventType type, int index) { + mListener.onChange(type, index, -1); } @Override @@ -122,7 +125,320 @@ private int getIndexForKey(String key) { throw new IllegalArgumentException("Key not found"); } - protected void notifyChangeListener(ChangeEventListener.EventType type, int index) { - mListener.onChange(type, index, -1); + @Override + public int size() { + return mSnapshots.size(); + } + + @Override + public boolean isEmpty() { + return mSnapshots.isEmpty(); + } + + @Override + public boolean contains(Object o) { + return mSnapshots.contains(o); + } + + @Override + public Object[] toArray() { + return mSnapshots.toArray(); + } + + @Override + public T[] toArray(T[] a) { + return mSnapshots.toArray(a); + } + + @Override + public boolean containsAll(Collection c) { + return mSnapshots.containsAll(c); + } + + @Override + public DataSnapshot get(int index) { + return mSnapshots.get(index); + } + + @Override + public int indexOf(Object o) { + return mSnapshots.indexOf(o); + } + + @Override + public int lastIndexOf(Object o) { + return mSnapshots.lastIndexOf(o); + } + + @Override + public Iterator iterator() { + return new ImmutableIterator(mSnapshots.iterator()); + } + + @Override + public ListIterator listIterator() { + return new ImmutableListIterator(mSnapshots.listIterator()); + } + + @Override + public ListIterator listIterator(int index) { + return new ImmutableListIterator(mSnapshots.listIterator(index)); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + FirebaseArray snapshots = (FirebaseArray) o; + + return mIsListening == snapshots.mIsListening + && mListener.equals(snapshots.mListener) + && mQuery.equals(snapshots.mQuery) + && mSnapshots.equals(snapshots.mSnapshots); + } + + @Override + public int hashCode() { + int result = mListener.hashCode(); + result = 31 * result + (mIsListening ? 1 : 0); + result = 31 * result + mQuery.hashCode(); + result = 31 * result + mSnapshots.hashCode(); + return result; + } + + @Override + public String toString() { + return "FirebaseArray{" + + "mListener=" + mListener + + ", mIsListening=" + mIsListening + + ", mQuery=" + mQuery + + ", mSnapshots=" + mSnapshots + + '}'; + } + + protected static class ImmutableIterator implements Iterator { + private Iterator mIterator; + + public ImmutableIterator(Iterator iterator) { + mIterator = iterator; + } + + @Override + public boolean hasNext() { + return mIterator.hasNext(); + } + + @Override + public DataSnapshot next() { + return mIterator.next(); + } + } + + protected static class ImmutableListIterator implements ListIterator { + private ListIterator mListIterator; + + public ImmutableListIterator(ListIterator listIterator) { + mListIterator = listIterator; + } + + @Override + public boolean hasNext() { + return mListIterator.hasNext(); + } + + @Override + public DataSnapshot next() { + return mListIterator.next(); + } + + @Override + public boolean hasPrevious() { + return mListIterator.hasPrevious(); + } + + @Override + public DataSnapshot previous() { + return mListIterator.previous(); + } + + @Override + public int nextIndex() { + return mListIterator.nextIndex(); + } + + @Override + public int previousIndex() { + return mListIterator.previousIndex(); + } + + /** + * Guaranteed to throw an exception and leave the collection unmodified. + * + * @throws UnsupportedOperationException always + * @deprecated Unsupported operation. + */ + @Deprecated + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the collection unmodified. + * + * @throws UnsupportedOperationException always + * @deprecated Unsupported operation. + */ + @Deprecated + @Override + public void set(DataSnapshot snapshot) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the collection unmodified. + * + * @throws UnsupportedOperationException always + * @deprecated Unsupported operation. + */ + @Deprecated + @Override + public void add(DataSnapshot snapshot) { + throw new UnsupportedOperationException(); + } + } + + + /** + * Guaranteed to throw an exception and leave the collection unmodified. + * + * @throws UnsupportedOperationException always + * @deprecated Unsupported operation. + */ + @Deprecated + @Override + public boolean add(DataSnapshot snapshot) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the collection unmodified. + * + * @throws UnsupportedOperationException always + * @deprecated Unsupported operation. + */ + @Deprecated + @Override + public boolean remove(Object o) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the collection unmodified. + * + * @throws UnsupportedOperationException always + * @deprecated Unsupported operation. + */ + @Deprecated + @Override + public boolean addAll(Collection c) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the collection unmodified. + * + * @throws UnsupportedOperationException always + * @deprecated Unsupported operation. + */ + @Deprecated + @Override + public boolean addAll(int index, Collection c) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the collection unmodified. + * + * @throws UnsupportedOperationException always + * @deprecated Unsupported operation. + */ + @Deprecated + @Override + public boolean removeAll(Collection c) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the collection unmodified. + * + * @throws UnsupportedOperationException always + * @deprecated Unsupported operation. + */ + @Deprecated + @Override + public boolean retainAll(Collection c) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the collection unmodified. + * + * @throws UnsupportedOperationException always + * @deprecated Unsupported operation. + */ + @Deprecated + @Override + public void clear() { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the collection unmodified. + * + * @throws UnsupportedOperationException always + * @deprecated Unsupported operation. + */ + @Deprecated + @Override + public DataSnapshot set(int index, DataSnapshot element) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the collection unmodified. + * + * @throws UnsupportedOperationException always + * @deprecated Unsupported operation. + */ + @Deprecated + @Override + public void add(int index, DataSnapshot element) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the collection unmodified. + * + * @throws UnsupportedOperationException always + * @deprecated Unsupported operation. + */ + @Deprecated + @Override + public DataSnapshot remove(int index) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the collection unmodified. + * + * @throws UnsupportedOperationException always + * @deprecated Unsupported operation. + */ + @Deprecated + @Override + public List subList(int fromIndex, int toIndex) { + throw new UnsupportedOperationException(); } } 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 491d0222f..63b2f168c 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseIndexArray.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseIndexArray.java @@ -23,9 +23,12 @@ import com.google.firebase.database.ValueEventListener; import java.util.ArrayList; +import java.util.Collection; import java.util.HashMap; import java.util.HashSet; +import java.util.Iterator; import java.util.List; +import java.util.ListIterator; import java.util.Map; import java.util.Set; @@ -57,7 +60,7 @@ public void setChangeEventListener(@NonNull ChangeEventListener listener) { } public void setJoinResolver(@NonNull JoinResolver joinResolver) { - if (mIsListening && joinResolver == null) { + if (isListening() && joinResolver == null) { throw new IllegalStateException("Join resolver cannot be null."); } mJoinResolver = joinResolver; @@ -79,16 +82,6 @@ public void stopListening() { mDataSnapshots.clear(); } - @Override - public int size() { - return mDataSnapshots.size(); - } - - @Override - public DataSnapshot get(int index) { - return mDataSnapshots.get(index); - } - @Override public void onChildAdded(DataSnapshot keySnapshot, String previousChildKey) { super.setChangeEventListener(NOOP_CHANGE_LISTENER); @@ -147,24 +140,6 @@ public void onCancelled(DatabaseError error) { super.onCancelled(error); } - private int getIndexForKey(String key) { - int dataCount = size(); - int index = 0; - for (int keyIndex = 0; index < dataCount; keyIndex++) { - String superKey = super.get(keyIndex).getKey(); - if (key.equals(superKey)) { - break; - } else if (mDataSnapshots.get(index).getKey().equals(superKey)) { - index++; - } - } - return index; - } - - private boolean isMatch(int index, String key) { - return index >= 0 && index < size() && mDataSnapshots.get(index).getKey().equals(key); - } - private class DataRefListener implements ValueEventListener { @Override public void onDataChange(DataSnapshot snapshot) { @@ -194,4 +169,115 @@ public void onCancelled(DatabaseError error) { mListener.onCancelled(error); } } + + private int getIndexForKey(String key) { + int dataCount = size(); + int index = 0; + for (int keyIndex = 0; index < dataCount; keyIndex++) { + String superKey = super.get(keyIndex).getKey(); + if (key.equals(superKey)) { + break; + } else if (mDataSnapshots.get(index).getKey().equals(superKey)) { + index++; + } + } + return index; + } + + private boolean isMatch(int index, String key) { + return index >= 0 && index < size() && mDataSnapshots.get(index).getKey().equals(key); + } + + @Override + public int size() { + return mDataSnapshots.size(); + } + + @Override + public boolean isEmpty() { + return mDataSnapshots.isEmpty(); + } + + @Override + public boolean contains(Object o) { + return mDataSnapshots.contains(o); + } + + @Override + public Object[] toArray() { + return mDataSnapshots.toArray(); + } + + @Override + public T[] toArray(T[] a) { + return mDataSnapshots.toArray(a); + } + + @Override + public boolean containsAll(Collection c) { + return mDataSnapshots.containsAll(c); + } + + @Override + public DataSnapshot get(int index) { + return mDataSnapshots.get(index); + } + + @Override + public int indexOf(Object o) { + return mDataSnapshots.indexOf(o); + } + + @Override + public int lastIndexOf(Object o) { + return mDataSnapshots.lastIndexOf(o); + } + + @Override + public Iterator iterator() { + return new ImmutableIterator(mDataSnapshots.iterator()); + } + + @Override + public ListIterator listIterator() { + return new ImmutableListIterator(mDataSnapshots.listIterator()); + } + + @Override + public ListIterator listIterator(int index) { + return new ImmutableListIterator(mDataSnapshots.listIterator(index)); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + + FirebaseIndexArray array = (FirebaseIndexArray) o; + + return mJoinResolver.equals(array.mJoinResolver) + && mRefs.equals(array.mRefs) + && mDataSnapshots.equals(array.mDataSnapshots); + } + + @Override + public int hashCode() { + int result = super.hashCode(); + result = 31 * result + mJoinResolver.hashCode(); + result = 31 * result + mRefs.hashCode(); + result = 31 * result + mDataSnapshots.hashCode(); + return result; + } + + @Override + public String toString() { + return "FirebaseIndexArray{" + + "mListener=" + mListener + + ", mIsListening=" + isListening() + + ", mJoinResolver=" + mJoinResolver + + ", mRefs=" + mRefs + + ", mDataSnapshots=" + mDataSnapshots + + '}'; + } } From 961f9d7152ade5ade2820786ded42d282cd53b56 Mon Sep 17 00:00:00 2001 From: Alex Saveau Date: Tue, 10 Jan 2017 20:28:08 -0800 Subject: [PATCH 12/93] Make stuff public Signed-off-by: Alex Saveau --- .../src/main/java/com/firebase/ui/database/FirebaseArray.java | 2 +- .../java/com/firebase/ui/database/FirebaseIndexArray.java | 4 ++-- .../java/com/firebase/ui/database/FirebaseListAdapter.java | 4 ++-- .../com/firebase/ui/database/FirebaseRecyclerAdapter.java | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseArray.java b/database/src/main/java/com/firebase/ui/database/FirebaseArray.java index 6e606ac6a..699af2f36 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseArray.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseArray.java @@ -30,7 +30,7 @@ /** * This class implements a collection on top of a Firebase location. */ -class FirebaseArray implements ChildEventListener, List { +public class FirebaseArray implements ChildEventListener, List { public interface ChangeEventListener { enum EventType {ADDED, CHANGED, REMOVED, MOVED} 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 63b2f168c..64eba30d4 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseIndexArray.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseIndexArray.java @@ -32,7 +32,7 @@ import java.util.Map; import java.util.Set; -class FirebaseIndexArray extends FirebaseArray { +public class FirebaseIndexArray extends FirebaseArray { private static final String TAG = "FirebaseIndexArray"; private static final ChangeEventListener NOOP_CHANGE_LISTENER = new ChangeEventListener() { @Override @@ -44,8 +44,8 @@ public void onCancelled(DatabaseError error) { } }; + protected JoinResolver mJoinResolver; private ChangeEventListener mListenerCopy; - private JoinResolver mJoinResolver; private Map mRefs = new HashMap<>(); private List mDataSnapshots = new ArrayList<>(); diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseListAdapter.java b/database/src/main/java/com/firebase/ui/database/FirebaseListAdapter.java index a75478baa..ced78d1d3 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseListAdapter.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseListAdapter.java @@ -53,8 +53,8 @@ public abstract class FirebaseListAdapter extends BaseAdapter { private static final String TAG = "FirebaseListAdapter"; - FirebaseArray mSnapshots; - private final Class mModelClass; + protected FirebaseArray mSnapshots; + protected final Class mModelClass; protected Activity mActivity; protected int mLayout; diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseRecyclerAdapter.java b/database/src/main/java/com/firebase/ui/database/FirebaseRecyclerAdapter.java index 80bd922cf..3fcdac47d 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseRecyclerAdapter.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseRecyclerAdapter.java @@ -75,8 +75,8 @@ public abstract class FirebaseRecyclerAdapter { private static final String TAG = "FirebaseRecyclerAdapter"; - FirebaseArray mSnapshots; - private Class mModelClass; + protected FirebaseArray mSnapshots; + protected Class mModelClass; protected Class mViewHolderClass; protected int mModelLayout; From 97d9cb0a9e5813c016b069dc336b3b3cc0f9944a Mon Sep 17 00:00:00 2001 From: Alex Saveau Date: Tue, 10 Jan 2017 20:34:31 -0800 Subject: [PATCH 13/93] Fix merge mistakes Signed-off-by: Alex Saveau --- .../java/com/firebase/ui/database/utils/TestUtils.java | 6 +++++- .../main/java/com/firebase/ui/database/FirebaseArray.java | 4 ++-- .../java/com/firebase/ui/database/FirebaseIndexArray.java | 8 ++++++-- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/database/src/androidTest/java/com/firebase/ui/database/utils/TestUtils.java b/database/src/androidTest/java/com/firebase/ui/database/utils/TestUtils.java index 761b63439..9520235d5 100644 --- a/database/src/androidTest/java/com/firebase/ui/database/utils/TestUtils.java +++ b/database/src/androidTest/java/com/firebase/ui/database/utils/TestUtils.java @@ -61,10 +61,14 @@ public static void runAndWaitUntil(FirebaseArray array, final Semaphore semaphore = new Semaphore(0); array.setChangeEventListener(new FirebaseArray.ChangeEventListener() { @Override - public void onChange(EventType type, int index, int oldIndex) { + public void onChildChanged(EventType type, int index, int oldIndex) { semaphore.release(); } + @Override + public void onDataChanged() { + } + @Override public void onCancelled(DatabaseError error) { throw new IllegalStateException(error.toException()); diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseArray.java b/database/src/main/java/com/firebase/ui/database/FirebaseArray.java index ceeda4fd1..9c6ddaed4 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseArray.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseArray.java @@ -107,7 +107,7 @@ public void onChildMoved(DataSnapshot snapshot, String previousChildKey) { mSnapshots.remove(oldIndex); int newIndex = previousChildKey == null ? 0 : (getIndexForKey(previousChildKey) + 1); mSnapshots.add(newIndex, snapshot); - mListener.onChange(ChangeEventListener.EventType.MOVED, newIndex, oldIndex); + mListener.onChildChanged(ChangeEventListener.EventType.MOVED, newIndex, oldIndex); } @Override @@ -133,6 +133,6 @@ private int getIndexForKey(String key) { } protected void notifyChangeListener(ChangeEventListener.EventType type, int index) { - mListener.onChange(type, index, -1); + mListener.onChildChanged(type, index, -1); } } 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 491d0222f..788713dd5 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseIndexArray.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseIndexArray.java @@ -33,7 +33,11 @@ class FirebaseIndexArray extends FirebaseArray { private static final String TAG = "FirebaseIndexArray"; private static final ChangeEventListener NOOP_CHANGE_LISTENER = new ChangeEventListener() { @Override - public void onChange(EventType type, int index, int oldIndex) { + public void onChildChanged(EventType type, int index, int oldIndex) { + } + + @Override + public void onDataChanged() { } @Override @@ -137,7 +141,7 @@ public void onChildMoved(DataSnapshot keySnapshot, String previousChildKey) { DataSnapshot snapshot = mDataSnapshots.remove(oldIndex); int newIndex = getIndexForKey(key); mDataSnapshots.add(newIndex, snapshot); - mListener.onChange(ChangeEventListener.EventType.MOVED, newIndex, oldIndex); + mListener.onChildChanged(ChangeEventListener.EventType.MOVED, newIndex, oldIndex); } } From 48ea75a0485c975788726ad7a78e096e683e6e1b Mon Sep 17 00:00:00 2001 From: Alex Saveau Date: Tue, 24 Jan 2017 17:42:28 -0800 Subject: [PATCH 14/93] Fix a ton of merge mistakes Signed-off-by: Alex Saveau --- .../com/firebase/uidemo/database/Chat.java | 41 ------------ .../uidemo/database/ChatActivity.java | 12 ++-- .../firebase/uidemo/database/ChatHolder.java | 67 ------------------- app/src/main/res/values/strings.xml | 2 - .../com/firebase/ui/database/TestUtils.java | 8 +-- .../firebase/ui/database/FirebaseArray.java | 13 ++-- .../ui/database/FirebaseIndexArray.java | 14 ++-- .../ui/database/FirebaseIndexListAdapter.java | 2 +- .../FirebaseIndexRecyclerAdapter.java | 2 +- .../ui/database/FirebaseListAdapter.java | 2 +- .../ui/database/FirebaseRecyclerAdapter.java | 2 +- 11 files changed, 28 insertions(+), 137 deletions(-) delete mode 100644 app/src/main/java/com/firebase/uidemo/database/Chat.java delete mode 100644 app/src/main/java/com/firebase/uidemo/database/ChatHolder.java diff --git a/app/src/main/java/com/firebase/uidemo/database/Chat.java b/app/src/main/java/com/firebase/uidemo/database/Chat.java deleted file mode 100644 index c4fc6efdb..000000000 --- a/app/src/main/java/com/firebase/uidemo/database/Chat.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.firebase.uidemo.database; - -public class Chat { - private String mName; - private String mMessage; - private String mUid; - - public Chat() { - // Needed for Firebase - } - - public Chat(String name, String message, String uid) { - mName = name; - mMessage = message; - mUid = uid; - } - - public String getName() { - return mName; - } - - public void setName(String name) { - mName = name; - } - - public String getMessage() { - return mMessage; - } - - public void setMessage(String message) { - mMessage = message; - } - - public String getUid() { - return mUid; - } - - public void setUid(String uid) { - mUid = uid; - } -} diff --git a/app/src/main/java/com/firebase/uidemo/database/ChatActivity.java b/app/src/main/java/com/firebase/uidemo/database/ChatActivity.java index 733a344c7..1c5944972 100644 --- a/app/src/main/java/com/firebase/uidemo/database/ChatActivity.java +++ b/app/src/main/java/com/firebase/uidemo/database/ChatActivity.java @@ -20,13 +20,19 @@ import android.graphics.drawable.RotateDrawable; import android.os.Bundle; import android.support.annotation.NonNull; +import android.support.v4.content.ContextCompat; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.util.Log; +import android.view.Gravity; import android.view.View; import android.widget.Button; import android.widget.EditText; +import android.widget.FrameLayout; +import android.widget.LinearLayout; +import android.widget.RelativeLayout; +import android.widget.TextView; import android.widget.Toast; import com.firebase.ui.database.FirebaseIndexRecyclerAdapter; @@ -41,7 +47,6 @@ import com.google.firebase.database.DatabaseError; import com.google.firebase.database.DatabaseReference; import com.google.firebase.database.FirebaseDatabase; -import com.google.firebase.database.Query; @SuppressWarnings("LogConditional") public class ChatActivity extends AppCompatActivity implements FirebaseAuth.AuthStateListener { @@ -141,7 +146,6 @@ public void onAuthStateChanged(@NonNull FirebaseAuth firebaseAuth) { } private void attachRecyclerViewAdapter() { - Query lastFifty = mChatRef.limitToLast(50); mRecyclerViewAdapter = new FirebaseIndexRecyclerAdapter( Chat.class, @@ -192,11 +196,11 @@ public void onSuccess(AuthResult result) { .addOnCompleteListener(new SignInResultNotifier(this)); } - public boolean isSignedIn() { + private boolean isSignedIn() { return mAuth.getCurrentUser() != null; } - public void updateUI() { + private void updateUI() { // Sending only allowed when signed in mSendButton.setEnabled(isSignedIn()); mMessageEdit.setEnabled(isSignedIn()); diff --git a/app/src/main/java/com/firebase/uidemo/database/ChatHolder.java b/app/src/main/java/com/firebase/uidemo/database/ChatHolder.java deleted file mode 100644 index 7c95dccdb..000000000 --- a/app/src/main/java/com/firebase/uidemo/database/ChatHolder.java +++ /dev/null @@ -1,67 +0,0 @@ -package com.firebase.uidemo.database; - -import android.graphics.PorterDuff; -import android.graphics.drawable.GradientDrawable; -import android.graphics.drawable.RotateDrawable; -import android.support.v4.content.ContextCompat; -import android.support.v7.widget.RecyclerView; -import android.view.Gravity; -import android.view.View; -import android.widget.FrameLayout; -import android.widget.LinearLayout; -import android.widget.RelativeLayout; -import android.widget.TextView; - -import com.firebase.uidemo.R; - -public class ChatHolder extends RecyclerView.ViewHolder { - private final TextView mNameField; - private final TextView mTextField; - private final FrameLayout mLeftArrow; - private final FrameLayout mRightArrow; - private final RelativeLayout mMessageContainer; - private final LinearLayout mMessage; - private final int mGreen300; - private final int mGray300; - - public ChatHolder(View itemView) { - super(itemView); - mNameField = (TextView) itemView.findViewById(R.id.name_text); - mTextField = (TextView) itemView.findViewById(R.id.message_text); - mLeftArrow = (FrameLayout) itemView.findViewById(R.id.left_arrow); - mRightArrow = (FrameLayout) itemView.findViewById(R.id.right_arrow); - mMessageContainer = (RelativeLayout) itemView.findViewById(R.id.message_container); - mMessage = (LinearLayout) itemView.findViewById(R.id.message); - mGreen300 = ContextCompat.getColor(itemView.getContext(), R.color.material_green_300); - mGray300 = ContextCompat.getColor(itemView.getContext(), R.color.material_gray_300); - } - - public void setIsSender(boolean isSender) { - final int color; - if (isSender) { - color = mGreen300; - mLeftArrow.setVisibility(View.GONE); - mRightArrow.setVisibility(View.VISIBLE); - mMessageContainer.setGravity(Gravity.END); - } else { - color = mGray300; - mLeftArrow.setVisibility(View.VISIBLE); - mRightArrow.setVisibility(View.GONE); - mMessageContainer.setGravity(Gravity.START); - } - - ((GradientDrawable) mMessage.getBackground()).setColor(color); - ((RotateDrawable) mLeftArrow.getBackground()).getDrawable() - .setColorFilter(color, PorterDuff.Mode.SRC); - ((RotateDrawable) mRightArrow.getBackground()).getDrawable() - .setColorFilter(color, PorterDuff.Mode.SRC); - } - - public void setName(String name) { - mNameField.setText(name); - } - - public void setText(String text) { - mTextField.setText(text); - } -} diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 24a94ed57..2ec3749b0 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -69,6 +69,4 @@ No messages. Start chatting at the bottom! - Sign In Failed - Signed In diff --git a/database/src/androidTest/java/com/firebase/ui/database/TestUtils.java b/database/src/androidTest/java/com/firebase/ui/database/TestUtils.java index 09072e2bc..8a50d8c26 100644 --- a/database/src/androidTest/java/com/firebase/ui/database/TestUtils.java +++ b/database/src/androidTest/java/com/firebase/ui/database/TestUtils.java @@ -37,7 +37,7 @@ private static FirebaseApp initializeApp(Context context) { } public static void setJoinResolver(FirebaseIndexArray array, final DatabaseReference ref) { - array.setJoinResolver(new JoinResolver() { + array.setJoinResolverListener(new JoinResolver() { @Override public Query onJoin(DataSnapshot keySnapshot, String previousChildKey) { return ref.child(keySnapshot.getKey()); @@ -59,7 +59,7 @@ public static void runAndWaitUntil(FirebaseArray array, Runnable task, Callable done) throws InterruptedException { final Semaphore semaphore = new Semaphore(0); - array.setOnChangedListener(new ChangeEventListener() { + array.setChangeEventListener(new ChangeEventListener() { @Override public void onChildChanged(ChangeEventListener.EventType type, int index, int oldIndex) { semaphore.release(); @@ -68,10 +68,6 @@ public void onChildChanged(ChangeEventListener.EventType type, int index, int ol @Override public void onDataChanged() {} - @Override - public void onDataChanged() { - } - @Override public void onCancelled(DatabaseError error) { throw new IllegalStateException(error.toException()); diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseArray.java b/database/src/main/java/com/firebase/ui/database/FirebaseArray.java index a81984952..ab6e1cf43 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseArray.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseArray.java @@ -29,8 +29,9 @@ * This class implements an array-like collection on top of a Firebase location. */ class FirebaseArray implements ChildEventListener, ValueEventListener { + protected ChangeEventListener mListener; + protected boolean mIsListening; private Query mQuery; - private ChangeEventListener mListener; private List mSnapshots = new ArrayList<>(); public FirebaseArray(Query ref) { @@ -73,21 +74,21 @@ public void onChildAdded(DataSnapshot snapshot, String previousChildKey) { index = getIndexForKey(previousChildKey) + 1; } mSnapshots.add(index, snapshot); - notifyChangedListeners(ChangeEventListener.EventType.ADDED, index); + notifyChangeEventListeners(ChangeEventListener.EventType.ADDED, index); } @Override public void onChildChanged(DataSnapshot snapshot, String previousChildKey) { int index = getIndexForKey(snapshot.getKey()); mSnapshots.set(index, snapshot); - notifyChangedListeners(ChangeEventListener.EventType.CHANGED, index); + notifyChangeEventListeners(ChangeEventListener.EventType.CHANGED, index); } @Override public void onChildRemoved(DataSnapshot snapshot) { int index = getIndexForKey(snapshot.getKey()); mSnapshots.remove(index); - notifyChangedListeners(ChangeEventListener.EventType.REMOVED, index); + notifyChangeEventListeners(ChangeEventListener.EventType.REMOVED, index); } @Override @@ -96,7 +97,7 @@ public void onChildMoved(DataSnapshot snapshot, String previousChildKey) { mSnapshots.remove(oldIndex); int newIndex = previousChildKey == null ? 0 : (getIndexForKey(previousChildKey) + 1); mSnapshots.add(newIndex, snapshot); - notifyChangedListeners(ChangeEventListener.EventType.MOVED, newIndex, oldIndex); + mListener.onChildChanged(ChangeEventListener.EventType.MOVED, newIndex, oldIndex); } @Override @@ -121,7 +122,7 @@ private int getIndexForKey(String key) { throw new IllegalArgumentException("Key not found"); } - protected void notifyChangeListener(ChangeEventListener.EventType type, int index) { + protected void notifyChangeEventListeners(ChangeEventListener.EventType type, int index) { mListener.onChildChanged(type, index, -1); } } 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 ac3c2761c..896bc68a1 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseIndexArray.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseIndexArray.java @@ -45,8 +45,8 @@ public void onCancelled(DatabaseError error) { } }; + protected JoinResolver mJoinResolver; private ChangeEventListener mListenerCopy; - private JoinResolver mJoinResolver; private Map mRefs = new HashMap<>(); private List mDataSnapshots = new ArrayList<>(); @@ -60,7 +60,7 @@ public void setChangeEventListener(@NonNull ChangeEventListener listener) { mListenerCopy = listener; } - public void setJoinResolver(@NonNull JoinResolver joinResolver) { + public void setJoinResolverListener(@NonNull JoinResolver joinResolver) { if (mIsListening && joinResolver == null) { throw new IllegalStateException("Join resolver cannot be null."); } @@ -124,7 +124,7 @@ public void onChildRemoved(DataSnapshot keySnapshot) { if (isMatch(index, key)) { mDataSnapshots.remove(index); - notifyChangedListeners(ChangeEventListener.EventType.REMOVED, index); + notifyChangeEventListeners(ChangeEventListener.EventType.REMOVED, index); } } @@ -141,7 +141,7 @@ public void onChildMoved(DataSnapshot keySnapshot, String previousChildKey) { DataSnapshot snapshot = mDataSnapshots.remove(oldIndex); int newIndex = getIndexForKey(key); mDataSnapshots.add(newIndex, snapshot); - notifyChangedListeners(ChangeEventListener.EventType.MOVED, newIndex, oldIndex); + mListener.onChildChanged(ChangeEventListener.EventType.MOVED, newIndex, oldIndex); } } @@ -178,15 +178,15 @@ public void onDataChange(DataSnapshot snapshot) { if (snapshot.getValue() != null) { if (!isMatch(index, key)) { mDataSnapshots.add(index, snapshot); - notifyChangedListeners(ChangeEventListener.EventType.ADDED, index); + notifyChangeEventListeners(ChangeEventListener.EventType.ADDED, index); } else { mDataSnapshots.set(index, snapshot); - notifyChangedListeners(ChangeEventListener.EventType.CHANGED, index); + notifyChangeEventListeners(ChangeEventListener.EventType.CHANGED, index); } } else { if (isMatch(index, key)) { mDataSnapshots.remove(index); - notifyChangedListeners(ChangeEventListener.EventType.REMOVED, index); + notifyChangeEventListeners(ChangeEventListener.EventType.REMOVED, index); } else { mJoinResolver.onJoinFailed(index, snapshot); } diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseIndexListAdapter.java b/database/src/main/java/com/firebase/ui/database/FirebaseIndexListAdapter.java index 214928a62..52376acba 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseIndexListAdapter.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseIndexListAdapter.java @@ -63,7 +63,7 @@ public FirebaseIndexListAdapter(Activity activity, Query dataRef) { super(activity, modelClass, modelLayout, new FirebaseIndexArray(keyRef), false); mDataQuery = dataRef; - ((FirebaseIndexArray) mSnapshots).setJoinResolver(this); + ((FirebaseIndexArray) mSnapshots).setJoinResolverListener(this); mSnapshots.startListening(); } diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseIndexRecyclerAdapter.java b/database/src/main/java/com/firebase/ui/database/FirebaseIndexRecyclerAdapter.java index 692f810c6..1ed61a2fa 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseIndexRecyclerAdapter.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseIndexRecyclerAdapter.java @@ -90,7 +90,7 @@ public FirebaseIndexRecyclerAdapter(Class modelClass, Query dataRef) { super(modelClass, modelLayout, viewHolderClass, new FirebaseIndexArray(keyRef), false); mDataQuery = dataRef; - ((FirebaseIndexArray) mSnapshots).setJoinResolver(this); + ((FirebaseIndexArray) mSnapshots).setJoinResolverListener(this); mSnapshots.startListening(); } diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseListAdapter.java b/database/src/main/java/com/firebase/ui/database/FirebaseListAdapter.java index e65f34448..a269f13ce 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseListAdapter.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseListAdapter.java @@ -68,7 +68,7 @@ public abstract class FirebaseListAdapter extends BaseAdapter { mLayout = modelLayout; mSnapshots = snapshots; - mSnapshots.setOnChangedListener(new ChangeEventListener() { + mSnapshots.setChangeEventListener(new ChangeEventListener() { @Override public void onChildChanged(EventType type, int index, int oldIndex) { FirebaseListAdapter.this.onChildChanged(type, index, oldIndex); diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseRecyclerAdapter.java b/database/src/main/java/com/firebase/ui/database/FirebaseRecyclerAdapter.java index f9df6f780..54eb86f6d 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseRecyclerAdapter.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseRecyclerAdapter.java @@ -90,7 +90,7 @@ public abstract class FirebaseRecyclerAdapter Date: Tue, 24 Jan 2017 18:46:41 -0800 Subject: [PATCH 15/93] Provide default join resolver Signed-off-by: Alex Saveau --- .../com/firebase/ui/database/TestUtils.java | 2 +- .../firebase/ui/database/FirebaseArray.java | 4 +-- .../ui/database/FirebaseIndexArray.java | 30 +++++++++++++---- .../ui/database/FirebaseIndexListAdapter.java | 29 ++--------------- .../FirebaseIndexRecyclerAdapter.java | 32 +++---------------- .../ui/database/FirebaseListAdapter.java | 18 ++++++++--- .../ui/database/FirebaseRecyclerAdapter.java | 18 ++++++++--- 7 files changed, 58 insertions(+), 75 deletions(-) diff --git a/database/src/androidTest/java/com/firebase/ui/database/TestUtils.java b/database/src/androidTest/java/com/firebase/ui/database/TestUtils.java index 8a50d8c26..fa8736bd5 100644 --- a/database/src/androidTest/java/com/firebase/ui/database/TestUtils.java +++ b/database/src/androidTest/java/com/firebase/ui/database/TestUtils.java @@ -37,7 +37,7 @@ private static FirebaseApp initializeApp(Context context) { } public static void setJoinResolver(FirebaseIndexArray array, final DatabaseReference ref) { - array.setJoinResolverListener(new JoinResolver() { + array.setJoinResolver(new JoinResolver() { @Override public Query onJoin(DataSnapshot keySnapshot, String previousChildKey) { return ref.child(keySnapshot.getKey()); diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseArray.java b/database/src/main/java/com/firebase/ui/database/FirebaseArray.java index ab6e1cf43..46cb0c5ba 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseArray.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseArray.java @@ -34,8 +34,8 @@ class FirebaseArray implements ChildEventListener, ValueEventListener { private Query mQuery; private List mSnapshots = new ArrayList<>(); - public FirebaseArray(Query ref) { - mQuery = ref; + public FirebaseArray(Query query) { + mQuery = query; } public void setChangeEventListener(@NonNull ChangeEventListener listener) { 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 896bc68a1..197e3d703 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseIndexArray.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseIndexArray.java @@ -46,12 +46,14 @@ public void onCancelled(DatabaseError error) { }; protected JoinResolver mJoinResolver; + private Query mDataQuery; private ChangeEventListener mListenerCopy; private Map mRefs = new HashMap<>(); private List mDataSnapshots = new ArrayList<>(); - public FirebaseIndexArray(Query keyRef) { - super(keyRef); + public FirebaseIndexArray(Query keyQuery, Query dataQuery) { + super(keyQuery); + mDataQuery = dataQuery; } @Override @@ -60,11 +62,8 @@ public void setChangeEventListener(@NonNull ChangeEventListener listener) { mListenerCopy = listener; } - public void setJoinResolverListener(@NonNull JoinResolver joinResolver) { - if (mIsListening && joinResolver == null) { - throw new IllegalStateException("Join resolver cannot be null."); - } - mJoinResolver = joinResolver; + public void setJoinResolver(JoinResolver joinResolver) { + mJoinResolver = joinResolver == null ? new DefaultJoinResolver() : joinResolver; } @Override @@ -198,4 +197,21 @@ public void onCancelled(DatabaseError error) { mListener.onCancelled(error); } } + + private class DefaultJoinResolver implements JoinResolver { + @Override + public Query onJoin(DataSnapshot keySnapshot, String previousChildKey) { + return mDataQuery.getRef().child(keySnapshot.getKey()); + } + + @Override + public Query onDisjoin(DataSnapshot keySnapshot) { + return mDataQuery.getRef().child(keySnapshot.getKey()); + } + + @Override + public void onJoinFailed(int index, DataSnapshot snapshot) { + Log.w(TAG, "Key not found at ref " + snapshot.getRef() + " for index " + index + "."); + } + } } diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseIndexListAdapter.java b/database/src/main/java/com/firebase/ui/database/FirebaseIndexListAdapter.java index 52376acba..a5878d632 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseIndexListAdapter.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseIndexListAdapter.java @@ -2,9 +2,7 @@ import android.app.Activity; import android.support.annotation.LayoutRes; -import android.util.Log; -import com.google.firebase.database.DataSnapshot; import com.google.firebase.database.Query; /** @@ -36,12 +34,7 @@ * @param The class type to use as a model for the data * contained in the children of the given Firebase location */ -public abstract class FirebaseIndexListAdapter extends FirebaseListAdapter - implements JoinResolver { - private static final String TAG = "IndexListAdapter"; - - protected Query mDataQuery; - +public abstract class FirebaseIndexListAdapter extends FirebaseListAdapter { /** * @param activity The activity containing the ListView * @param modelClass Firebase will marshall the data at a location into @@ -61,24 +54,6 @@ public FirebaseIndexListAdapter(Activity activity, @LayoutRes int modelLayout, Query keyRef, Query dataRef) { - super(activity, modelClass, modelLayout, new FirebaseIndexArray(keyRef), false); - mDataQuery = dataRef; - ((FirebaseIndexArray) mSnapshots).setJoinResolverListener(this); - mSnapshots.startListening(); - } - - @Override - public Query onJoin(DataSnapshot keySnapshot, String previousChildKey) { - return mDataQuery.getRef().child(keySnapshot.getKey()); - } - - @Override - public Query onDisjoin(DataSnapshot keySnapshot) { - return mDataQuery.getRef().child(keySnapshot.getKey()); - } - - @Override - public void onJoinFailed(int index, DataSnapshot snapshot) { - Log.w(TAG, "Key not found at ref " + snapshot.getRef() + " for index " + index + "."); + super(activity, modelClass, modelLayout, new FirebaseIndexArray(keyRef, dataRef)); } } diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseIndexRecyclerAdapter.java b/database/src/main/java/com/firebase/ui/database/FirebaseIndexRecyclerAdapter.java index 1ed61a2fa..1dcb974fa 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseIndexRecyclerAdapter.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseIndexRecyclerAdapter.java @@ -16,14 +16,12 @@ import android.support.annotation.LayoutRes; import android.support.v7.widget.RecyclerView; -import android.util.Log; -import com.google.firebase.database.DataSnapshot; import com.google.firebase.database.Query; /** - * This class is a generic way of backing an RecyclerView with a Firebase location. - * It handles all of the child events at the given Firebase location. It marshals received data into the given + * This class is a generic way of backing an RecyclerView with a Firebase location. It handles all + * of the child events at the given Firebase location. It marshals received data into the given * class type. *

* To use this class in your app, subclass it passing in all required parameters and implement the @@ -64,11 +62,7 @@ * @param The ViewHolder class that contains the Views in the layout that is shown for each object. */ public abstract class FirebaseIndexRecyclerAdapter - extends FirebaseRecyclerAdapter implements JoinResolver { - private static final String TAG = "IndexRecyclerAdapter"; - - protected Query mDataQuery; - + extends FirebaseRecyclerAdapter { /** * @param modelClass Firebase will marshall the data at a location into an instance * of a class that you provide @@ -88,24 +82,6 @@ public FirebaseIndexRecyclerAdapter(Class modelClass, Class viewHolderClass, Query keyRef, Query dataRef) { - super(modelClass, modelLayout, viewHolderClass, new FirebaseIndexArray(keyRef), false); - mDataQuery = dataRef; - ((FirebaseIndexArray) mSnapshots).setJoinResolverListener(this); - mSnapshots.startListening(); - } - - @Override - public Query onJoin(DataSnapshot keySnapshot, String previousChildKey) { - return mDataQuery.getRef().child(keySnapshot.getKey()); - } - - @Override - public Query onDisjoin(DataSnapshot keySnapshot) { - return mDataQuery.getRef().child(keySnapshot.getKey()); - } - - @Override - public void onJoinFailed(int index, DataSnapshot snapshot) { - Log.w(TAG, "Key not found at ref " + snapshot.getRef() + " for index " + index + "."); + super(modelClass, modelLayout, viewHolderClass, new FirebaseIndexArray(keyRef, dataRef)); } } diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseListAdapter.java b/database/src/main/java/com/firebase/ui/database/FirebaseListAdapter.java index a269f13ce..d6fd3444b 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseListAdapter.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseListAdapter.java @@ -53,7 +53,7 @@ public abstract class FirebaseListAdapter extends BaseAdapter { private static final String TAG = "FirebaseListAdapter"; - FirebaseArray mSnapshots; + private FirebaseArray mSnapshots; private final Class mModelClass; protected Activity mActivity; protected int mLayout; @@ -61,8 +61,7 @@ public abstract class FirebaseListAdapter extends BaseAdapter { FirebaseListAdapter(Activity activity, Class modelClass, @LayoutRes int modelLayout, - FirebaseArray snapshots, - boolean shouldStartListening) { + FirebaseArray snapshots) { mActivity = activity; mModelClass = modelClass; mLayout = modelLayout; @@ -84,7 +83,7 @@ public void onCancelled(DatabaseError error) { FirebaseListAdapter.this.onCancelled(error); } }); - if (shouldStartListening) mSnapshots.startListening(); + startListening(); } /** @@ -101,7 +100,16 @@ public FirebaseListAdapter(Activity activity, Class modelClass, int modelLayout, Query ref) { - this(activity, modelClass, modelLayout, new FirebaseArray(ref), true); + this(activity, modelClass, modelLayout, new FirebaseArray(ref)); + } + + /** + * If you need to do some setup before we start listening for change events in the database + * (such as setting a custom {@link JoinResolver}), do so it here and then call {@code + * super.startListening()}. + */ + protected void startListening() { + mSnapshots.startListening(); } public void cleanup() { diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseRecyclerAdapter.java b/database/src/main/java/com/firebase/ui/database/FirebaseRecyclerAdapter.java index 54eb86f6d..99b3d2194 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseRecyclerAdapter.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseRecyclerAdapter.java @@ -75,7 +75,7 @@ public abstract class FirebaseRecyclerAdapter { private static final String TAG = "FirebaseRecyclerAdapter"; - FirebaseArray mSnapshots; + private FirebaseArray mSnapshots; private Class mModelClass; protected Class mViewHolderClass; protected int mModelLayout; @@ -83,8 +83,7 @@ public abstract class FirebaseRecyclerAdapter modelClass, @LayoutRes int modelLayout, Class viewHolderClass, - FirebaseArray snapshots, - boolean shouldStartListening) { + FirebaseArray snapshots) { mModelClass = modelClass; mModelLayout = modelLayout; mViewHolderClass = viewHolderClass; @@ -106,7 +105,7 @@ public void onCancelled(DatabaseError error) { FirebaseRecyclerAdapter.this.onCancelled(error); } }); - if (shouldStartListening) mSnapshots.startListening(); + startListening(); } /** @@ -123,7 +122,16 @@ public FirebaseRecyclerAdapter(Class modelClass, int modelLayout, Class viewHolderClass, Query ref) { - this(modelClass, modelLayout, viewHolderClass, new FirebaseArray(ref), true); + this(modelClass, modelLayout, viewHolderClass, new FirebaseArray(ref)); + } + + /** + * If you need to do some setup before we start listening for change events in the database + * (such as setting a custom {@link JoinResolver}), do so it here and then call {@code + * super.startListening()}. + */ + protected void startListening() { + mSnapshots.startListening(); } public void cleanup() { From dbbf0dc2c6961d35af031fe2b9799ec02d1c5edf Mon Sep 17 00:00:00 2001 From: Alex Saveau Date: Tue, 24 Jan 2017 18:54:37 -0800 Subject: [PATCH 16/93] Fix tests and bugs Signed-off-by: Alex Saveau --- .../FirebaseIndexArrayOfObjectsTest.java | 4 +--- .../ui/database/FirebaseIndexArrayTest.java | 4 +--- .../com/firebase/ui/database/TestUtils.java | 22 ------------------- .../firebase/ui/database/FirebaseArray.java | 2 +- .../ui/database/FirebaseIndexArray.java | 10 +++++++-- 5 files changed, 11 insertions(+), 31 deletions(-) diff --git a/database/src/androidTest/java/com/firebase/ui/database/FirebaseIndexArrayOfObjectsTest.java b/database/src/androidTest/java/com/firebase/ui/database/FirebaseIndexArrayOfObjectsTest.java index d795b3c89..677a72260 100644 --- a/database/src/androidTest/java/com/firebase/ui/database/FirebaseIndexArrayOfObjectsTest.java +++ b/database/src/androidTest/java/com/firebase/ui/database/FirebaseIndexArrayOfObjectsTest.java @@ -32,7 +32,6 @@ import static com.firebase.ui.database.TestUtils.getAppInstance; import static com.firebase.ui.database.TestUtils.getBean; import static com.firebase.ui.database.TestUtils.runAndWaitUntil; -import static com.firebase.ui.database.TestUtils.setJoinResolver; @RunWith(AndroidJUnit4.class) @SmallTest @@ -48,8 +47,7 @@ public void setUp() throws Exception { mRef = databaseInstance.getReference().child("firebasearray").child("objects"); mKeyRef = databaseInstance.getReference().child("firebaseindexarray").child("objects"); - mArray = new FirebaseIndexArray(mKeyRef); - setJoinResolver(mArray, mRef); + mArray = new FirebaseIndexArray(mKeyRef, mRef); mRef.removeValue(); mKeyRef.removeValue(); diff --git a/database/src/androidTest/java/com/firebase/ui/database/FirebaseIndexArrayTest.java b/database/src/androidTest/java/com/firebase/ui/database/FirebaseIndexArrayTest.java index 58365e4ed..2480c23f2 100644 --- a/database/src/androidTest/java/com/firebase/ui/database/FirebaseIndexArrayTest.java +++ b/database/src/androidTest/java/com/firebase/ui/database/FirebaseIndexArrayTest.java @@ -31,7 +31,6 @@ import static com.firebase.ui.database.TestUtils.getAppInstance; import static com.firebase.ui.database.TestUtils.isValuesEqual; import static com.firebase.ui.database.TestUtils.runAndWaitUntil; -import static com.firebase.ui.database.TestUtils.setJoinResolver; @RunWith(AndroidJUnit4.class) @SmallTest @@ -48,8 +47,7 @@ public void setUp() throws Exception { mRef = databaseInstance.getReference().child("firebasearray"); mKeyRef = databaseInstance.getReference().child("firebaseindexarray"); - mArray = new FirebaseIndexArray(mKeyRef); - setJoinResolver(mArray, mRef); + mArray = new FirebaseIndexArray(mKeyRef, mRef); mRef.removeValue(); mKeyRef.removeValue(); diff --git a/database/src/androidTest/java/com/firebase/ui/database/TestUtils.java b/database/src/androidTest/java/com/firebase/ui/database/TestUtils.java index fa8736bd5..a1e4e6fb1 100644 --- a/database/src/androidTest/java/com/firebase/ui/database/TestUtils.java +++ b/database/src/androidTest/java/com/firebase/ui/database/TestUtils.java @@ -5,10 +5,7 @@ import com.firebase.ui.database.utils.Bean; import com.google.firebase.FirebaseApp; import com.google.firebase.FirebaseOptions; -import com.google.firebase.database.DataSnapshot; import com.google.firebase.database.DatabaseError; -import com.google.firebase.database.DatabaseReference; -import com.google.firebase.database.Query; import junit.framework.AssertionFailedError; @@ -36,25 +33,6 @@ private static FirebaseApp initializeApp(Context context) { .build(), APP_NAME); } - public static void setJoinResolver(FirebaseIndexArray array, final DatabaseReference ref) { - array.setJoinResolver(new JoinResolver() { - @Override - public Query onJoin(DataSnapshot keySnapshot, String previousChildKey) { - return ref.child(keySnapshot.getKey()); - } - - @Override - public Query onDisjoin(DataSnapshot keySnapshot) { - return ref.child(keySnapshot.getKey()); - } - - @Override - public void onJoinFailed(int index, DataSnapshot snapshot) { - throw new IllegalStateException(index + ": " + snapshot); - } - }); - } - public static void runAndWaitUntil(FirebaseArray array, Runnable task, Callable done) throws InterruptedException { diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseArray.java b/database/src/main/java/com/firebase/ui/database/FirebaseArray.java index 46cb0c5ba..1830bd16c 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseArray.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseArray.java @@ -40,7 +40,7 @@ public FirebaseArray(Query query) { public void setChangeEventListener(@NonNull ChangeEventListener listener) { if (mIsListening && listener == null) { - throw new IllegalStateException("Listener cannot be null."); + throw new IllegalArgumentException("Listener cannot be null."); } mListener = listener; } 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 197e3d703..205e62e9e 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseIndexArray.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseIndexArray.java @@ -63,12 +63,18 @@ public void setChangeEventListener(@NonNull ChangeEventListener listener) { } public void setJoinResolver(JoinResolver joinResolver) { - mJoinResolver = joinResolver == null ? new DefaultJoinResolver() : joinResolver; + if (mIsListening && joinResolver == null) { + throw new IllegalArgumentException( + "Join resolver cannot be null while FirebaseIndexArray is listening for data."); + } + mJoinResolver = joinResolver; } @Override public void startListening() { - if (mJoinResolver == null) throw new IllegalStateException("Join resolver cannot be null."); + if (mJoinResolver == null) { + mJoinResolver = new DefaultJoinResolver(); + } super.startListening(); } From b37c8bdeb6c6a19a557134fe839d6440f4f78fd7 Mon Sep 17 00:00:00 2001 From: Alex Saveau Date: Tue, 24 Jan 2017 19:06:39 -0800 Subject: [PATCH 17/93] Cleanup Signed-off-by: Alex Saveau --- app/src/main/res/values/strings.xml | 2 +- .../ui/database/FirebaseIndexArrayOfObjectsTest.java | 2 +- .../com/firebase/ui/database/FirebaseIndexArrayTest.java | 2 +- .../main/java/com/firebase/ui/database/FirebaseArray.java | 5 ++++- .../src/main/java/com/firebase/ui/database/JoinResolver.java | 3 +++ 5 files changed, 10 insertions(+), 4 deletions(-) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2ec3749b0..979ddfbdd 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -67,6 +67,6 @@ Downloaded image Drive File - + No messages. Start chatting at the bottom! diff --git a/database/src/androidTest/java/com/firebase/ui/database/FirebaseIndexArrayOfObjectsTest.java b/database/src/androidTest/java/com/firebase/ui/database/FirebaseIndexArrayOfObjectsTest.java index 677a72260..40ec8fb36 100644 --- a/database/src/androidTest/java/com/firebase/ui/database/FirebaseIndexArrayOfObjectsTest.java +++ b/database/src/androidTest/java/com/firebase/ui/database/FirebaseIndexArrayOfObjectsTest.java @@ -38,7 +38,7 @@ public class FirebaseIndexArrayOfObjectsTest extends InstrumentationTestCase { private DatabaseReference mRef; private DatabaseReference mKeyRef; - private FirebaseIndexArray mArray; + private FirebaseArray mArray; @Before public void setUp() throws Exception { diff --git a/database/src/androidTest/java/com/firebase/ui/database/FirebaseIndexArrayTest.java b/database/src/androidTest/java/com/firebase/ui/database/FirebaseIndexArrayTest.java index 2480c23f2..49613ecbd 100644 --- a/database/src/androidTest/java/com/firebase/ui/database/FirebaseIndexArrayTest.java +++ b/database/src/androidTest/java/com/firebase/ui/database/FirebaseIndexArrayTest.java @@ -37,7 +37,7 @@ public class FirebaseIndexArrayTest extends InstrumentationTestCase { private DatabaseReference mRef; private DatabaseReference mKeyRef; - private FirebaseIndexArray mArray; + private FirebaseArray mArray; @Before public void setUp() throws Exception { diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseArray.java b/database/src/main/java/com/firebase/ui/database/FirebaseArray.java index 1830bd16c..37cccd82d 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseArray.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseArray.java @@ -46,7 +46,10 @@ public void setChangeEventListener(@NonNull ChangeEventListener listener) { } public void startListening() { - if (mListener == null) throw new IllegalStateException("Listener cannot be null."); + if (mListener == null) { + throw new IllegalStateException("Listener cannot be null."); + } + mQuery.addChildEventListener(this); mQuery.addValueEventListener(this); mIsListening = true; diff --git a/database/src/main/java/com/firebase/ui/database/JoinResolver.java b/database/src/main/java/com/firebase/ui/database/JoinResolver.java index 631cdc8b4..7ac03bd50 100644 --- a/database/src/main/java/com/firebase/ui/database/JoinResolver.java +++ b/database/src/main/java/com/firebase/ui/database/JoinResolver.java @@ -3,6 +3,9 @@ import com.google.firebase.database.DataSnapshot; import com.google.firebase.database.Query; +/** + * Handles joining two queries together. + */ public interface JoinResolver { /** * Called after an {@code onChildAdded} event from {@code keyRef}. From d25245bacc9a84bcf80700f06c081d155696fe4c Mon Sep 17 00:00:00 2001 From: Alex Saveau Date: Tue, 24 Jan 2017 19:08:46 -0800 Subject: [PATCH 18/93] More cleanup Signed-off-by: Alex Saveau --- app/src/main/res/values/strings.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 979ddfbdd..72dd1a622 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -38,8 +38,6 @@ No internet connection You are signed in! Sign out - Signed In - Sign In Failed Delete account Sign out failed Delete account failed @@ -67,6 +65,8 @@ Downloaded image Drive File - + No messages. Start chatting at the bottom! + Signed In + Sign In Failed From c9aa71763f8d750409ade0c87e93198e89b92cfb Mon Sep 17 00:00:00 2001 From: Alex Saveau Date: Tue, 24 Jan 2017 19:21:13 -0800 Subject: [PATCH 19/93] Cleanup Signed-off-by: Alex Saveau --- .../ui/database/FirebaseIndexArray.java | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) 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 a122ccbc4..0c6dcefca 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseIndexArray.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseIndexArray.java @@ -91,6 +91,24 @@ public void stopListening() { mDataSnapshots.clear(); } + private int getIndexForKey(String key) { + int dataCount = size(); + int index = 0; + for (int keyIndex = 0; index < dataCount; keyIndex++) { + String superKey = super.get(keyIndex).getKey(); + if (key.equals(superKey)) { + break; + } else if (mDataSnapshots.get(index).getKey().equals(superKey)) { + index++; + } + } + return index; + } + + private boolean isMatch(int index, String key) { + return index >= 0 && index < size() && mDataSnapshots.get(index).getKey().equals(key); + } + @Override public void onChildAdded(DataSnapshot keySnapshot, String previousChildKey) { super.setChangeEventListener(NOOP_CHANGE_LISTENER); @@ -196,24 +214,6 @@ public void onJoinFailed(int index, DataSnapshot snapshot) { } } - private int getIndexForKey(String key) { - int dataCount = size(); - int index = 0; - for (int keyIndex = 0; index < dataCount; keyIndex++) { - String superKey = super.get(keyIndex).getKey(); - if (key.equals(superKey)) { - break; - } else if (mDataSnapshots.get(index).getKey().equals(superKey)) { - index++; - } - } - return index; - } - - private boolean isMatch(int index, String key) { - return index >= 0 && index < size() && mDataSnapshots.get(index).getKey().equals(key); - } - @Override public int size() { return mDataSnapshots.size(); From 6b6c25382d4aca7f9b5b84d5e001388a8977f6b4 Mon Sep 17 00:00:00 2001 From: Alex Saveau Date: Tue, 24 Jan 2017 19:27:45 -0800 Subject: [PATCH 20/93] Update equals toString and hashCode Signed-off-by: Alex Saveau --- .../com/firebase/ui/database/FirebaseArray.java | 3 +-- .../firebase/ui/database/FirebaseIndexArray.java | 13 +++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseArray.java b/database/src/main/java/com/firebase/ui/database/FirebaseArray.java index 9f683f7f2..eff291086 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseArray.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseArray.java @@ -213,8 +213,7 @@ public int hashCode() { @Override public String toString() { return "FirebaseArray{" + - "mListener=" + mListener + - ", mIsListening=" + mIsListening + + "mIsListening=" + mIsListening + ", mQuery=" + mQuery + ", mSnapshots=" + mSnapshots + '}'; 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 0c6dcefca..e11edce04 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseIndexArray.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseIndexArray.java @@ -282,7 +282,9 @@ public boolean equals(Object o) { FirebaseIndexArray array = (FirebaseIndexArray) o; - return mJoinResolver.equals(array.mJoinResolver) + return (mJoinResolver == null ? array.mJoinResolver == null : mJoinResolver.equals(array.mJoinResolver)) + && (mListenerCopy == null ? array.mListenerCopy == null : mListenerCopy.equals(array.mListenerCopy)) + && mDataQuery.equals(array.mDataQuery) && mRefs.equals(array.mRefs) && mDataSnapshots.equals(array.mDataSnapshots); } @@ -290,7 +292,9 @@ public boolean equals(Object o) { @Override public int hashCode() { int result = super.hashCode(); - result = 31 * result + mJoinResolver.hashCode(); + result = 31 * result + (mJoinResolver == null ? 0 : mJoinResolver.hashCode()); + result = 31 * result + (mListenerCopy == null ? 0 : mListenerCopy.hashCode()); + result = 31 * result + mDataQuery.hashCode(); result = 31 * result + mRefs.hashCode(); result = 31 * result + mDataSnapshots.hashCode(); return result; @@ -299,10 +303,7 @@ public int hashCode() { @Override public String toString() { return "FirebaseIndexArray{" + - "mListener=" + mListener + - ", mIsListening=" + isListening() + - ", mJoinResolver=" + mJoinResolver + - ", mRefs=" + mRefs + + "mDataQuery=" + mDataQuery + ", mDataSnapshots=" + mDataSnapshots + '}'; } From 7e01eb1284cdf2ecdd654285a2fe1eb0eebb61df Mon Sep 17 00:00:00 2001 From: Alex Saveau Date: Tue, 24 Jan 2017 19:42:33 -0800 Subject: [PATCH 21/93] Fix spacing Signed-off-by: Alex Saveau --- .../uidemo/database/ChatActivity.java | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/com/firebase/uidemo/database/ChatActivity.java b/app/src/main/java/com/firebase/uidemo/database/ChatActivity.java index 1c5944972..805c82ca5 100644 --- a/app/src/main/java/com/firebase/uidemo/database/ChatActivity.java +++ b/app/src/main/java/com/firebase/uidemo/database/ChatActivity.java @@ -146,17 +146,16 @@ public void onAuthStateChanged(@NonNull FirebaseAuth firebaseAuth) { } private void attachRecyclerViewAdapter() { - mRecyclerViewAdapter = - new FirebaseIndexRecyclerAdapter( - Chat.class, - R.layout.message, - ChatHolder.class, - mChatIndicesRef.limitToLast(50), - mChatRef) { - @Override - public void populateViewHolder(ChatHolder chatView, Chat chat, int position) { - chatView.setName(chat.getName()); - chatView.setText(chat.getMessage()); + mRecyclerViewAdapter = new FirebaseIndexRecyclerAdapter( + Chat.class, + R.layout.message, + ChatHolder.class, + mChatIndicesRef.limitToLast(50), + mChatRef) { + @Override + public void populateViewHolder(ChatHolder chatView, Chat chat, int position) { + chatView.setName(chat.getName()); + chatView.setText(chat.getMessage()); FirebaseUser currentUser = mAuth.getCurrentUser(); if (currentUser != null && chat.getUid().equals(currentUser.getUid())) { From 78f0a904fd22ebdf1538f2e72c9e7eeffe6aa7a4 Mon Sep 17 00:00:00 2001 From: Alex Saveau Date: Tue, 24 Jan 2017 19:46:10 -0800 Subject: [PATCH 22/93] Remove references to JoinResolver.java Signed-off-by: Alex Saveau --- .../ui/database/FirebaseIndexArray.java | 48 +++---------------- .../firebase/ui/database/JoinResolver.java | 36 -------------- 2 files changed, 6 insertions(+), 78 deletions(-) delete mode 100644 database/src/main/java/com/firebase/ui/database/JoinResolver.java 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 e11edce04..6592683de 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseIndexArray.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseIndexArray.java @@ -48,7 +48,6 @@ public void onCancelled(DatabaseError error) { } }; - protected JoinResolver mJoinResolver; private Query mDataQuery; private ChangeEventListener mListenerCopy; private Map mRefs = new HashMap<>(); @@ -65,22 +64,6 @@ public void setChangeEventListener(@NonNull ChangeEventListener listener) { mListenerCopy = listener; } - public void setJoinResolver(JoinResolver joinResolver) { - if (isListening() && joinResolver == null) { - throw new IllegalArgumentException( - "Join resolver cannot be null while FirebaseIndexArray is listening for data."); - } - mJoinResolver = joinResolver; - } - - @Override - public void startListening() { - if (mJoinResolver == null) { - mJoinResolver = new DefaultJoinResolver(); - } - super.startListening(); - } - @Override public void stopListening() { super.stopListening(); @@ -115,7 +98,7 @@ public void onChildAdded(DataSnapshot keySnapshot, String previousChildKey) { super.onChildAdded(keySnapshot, previousChildKey); super.setChangeEventListener(mListenerCopy); - Query ref = mJoinResolver.onJoin(keySnapshot, previousChildKey); + Query ref = mDataQuery.getRef().child(keySnapshot.getKey()); mRefs.put(ref, ref.addValueEventListener(new DataRefListener())); } @@ -130,9 +113,9 @@ public void onChildChanged(DataSnapshot snapshot, String previousChildKey) { public void onChildRemoved(DataSnapshot keySnapshot) { String key = keySnapshot.getKey(); int index = getIndexForKey(key); - - Query removeQuery = mJoinResolver.onDisjoin(keySnapshot); - removeQuery.removeEventListener(mRefs.remove(removeQuery)); + mDataQuery.getRef() + .child(key) + .removeEventListener(mRefs.remove(mDataQuery.getRef().child(key))); super.setChangeEventListener(NOOP_CHANGE_LISTENER); super.onChildRemoved(keySnapshot); @@ -186,7 +169,7 @@ public void onDataChange(DataSnapshot snapshot) { mDataSnapshots.remove(index); notifyChangeEventListeners(ChangeEventListener.EventType.REMOVED, index); } else { - mJoinResolver.onJoinFailed(index, snapshot); + Log.w(TAG, "Key not found at ref: " + snapshot.getRef()); } } } @@ -197,23 +180,6 @@ public void onCancelled(DatabaseError error) { } } - private class DefaultJoinResolver implements JoinResolver { - @Override - public Query onJoin(DataSnapshot keySnapshot, String previousChildKey) { - return mDataQuery.getRef().child(keySnapshot.getKey()); - } - - @Override - public Query onDisjoin(DataSnapshot keySnapshot) { - return mDataQuery.getRef().child(keySnapshot.getKey()); - } - - @Override - public void onJoinFailed(int index, DataSnapshot snapshot) { - Log.w(TAG, "Key not found at ref " + snapshot.getRef() + " for index " + index + "."); - } - } - @Override public int size() { return mDataSnapshots.size(); @@ -282,8 +248,7 @@ public boolean equals(Object o) { FirebaseIndexArray array = (FirebaseIndexArray) o; - return (mJoinResolver == null ? array.mJoinResolver == null : mJoinResolver.equals(array.mJoinResolver)) - && (mListenerCopy == null ? array.mListenerCopy == null : mListenerCopy.equals(array.mListenerCopy)) + return (mListenerCopy == null ? array.mListenerCopy == null : mListenerCopy.equals(array.mListenerCopy)) && mDataQuery.equals(array.mDataQuery) && mRefs.equals(array.mRefs) && mDataSnapshots.equals(array.mDataSnapshots); @@ -292,7 +257,6 @@ public boolean equals(Object o) { @Override public int hashCode() { int result = super.hashCode(); - result = 31 * result + (mJoinResolver == null ? 0 : mJoinResolver.hashCode()); result = 31 * result + (mListenerCopy == null ? 0 : mListenerCopy.hashCode()); result = 31 * result + mDataQuery.hashCode(); result = 31 * result + mRefs.hashCode(); diff --git a/database/src/main/java/com/firebase/ui/database/JoinResolver.java b/database/src/main/java/com/firebase/ui/database/JoinResolver.java deleted file mode 100644 index 7ac03bd50..000000000 --- a/database/src/main/java/com/firebase/ui/database/JoinResolver.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.firebase.ui.database; - -import com.google.firebase.database.DataSnapshot; -import com.google.firebase.database.Query; - -/** - * Handles joining two queries together. - */ -public interface JoinResolver { - /** - * Called after an {@code onChildAdded} event from {@code keyRef}. - * - * @param keySnapshot The snapshot supplied in {@code onChildAdded}. - * @param previousChildKey The previous child's key 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. - */ - Query onJoin(DataSnapshot keySnapshot, String previousChildKey); - - /** - * Called after an {@code onChildRemoved} 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}. - */ - Query onDisjoin(DataSnapshot keySnapshot); - - /** - * Called when a key in {@code keyRef} could not be found in {@code dataRef}. - * - * @param index The index of a {@code snapshot} in {@code keyRef} that could not be found in {@code dataRef}. - * @param snapshot The snapshot who's key could not be found in {@code dataRef}. - */ - void onJoinFailed(int index, DataSnapshot snapshot); -} From 26ae8294fb3017256a3d22faea577553039fdf9b Mon Sep 17 00:00:00 2001 From: Alex Saveau Date: Tue, 24 Jan 2017 20:42:31 -0800 Subject: [PATCH 23/93] Update documentation Signed-off-by: Alex Saveau --- .../firebase/ui/database/FirebaseArray.java | 33 +++++++++++++++++++ .../ui/database/FirebaseIndexArray.java | 3 +- 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseArray.java b/database/src/main/java/com/firebase/ui/database/FirebaseArray.java index eff291086..2a6c97a46 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseArray.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseArray.java @@ -34,6 +34,7 @@ public class FirebaseArray implements ChildEventListener, ValueEventListener, List { protected ChangeEventListener mListener; private boolean mIsListening; + private Query mQuery; private List mSnapshots = new ArrayList<>(); @@ -41,6 +42,11 @@ public FirebaseArray(Query query) { mQuery = query; } + /** + * @param listener The listener to be notified of change events and errors. + * @throws IllegalArgumentException if the {@link ChangeEventListener} is null while {@link + * FirebaseArray} is listening for change events. + */ public void setChangeEventListener(@NonNull ChangeEventListener listener) { if (mIsListening && listener == null) { throw new IllegalArgumentException("Listener cannot be null."); @@ -48,6 +54,12 @@ public void setChangeEventListener(@NonNull ChangeEventListener listener) { mListener = listener; } + /** + * Start listening for change events. Should be followed by a call to {@link #stopListening()} + * at some point. + * + * @throws IllegalStateException if {@link ChangeEventListener} is null. + */ public void startListening() { if (mListener == null) { throw new IllegalStateException("Listener cannot be null."); @@ -58,6 +70,9 @@ public void startListening() { mIsListening = true; } + /** + * Stop listening for change events. The list will be empty after this call returns. + */ public void stopListening() { mQuery.removeEventListener((ValueEventListener) this); mQuery.removeEventListener((ChildEventListener) this); @@ -65,6 +80,9 @@ public void stopListening() { mIsListening = false; } + /** + * @return true if {@link FirebaseArray} is listening for change events, false otherwise. + */ public boolean isListening() { return mIsListening; } @@ -173,16 +191,31 @@ public int lastIndexOf(Object o) { return mSnapshots.lastIndexOf(o); } + /** + * {@inheritDoc} + * + * @return an immutable iterator + */ @Override public Iterator iterator() { return new ImmutableIterator(mSnapshots.iterator()); } + /** + * {@inheritDoc} + * + * @return an immutable list iterator + */ @Override public ListIterator listIterator() { return new ImmutableListIterator(mSnapshots.listIterator()); } + /** + * {@inheritDoc} + * + * @return an immutable list iterator + */ @Override public ListIterator listIterator(int index) { return new ImmutableListIterator(mSnapshots.listIterator(index)); 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 6592683de..0b5d5718a 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseIndexArray.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseIndexArray.java @@ -267,7 +267,8 @@ public int hashCode() { @Override public String toString() { return "FirebaseIndexArray{" + - "mDataQuery=" + mDataQuery + + "mIsListening=" + isListening() + + ", mDataQuery=" + mDataQuery + ", mDataSnapshots=" + mDataSnapshots + '}'; } From 98412fa89a790a8c31b4c2304e395b775d7fbec3 Mon Sep 17 00:00:00 2001 From: Alex Saveau Date: Tue, 24 Jan 2017 22:20:19 -0800 Subject: [PATCH 24/93] Update api to follow a add/remove model which allows for more flexibility compared to the stop/start model. Signed-off-by: Alex Saveau --- .../firebase/ui/database/FirebaseArray.java | 108 +++++++++++------- .../ui/database/FirebaseIndexArray.java | 58 +++------- 2 files changed, 88 insertions(+), 78 deletions(-) diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseArray.java b/database/src/main/java/com/firebase/ui/database/FirebaseArray.java index 2a6c97a46..f0e13c575 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseArray.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseArray.java @@ -32,10 +32,9 @@ * This class implements a collection on top of a Firebase location. */ public class FirebaseArray implements ChildEventListener, ValueEventListener, List { - protected ChangeEventListener mListener; - private boolean mIsListening; - private Query mQuery; + private boolean mNotifyListeners = true; + private List mListeners = new ArrayList<>(); private List mSnapshots = new ArrayList<>(); public FirebaseArray(Query query) { @@ -43,52 +42,85 @@ public FirebaseArray(Query query) { } /** - * @param listener The listener to be notified of change events and errors. - * @throws IllegalArgumentException if the {@link ChangeEventListener} is null while {@link - * FirebaseArray} is listening for change events. + * Add a listener for change events and errors occurring at the location provided in {@link + * #FirebaseArray(Query)}. + * + * @param listener the listener to be called with changes + * @return a reference to the listener provided. Save this to remove the listener later + * @throws IllegalArgumentException if the listener is null */ - public void setChangeEventListener(@NonNull ChangeEventListener listener) { - if (mIsListening && listener == null) { - throw new IllegalArgumentException("Listener cannot be null."); + public ChangeEventListener addChangeEventListener(@NonNull ChangeEventListener listener) { + checkNotNull(listener); + + mListeners.add(listener); + if (mListeners.size() <= 1) { + mQuery.addChildEventListener(this); + mQuery.addValueEventListener(this); } - mListener = listener; + + return listener; } /** - * Start listening for change events. Should be followed by a call to {@link #stopListening()} - * at some point. + * Remove the {@link ChangeEventListener} from the location provided in {@link + * #FirebaseArray(Query)}. The list will be empty after this call returns. * - * @throws IllegalStateException if {@link ChangeEventListener} is null. + * @param listener the listener to remove + * @throws IllegalArgumentException if the listener is null */ - public void startListening() { - if (mListener == null) { - throw new IllegalStateException("Listener cannot be null."); + public void removeChangeEventListener(@NonNull ChangeEventListener listener) { + checkNotNull(listener); + + mListeners.remove(listener); + if (mListeners.isEmpty()) { + mQuery.removeEventListener((ValueEventListener) this); + mQuery.removeEventListener((ChildEventListener) this); + mSnapshots.clear(); } - - mQuery.addChildEventListener(this); - mQuery.addValueEventListener(this); - mIsListening = true; } - /** - * Stop listening for change events. The list will be empty after this call returns. - */ - public void stopListening() { - mQuery.removeEventListener((ValueEventListener) this); - mQuery.removeEventListener((ChildEventListener) this); - mSnapshots.clear(); - mIsListening = false; + private static void checkNotNull(ChangeEventListener listener) { + if (listener == null) { + throw new IllegalArgumentException("ChangeEventListener cannot be null."); + } } /** - * @return true if {@link FirebaseArray} is listening for change events, false otherwise. + * @return true if {@link FirebaseArray} is listening for change events, false otherwise */ public boolean isListening() { - return mIsListening; + return mListeners.size() > 0; + } + + protected void setShouldNotifyListeners(boolean notifyListeners) { + mNotifyListeners = notifyListeners; } protected void notifyChangeEventListeners(ChangeEventListener.EventType type, int index) { - mListener.onChildChanged(type, index, -1); + notifyChangeEventListeners(type, index, -1); + } + + protected void notifyChangeEventListeners(ChangeEventListener.EventType type, + int index, + int oldIndex) { + if (!mNotifyListeners) return; + for (ChangeEventListener listener : mListeners) { + listener.onChildChanged(type, index, oldIndex); + } + } + + protected void notifyListenersOnDataChanged() { + if (!mNotifyListeners) return; + for (ChangeEventListener listener : mListeners) { + listener.onDataChanged(); + } + } + + protected void notifyListenersOnCancelled(DatabaseError error) { + if (!mNotifyListeners) return; + for (ChangeEventListener listener : mListeners) { + listener.onCancelled(error); + } } @Override @@ -121,17 +153,17 @@ public void onChildMoved(DataSnapshot snapshot, String previousChildKey) { mSnapshots.remove(oldIndex); int newIndex = previousChildKey == null ? 0 : (getIndexForKey(previousChildKey) + 1); mSnapshots.add(newIndex, snapshot); - mListener.onChildChanged(ChangeEventListener.EventType.MOVED, newIndex, oldIndex); + notifyChangeEventListeners(ChangeEventListener.EventType.MOVED, newIndex, oldIndex); } @Override public void onDataChange(DataSnapshot dataSnapshot) { - mListener.onDataChanged(); + notifyListenersOnDataChanged(); } @Override public void onCancelled(DatabaseError error) { - mListener.onCancelled(error); + notifyListenersOnCancelled(error); } private int getIndexForKey(String key) { @@ -228,16 +260,14 @@ public boolean equals(Object o) { FirebaseArray snapshots = (FirebaseArray) o; - return mIsListening == snapshots.mIsListening - && mListener.equals(snapshots.mListener) + return mListeners.equals(snapshots.mListeners) && mQuery.equals(snapshots.mQuery) && mSnapshots.equals(snapshots.mSnapshots); } @Override public int hashCode() { - int result = mListener.hashCode(); - result = 31 * result + (mIsListening ? 1 : 0); + int result = mListeners.hashCode(); result = 31 * result + mQuery.hashCode(); result = 31 * result + mSnapshots.hashCode(); return result; @@ -246,7 +276,7 @@ public int hashCode() { @Override public String toString() { return "FirebaseArray{" + - "mIsListening=" + mIsListening + + "mIsListening=" + isListening() + ", mQuery=" + mQuery + ", mSnapshots=" + mSnapshots + '}'; 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 0b5d5718a..286064f26 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseIndexArray.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseIndexArray.java @@ -34,22 +34,8 @@ public class FirebaseIndexArray extends FirebaseArray { private static final String TAG = "FirebaseIndexArray"; - private static final ChangeEventListener NOOP_CHANGE_LISTENER = new ChangeEventListener() { - @Override - public void onChildChanged(EventType type, int index, int oldIndex) { - } - - @Override - public void onDataChanged() { - } - - @Override - public void onCancelled(DatabaseError error) { - } - }; private Query mDataQuery; - private ChangeEventListener mListenerCopy; private Map mRefs = new HashMap<>(); private List mDataSnapshots = new ArrayList<>(); @@ -59,19 +45,15 @@ public FirebaseIndexArray(Query keyQuery, Query dataQuery) { } @Override - public void setChangeEventListener(@NonNull ChangeEventListener listener) { - super.setChangeEventListener(listener); - mListenerCopy = listener; - } - - @Override - public void stopListening() { - super.stopListening(); - Set refs = new HashSet<>(mRefs.keySet()); - for (Query ref : refs) { - ref.removeEventListener(mRefs.remove(ref)); + public void removeChangeEventListener(@NonNull ChangeEventListener listener) { + super.removeChangeEventListener(listener); + if (!isListening()) { + Set refs = new HashSet<>(mRefs.keySet()); + for (Query ref : refs) { + ref.removeEventListener(mRefs.remove(ref)); + } + mDataSnapshots.clear(); } - mDataSnapshots.clear(); } private int getIndexForKey(String key) { @@ -94,9 +76,9 @@ private boolean isMatch(int index, String key) { @Override public void onChildAdded(DataSnapshot keySnapshot, String previousChildKey) { - super.setChangeEventListener(NOOP_CHANGE_LISTENER); + setShouldNotifyListeners(false); super.onChildAdded(keySnapshot, previousChildKey); - super.setChangeEventListener(mListenerCopy); + setShouldNotifyListeners(true); Query ref = mDataQuery.getRef().child(keySnapshot.getKey()); mRefs.put(ref, ref.addValueEventListener(new DataRefListener())); @@ -104,9 +86,9 @@ public void onChildAdded(DataSnapshot keySnapshot, String previousChildKey) { @Override public void onChildChanged(DataSnapshot snapshot, String previousChildKey) { - super.setChangeEventListener(NOOP_CHANGE_LISTENER); + setShouldNotifyListeners(false); super.onChildChanged(snapshot, previousChildKey); - super.setChangeEventListener(mListenerCopy); + setShouldNotifyListeners(true); } @Override @@ -117,9 +99,9 @@ public void onChildRemoved(DataSnapshot keySnapshot) { .child(key) .removeEventListener(mRefs.remove(mDataQuery.getRef().child(key))); - super.setChangeEventListener(NOOP_CHANGE_LISTENER); + setShouldNotifyListeners(false); super.onChildRemoved(keySnapshot); - super.setChangeEventListener(mListenerCopy); + setShouldNotifyListeners(true); if (isMatch(index, key)) { mDataSnapshots.remove(index); @@ -132,15 +114,15 @@ public void onChildMoved(DataSnapshot keySnapshot, String previousChildKey) { String key = keySnapshot.getKey(); int oldIndex = getIndexForKey(key); - super.setChangeEventListener(NOOP_CHANGE_LISTENER); + setShouldNotifyListeners(false); super.onChildMoved(keySnapshot, previousChildKey); - super.setChangeEventListener(mListenerCopy); + setShouldNotifyListeners(true); if (isMatch(oldIndex, key)) { DataSnapshot snapshot = mDataSnapshots.remove(oldIndex); int newIndex = getIndexForKey(key); mDataSnapshots.add(newIndex, snapshot); - mListener.onChildChanged(ChangeEventListener.EventType.MOVED, newIndex, oldIndex); + notifyChangeEventListeners(ChangeEventListener.EventType.MOVED, newIndex, oldIndex); } } @@ -176,7 +158,7 @@ public void onDataChange(DataSnapshot snapshot) { @Override public void onCancelled(DatabaseError error) { - mListener.onCancelled(error); + notifyListenersOnCancelled(error); } } @@ -248,8 +230,7 @@ public boolean equals(Object o) { FirebaseIndexArray array = (FirebaseIndexArray) o; - return (mListenerCopy == null ? array.mListenerCopy == null : mListenerCopy.equals(array.mListenerCopy)) - && mDataQuery.equals(array.mDataQuery) + return mDataQuery.equals(array.mDataQuery) && mRefs.equals(array.mRefs) && mDataSnapshots.equals(array.mDataSnapshots); } @@ -257,7 +238,6 @@ public boolean equals(Object o) { @Override public int hashCode() { int result = super.hashCode(); - result = 31 * result + (mListenerCopy == null ? 0 : mListenerCopy.hashCode()); result = 31 * result + mDataQuery.hashCode(); result = 31 * result + mRefs.hashCode(); result = 31 * result + mDataSnapshots.hashCode(); From 2b9ff05e161c305a8392cae275a5a0a2293b83fb Mon Sep 17 00:00:00 2001 From: Alex Saveau Date: Tue, 24 Jan 2017 22:37:28 -0800 Subject: [PATCH 25/93] Fix broken everything Signed-off-by: Alex Saveau --- .../database/FirebaseArrayOfObjectsTest.java | 14 ++--- .../ui/database/FirebaseArrayTest.java | 8 +-- .../FirebaseIndexArrayOfObjectsTest.java | 6 +-- .../ui/database/FirebaseIndexArrayTest.java | 6 +-- .../com/firebase/ui/database/TestUtils.java | 12 +++-- .../firebase/ui/database/FirebaseArray.java | 53 +++++-------------- .../ui/database/FirebaseListAdapter.java | 36 ++++++------- .../ui/database/FirebaseRecyclerAdapter.java | 36 ++++++------- 8 files changed, 74 insertions(+), 97 deletions(-) diff --git a/database/src/androidTest/java/com/firebase/ui/database/FirebaseArrayOfObjectsTest.java b/database/src/androidTest/java/com/firebase/ui/database/FirebaseArrayOfObjectsTest.java index 0ba5abffb..8dc675909 100644 --- a/database/src/androidTest/java/com/firebase/ui/database/FirebaseArrayOfObjectsTest.java +++ b/database/src/androidTest/java/com/firebase/ui/database/FirebaseArrayOfObjectsTest.java @@ -39,19 +39,21 @@ public class FirebaseArrayOfObjectsTest extends InstrumentationTestCase { private DatabaseReference mRef; private FirebaseArray mArray; + private ChangeEventListener mListener; @Before public void setUp() throws Exception { super.setUp(); FirebaseApp app = getAppInstance(getInstrumentation().getContext()); - mRef = FirebaseDatabase.getInstance(app).getReference() - .child("firebasearray").child("objects"); + mRef = FirebaseDatabase.getInstance(app) + .getReference() + .child("firebasearray") + .child("objects"); mArray = new FirebaseArray(mRef); - mRef.removeValue(); - runAndWaitUntil(mArray, new Runnable() { + + mListener = runAndWaitUntil(mArray, new Runnable() { @Override public void run() { - mArray.startListening(); for (int i = 1; i <= 3; i++) { mRef.push().setValue(new Bean(i, "Text " + i, i % 2 == 0), i); } @@ -67,8 +69,8 @@ public Boolean call() throws Exception { @After public void tearDown() throws Exception { + mArray.removeChangeEventListener(mListener); mRef.getRoot().removeValue(); - mArray.stopListening(); super.tearDown(); } diff --git a/database/src/androidTest/java/com/firebase/ui/database/FirebaseArrayTest.java b/database/src/androidTest/java/com/firebase/ui/database/FirebaseArrayTest.java index b664f4bad..221d42853 100644 --- a/database/src/androidTest/java/com/firebase/ui/database/FirebaseArrayTest.java +++ b/database/src/androidTest/java/com/firebase/ui/database/FirebaseArrayTest.java @@ -38,6 +38,7 @@ public class FirebaseArrayTest extends InstrumentationTestCase { private DatabaseReference mRef; private FirebaseArray mArray; + private ChangeEventListener mListener; @Before public void setUp() throws Exception { @@ -45,10 +46,9 @@ public void setUp() throws Exception { FirebaseApp app = getAppInstance(getInstrumentation().getContext()); mRef = FirebaseDatabase.getInstance(app).getReference().child("firebasearray"); mArray = new FirebaseArray(mRef); - mRef.removeValue(); - runAndWaitUntil(mArray, new Runnable() { + + mListener = runAndWaitUntil(mArray, new Runnable() { public void run() { - mArray.startListening(); for (int i = 1; i <= 3; i++) { mRef.push().setValue(i, i); } @@ -62,8 +62,8 @@ public Boolean call() throws Exception { @After public void tearDown() throws Exception { + mArray.removeChangeEventListener(mListener); mRef.getRoot().removeValue(); - mArray.stopListening(); super.tearDown(); } diff --git a/database/src/androidTest/java/com/firebase/ui/database/FirebaseIndexArrayOfObjectsTest.java b/database/src/androidTest/java/com/firebase/ui/database/FirebaseIndexArrayOfObjectsTest.java index 40ec8fb36..147dde831 100644 --- a/database/src/androidTest/java/com/firebase/ui/database/FirebaseIndexArrayOfObjectsTest.java +++ b/database/src/androidTest/java/com/firebase/ui/database/FirebaseIndexArrayOfObjectsTest.java @@ -39,6 +39,7 @@ public class FirebaseIndexArrayOfObjectsTest extends InstrumentationTestCase { private DatabaseReference mRef; private DatabaseReference mKeyRef; private FirebaseArray mArray; + private ChangeEventListener mListener; @Before public void setUp() throws Exception { @@ -51,10 +52,9 @@ public void setUp() throws Exception { mRef.removeValue(); mKeyRef.removeValue(); - runAndWaitUntil(mArray, new Runnable() { + mListener = runAndWaitUntil(mArray, new Runnable() { @Override public void run() { - mArray.startListening(); for (int i = 1; i <= 3; i++) { setValue(new Bean(i, "Text " + i, i % 2 == 0), i); } @@ -70,8 +70,8 @@ public Boolean call() throws Exception { @After public void tearDown() throws Exception { + mArray.removeChangeEventListener(mListener); mRef.getRoot().removeValue(); - mArray.stopListening(); super.tearDown(); } diff --git a/database/src/androidTest/java/com/firebase/ui/database/FirebaseIndexArrayTest.java b/database/src/androidTest/java/com/firebase/ui/database/FirebaseIndexArrayTest.java index 49613ecbd..3b136084c 100644 --- a/database/src/androidTest/java/com/firebase/ui/database/FirebaseIndexArrayTest.java +++ b/database/src/androidTest/java/com/firebase/ui/database/FirebaseIndexArrayTest.java @@ -38,6 +38,7 @@ public class FirebaseIndexArrayTest extends InstrumentationTestCase { private DatabaseReference mRef; private DatabaseReference mKeyRef; private FirebaseArray mArray; + private ChangeEventListener mListener; @Before public void setUp() throws Exception { @@ -51,10 +52,9 @@ public void setUp() throws Exception { mRef.removeValue(); mKeyRef.removeValue(); - runAndWaitUntil(mArray, new Runnable() { + mListener = runAndWaitUntil(mArray, new Runnable() { @Override public void run() { - mArray.startListening(); for (int i = 1; i <= 3; i++) { setValue(i, i); } @@ -69,8 +69,8 @@ public Boolean call() throws Exception { @After public void tearDown() throws Exception { + mArray.removeChangeEventListener(mListener); mRef.getRoot().removeValue(); - mArray.stopListening(); super.tearDown(); } diff --git a/database/src/androidTest/java/com/firebase/ui/database/TestUtils.java b/database/src/androidTest/java/com/firebase/ui/database/TestUtils.java index a1e4e6fb1..2bf140c13 100644 --- a/database/src/androidTest/java/com/firebase/ui/database/TestUtils.java +++ b/database/src/androidTest/java/com/firebase/ui/database/TestUtils.java @@ -33,11 +33,11 @@ private static FirebaseApp initializeApp(Context context) { .build(), APP_NAME); } - public static void runAndWaitUntil(FirebaseArray array, - Runnable task, - Callable done) throws InterruptedException { + public static ChangeEventListener runAndWaitUntil(FirebaseArray array, + Runnable task, + Callable done) throws InterruptedException { final Semaphore semaphore = new Semaphore(0); - array.setChangeEventListener(new ChangeEventListener() { + ChangeEventListener listener = array.addChangeEventListener(new ChangeEventListener() { @Override public void onChildChanged(ChangeEventListener.EventType type, int index, int oldIndex) { semaphore.release(); @@ -52,6 +52,7 @@ public void onCancelled(DatabaseError error) { } }); task.run(); + boolean isDone = false; long startedAt = System.currentTimeMillis(); while (!isDone && System.currentTimeMillis() - startedAt < TIMEOUT) { @@ -63,9 +64,12 @@ public void onCancelled(DatabaseError error) { // and we're not done } } + if (!isDone) { throw new AssertionFailedError(); } + + return listener; } public static boolean isValuesEqual(FirebaseArray array, int[] expected) { diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseArray.java b/database/src/main/java/com/firebase/ui/database/FirebaseArray.java index f0e13c575..cebbfc0d1 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseArray.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseArray.java @@ -15,6 +15,7 @@ package com.firebase.ui.database; import android.support.annotation.NonNull; +import android.support.annotation.RestrictTo; import com.google.firebase.database.ChildEventListener; import com.google.firebase.database.DataSnapshot; @@ -337,37 +338,16 @@ public int previousIndex() { return mListIterator.previousIndex(); } - /** - * Guaranteed to throw an exception and leave the collection unmodified. - * - * @throws UnsupportedOperationException always - * @deprecated Unsupported operation. - */ - @Deprecated @Override public void remove() { throw new UnsupportedOperationException(); } - /** - * Guaranteed to throw an exception and leave the collection unmodified. - * - * @throws UnsupportedOperationException always - * @deprecated Unsupported operation. - */ - @Deprecated @Override public void set(DataSnapshot snapshot) { throw new UnsupportedOperationException(); } - /** - * Guaranteed to throw an exception and leave the collection unmodified. - * - * @throws UnsupportedOperationException always - * @deprecated Unsupported operation. - */ - @Deprecated @Override public void add(DataSnapshot snapshot) { throw new UnsupportedOperationException(); @@ -379,9 +359,8 @@ public void add(DataSnapshot snapshot) { * Guaranteed to throw an exception and leave the collection unmodified. * * @throws UnsupportedOperationException always - * @deprecated Unsupported operation. */ - @Deprecated + @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) @Override public boolean add(DataSnapshot snapshot) { throw new UnsupportedOperationException(); @@ -391,9 +370,8 @@ public boolean add(DataSnapshot snapshot) { * Guaranteed to throw an exception and leave the collection unmodified. * * @throws UnsupportedOperationException always - * @deprecated Unsupported operation. */ - @Deprecated + @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) @Override public boolean remove(Object o) { throw new UnsupportedOperationException(); @@ -405,7 +383,7 @@ public boolean remove(Object o) { * @throws UnsupportedOperationException always * @deprecated Unsupported operation. */ - @Deprecated + @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) @Override public boolean addAll(Collection c) { throw new UnsupportedOperationException(); @@ -417,7 +395,7 @@ public boolean addAll(Collection c) { * @throws UnsupportedOperationException always * @deprecated Unsupported operation. */ - @Deprecated + @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) @Override public boolean addAll(int index, Collection c) { throw new UnsupportedOperationException(); @@ -427,9 +405,8 @@ public boolean addAll(int index, Collection c) { * Guaranteed to throw an exception and leave the collection unmodified. * * @throws UnsupportedOperationException always - * @deprecated Unsupported operation. */ - @Deprecated + @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) @Override public boolean removeAll(Collection c) { throw new UnsupportedOperationException(); @@ -439,9 +416,8 @@ public boolean removeAll(Collection c) { * Guaranteed to throw an exception and leave the collection unmodified. * * @throws UnsupportedOperationException always - * @deprecated Unsupported operation. */ - @Deprecated + @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) @Override public boolean retainAll(Collection c) { throw new UnsupportedOperationException(); @@ -451,9 +427,8 @@ public boolean retainAll(Collection c) { * Guaranteed to throw an exception and leave the collection unmodified. * * @throws UnsupportedOperationException always - * @deprecated Unsupported operation. */ - @Deprecated + @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) @Override public void clear() { throw new UnsupportedOperationException(); @@ -463,9 +438,8 @@ public void clear() { * Guaranteed to throw an exception and leave the collection unmodified. * * @throws UnsupportedOperationException always - * @deprecated Unsupported operation. */ - @Deprecated + @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) @Override public DataSnapshot set(int index, DataSnapshot element) { throw new UnsupportedOperationException(); @@ -475,9 +449,8 @@ public DataSnapshot set(int index, DataSnapshot element) { * Guaranteed to throw an exception and leave the collection unmodified. * * @throws UnsupportedOperationException always - * @deprecated Unsupported operation. */ - @Deprecated + @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) @Override public void add(int index, DataSnapshot element) { throw new UnsupportedOperationException(); @@ -487,9 +460,8 @@ public void add(int index, DataSnapshot element) { * Guaranteed to throw an exception and leave the collection unmodified. * * @throws UnsupportedOperationException always - * @deprecated Unsupported operation. */ - @Deprecated + @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) @Override public DataSnapshot remove(int index) { throw new UnsupportedOperationException(); @@ -499,9 +471,8 @@ public DataSnapshot remove(int index) { * Guaranteed to throw an exception and leave the collection unmodified. * * @throws UnsupportedOperationException always - * @deprecated Unsupported operation. */ - @Deprecated + @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) @Override public List subList(int fromIndex, int toIndex) { throw new UnsupportedOperationException(); diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseListAdapter.java b/database/src/main/java/com/firebase/ui/database/FirebaseListAdapter.java index 1c2f130c9..55c306b22 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseListAdapter.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseListAdapter.java @@ -57,6 +57,7 @@ public abstract class FirebaseListAdapter extends BaseAdapter { protected final Class mModelClass; protected Activity mActivity; protected int mLayout; + private ChangeEventListener mListener; FirebaseListAdapter(Activity activity, Class modelClass, @@ -67,22 +68,6 @@ public abstract class FirebaseListAdapter extends BaseAdapter { mLayout = modelLayout; mSnapshots = snapshots; - mSnapshots.setChangeEventListener(new ChangeEventListener() { - @Override - public void onChildChanged(EventType type, int index, int oldIndex) { - FirebaseListAdapter.this.onChildChanged(type, index, oldIndex); - } - - @Override - public void onDataChanged() { - FirebaseListAdapter.this.onDataChanged(); - } - - @Override - public void onCancelled(DatabaseError error) { - FirebaseListAdapter.this.onCancelled(error); - } - }); startListening(); } @@ -109,11 +94,26 @@ public FirebaseListAdapter(Activity activity, * super.startListening()}. */ protected void startListening() { - mSnapshots.startListening(); + mListener = mSnapshots.addChangeEventListener(new ChangeEventListener() { + @Override + public void onChildChanged(EventType type, int index, int oldIndex) { + FirebaseListAdapter.this.onChildChanged(type, index, oldIndex); + } + + @Override + public void onDataChanged() { + FirebaseListAdapter.this.onDataChanged(); + } + + @Override + public void onCancelled(DatabaseError error) { + FirebaseListAdapter.this.onCancelled(error); + } + }); } public void cleanup() { - mSnapshots.stopListening(); + mSnapshots.removeChangeEventListener(mListener); } @Override diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseRecyclerAdapter.java b/database/src/main/java/com/firebase/ui/database/FirebaseRecyclerAdapter.java index f1c48ee02..c732f8cfc 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseRecyclerAdapter.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseRecyclerAdapter.java @@ -79,6 +79,7 @@ public abstract class FirebaseRecyclerAdapter mModelClass; protected Class mViewHolderClass; protected int mModelLayout; + private ChangeEventListener mListener; FirebaseRecyclerAdapter(Class modelClass, @LayoutRes int modelLayout, @@ -89,22 +90,6 @@ public abstract class FirebaseRecyclerAdapter modelClass, * super.startListening()}. */ protected void startListening() { - mSnapshots.startListening(); + mListener = mSnapshots.addChangeEventListener(new ChangeEventListener() { + @Override + public void onChildChanged(EventType type, int index, int oldIndex) { + FirebaseRecyclerAdapter.this.onChildChanged(type, index, oldIndex); + } + + @Override + public void onDataChanged() { + FirebaseRecyclerAdapter.this.onDataChanged(); + } + + @Override + public void onCancelled(DatabaseError error) { + FirebaseRecyclerAdapter.this.onCancelled(error); + } + }); } public void cleanup() { - mSnapshots.stopListening(); + mSnapshots.removeChangeEventListener(mListener); } @Override From b68ec5c18ee0b893245d5b2c1e5f3a02eb0838b7 Mon Sep 17 00:00:00 2001 From: Alex Saveau Date: Wed, 25 Jan 2017 13:59:40 -0800 Subject: [PATCH 26/93] Add a wrapper around a list of DataSnapshots Signed-off-by: Alex Saveau --- .../firebase/ui/database/FirebaseArray.java | 232 ++---------------- .../ui/database/FirebaseIndexArray.java | 16 +- .../ui/database/FirebaseObjectsArray.java | 137 +++++++++++ .../ui/database/UnmodifiableList.java | 202 +++++++++++++++ 4 files changed, 365 insertions(+), 222 deletions(-) create mode 100644 database/src/main/java/com/firebase/ui/database/FirebaseObjectsArray.java create mode 100644 database/src/main/java/com/firebase/ui/database/UnmodifiableList.java diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseArray.java b/database/src/main/java/com/firebase/ui/database/FirebaseArray.java index cebbfc0d1..3b9c9d195 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseArray.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseArray.java @@ -15,7 +15,6 @@ package com.firebase.ui.database; import android.support.annotation.NonNull; -import android.support.annotation.RestrictTo; import com.google.firebase.database.ChildEventListener; import com.google.firebase.database.DataSnapshot; @@ -32,7 +31,7 @@ /** * This class implements a collection on top of a Firebase location. */ -public class FirebaseArray implements ChildEventListener, ValueEventListener, List { +public class FirebaseArray extends UnmodifiableList implements ChildEventListener, ValueEventListener { private Query mQuery; private boolean mNotifyListeners = true; private List mListeners = new ArrayList<>(); @@ -90,7 +89,7 @@ private static void checkNotNull(ChangeEventListener listener) { * @return true if {@link FirebaseArray} is listening for change events, false otherwise */ public boolean isListening() { - return mListeners.size() > 0; + return !mListeners.isEmpty(); } protected void setShouldNotifyListeners(boolean notifyListeners) { @@ -194,6 +193,16 @@ public boolean contains(Object o) { return mSnapshots.contains(o); } + /** + * {@inheritDoc} + * + * @return an immutable iterator + */ + @Override + public Iterator iterator() { + return new ImmutableIterator<>(mSnapshots.iterator()); + } + @Override public Object[] toArray() { return mSnapshots.toArray(); @@ -224,16 +233,6 @@ public int lastIndexOf(Object o) { return mSnapshots.lastIndexOf(o); } - /** - * {@inheritDoc} - * - * @return an immutable iterator - */ - @Override - public Iterator iterator() { - return new ImmutableIterator(mSnapshots.iterator()); - } - /** * {@inheritDoc} * @@ -241,7 +240,7 @@ public Iterator iterator() { */ @Override public ListIterator listIterator() { - return new ImmutableListIterator(mSnapshots.listIterator()); + return new ImmutableListIterator<>(mSnapshots.listIterator()); } /** @@ -251,15 +250,15 @@ public ListIterator listIterator() { */ @Override public ListIterator listIterator(int index) { - return new ImmutableListIterator(mSnapshots.listIterator(index)); + return new ImmutableListIterator<>(mSnapshots.listIterator(index)); } @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null || getClass() != obj.getClass()) return false; - FirebaseArray snapshots = (FirebaseArray) o; + FirebaseArray snapshots = (FirebaseArray) obj; return mListeners.equals(snapshots.mListeners) && mQuery.equals(snapshots.mQuery) @@ -282,199 +281,4 @@ public String toString() { ", mSnapshots=" + mSnapshots + '}'; } - - protected static class ImmutableIterator implements Iterator { - private Iterator mIterator; - - public ImmutableIterator(Iterator iterator) { - mIterator = iterator; - } - - @Override - public boolean hasNext() { - return mIterator.hasNext(); - } - - @Override - public DataSnapshot next() { - return mIterator.next(); - } - } - - protected static class ImmutableListIterator implements ListIterator { - private ListIterator mListIterator; - - public ImmutableListIterator(ListIterator listIterator) { - mListIterator = listIterator; - } - - @Override - public boolean hasNext() { - return mListIterator.hasNext(); - } - - @Override - public DataSnapshot next() { - return mListIterator.next(); - } - - @Override - public boolean hasPrevious() { - return mListIterator.hasPrevious(); - } - - @Override - public DataSnapshot previous() { - return mListIterator.previous(); - } - - @Override - public int nextIndex() { - return mListIterator.nextIndex(); - } - - @Override - public int previousIndex() { - return mListIterator.previousIndex(); - } - - @Override - public void remove() { - throw new UnsupportedOperationException(); - } - - @Override - public void set(DataSnapshot snapshot) { - throw new UnsupportedOperationException(); - } - - @Override - public void add(DataSnapshot snapshot) { - throw new UnsupportedOperationException(); - } - } - - - /** - * Guaranteed to throw an exception and leave the collection unmodified. - * - * @throws UnsupportedOperationException always - */ - @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) - @Override - public boolean add(DataSnapshot snapshot) { - throw new UnsupportedOperationException(); - } - - /** - * Guaranteed to throw an exception and leave the collection unmodified. - * - * @throws UnsupportedOperationException always - */ - @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) - @Override - public boolean remove(Object o) { - throw new UnsupportedOperationException(); - } - - /** - * Guaranteed to throw an exception and leave the collection unmodified. - * - * @throws UnsupportedOperationException always - * @deprecated Unsupported operation. - */ - @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) - @Override - public boolean addAll(Collection c) { - throw new UnsupportedOperationException(); - } - - /** - * Guaranteed to throw an exception and leave the collection unmodified. - * - * @throws UnsupportedOperationException always - * @deprecated Unsupported operation. - */ - @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) - @Override - public boolean addAll(int index, Collection c) { - throw new UnsupportedOperationException(); - } - - /** - * Guaranteed to throw an exception and leave the collection unmodified. - * - * @throws UnsupportedOperationException always - */ - @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) - @Override - public boolean removeAll(Collection c) { - throw new UnsupportedOperationException(); - } - - /** - * Guaranteed to throw an exception and leave the collection unmodified. - * - * @throws UnsupportedOperationException always - */ - @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) - @Override - public boolean retainAll(Collection c) { - throw new UnsupportedOperationException(); - } - - /** - * Guaranteed to throw an exception and leave the collection unmodified. - * - * @throws UnsupportedOperationException always - */ - @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) - @Override - public void clear() { - throw new UnsupportedOperationException(); - } - - /** - * Guaranteed to throw an exception and leave the collection unmodified. - * - * @throws UnsupportedOperationException always - */ - @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) - @Override - public DataSnapshot set(int index, DataSnapshot element) { - throw new UnsupportedOperationException(); - } - - /** - * Guaranteed to throw an exception and leave the collection unmodified. - * - * @throws UnsupportedOperationException always - */ - @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) - @Override - public void add(int index, DataSnapshot element) { - throw new UnsupportedOperationException(); - } - - /** - * Guaranteed to throw an exception and leave the collection unmodified. - * - * @throws UnsupportedOperationException always - */ - @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) - @Override - public DataSnapshot remove(int index) { - throw new UnsupportedOperationException(); - } - - /** - * Guaranteed to throw an exception and leave the collection unmodified. - * - * @throws UnsupportedOperationException always - */ - @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) - @Override - public List subList(int fromIndex, int toIndex) { - throw new UnsupportedOperationException(); - } } 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 286064f26..4df7e2159 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseIndexArray.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseIndexArray.java @@ -209,26 +209,26 @@ public int lastIndexOf(Object o) { @Override public Iterator iterator() { - return new ImmutableIterator(mDataSnapshots.iterator()); + return new ImmutableIterator<>(mDataSnapshots.iterator()); } @Override public ListIterator listIterator() { - return new ImmutableListIterator(mDataSnapshots.listIterator()); + return new ImmutableListIterator<>(mDataSnapshots.listIterator()); } @Override public ListIterator listIterator(int index) { - return new ImmutableListIterator(mDataSnapshots.listIterator(index)); + return new ImmutableListIterator<>(mDataSnapshots.listIterator(index)); } @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - if (!super.equals(o)) return false; + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null || getClass() != obj.getClass()) return false; + if (!super.equals(obj)) return false; - FirebaseIndexArray array = (FirebaseIndexArray) o; + FirebaseIndexArray array = (FirebaseIndexArray) obj; return mDataQuery.equals(array.mDataQuery) && mRefs.equals(array.mRefs) diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseObjectsArray.java b/database/src/main/java/com/firebase/ui/database/FirebaseObjectsArray.java new file mode 100644 index 000000000..a86901897 --- /dev/null +++ b/database/src/main/java/com/firebase/ui/database/FirebaseObjectsArray.java @@ -0,0 +1,137 @@ +package com.firebase.ui.database; + +import com.google.firebase.database.DataSnapshot; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; + +/** + * Acts as a bridge between a list of {@link DataSnapshot}s and a list of objects of type E. + * + * @param the object representation of a {@link DataSnapshot} + */ +public class FirebaseObjectsArray extends UnmodifiableList { + private List mSnapshots; + private Class mEClass; + + public FirebaseObjectsArray(List snapshots, Class eClass) { + mSnapshots = snapshots; + mEClass = eClass; + } + + protected List getObjects() { + List objects = new ArrayList<>(mSnapshots.size()); + for (int i = 0; i < mSnapshots.size(); i++) { + objects.add(get(i)); + } + return objects; + } + + @Override + public int size() { + return mSnapshots.size(); + } + + @Override + public boolean isEmpty() { + return mSnapshots.isEmpty(); + } + + @Override + public boolean contains(Object o) { + for (int i = 0; i < mSnapshots.size(); i++) { + E value = get(i); + if (o == null ? value == null : value.equals(o)) { + return true; + } + } + return false; + } + + /** + * {@inheritDoc} + * + * @return an immutable iterator + */ + @Override + public Iterator iterator() { + return new ImmutableIterator<>(getObjects().iterator()); + } + + @Override + public Object[] toArray() { + return getObjects().toArray(); + } + + @Override + public T[] toArray(T[] a) { + return getObjects().toArray(a); + } + + @Override + public boolean containsAll(Collection c) { + return getObjects().containsAll(c); + } + + @Override + public E get(int index) { + return mSnapshots.get(index).getValue(mEClass); + } + + @Override + public int indexOf(Object o) { + return getObjects().indexOf(o); + } + + @Override + public int lastIndexOf(Object o) { + return getObjects().lastIndexOf(o); + } + + /** + * {@inheritDoc} + * + * @return an immutable list iterator + */ + @Override + public ListIterator listIterator() { + return new ImmutableListIterator<>(getObjects().listIterator()); + } + + /** + * {@inheritDoc} + * + * @return an immutable list iterator + */ + @Override + public ListIterator listIterator(int index) { + return new ImmutableListIterator<>(getObjects().listIterator(index)); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null || getClass() != obj.getClass()) return false; + + FirebaseObjectsArray array = (FirebaseObjectsArray) obj; + + return mSnapshots.equals(array.mSnapshots) && mEClass.equals(array.mEClass); + } + + @Override + public int hashCode() { + int result = mSnapshots.hashCode(); + result = 31 * result + mEClass.hashCode(); + return result; + } + + @Override + public String toString() { + return "FirebaseObjectsArray{" + + "mSnapshots=" + mSnapshots + + '}'; + } +} diff --git a/database/src/main/java/com/firebase/ui/database/UnmodifiableList.java b/database/src/main/java/com/firebase/ui/database/UnmodifiableList.java new file mode 100644 index 000000000..cb8f3b5d8 --- /dev/null +++ b/database/src/main/java/com/firebase/ui/database/UnmodifiableList.java @@ -0,0 +1,202 @@ +package com.firebase.ui.database; + +import android.support.annotation.RestrictTo; + +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; + +public abstract class UnmodifiableList implements List { + /** + * Guaranteed to throw an exception and leave the collection unmodified. + * + * @throws UnsupportedOperationException always + */ + @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) + @Override + public boolean add(E element) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the collection unmodified. + * + * @throws UnsupportedOperationException always + */ + @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) + @Override + public boolean remove(Object o) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the collection unmodified. + * + * @throws UnsupportedOperationException always + */ + @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) + @Override + public boolean addAll(Collection c) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the collection unmodified. + * + * @throws UnsupportedOperationException always + */ + @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) + @Override + public boolean addAll(int index, Collection c) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the collection unmodified. + * + * @throws UnsupportedOperationException always + */ + @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) + @Override + public boolean removeAll(Collection c) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the collection unmodified. + * + * @throws UnsupportedOperationException always + */ + @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) + @Override + public boolean retainAll(Collection c) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the collection unmodified. + * + * @throws UnsupportedOperationException always + */ + @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) + @Override + public void clear() { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the collection unmodified. + * + * @throws UnsupportedOperationException always + */ + @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) + @Override + public E set(int index, E element) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the collection unmodified. + * + * @throws UnsupportedOperationException always + */ + @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) + @Override + public void add(int index, E element) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the collection unmodified. + * + * @throws UnsupportedOperationException always + */ + @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) + @Override + public E remove(int index) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the collection unmodified. + * + * @throws UnsupportedOperationException always + */ + @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) + @Override + public List subList(int fromIndex, int toIndex) { + throw new UnsupportedOperationException(); + } + + protected static class ImmutableIterator implements Iterator { + private Iterator mIterator; + + public ImmutableIterator(Iterator iterator) { + mIterator = iterator; + } + + @Override + public boolean hasNext() { + return mIterator.hasNext(); + } + + @Override + public T next() { + return mIterator.next(); + } + } + + protected static class ImmutableListIterator implements ListIterator { + private ListIterator mListIterator; + + public ImmutableListIterator(ListIterator listIterator) { + mListIterator = listIterator; + } + + @Override + public boolean hasNext() { + return mListIterator.hasNext(); + } + + @Override + public T next() { + return mListIterator.next(); + } + + @Override + public boolean hasPrevious() { + return mListIterator.hasPrevious(); + } + + @Override + public T previous() { + return mListIterator.previous(); + } + + @Override + public int nextIndex() { + return mListIterator.nextIndex(); + } + + @Override + public int previousIndex() { + return mListIterator.previousIndex(); + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + + @Override + public void set(T t) { + throw new UnsupportedOperationException(); + } + + @Override + public void add(T t) { + throw new UnsupportedOperationException(); + } + } +} From 712320f035e289813942121b0bcf6ded9b8e8f10 Mon Sep 17 00:00:00 2001 From: Alex Saveau Date: Wed, 25 Jan 2017 15:30:43 -0800 Subject: [PATCH 27/93] Update api Signed-off-by: Alex Saveau --- .../firebase/ui/database/FirebaseArray.java | 6 ++--- .../ui/database/FirebaseIndexArray.java | 14 ++++++------ .../ui/database/FirebaseObjectsArray.java | 6 ++--- .../ui/database/UnmodifiableList.java | 22 +++++++++---------- 4 files changed, 24 insertions(+), 24 deletions(-) diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseArray.java b/database/src/main/java/com/firebase/ui/database/FirebaseArray.java index 3b9c9d195..f618b61a9 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseArray.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseArray.java @@ -200,7 +200,7 @@ public boolean contains(Object o) { */ @Override public Iterator iterator() { - return new ImmutableIterator<>(mSnapshots.iterator()); + return new ImmutableIterator(mSnapshots.iterator()); } @Override @@ -240,7 +240,7 @@ public int lastIndexOf(Object o) { */ @Override public ListIterator listIterator() { - return new ImmutableListIterator<>(mSnapshots.listIterator()); + return new ImmutableListIterator(mSnapshots.listIterator()); } /** @@ -250,7 +250,7 @@ public ListIterator listIterator() { */ @Override public ListIterator listIterator(int index) { - return new ImmutableListIterator<>(mSnapshots.listIterator(index)); + return new ImmutableListIterator(mSnapshots.listIterator(index)); } @Override 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 4df7e2159..234a3542c 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseIndexArray.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseIndexArray.java @@ -177,6 +177,11 @@ public boolean contains(Object o) { return mDataSnapshots.contains(o); } + @Override + public Iterator iterator() { + return new ImmutableIterator(mDataSnapshots.iterator()); + } + @Override public Object[] toArray() { return mDataSnapshots.toArray(); @@ -207,19 +212,14 @@ public int lastIndexOf(Object o) { return mDataSnapshots.lastIndexOf(o); } - @Override - public Iterator iterator() { - return new ImmutableIterator<>(mDataSnapshots.iterator()); - } - @Override public ListIterator listIterator() { - return new ImmutableListIterator<>(mDataSnapshots.listIterator()); + return new ImmutableListIterator(mDataSnapshots.listIterator()); } @Override public ListIterator listIterator(int index) { - return new ImmutableListIterator<>(mDataSnapshots.listIterator(index)); + return new ImmutableListIterator(mDataSnapshots.listIterator(index)); } @Override diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseObjectsArray.java b/database/src/main/java/com/firebase/ui/database/FirebaseObjectsArray.java index a86901897..e9876d75f 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseObjectsArray.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseObjectsArray.java @@ -58,7 +58,7 @@ public boolean contains(Object o) { */ @Override public Iterator iterator() { - return new ImmutableIterator<>(getObjects().iterator()); + return new ImmutableIterator(getObjects().iterator()); } @Override @@ -98,7 +98,7 @@ public int lastIndexOf(Object o) { */ @Override public ListIterator listIterator() { - return new ImmutableListIterator<>(getObjects().listIterator()); + return new ImmutableListIterator(getObjects().listIterator()); } /** @@ -108,7 +108,7 @@ public ListIterator listIterator() { */ @Override public ListIterator listIterator(int index) { - return new ImmutableListIterator<>(getObjects().listIterator(index)); + return new ImmutableListIterator(getObjects().listIterator(index)); } @Override diff --git a/database/src/main/java/com/firebase/ui/database/UnmodifiableList.java b/database/src/main/java/com/firebase/ui/database/UnmodifiableList.java index cb8f3b5d8..d3689c244 100644 --- a/database/src/main/java/com/firebase/ui/database/UnmodifiableList.java +++ b/database/src/main/java/com/firebase/ui/database/UnmodifiableList.java @@ -129,10 +129,10 @@ public List subList(int fromIndex, int toIndex) { throw new UnsupportedOperationException(); } - protected static class ImmutableIterator implements Iterator { - private Iterator mIterator; + protected class ImmutableIterator implements Iterator { + protected Iterator mIterator; - public ImmutableIterator(Iterator iterator) { + public ImmutableIterator(Iterator iterator) { mIterator = iterator; } @@ -142,15 +142,15 @@ public boolean hasNext() { } @Override - public T next() { + public E next() { return mIterator.next(); } } - protected static class ImmutableListIterator implements ListIterator { - private ListIterator mListIterator; + protected class ImmutableListIterator implements ListIterator { + protected ListIterator mListIterator; - public ImmutableListIterator(ListIterator listIterator) { + public ImmutableListIterator(ListIterator listIterator) { mListIterator = listIterator; } @@ -160,7 +160,7 @@ public boolean hasNext() { } @Override - public T next() { + public E next() { return mListIterator.next(); } @@ -170,7 +170,7 @@ public boolean hasPrevious() { } @Override - public T previous() { + public E previous() { return mListIterator.previous(); } @@ -190,12 +190,12 @@ public void remove() { } @Override - public void set(T t) { + public void set(E e) { throw new UnsupportedOperationException(); } @Override - public void add(T t) { + public void add(E e) { throw new UnsupportedOperationException(); } } From 964c91211c95f041186cadb0e5460fdd4716844d Mon Sep 17 00:00:00 2001 From: Alex Saveau Date: Wed, 25 Jan 2017 15:32:16 -0800 Subject: [PATCH 28/93] Incentivize people to move to the new FirebaseRecyclerAdapter.java api Signed-off-by: Alex Saveau --- .../ui/database/FirebaseListAdapter.java | 45 ++++++++----------- .../ui/database/FirebaseRecyclerAdapter.java | 45 ++++++++----------- 2 files changed, 36 insertions(+), 54 deletions(-) diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseListAdapter.java b/database/src/main/java/com/firebase/ui/database/FirebaseListAdapter.java index 55c306b22..87c51c877 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseListAdapter.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseListAdapter.java @@ -53,8 +53,8 @@ public abstract class FirebaseListAdapter extends BaseAdapter { private static final String TAG = "FirebaseListAdapter"; - protected FirebaseArray mSnapshots; - protected final Class mModelClass; + private FirebaseArray mSnapshots; + private final Class mModelClass; protected Activity mActivity; protected int mLayout; private ChangeEventListener mListener; @@ -68,7 +68,22 @@ public abstract class FirebaseListAdapter extends BaseAdapter { mLayout = modelLayout; mSnapshots = snapshots; - startListening(); + mListener = mSnapshots.addChangeEventListener(new ChangeEventListener() { + @Override + public void onChildChanged(EventType type, int index, int oldIndex) { + FirebaseListAdapter.this.onChildChanged(type, index, oldIndex); + } + + @Override + public void onDataChanged() { + FirebaseListAdapter.this.onDataChanged(); + } + + @Override + public void onCancelled(DatabaseError error) { + FirebaseListAdapter.this.onCancelled(error); + } + }); } /** @@ -88,30 +103,6 @@ public FirebaseListAdapter(Activity activity, this(activity, modelClass, modelLayout, new FirebaseArray(ref)); } - /** - * If you need to do some setup before we start listening for change events in the database - * (such as setting a custom {@link JoinResolver}), do so it here and then call {@code - * super.startListening()}. - */ - protected void startListening() { - mListener = mSnapshots.addChangeEventListener(new ChangeEventListener() { - @Override - public void onChildChanged(EventType type, int index, int oldIndex) { - FirebaseListAdapter.this.onChildChanged(type, index, oldIndex); - } - - @Override - public void onDataChanged() { - FirebaseListAdapter.this.onDataChanged(); - } - - @Override - public void onCancelled(DatabaseError error) { - FirebaseListAdapter.this.onCancelled(error); - } - }); - } - public void cleanup() { mSnapshots.removeChangeEventListener(mListener); } diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseRecyclerAdapter.java b/database/src/main/java/com/firebase/ui/database/FirebaseRecyclerAdapter.java index c732f8cfc..7bac57d46 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseRecyclerAdapter.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseRecyclerAdapter.java @@ -75,8 +75,8 @@ public abstract class FirebaseRecyclerAdapter { private static final String TAG = "FirebaseRecyclerAdapter"; - protected FirebaseArray mSnapshots; - protected Class mModelClass; + private FirebaseArray mSnapshots; + private Class mModelClass; protected Class mViewHolderClass; protected int mModelLayout; private ChangeEventListener mListener; @@ -90,7 +90,22 @@ public abstract class FirebaseRecyclerAdapter modelClass, this(modelClass, modelLayout, viewHolderClass, new FirebaseArray(ref)); } - /** - * If you need to do some setup before we start listening for change events in the database - * (such as setting a custom {@link JoinResolver}), do so it here and then call {@code - * super.startListening()}. - */ - protected void startListening() { - mListener = mSnapshots.addChangeEventListener(new ChangeEventListener() { - @Override - public void onChildChanged(EventType type, int index, int oldIndex) { - FirebaseRecyclerAdapter.this.onChildChanged(type, index, oldIndex); - } - - @Override - public void onDataChanged() { - FirebaseRecyclerAdapter.this.onDataChanged(); - } - - @Override - public void onCancelled(DatabaseError error) { - FirebaseRecyclerAdapter.this.onCancelled(error); - } - }); - } - public void cleanup() { mSnapshots.removeChangeEventListener(mListener); } From 692b8194226ef5eabb136a5e4b74fb92700f6959 Mon Sep 17 00:00:00 2001 From: Alex Saveau Date: Wed, 25 Jan 2017 16:00:52 -0800 Subject: [PATCH 29/93] Add a DataSnapshot to list of object converter method Signed-off-by: Alex Saveau --- .../firebase/ui/database/FirebaseArray.java | 73 +++++++++++-------- 1 file changed, 42 insertions(+), 31 deletions(-) diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseArray.java b/database/src/main/java/com/firebase/ui/database/FirebaseArray.java index f618b61a9..e771fc505 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseArray.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseArray.java @@ -92,37 +92,6 @@ public boolean isListening() { return !mListeners.isEmpty(); } - protected void setShouldNotifyListeners(boolean notifyListeners) { - mNotifyListeners = notifyListeners; - } - - protected void notifyChangeEventListeners(ChangeEventListener.EventType type, int index) { - notifyChangeEventListeners(type, index, -1); - } - - protected void notifyChangeEventListeners(ChangeEventListener.EventType type, - int index, - int oldIndex) { - if (!mNotifyListeners) return; - for (ChangeEventListener listener : mListeners) { - listener.onChildChanged(type, index, oldIndex); - } - } - - protected void notifyListenersOnDataChanged() { - if (!mNotifyListeners) return; - for (ChangeEventListener listener : mListeners) { - listener.onDataChanged(); - } - } - - protected void notifyListenersOnCancelled(DatabaseError error) { - if (!mNotifyListeners) return; - for (ChangeEventListener listener : mListeners) { - listener.onCancelled(error); - } - } - @Override public void onChildAdded(DataSnapshot snapshot, String previousChildKey) { int index = 0; @@ -178,6 +147,37 @@ private int getIndexForKey(String key) { throw new IllegalArgumentException("Key not found"); } + protected void setShouldNotifyListeners(boolean notifyListeners) { + mNotifyListeners = notifyListeners; + } + + protected void notifyChangeEventListeners(ChangeEventListener.EventType type, int index) { + notifyChangeEventListeners(type, index, -1); + } + + protected void notifyChangeEventListeners(ChangeEventListener.EventType type, + int index, + int oldIndex) { + if (!mNotifyListeners) return; + for (ChangeEventListener listener : mListeners) { + listener.onChildChanged(type, index, oldIndex); + } + } + + protected void notifyListenersOnDataChanged() { + if (!mNotifyListeners) return; + for (ChangeEventListener listener : mListeners) { + listener.onDataChanged(); + } + } + + protected void notifyListenersOnCancelled(DatabaseError error) { + if (!mNotifyListeners) return; + for (ChangeEventListener listener : mListeners) { + listener.onCancelled(error); + } + } + @Override public int size() { return mSnapshots.size(); @@ -213,6 +213,17 @@ public T[] toArray(T[] a) { return mSnapshots.toArray(a); } + /** + * Get a continually updated list of objects representing the {@link DataSnapshot}s in this + * list. + * + * @param the object representation of a {@link DataSnapshot} + * @return a list that represents the objects in this list of {@link DataSnapshot} + */ + public List toObjectRepresentation(Class tClass) { + return new FirebaseObjectsArray<>(this, tClass); + } + @Override public boolean containsAll(Collection c) { return mSnapshots.containsAll(c); From e96ae37eb334685373a51158ca12813f5c99c446 Mon Sep 17 00:00:00 2001 From: Alex Saveau Date: Wed, 25 Jan 2017 16:36:37 -0800 Subject: [PATCH 30/93] Deprecate old adapters and add the new ones Signed-off-by: Alex Saveau --- .../uidemo/database/ChatActivity.java | 9 +- .../ui/database/FirebaseIndexListAdapter.java | 38 +-- .../FirebaseIndexRecyclerAdapter.java | 34 +-- .../ui/database/FirebaseListAdapter.java | 36 +-- .../ui/database/FirebaseRecyclerAdapter.java | 32 ++- .../adapter/FirebaseIndexListAdapter.java | 26 ++ .../adapter/FirebaseIndexRecyclerAdapter.java | 27 +++ .../database/adapter/FirebaseListAdapter.java | 171 ++++++++++++++ .../adapter/FirebaseRecyclerAdapter.java | 223 ++++++++++++++++++ 9 files changed, 530 insertions(+), 66 deletions(-) create mode 100644 database/src/main/java/com/firebase/ui/database/adapter/FirebaseIndexListAdapter.java create mode 100644 database/src/main/java/com/firebase/ui/database/adapter/FirebaseIndexRecyclerAdapter.java create mode 100644 database/src/main/java/com/firebase/ui/database/adapter/FirebaseListAdapter.java create mode 100644 database/src/main/java/com/firebase/ui/database/adapter/FirebaseRecyclerAdapter.java diff --git a/app/src/main/java/com/firebase/uidemo/database/ChatActivity.java b/app/src/main/java/com/firebase/uidemo/database/ChatActivity.java index 805c82ca5..46f7cf912 100644 --- a/app/src/main/java/com/firebase/uidemo/database/ChatActivity.java +++ b/app/src/main/java/com/firebase/uidemo/database/ChatActivity.java @@ -35,8 +35,8 @@ import android.widget.TextView; import android.widget.Toast; -import com.firebase.ui.database.FirebaseIndexRecyclerAdapter; -import com.firebase.ui.database.FirebaseRecyclerAdapter; +import com.firebase.ui.database.adapter.FirebaseIndexRecyclerAdapter; +import com.firebase.ui.database.adapter.FirebaseRecyclerAdapter; import com.firebase.uidemo.R; import com.google.android.gms.tasks.OnCompleteListener; import com.google.android.gms.tasks.OnSuccessListener; @@ -48,7 +48,6 @@ import com.google.firebase.database.DatabaseReference; import com.google.firebase.database.FirebaseDatabase; -@SuppressWarnings("LogConditional") public class ChatActivity extends AppCompatActivity implements FirebaseAuth.AuthStateListener { private static final String TAG = "RecyclerViewDemo"; @@ -148,8 +147,8 @@ public void onAuthStateChanged(@NonNull FirebaseAuth firebaseAuth) { private void attachRecyclerViewAdapter() { mRecyclerViewAdapter = new FirebaseIndexRecyclerAdapter( Chat.class, - R.layout.message, ChatHolder.class, + R.layout.message, mChatIndicesRef.limitToLast(50), mChatRef) { @Override @@ -166,7 +165,7 @@ public void populateViewHolder(ChatHolder chatView, Chat chat, int position) { } @Override - protected void onDataChanged() { + public void onDataChanged() { // if there are no chat messages, show a view that invites the user to add a message mEmptyListView.setVisibility(mRecyclerViewAdapter.getItemCount() == 0 ? View.VISIBLE : View.INVISIBLE); } diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseIndexListAdapter.java b/database/src/main/java/com/firebase/ui/database/FirebaseIndexListAdapter.java index a5878d632..b93c78e25 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseIndexListAdapter.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseIndexListAdapter.java @@ -6,11 +6,12 @@ import com.google.firebase.database.Query; /** - * This class is a generic way of backing an Android ListView with a Firebase location. - * It handles all of the child events at the given Firebase location. It marshals received data into the given - * class type. Extend this class and provide an implementation of {@code populateView}, which will be given an - * instance of your list item mLayout and an instance your class that holds your data. - * Simply populate the view however you like and this class will handle updating the list as the data changes. + * This class is a generic way of backing an Android ListView with a Firebase location. It handles + * all of the child events at the given Firebase location. It marshals received data into the given + * class type. Extend this class and provide an implementation of {@code populateView}, which will + * be given an instance of your list item mLayout and an instance your class that holds your data. + * Simply populate the view however you like and this class will handle updating the list as the + * data changes. *

* If your data is not indexed: *

@@ -31,23 +32,24 @@
  *     listView.setListAdapter(adapter);
  * 
* - * @param The class type to use as a model for the data - * contained in the children of the given Firebase location + * @param The class type to use as a model for the data contained in the children of the given + * Firebase location + * @deprecated use {@link com.firebase.ui.database.adapter.FirebaseIndexListAdapter} instead */ +@Deprecated public abstract class FirebaseIndexListAdapter extends FirebaseListAdapter { /** * @param activity The activity containing the ListView - * @param modelClass Firebase will marshall the data at a location into - * an instance of a class that you provide - * @param modelLayout This is the layout used to represent a single list item. - * You will be responsible for populating an instance of the corresponding - * view with the data from an instance of modelClass. - * @param keyRef 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 in {@code keyRef}'s location represents - * a list item in the {@code ListView}. + * @param modelClass Firebase will marshall the data at a location into an instance of a class + * that you provide + * @param modelLayout This is the layout used to represent a single list item. You will be + * responsible for populating an instance of the corresponding view with the + * data from an instance of modelClass. + * @param keyRef 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 in + * {@code keyRef}'s location represents a list item in the {@code ListView}. */ public FirebaseIndexListAdapter(Activity activity, Class modelClass, diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseIndexRecyclerAdapter.java b/database/src/main/java/com/firebase/ui/database/FirebaseIndexRecyclerAdapter.java index 1dcb974fa..cbe05e75f 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseIndexRecyclerAdapter.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseIndexRecyclerAdapter.java @@ -47,7 +47,8 @@ * recycler.setLayoutManager(new LinearLayoutManager(this)); * * adapter = new FirebaseIndexRecyclerAdapter( - * ChatMessage.class, android.R.layout.two_line_list_item, ChatMessageViewHolder.class, keyRef, dataRef) { + * ChatMessage.class, android.R.layout.two_line_list_item, ChatMessageViewHolder.class, + * keyRef, dataRef) { * public void populateViewHolder(ChatMessageViewHolder chatMessageViewHolder, * ChatMessage chatMessage, * int position) { @@ -59,23 +60,28 @@ * * * @param The Java class that maps to the type of objects stored in the Firebase location. - * @param The ViewHolder class that contains the Views in the layout that is shown for each object. + * @param The ViewHolder class that contains the Views in the layout that is shown for each + * object. + * @deprecated use {@link com.firebase.ui.database.adapter.FirebaseIndexRecyclerAdapter} instead */ +@Deprecated public abstract class FirebaseIndexRecyclerAdapter extends FirebaseRecyclerAdapter { /** - * @param modelClass Firebase will marshall the data at a location into an instance - * of a class that you provide - * @param modelLayout This is the layout used to represent a single item in the list. - * You will be responsible for populating an - * instance of the corresponding view with the data from an instance of modelClass. - * @param viewHolderClass The class that hold references to all sub-views in an instance modelLayout. - * @param keyRef 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 keyRef}'s location represents - * a list item in the {@code RecyclerView}. + * @param modelClass Firebase will marshall the data at a location into an instance of a + * class that you provide + * @param modelLayout This is the layout used to represent a single item in the list. You + * will be responsible for populating an instance of the corresponding + * view with the data from an instance of modelClass. + * @param viewHolderClass The class that hold references to all sub-views in an instance + * modelLayout. + * @param keyRef 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 keyRef}'s location represents a list item in the {@code + * RecyclerView}. */ public FirebaseIndexRecyclerAdapter(Class modelClass, @LayoutRes int modelLayout, diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseListAdapter.java b/database/src/main/java/com/firebase/ui/database/FirebaseListAdapter.java index 87c51c877..2aea95215 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseListAdapter.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseListAdapter.java @@ -27,11 +27,12 @@ import com.google.firebase.database.Query; /** - * This class is a generic way of backing an Android ListView with a Firebase location. - * It handles all of the child events at the given Firebase location. It marshals received data into the given - * class type. Extend this class and provide an implementation of {@code populateView}, which will be given an - * instance of your list item mLayout and an instance your class that holds your data. - * Simply populate the view however you like and this class will handle updating the list as the data changes. + * This class is a generic way of backing an Android ListView with a Firebase location. It handles + * all of the child events at the given Firebase location. It marshals received data into the given + * class type. Extend this class and provide an implementation of {@code populateView}, which will + * be given an instance of your list item mLayout and an instance your class that holds your data. + * Simply populate the view however you like and this class will handle updating the list as the + * data changes. *

*

  *     DatabaseReference ref = FirebaseDatabase.getInstance().getReference();
@@ -47,9 +48,11 @@
  *     listView.setListAdapter(adapter);
  * 
* - * @param The class type to use as a model for the data - * contained in the children of the given Firebase location + * @param The class type to use as a model for the data contained in the children of the given + * Firebase location + * @deprecated use {@link com.firebase.ui.database.adapter.FirebaseListAdapter} instead */ +@Deprecated public abstract class FirebaseListAdapter extends BaseAdapter { private static final String TAG = "FirebaseListAdapter"; @@ -88,13 +91,14 @@ public void onCancelled(DatabaseError error) { /** * @param activity The activity containing the ListView - * @param modelClass Firebase will marshall the data at a location into - * an instance of a class that you provide - * @param modelLayout This is the layout used to represent a single list item. - * You will be responsible for populating an instance of the corresponding - * view with the data from an instance of modelClass. - * @param ref The Firebase location to watch for data changes. Can also be a slice of a location, - * using some combination of {@code limit()}, {@code startAt()}, and {@code endAt()}. + * @param modelClass Firebase will marshall the data at a location into an instance of a class + * that you provide + * @param modelLayout This is the layout used to represent a single list item. You will be + * responsible for populating an instance of the corresponding view with the + * data from an instance of modelClass. + * @param ref The Firebase location to watch for data changes. Can also be a slice of a + * location, using some combination of {@code limit()}, {@code startAt()}, + * and {@code endAt()}. */ public FirebaseListAdapter(Activity activity, Class modelClass, @@ -118,8 +122,8 @@ public T getItem(int position) { } /** - * This method parses the DataSnapshot into the requested type. You can override it in subclasses - * to do custom parsing. + * This method parses the DataSnapshot into the requested type. You can override it in + * subclasses to do custom parsing. * * @param snapshot the DataSnapshot to extract the model from * @return the model extracted from the DataSnapshot diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseRecyclerAdapter.java b/database/src/main/java/com/firebase/ui/database/FirebaseRecyclerAdapter.java index 7bac57d46..c7980f7c8 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseRecyclerAdapter.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseRecyclerAdapter.java @@ -30,8 +30,8 @@ import java.lang.reflect.InvocationTargetException; /** - * This class is a generic way of backing an RecyclerView with a Firebase location. - * It handles all of the child events at the given Firebase location. It marshals received data into the given + * This class is a generic way of backing an RecyclerView with a Firebase location. It handles all + * of the child events at the given Firebase location. It marshals received data into the given * class type. *

* To use this class in your app, subclass it passing in all required parameters and implement the @@ -57,7 +57,8 @@ * recycler.setLayoutManager(new LinearLayoutManager(this)); * * adapter = new FirebaseRecyclerAdapter( - * ChatMessage.class, android.R.layout.two_line_list_item, ChatMessageViewHolder.class, ref) { + * ChatMessage.class, android.R.layout.two_line_list_item, ChatMessageViewHolder.class, + * ref) { * public void populateViewHolder(ChatMessageViewHolder chatMessageViewHolder, * ChatMessage chatMessage, * int position) { @@ -69,8 +70,11 @@ * * * @param The Java class that maps to the type of objects stored in the Firebase location. - * @param The ViewHolder class that contains the Views in the layout that is shown for each object. + * @param The ViewHolder class that contains the Views in the layout that is shown for each + * object. + * @deprecated use {@link com.firebase.ui.database.adapter.FirebaseRecyclerAdapter} instead */ +@Deprecated public abstract class FirebaseRecyclerAdapter extends RecyclerView.Adapter { private static final String TAG = "FirebaseRecyclerAdapter"; @@ -109,14 +113,16 @@ public void onCancelled(DatabaseError error) { } /** - * @param modelClass Firebase will marshall the data at a location into - * an instance of a class that you provide - * @param modelLayout This is the layout used to represent a single item in the list. - * You will be responsible for populating an instance of the corresponding + * @param modelClass Firebase will marshall the data at a location into an instance of a + * class that you provide + * @param modelLayout This is the layout used to represent a single item in the list. You + * will be responsible for populating an instance of the corresponding * view with the data from an instance of modelClass. - * @param viewHolderClass The class that hold references to all sub-views in an instance modelLayout. - * @param ref The Firebase location to watch for data changes. Can also be a slice of a location, - * using some combination of {@code limit()}, {@code startAt()}, and {@code endAt()}. + * @param viewHolderClass The class that hold references to all sub-views in an instance + * modelLayout. + * @param ref The Firebase location to watch for data changes. Can also be a slice + * of a location, using some combination of {@code limit()}, {@code + * startAt()}, and {@code endAt()}. */ public FirebaseRecyclerAdapter(Class modelClass, int modelLayout, @@ -139,8 +145,8 @@ public T getItem(int position) { } /** - * This method parses the DataSnapshot into the requested type. You can override it in subclasses - * to do custom parsing. + * This method parses the DataSnapshot into the requested type. You can override it in + * subclasses to do custom parsing. * * @param snapshot the DataSnapshot to extract the model from * @return the model extracted from the DataSnapshot diff --git a/database/src/main/java/com/firebase/ui/database/adapter/FirebaseIndexListAdapter.java b/database/src/main/java/com/firebase/ui/database/adapter/FirebaseIndexListAdapter.java new file mode 100644 index 000000000..bc33fcefb --- /dev/null +++ b/database/src/main/java/com/firebase/ui/database/adapter/FirebaseIndexListAdapter.java @@ -0,0 +1,26 @@ +package com.firebase.ui.database.adapter; + +import android.app.Activity; +import android.support.annotation.LayoutRes; + +import com.firebase.ui.database.FirebaseArray; +import com.firebase.ui.database.FirebaseIndexArray; +import com.google.firebase.database.Query; + +public abstract class FirebaseIndexListAdapter extends FirebaseListAdapter { + /** + * @param keyRef 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 in {@code + * keyRef}'s location represents a list item in the {@code ListView}. + * @see FirebaseListAdapter#FirebaseListAdapter(Activity, FirebaseArray, Class, int) + */ + public FirebaseIndexListAdapter(Activity activity, + Class modelClass, + @LayoutRes int modelLayout, + Query keyRef, + Query dataRef) { + super(activity, new FirebaseIndexArray(keyRef, dataRef), modelClass, modelLayout); + } +} diff --git a/database/src/main/java/com/firebase/ui/database/adapter/FirebaseIndexRecyclerAdapter.java b/database/src/main/java/com/firebase/ui/database/adapter/FirebaseIndexRecyclerAdapter.java new file mode 100644 index 000000000..c3f25bf70 --- /dev/null +++ b/database/src/main/java/com/firebase/ui/database/adapter/FirebaseIndexRecyclerAdapter.java @@ -0,0 +1,27 @@ +package com.firebase.ui.database.adapter; + +import android.support.annotation.LayoutRes; +import android.support.v7.widget.RecyclerView; + +import com.firebase.ui.database.FirebaseArray; +import com.firebase.ui.database.FirebaseIndexArray; +import com.google.firebase.database.Query; + +public abstract class FirebaseIndexRecyclerAdapter + extends FirebaseRecyclerAdapter { + /** + * @param keyRef 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 + * keyRef}'s location represents a list item in the {@link RecyclerView}. + * @see FirebaseRecyclerAdapter#FirebaseRecyclerAdapter(FirebaseArray, Class, Class, int) + */ + public FirebaseIndexRecyclerAdapter(Class modelClass, + Class viewHolderClass, + @LayoutRes int modelLayout, + Query keyRef, + Query dataRef) { + super(new FirebaseIndexArray(keyRef, dataRef), modelClass, viewHolderClass, modelLayout); + } +} diff --git a/database/src/main/java/com/firebase/ui/database/adapter/FirebaseListAdapter.java b/database/src/main/java/com/firebase/ui/database/adapter/FirebaseListAdapter.java new file mode 100644 index 000000000..8ed5ad0ee --- /dev/null +++ b/database/src/main/java/com/firebase/ui/database/adapter/FirebaseListAdapter.java @@ -0,0 +1,171 @@ +package com.firebase.ui.database.adapter; + +import android.app.Activity; +import android.support.annotation.LayoutRes; +import android.util.Log; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.ListView; + +import com.firebase.ui.database.ChangeEventListener; +import com.firebase.ui.database.FirebaseArray; +import com.google.firebase.database.DataSnapshot; +import com.google.firebase.database.DatabaseError; +import com.google.firebase.database.DatabaseReference; +import com.google.firebase.database.Query; + +/** + * This class is a generic way of backing an Android {@link android.widget.ListView} with a Firebase + * location. It handles all of the child events at the given Firebase location. It marshals received + * data into the given class type. Extend this class and provide an implementation of {@link + * #populateView(View, Object, int)}, which will be given an instance of your list item mLayout and + * an instance your class that holds your data. Simply populate the view however you like and this + * class will handle updating the list as the data changes. + *

+ *

+ *     DatabaseReference ref = FirebaseDatabase.getInstance().getReference();
+ *     ListAdapter adapter = new FirebaseListAdapter(
+ *              this, ChatMessage.class, android.R.layout.two_line_list_item, ref)
+ *     {
+ *         protected void populateView(View view, ChatMessage chatMessage, int position)
+ *         {
+ *             ((TextView)view.findViewById(android.R.id.text1)).setText(chatMessage.getName());
+ *             ((TextView)view.findViewById(android.R.id.text2)).setText(chatMessage.getMessage());
+ *         }
+ *     };
+ *     listView.setListAdapter(adapter);
+ * 
+ * + * @param The class type to use as a model for the data contained in the children of the given + * Firebase location + */ +public abstract class FirebaseListAdapter extends BaseAdapter implements ChangeEventListener { + private static final String TAG = "FirebaseListAdapter"; + + protected Activity mActivity; + protected FirebaseArray mSnapshots; + protected Class mModelClass; + protected int mLayout; + + /** + * @param activity The {@link Activity} containing the {@link ListView} + * @param modelClass Firebase will marshall the data at a location into an instance of a class + * that you provide + * @param modelLayout This is the layout used to represent a single list item. You will be + * responsible for populating an instance of the corresponding view with the + * data from an instance of modelClass. + * @param snapshots The data used to populate the adapter + */ + public FirebaseListAdapter(Activity activity, + FirebaseArray snapshots, + Class modelClass, + @LayoutRes int modelLayout) { + mActivity = activity; + mSnapshots = snapshots; + mModelClass = modelClass; + mLayout = modelLayout; + + startListening(); + } + + /** + * @param ref The Firebase location to watch for data changes. Can also be a slice of a + * location, using some combination of {@code limit()}, {@code startAt()}, and {@code + * endAt()}. + * @see #FirebaseListAdapter(Activity, FirebaseArray, Class, int) + */ + public FirebaseListAdapter(Activity activity, + Class modelClass, + @LayoutRes int modelLayout, + Query ref) { + this(activity, new FirebaseArray(ref), modelClass, modelLayout); + } + + /** + * If you need to do some setup before we start listening for change events in the database + * (such as setting a custom {@link JoinResolver}), do so it here and then call {@code + * super.startListening()}. + */ + protected void startListening() { + if (!mSnapshots.isListening()) { + mSnapshots.addChangeEventListener(this); + } + } + + public void cleanup() { + mSnapshots.removeChangeEventListener(this); + } + + @Override + public void onChildChanged(ChangeEventListener.EventType type, int index, int oldIndex) { + notifyDataSetChanged(); + } + + @Override + public void onDataChanged() { + } + + @Override + public void onCancelled(DatabaseError error) { + Log.w(TAG, error.toException()); + } + + @Override + public T getItem(int position) { + return parseSnapshot(mSnapshots.get(position)); + } + + /** + * This method parses the DataSnapshot into the requested type. You can override it in + * subclasses to do custom parsing. + * + * @param snapshot the {@link DataSnapshot} to extract the model from + * @return the model extracted from the DataSnapshot + */ + protected T parseSnapshot(DataSnapshot snapshot) { + return snapshot.getValue(mModelClass); + } + + public DatabaseReference getRef(int position) { + return mSnapshots.get(position).getRef(); + } + + @Override + public int getCount() { + return mSnapshots.size(); + } + + @Override + public long getItemId(int i) { + // http://stackoverflow.com/questions/5100071/whats-the-purpose-of-item-ids-in-android-listview-adapter + return mSnapshots.get(i).getKey().hashCode(); + } + + @Override + public View getView(int position, View view, ViewGroup viewGroup) { + if (view == null) { + view = mActivity.getLayoutInflater().inflate(mLayout, viewGroup, false); + } + + T model = getItem(position); + + // Call out to subclass to marshall this model into the provided view + populateView(view, model, position); + return view; + } + + /** + * Each time the data at the given Firebase location changes, + * this method will be called for each item that needs to be displayed. + * The first two arguments correspond to the mLayout and mModelClass given to the constructor of + * this class. The third argument is the item's position in the list. + *

+ * Your implementation should populate the view using the data contained in the model. + * + * @param v The view to populate + * @param model The object containing the data used to populate the view + * @param position The position in the list of the view being populated + */ + protected abstract void populateView(View v, T model, int position); +} diff --git a/database/src/main/java/com/firebase/ui/database/adapter/FirebaseRecyclerAdapter.java b/database/src/main/java/com/firebase/ui/database/adapter/FirebaseRecyclerAdapter.java new file mode 100644 index 000000000..efe3551d0 --- /dev/null +++ b/database/src/main/java/com/firebase/ui/database/adapter/FirebaseRecyclerAdapter.java @@ -0,0 +1,223 @@ +package com.firebase.ui.database.adapter; + +import android.support.annotation.LayoutRes; +import android.support.v7.widget.RecyclerView; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import com.firebase.ui.database.ChangeEventListener; +import com.firebase.ui.database.FirebaseArray; +import com.google.firebase.database.DataSnapshot; +import com.google.firebase.database.DatabaseError; +import com.google.firebase.database.DatabaseReference; +import com.google.firebase.database.Query; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; + +/** + * This class is a generic way of backing an {@link RecyclerView} with a Firebase location. It + * handles all of the child events at the given Firebase location. It marshals received data into + * the given class type. + *

+ * To use this class in your app, subclass it passing in all required parameters and implement the + * {@link #populateViewHolder(RecyclerView.ViewHolder, Object, int)} method. + *

+ *

+ *     private static class ChatMessageViewHolder extends RecyclerView.ViewHolder {
+ *         TextView messageText;
+ *         TextView nameText;
+ *
+ *         public ChatMessageViewHolder(View itemView) {
+ *             super(itemView);
+ *             nameText = (TextView)itemView.findViewById(android.R.id.text1);
+ *             messageText = (TextView) itemView.findViewById(android.R.id.text2);
+ *         }
+ *     }
+ *
+ *     FirebaseRecyclerAdapter adapter;
+ *     DatabaseReference ref = FirebaseDatabase.getInstance().getReference();
+ *
+ *     RecyclerView recycler = (RecyclerView) findViewById(R.id.messages_recycler);
+ *     recycler.setHasFixedSize(true);
+ *     recycler.setLayoutManager(new LinearLayoutManager(this));
+ *
+ *     adapter = new FirebaseRecyclerAdapter(
+ *           ChatMessage.class, android.R.layout.two_line_list_item, ChatMessageViewHolder.class,
+ * ref) {
+ *         public void populateViewHolder(ChatMessageViewHolder chatMessageViewHolder,
+ *                                        ChatMessage chatMessage,
+ *                                        int position) {
+ *             chatMessageViewHolder.nameText.setText(chatMessage.getName());
+ *             chatMessageViewHolder.messageText.setText(chatMessage.getMessage());
+ *         }
+ *     };
+ *     recycler.setAdapter(mAdapter);
+ * 
+ * + * @param The Java class that maps to the type of objects stored in the Firebase location. + * @param The {@link RecyclerView.ViewHolder} class that contains the Views in the layout that + * is shown for each object. + */ +public abstract class FirebaseRecyclerAdapter + extends RecyclerView.Adapter implements ChangeEventListener { + private static final String TAG = "FirebaseRecyclerAdapter"; + + protected FirebaseArray mSnapshots; + protected Class mModelClass; + protected Class mViewHolderClass; + protected int mModelLayout; + + /** + * @param modelClass Firebase will marshall the data at a location into an instance of a + * class that you provide + * @param modelLayout This is the layout used to represent a single item in the list. You + * will be responsible for populating an instance of the corresponding + * view with the data from an instance of modelClass. + * @param viewHolderClass The class that hold references to all sub-views in an instance + * modelLayout. + * @param snapshots The data used to populate the adapter + */ + public FirebaseRecyclerAdapter(FirebaseArray snapshots, + Class modelClass, + Class viewHolderClass, + @LayoutRes int modelLayout) { + mSnapshots = snapshots; + mModelClass = modelClass; + mViewHolderClass = viewHolderClass; + mModelLayout = modelLayout; + + startListening(); + } + + /** + * @param ref The Firebase location to watch for data changes. Can also be a slice of a + * location, using some combination of {@code limit()}, {@code startAt()}, and {@code + * endAt()}. + * @see #FirebaseRecyclerAdapter(FirebaseArray, Class, Class, int) + */ + public FirebaseRecyclerAdapter(Class modelClass, + Class viewHolderClass, + @LayoutRes int modelLayout, + Query ref) { + this(new FirebaseArray(ref), modelClass, viewHolderClass, modelLayout); + } + + /** + * If you need to do some setup before we start listening for change events in the database + * (such as setting a custom {@link JoinResolver}), do so it here and then call {@code + * super.startListening()}. + */ + protected void startListening() { + if (!mSnapshots.isListening()) { + mSnapshots.addChangeEventListener(this); + } + } + + public void cleanup() { + mSnapshots.removeChangeEventListener(this); + } + + @Override + public void onChildChanged(ChangeEventListener.EventType type, int index, int oldIndex) { + switch (type) { + case ADDED: + notifyItemInserted(index); + break; + case CHANGED: + notifyItemChanged(index); + break; + case REMOVED: + notifyItemRemoved(index); + break; + case MOVED: + notifyItemMoved(oldIndex, index); + break; + default: + throw new IllegalStateException("Incomplete case statement"); + } + } + + @Override + public void onDataChanged() { + } + + @Override + public void onCancelled(DatabaseError error) { + Log.w(TAG, error.toException()); + } + + public T getItem(int position) { + return parseSnapshot(mSnapshots.get(position)); + } + + /** + * This method parses the DataSnapshot into the requested type. You can override it in + * subclasses to do custom parsing. + * + * @param snapshot the DataSnapshot to extract the model from + * @return the model extracted from the DataSnapshot + */ + protected T parseSnapshot(DataSnapshot snapshot) { + return snapshot.getValue(mModelClass); + } + + public DatabaseReference getRef(int position) { + return mSnapshots.get(position).getRef(); + } + + @Override + public int getItemCount() { + return mSnapshots.size(); + } + + @Override + public long getItemId(int position) { + // http://stackoverflow.com/questions/5100071/whats-the-purpose-of-item-ids-in-android-listview-adapter + return mSnapshots.get(position).getKey().hashCode(); + } + + @Override + public VH onCreateViewHolder(ViewGroup parent, int viewType) { + View view = LayoutInflater.from(parent.getContext()).inflate(viewType, parent, false); + try { + Constructor constructor = mViewHolderClass.getConstructor(View.class); + return constructor.newInstance(view); + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } catch (InvocationTargetException e) { + throw new RuntimeException(e); + } catch (InstantiationException e) { + throw new RuntimeException(e); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + } + + @Override + public int getItemViewType(int position) { + return mModelLayout; + } + + @Override + public void onBindViewHolder(VH viewHolder, int position) { + T model = getItem(position); + populateViewHolder(viewHolder, model, position); + } + + /** + * Each time the data at the given Firebase location changes, + * this method will be called for each item that needs to be displayed. + * The first two arguments correspond to the mLayout and mModelClass given to the constructor of + * this class. The third argument is the item's position in the list. + *

+ * Your implementation should populate the view using the data contained in the model. + * + * @param viewHolder The view to populate + * @param model The object containing the data used to populate the view + * @param position The position in the list of the view being populated + */ + protected abstract void populateViewHolder(VH viewHolder, T model, int position); +} From 1ac786f778da977241370a5e263af2cf37b3bd2c Mon Sep 17 00:00:00 2001 From: Alex Saveau Date: Wed, 25 Jan 2017 16:53:27 -0800 Subject: [PATCH 31/93] Cleanup Signed-off-by: Alex Saveau --- .../com/firebase/ui/database/FirebaseObjectsArray.java | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseObjectsArray.java b/database/src/main/java/com/firebase/ui/database/FirebaseObjectsArray.java index e9876d75f..f8781dbd8 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseObjectsArray.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseObjectsArray.java @@ -42,13 +42,7 @@ public boolean isEmpty() { @Override public boolean contains(Object o) { - for (int i = 0; i < mSnapshots.size(); i++) { - E value = get(i); - if (o == null ? value == null : value.equals(o)) { - return true; - } - } - return false; + return indexOf(o) >= 0; } /** From 6ef42aa26ec6f6fffe855a8e8451e7aea1702185 Mon Sep 17 00:00:00 2001 From: Alex Saveau Date: Wed, 25 Jan 2017 17:16:16 -0800 Subject: [PATCH 32/93] Cleanup Signed-off-by: Alex Saveau --- .../main/java/com/firebase/ui/database/FirebaseArray.java | 5 +++-- .../java/com/firebase/ui/database/FirebaseObjectsArray.java | 2 +- .../database/{UnmodifiableList.java => ImmutableList.java} | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) rename database/src/main/java/com/firebase/ui/database/{UnmodifiableList.java => ImmutableList.java} (98%) diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseArray.java b/database/src/main/java/com/firebase/ui/database/FirebaseArray.java index e771fc505..f57e91f5a 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseArray.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseArray.java @@ -31,7 +31,7 @@ /** * This class implements a collection on top of a Firebase location. */ -public class FirebaseArray extends UnmodifiableList implements ChildEventListener, ValueEventListener { +public class FirebaseArray extends ImmutableList implements ChildEventListener, ValueEventListener { private Query mQuery; private boolean mNotifyListeners = true; private List mListeners = new ArrayList<>(); @@ -86,7 +86,8 @@ private static void checkNotNull(ChangeEventListener listener) { } /** - * @return true if {@link FirebaseArray} is listening for change events, false otherwise + * @return true if {@link FirebaseArray} is listening for change events from the Firebase + * database, false otherwise */ public boolean isListening() { return !mListeners.isEmpty(); diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseObjectsArray.java b/database/src/main/java/com/firebase/ui/database/FirebaseObjectsArray.java index f8781dbd8..85359d967 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseObjectsArray.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseObjectsArray.java @@ -13,7 +13,7 @@ * * @param the object representation of a {@link DataSnapshot} */ -public class FirebaseObjectsArray extends UnmodifiableList { +public class FirebaseObjectsArray extends ImmutableList { private List mSnapshots; private Class mEClass; diff --git a/database/src/main/java/com/firebase/ui/database/UnmodifiableList.java b/database/src/main/java/com/firebase/ui/database/ImmutableList.java similarity index 98% rename from database/src/main/java/com/firebase/ui/database/UnmodifiableList.java rename to database/src/main/java/com/firebase/ui/database/ImmutableList.java index d3689c244..9449eb1f2 100644 --- a/database/src/main/java/com/firebase/ui/database/UnmodifiableList.java +++ b/database/src/main/java/com/firebase/ui/database/ImmutableList.java @@ -7,7 +7,7 @@ import java.util.List; import java.util.ListIterator; -public abstract class UnmodifiableList implements List { +public abstract class ImmutableList implements List { /** * Guaranteed to throw an exception and leave the collection unmodified. * From 9f6bf5e210ac2b90df509c9cf2a51315f05c56d8 Mon Sep 17 00:00:00 2001 From: Alex Saveau Date: Wed, 25 Jan 2017 17:33:16 -0800 Subject: [PATCH 33/93] Nuke ListView adapters. This is up to debate, but I don't think we should support them given recent performance improvement in the RecyclerView. People really should upgrade to RecyclerView at this point. Signed-off-by: Alex Saveau --- .../adapter/FirebaseIndexListAdapter.java | 26 --- .../database/adapter/FirebaseListAdapter.java | 171 ------------------ 2 files changed, 197 deletions(-) delete mode 100644 database/src/main/java/com/firebase/ui/database/adapter/FirebaseIndexListAdapter.java delete mode 100644 database/src/main/java/com/firebase/ui/database/adapter/FirebaseListAdapter.java diff --git a/database/src/main/java/com/firebase/ui/database/adapter/FirebaseIndexListAdapter.java b/database/src/main/java/com/firebase/ui/database/adapter/FirebaseIndexListAdapter.java deleted file mode 100644 index bc33fcefb..000000000 --- a/database/src/main/java/com/firebase/ui/database/adapter/FirebaseIndexListAdapter.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.firebase.ui.database.adapter; - -import android.app.Activity; -import android.support.annotation.LayoutRes; - -import com.firebase.ui.database.FirebaseArray; -import com.firebase.ui.database.FirebaseIndexArray; -import com.google.firebase.database.Query; - -public abstract class FirebaseIndexListAdapter extends FirebaseListAdapter { - /** - * @param keyRef 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 in {@code - * keyRef}'s location represents a list item in the {@code ListView}. - * @see FirebaseListAdapter#FirebaseListAdapter(Activity, FirebaseArray, Class, int) - */ - public FirebaseIndexListAdapter(Activity activity, - Class modelClass, - @LayoutRes int modelLayout, - Query keyRef, - Query dataRef) { - super(activity, new FirebaseIndexArray(keyRef, dataRef), modelClass, modelLayout); - } -} diff --git a/database/src/main/java/com/firebase/ui/database/adapter/FirebaseListAdapter.java b/database/src/main/java/com/firebase/ui/database/adapter/FirebaseListAdapter.java deleted file mode 100644 index 8ed5ad0ee..000000000 --- a/database/src/main/java/com/firebase/ui/database/adapter/FirebaseListAdapter.java +++ /dev/null @@ -1,171 +0,0 @@ -package com.firebase.ui.database.adapter; - -import android.app.Activity; -import android.support.annotation.LayoutRes; -import android.util.Log; -import android.view.View; -import android.view.ViewGroup; -import android.widget.BaseAdapter; -import android.widget.ListView; - -import com.firebase.ui.database.ChangeEventListener; -import com.firebase.ui.database.FirebaseArray; -import com.google.firebase.database.DataSnapshot; -import com.google.firebase.database.DatabaseError; -import com.google.firebase.database.DatabaseReference; -import com.google.firebase.database.Query; - -/** - * This class is a generic way of backing an Android {@link android.widget.ListView} with a Firebase - * location. It handles all of the child events at the given Firebase location. It marshals received - * data into the given class type. Extend this class and provide an implementation of {@link - * #populateView(View, Object, int)}, which will be given an instance of your list item mLayout and - * an instance your class that holds your data. Simply populate the view however you like and this - * class will handle updating the list as the data changes. - *

- *

- *     DatabaseReference ref = FirebaseDatabase.getInstance().getReference();
- *     ListAdapter adapter = new FirebaseListAdapter(
- *              this, ChatMessage.class, android.R.layout.two_line_list_item, ref)
- *     {
- *         protected void populateView(View view, ChatMessage chatMessage, int position)
- *         {
- *             ((TextView)view.findViewById(android.R.id.text1)).setText(chatMessage.getName());
- *             ((TextView)view.findViewById(android.R.id.text2)).setText(chatMessage.getMessage());
- *         }
- *     };
- *     listView.setListAdapter(adapter);
- * 
- * - * @param The class type to use as a model for the data contained in the children of the given - * Firebase location - */ -public abstract class FirebaseListAdapter extends BaseAdapter implements ChangeEventListener { - private static final String TAG = "FirebaseListAdapter"; - - protected Activity mActivity; - protected FirebaseArray mSnapshots; - protected Class mModelClass; - protected int mLayout; - - /** - * @param activity The {@link Activity} containing the {@link ListView} - * @param modelClass Firebase will marshall the data at a location into an instance of a class - * that you provide - * @param modelLayout This is the layout used to represent a single list item. You will be - * responsible for populating an instance of the corresponding view with the - * data from an instance of modelClass. - * @param snapshots The data used to populate the adapter - */ - public FirebaseListAdapter(Activity activity, - FirebaseArray snapshots, - Class modelClass, - @LayoutRes int modelLayout) { - mActivity = activity; - mSnapshots = snapshots; - mModelClass = modelClass; - mLayout = modelLayout; - - startListening(); - } - - /** - * @param ref The Firebase location to watch for data changes. Can also be a slice of a - * location, using some combination of {@code limit()}, {@code startAt()}, and {@code - * endAt()}. - * @see #FirebaseListAdapter(Activity, FirebaseArray, Class, int) - */ - public FirebaseListAdapter(Activity activity, - Class modelClass, - @LayoutRes int modelLayout, - Query ref) { - this(activity, new FirebaseArray(ref), modelClass, modelLayout); - } - - /** - * If you need to do some setup before we start listening for change events in the database - * (such as setting a custom {@link JoinResolver}), do so it here and then call {@code - * super.startListening()}. - */ - protected void startListening() { - if (!mSnapshots.isListening()) { - mSnapshots.addChangeEventListener(this); - } - } - - public void cleanup() { - mSnapshots.removeChangeEventListener(this); - } - - @Override - public void onChildChanged(ChangeEventListener.EventType type, int index, int oldIndex) { - notifyDataSetChanged(); - } - - @Override - public void onDataChanged() { - } - - @Override - public void onCancelled(DatabaseError error) { - Log.w(TAG, error.toException()); - } - - @Override - public T getItem(int position) { - return parseSnapshot(mSnapshots.get(position)); - } - - /** - * This method parses the DataSnapshot into the requested type. You can override it in - * subclasses to do custom parsing. - * - * @param snapshot the {@link DataSnapshot} to extract the model from - * @return the model extracted from the DataSnapshot - */ - protected T parseSnapshot(DataSnapshot snapshot) { - return snapshot.getValue(mModelClass); - } - - public DatabaseReference getRef(int position) { - return mSnapshots.get(position).getRef(); - } - - @Override - public int getCount() { - return mSnapshots.size(); - } - - @Override - public long getItemId(int i) { - // http://stackoverflow.com/questions/5100071/whats-the-purpose-of-item-ids-in-android-listview-adapter - return mSnapshots.get(i).getKey().hashCode(); - } - - @Override - public View getView(int position, View view, ViewGroup viewGroup) { - if (view == null) { - view = mActivity.getLayoutInflater().inflate(mLayout, viewGroup, false); - } - - T model = getItem(position); - - // Call out to subclass to marshall this model into the provided view - populateView(view, model, position); - return view; - } - - /** - * Each time the data at the given Firebase location changes, - * this method will be called for each item that needs to be displayed. - * The first two arguments correspond to the mLayout and mModelClass given to the constructor of - * this class. The third argument is the item's position in the list. - *

- * Your implementation should populate the view using the data contained in the model. - * - * @param v The view to populate - * @param model The object containing the data used to populate the view - * @param position The position in the list of the view being populated - */ - protected abstract void populateView(View v, T model, int position); -} From 6543bbf72e90bebdcc3ccdd2b14e3cc39e6bcc84 Mon Sep 17 00:00:00 2001 From: Alex Saveau Date: Wed, 25 Jan 2017 18:12:06 -0800 Subject: [PATCH 34/93] Update documentation to use FirebaseRecyclerAdapter.java Signed-off-by: Alex Saveau --- .../uidemo/database/ChatActivity.java | 26 +-- database/README.md | 179 ++++++++---------- .../ui/database/FirebaseIndexListAdapter.java | 3 +- .../ui/database/FirebaseListAdapter.java | 3 +- 4 files changed, 96 insertions(+), 115 deletions(-) diff --git a/app/src/main/java/com/firebase/uidemo/database/ChatActivity.java b/app/src/main/java/com/firebase/uidemo/database/ChatActivity.java index 46f7cf912..66e307837 100644 --- a/app/src/main/java/com/firebase/uidemo/database/ChatActivity.java +++ b/app/src/main/java/com/firebase/uidemo/database/ChatActivity.java @@ -59,7 +59,7 @@ public class ChatActivity extends AppCompatActivity implements FirebaseAuth.Auth private RecyclerView mMessages; private LinearLayoutManager mManager; - private FirebaseRecyclerAdapter mRecyclerViewAdapter; + private FirebaseRecyclerAdapter mAdapter; private View mEmptyListView; @Override @@ -126,8 +126,8 @@ public void onStart() { @Override public void onStop() { super.onStop(); - if (mRecyclerViewAdapter != null) { - mRecyclerViewAdapter.cleanup(); + if (mAdapter != null) { + mAdapter.cleanup(); } } @@ -145,41 +145,41 @@ public void onAuthStateChanged(@NonNull FirebaseAuth firebaseAuth) { } private void attachRecyclerViewAdapter() { - mRecyclerViewAdapter = new FirebaseIndexRecyclerAdapter( + mAdapter = new FirebaseIndexRecyclerAdapter( Chat.class, ChatHolder.class, R.layout.message, mChatIndicesRef.limitToLast(50), mChatRef) { @Override - public void populateViewHolder(ChatHolder chatView, Chat chat, int position) { - chatView.setName(chat.getName()); - chatView.setText(chat.getMessage()); + public void populateViewHolder(ChatHolder holder, Chat chat, int position) { + holder.setName(chat.getName()); + holder.setText(chat.getMessage()); FirebaseUser currentUser = mAuth.getCurrentUser(); if (currentUser != null && chat.getUid().equals(currentUser.getUid())) { - chatView.setIsSender(true); + holder.setIsSender(true); } else { - chatView.setIsSender(false); + holder.setIsSender(false); } } @Override public void onDataChanged() { // if there are no chat messages, show a view that invites the user to add a message - mEmptyListView.setVisibility(mRecyclerViewAdapter.getItemCount() == 0 ? View.VISIBLE : View.INVISIBLE); + mEmptyListView.setVisibility(mAdapter.getItemCount() == 0 ? View.VISIBLE : View.INVISIBLE); } }; // Scroll to bottom on new messages - mRecyclerViewAdapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() { + mAdapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() { @Override public void onItemRangeInserted(int positionStart, int itemCount) { - mManager.smoothScrollToPosition(mMessages, null, mRecyclerViewAdapter.getItemCount()); + mManager.smoothScrollToPosition(mMessages, null, mAdapter.getItemCount()); } }); - mMessages.setAdapter(mRecyclerViewAdapter); + mMessages.setAdapter(mAdapter); } private void signInAnonymously() { diff --git a/database/README.md b/database/README.md index 0b572b71b..fb3d910f0 100644 --- a/database/README.md +++ b/database/README.md @@ -1,15 +1,15 @@ # firebase-ui-database -## Using FirebaseUI to Populate a ListView +## Using FirebaseUI to Populate a RecyclerView To use the FirebaseUI to display Firebase data, we need a few things: 1. A Java class that represents our database objects - 1. A custom list adapter to map from a collection from Firebase to Android + 1. A custom recycler view adapter to map from a collection from Firebase to Android ### Creating a model class -In your app, create a class that represents the data from Firebase that you want to show in the ListView. +In your app, create a class that represents the data from Firebase that you want to show in the RecyclerView. So say we have these chat messages in our Firebase database: @@ -96,11 +96,11 @@ we get the `Chat` objects from the `DataSnapshot` with `getValue(Chat.class)`. T then read the properties that it got from the database and map them to the fields of our `Chat` class. But when we build our app using FirebaseUI, we often won't need to register our own EventListener. The -`FirebaseListAdapter` takes care of that for us. +`FirebaseRecyclerAdapter` takes care of that for us. -### Find the ListView +### Find the RecyclerView -We'll assume you've already added a `ListView` to your layout and have looked it up in the `onCreate` method of your activity: +We'll assume you've already added a `RecyclerVview` to your layout and have looked it up in the `onCreate` method of your activity: ```java @Override @@ -108,10 +108,40 @@ protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); - ListView messagesView = (ListView) findViewById(R.id.messages_list); + RecyclerView messages = (RecyclerView) findViewById(R.id.messages); + messages.setLayoutManager(new LinearLayoutManager(this)); +} +``` + +### Create a custom ViewHolder + +A ViewHolder is similar to container of a ViewGroup that allows simple lookup of the sub-views of the group. +If we use the same layout as before (`android.R.layout.two_line_list_item`), there are two `TextView`s in there. +We can wrap that in a ViewHolder with: + +```java +public static class ChatHolder extends RecyclerView.ViewHolder { + private final TextView mNameField; + private final TextView mTextField; + + public ChatHolder(View itemView) { + super(itemView); + mNameField = (TextView) itemView.findViewById(android.R.id.text1); + mTextField = (TextView) itemView.findViewById(android.R.id.text2); + } + + public void setName(String name) { + mNameField.setText(name); + } + + public void setText(String text) { + mTextField.setText(text); + } } ``` +There's nothing magical going on here; we're just mapping numeric IDs and casts into a nice, type-safe contract. + ### Connect to Firebase First we'll set up a reference to the database of chat messages: @@ -122,15 +152,17 @@ protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); - ListView messagesView = (ListView) findViewById(R.id.messages_list); + RecyclerView messages = (RecyclerView) findViewById(R.id.messages); + messages.setLayoutManager(new LinearLayoutManager(this)); DatabaseReference ref = FirebaseDatabase.getInstance().getReference(); } ``` -### Create custom FirebaseListAdapter subclass +### Create custom FirebaseRecyclerAdapter subclass -Next, we need to create a subclass of the `FirebaseListAdapter` with the correct parameters and implement its `populateView` method: +Next, we need to create a subclass of the `FirebaseRecyclerAdapter` with the correct parameters +and implement its `populateViewHolder` method: ```java @Override @@ -138,31 +170,36 @@ protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); - ListView messagesView = (ListView) findViewById(R.id.messages_list); + RecyclerView messages = (RecyclerView) findViewById(R.id.messages); + messages.setLayoutManager(new LinearLayoutManager(this)); DatabaseReference ref = FirebaseDatabase.getInstance().getReference(); - - mAdapter = new FirebaseListAdapter(this, Chat.class, android.R.layout.two_line_list_item, ref) { - @Override - protected void populateView(View view, Chat chatMessage, int position) { - ((TextView)view.findViewById(android.R.id.text1)).setText(chatMessage.getName()); - ((TextView)view.findViewById(android.R.id.text2)).setText(chatMessage.getText()); + mAdapter = new FirebaseRecyclerAdapter( + Chat.class, + ChatHolder.class, + R.layout.message, + ref) { + @Override + public void populateViewHolder(ChatHolder holder, Chat chat, int position) { + holder.setName(chat.getName()); + holder.setText(chat.getMessage()); } }; - messagesView.setAdapter(mAdapter); + + messages.setAdapter(mAdapter); } ``` -In this last snippet we create a subclass of `FirebaseListAdapter`. +In this last snippet we create a subclass of `FirebaseRecyclerAdapter`. We tell is that it is of type ``, so that it is a type-safe collection. We also tell it to use `Chat.class` when reading messages from the database. Next we say that each message will be displayed in a `android.R.layout.two_line_list_item`, which is a built-in layout in Android that has two `TextView` elements under each other. Then we say that the adapter belongs to `this` activity and that it needs to monitor the data location in `ref`. -We also have to override the `populateView()` method, from the `FirebaseListAdapter`. The -`FirebaseListAdapter` will call our `populateView` method for each `Chat` it finds in the database. +We also have to override the `populateViewHolder` method, from the `FirebaseRecyclerAdapter`. The +`FirebaseRecyclerAdapter` will call our `populateViewHolder` method for each `Chat` it finds in the database. It passes us the `Chat` and a `View`, which is an instance of the `android.R.layout.two_line_list_item` we specified in the constructor. So what we do in our subclass is map the fields from `chatMessage` to the correct `TextView` controls from the `view`. The code is a bit verbose, but hey... that's Java and Android for you. @@ -170,7 +207,7 @@ correct `TextView` controls from the `view`. The code is a bit verbose, but hey. ### Clean up When the Activity is Destroyed Finally, we need to clean up after ourselves. When the activity is destroyed, we need to call `cleanup()` -on the `ListAdapter` so that it can stop listening for changes in the Firebase database. +on the adapter so that it can stop listening for changes in the Firebase database. ```java @Override @@ -191,25 +228,31 @@ protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); - ListView messagesView = (ListView) findViewById(R.id.messages_list); + RecyclerView messages = (RecyclerView) findViewById(R.id.messages); + messages.setLayoutManager(new LinearLayoutManager(this)); DatabaseReference ref = FirebaseDatabase.getInstance().getReference(); - mAdapter = new FirebaseListAdapter(this, Chat.class, android.R.layout.two_line_list_item, ref) { + mAdapter = new FirebaseRecyclerAdapter( + Chat.class, + ChatHolder.class, + R.layout.message, + ref) { @Override - protected void populateView(View view, Chat chatMessage, int position) { - ((TextView)view.findViewById(android.R.id.text1)).setText(chatMessage.getName()); - ((TextView)view.findViewById(android.R.id.text2)).setText(chatMessage.getText()); + public void populateViewHolder(ChatHolder holder, Chat chat, int position) { + holder.setName(chat.getName()); + holder.setText(chat.getMessage()); } }; - messagesView.setAdapter(mAdapter); - final EditText mMessage = (EditText) findViewById(R.id.message_text); + messages.setAdapter(mAdapter); + + final EditText message = (EditText) findViewById(R.id.message_text); findViewById(R.id.send_button).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { ref.push().setValue(new Chat("puf", "1234", mMessage.getText().toString())); - mMessage.setText(""); + message.setText(""); } }); } @@ -223,82 +266,18 @@ protected void onDestroy() { You're done! You now have a minimal, yet fully functional, chat app in about 30 lines of code. Not bad, right? -## Using FirebaseUI to Populate a RecyclerView - -RecyclerView is the new preferred way to handle potentially long lists of items. Since Firebase collections -can contain many items, there is an `FirebaseRecyclerAdapter` too. Here's how you use it: - -1. Create a custom ViewHolder class -2. Create a custom subclass FirebaseRecyclerAdapter - -The rest of the steps is the same as for the `FirebaseListAdapter` above, so be sure to read that first. - -### Create a custom ViewHolder - -A ViewHolder is similar to container of a ViewGroup that allows simple lookup of the sub-views of the group. -If we use the same layout as before (`android.R.layout.two_line_list_item`), there are two `TextView`s in there. -We can wrap that in a ViewHolder with: - -```java -public static class ChatHolder extends RecyclerView.ViewHolder { - private final TextView mNameField; - private final TextView mTextField; - - public ChatHolder(View itemView) { - super(itemView); - mNameField = (TextView) itemView.findViewById(android.R.id.text1); - mTextField = (TextView) itemView.findViewById(android.R.id.text2); - } - - public void setName(String name) { - mNameField.setText(name); - } - - public void setText(String text) { - mTextField.setText(text); - } -} -``` - -There's nothing magical going on here; we're just mapping numeric IDs and casts into a nice, type-safe contract. - -### Create a custom FirebaseRecyclerAdapter - -Just like we did for `FirebaseListAdapter`, we'll create an anonymous subclass for our Chats, but this time we'll use `FirebaseRecyclerAdapter`: - -```java -RecyclerView recycler = (RecyclerView) findViewById(R.id.messages_recycler); -recycler.setHasFixedSize(true); -recycler.setLayoutManager(new LinearLayoutManager(this)); - -mAdapter = new FirebaseRecyclerAdapter(Chat.class, android.R.layout.two_line_list_item, ChatHolder.class, mRef) { - @Override - public void populateViewHolder(ChatHolder chatMessageViewHolder, Chat chatMessage, int position) { - chatMessageViewHolder.setName(chatMessage.getName()); - chatMessageViewHolder.setText(chatMessage.getText()); - } -}; -recycler.setAdapter(mAdapter); -``` - -Like before, we get a custom RecyclerView populated with data from Firebase by setting the properties to the correct fields. - ## Using FirebaseUI with indexed data If your data is [properly indexed](https://firebase.google.com/docs/database/android/structure-data#best_practices_for_data_structure), change your adapter initalization like so: For a `RecyclerView`, use `FirebaseIndexRecyclerAdapter` instead of `FirebaseRecyclerAdapter`: ```java -new FirebaseIndexRecyclerAdapter(Chat.class, - android.R.layout.two_line_list_item, - ChatHolder.class, - keyRef, // The Firebase location containing the list of keys to be found in dataRef. - dataRef) //The Firebase location to watch for data changes. Each key key found at keyRef's location represents a list item in the RecyclerView. -``` - -And for a `ListView`, use `FirebaseIndexListAdapter`; -```java -new FirebaseIndexListAdapter(this, Chat.class, android.R.layout.two_line_list_item, keyRef, dataRef) +new FirebaseIndexRecyclerAdapter( + Chat.class, + android.R.layout.two_line_list_item, + ChatHolder.class, + keyRef, // The Firebase location containing the list of keys to be found in dataRef. + dataRef) //The Firebase location to watch for data changes. Each key key found at keyRef's location represents a list item in the RecyclerView. ``` `keyRef` is the location of your keys, and `dataRef` is the location of your data. diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseIndexListAdapter.java b/database/src/main/java/com/firebase/ui/database/FirebaseIndexListAdapter.java index b93c78e25..bbb67469f 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseIndexListAdapter.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseIndexListAdapter.java @@ -34,7 +34,8 @@ * * @param The class type to use as a model for the data contained in the children of the given * Firebase location - * @deprecated use {@link com.firebase.ui.database.adapter.FirebaseIndexListAdapter} instead + * @deprecated ListView is no longer supported, use {@link com.firebase.ui.database.adapter.FirebaseIndexRecyclerAdapter} + * instead */ @Deprecated public abstract class FirebaseIndexListAdapter extends FirebaseListAdapter { diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseListAdapter.java b/database/src/main/java/com/firebase/ui/database/FirebaseListAdapter.java index 2aea95215..8e096ecde 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseListAdapter.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseListAdapter.java @@ -50,7 +50,8 @@ * * @param The class type to use as a model for the data contained in the children of the given * Firebase location - * @deprecated use {@link com.firebase.ui.database.adapter.FirebaseListAdapter} instead + * @deprecated ListView is no longer supported, use {@link com.firebase.ui.database.adapter.FirebaseRecyclerAdapter} + * instead */ @Deprecated public abstract class FirebaseListAdapter extends BaseAdapter { From 6e89a1ad560d021828f9753ce6369aa7ee86ac32 Mon Sep 17 00:00:00 2001 From: Alex Saveau Date: Wed, 25 Jan 2017 18:18:23 -0800 Subject: [PATCH 35/93] Remove old documentation Signed-off-by: Alex Saveau --- .../adapter/FirebaseRecyclerAdapter.java | 40 ++----------------- 1 file changed, 4 insertions(+), 36 deletions(-) diff --git a/database/src/main/java/com/firebase/ui/database/adapter/FirebaseRecyclerAdapter.java b/database/src/main/java/com/firebase/ui/database/adapter/FirebaseRecyclerAdapter.java index efe3551d0..92fab7897 100644 --- a/database/src/main/java/com/firebase/ui/database/adapter/FirebaseRecyclerAdapter.java +++ b/database/src/main/java/com/firebase/ui/database/adapter/FirebaseRecyclerAdapter.java @@ -18,44 +18,12 @@ import java.lang.reflect.InvocationTargetException; /** - * This class is a generic way of backing an {@link RecyclerView} with a Firebase location. It - * handles all of the child events at the given Firebase location. It marshals received data into + * This class is a generic way of backing a {@link RecyclerView} with a Firebase location. It + * handles all of the child events at the given Firebase location and marshals received data into * the given class type. *

- * To use this class in your app, subclass it passing in all required parameters and implement the - * {@link #populateViewHolder(RecyclerView.ViewHolder, Object, int)} method. - *

- *

- *     private static class ChatMessageViewHolder extends RecyclerView.ViewHolder {
- *         TextView messageText;
- *         TextView nameText;
- *
- *         public ChatMessageViewHolder(View itemView) {
- *             super(itemView);
- *             nameText = (TextView)itemView.findViewById(android.R.id.text1);
- *             messageText = (TextView) itemView.findViewById(android.R.id.text2);
- *         }
- *     }
- *
- *     FirebaseRecyclerAdapter adapter;
- *     DatabaseReference ref = FirebaseDatabase.getInstance().getReference();
- *
- *     RecyclerView recycler = (RecyclerView) findViewById(R.id.messages_recycler);
- *     recycler.setHasFixedSize(true);
- *     recycler.setLayoutManager(new LinearLayoutManager(this));
- *
- *     adapter = new FirebaseRecyclerAdapter(
- *           ChatMessage.class, android.R.layout.two_line_list_item, ChatMessageViewHolder.class,
- * ref) {
- *         public void populateViewHolder(ChatMessageViewHolder chatMessageViewHolder,
- *                                        ChatMessage chatMessage,
- *                                        int position) {
- *             chatMessageViewHolder.nameText.setText(chatMessage.getName());
- *             chatMessageViewHolder.messageText.setText(chatMessage.getMessage());
- *         }
- *     };
- *     recycler.setAdapter(mAdapter);
- * 
+ * See the README + * for an in-depth tutorial on how to set up the FirebaseRecyclerAdapter. * * @param The Java class that maps to the type of objects stored in the Firebase location. * @param The {@link RecyclerView.ViewHolder} class that contains the Views in the layout that From 32eab681d572dc9e29a407ca3deb3a9c3071e76b Mon Sep 17 00:00:00 2001 From: Alex Saveau Date: Wed, 25 Jan 2017 18:56:57 -0800 Subject: [PATCH 36/93] Add temporary fix for https://github.com/firebase/FirebaseUI-Android/issues/546 Signed-off-by: Alex Saveau --- .../java/com/firebase/uidemo/database/ChatActivity.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/src/main/java/com/firebase/uidemo/database/ChatActivity.java b/app/src/main/java/com/firebase/uidemo/database/ChatActivity.java index 66e307837..b048e2d44 100644 --- a/app/src/main/java/com/firebase/uidemo/database/ChatActivity.java +++ b/app/src/main/java/com/firebase/uidemo/database/ChatActivity.java @@ -164,6 +164,14 @@ public void populateViewHolder(ChatHolder holder, Chat chat, int position) { } } + @Override + public void onChildChanged(EventType type, int index, int oldIndex) { + super.onChildChanged(type, index, oldIndex); + + // TODO temporary fix for https://github.com/firebase/FirebaseUI-Android/issues/546 + onDataChanged(); + } + @Override public void onDataChanged() { // if there are no chat messages, show a view that invites the user to add a message From cdb2a380b52f9dd574b5a8a8ae7a673069e11a6d Mon Sep 17 00:00:00 2001 From: Alex Saveau Date: Wed, 25 Jan 2017 20:02:03 -0800 Subject: [PATCH 37/93] Fix for #547 Signed-off-by: Alex Saveau --- .../java/com/firebase/ui/database/FirebaseIndexArray.java | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) 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 234a3542c..de961e3aa 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseIndexArray.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseIndexArray.java @@ -25,12 +25,10 @@ import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; -import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.ListIterator; import java.util.Map; -import java.util.Set; public class FirebaseIndexArray extends FirebaseArray { private static final String TAG = "FirebaseIndexArray"; @@ -48,10 +46,10 @@ public FirebaseIndexArray(Query keyQuery, Query dataQuery) { public void removeChangeEventListener(@NonNull ChangeEventListener listener) { super.removeChangeEventListener(listener); if (!isListening()) { - Set refs = new HashSet<>(mRefs.keySet()); - for (Query ref : refs) { - ref.removeEventListener(mRefs.remove(ref)); + for (Query query : mRefs.keySet()) { + query.removeEventListener(mRefs.get(query)); } + mRefs.clear(); mDataSnapshots.clear(); } } From c329d0b51a82ec0d160ec8f3299745e6040ccc46 Mon Sep 17 00:00:00 2001 From: Alex Saveau Date: Thu, 26 Jan 2017 16:14:12 -0800 Subject: [PATCH 38/93] Fix garbagey hashCode methods Signed-off-by: Alex Saveau --- .../main/java/com/firebase/ui/database/FirebaseArray.java | 7 ++----- .../java/com/firebase/ui/database/FirebaseIndexArray.java | 7 ++----- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseArray.java b/database/src/main/java/com/firebase/ui/database/FirebaseArray.java index f57e91f5a..36b40a1ad 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseArray.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseArray.java @@ -272,15 +272,12 @@ public boolean equals(Object obj) { FirebaseArray snapshots = (FirebaseArray) obj; - return mListeners.equals(snapshots.mListeners) - && mQuery.equals(snapshots.mQuery) - && mSnapshots.equals(snapshots.mSnapshots); + return mQuery.equals(snapshots.mQuery) && mSnapshots.equals(snapshots.mSnapshots); } @Override public int hashCode() { - int result = mListeners.hashCode(); - result = 31 * result + mQuery.hashCode(); + int result = mQuery.hashCode(); result = 31 * result + mSnapshots.hashCode(); return result; } 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 de961e3aa..d652a700b 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseIndexArray.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseIndexArray.java @@ -130,7 +130,7 @@ public void onCancelled(DatabaseError error) { super.onCancelled(error); } - private class DataRefListener implements ValueEventListener { + protected class DataRefListener implements ValueEventListener { @Override public void onDataChange(DataSnapshot snapshot) { String key = snapshot.getKey(); @@ -228,16 +228,13 @@ public boolean equals(Object obj) { FirebaseIndexArray array = (FirebaseIndexArray) obj; - return mDataQuery.equals(array.mDataQuery) - && mRefs.equals(array.mRefs) - && mDataSnapshots.equals(array.mDataSnapshots); + return mDataQuery.equals(array.mDataQuery) && mDataSnapshots.equals(array.mDataSnapshots); } @Override public int hashCode() { int result = super.hashCode(); result = 31 * result + mDataQuery.hashCode(); - result = 31 * result + mRefs.hashCode(); result = 31 * result + mDataSnapshots.hashCode(); return result; } From ecd88a9e3b15b86d68af76d6b320f9e36e6a068a Mon Sep 17 00:00:00 2001 From: Alex Saveau Date: Thu, 26 Jan 2017 16:39:41 -0800 Subject: [PATCH 39/93] Add SnapshotParser.java interface Signed-off-by: Alex Saveau --- .../firebase/ui/database/FirebaseArray.java | 15 +++++++++++-- .../ui/database/FirebaseObjectsArray.java | 21 ++++++++++++++++--- .../firebase/ui/database/SnapshotParser.java | 13 ++++++++++++ .../adapter/FirebaseRecyclerAdapter.java | 13 ++++-------- 4 files changed, 48 insertions(+), 14 deletions(-) create mode 100644 database/src/main/java/com/firebase/ui/database/SnapshotParser.java diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseArray.java b/database/src/main/java/com/firebase/ui/database/FirebaseArray.java index 36b40a1ad..1bf75e76e 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseArray.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseArray.java @@ -218,13 +218,24 @@ public T[] toArray(T[] a) { * Get a continually updated list of objects representing the {@link DataSnapshot}s in this * list. * - * @param the object representation of a {@link DataSnapshot} + * @param the model representation of a {@link DataSnapshot} * @return a list that represents the objects in this list of {@link DataSnapshot} */ - public List toObjectRepresentation(Class tClass) { + public List toModelList(Class tClass) { return new FirebaseObjectsArray<>(this, tClass); } + /** + * Get a continually updated list of objects representing the {@link DataSnapshot}s in this + * list. + * + * @param the model representation of a {@link DataSnapshot} + * @return a list that represents the objects in this list of {@link DataSnapshot} + */ + public List toModelList(Class tClass, SnapshotParser parser) { + return new FirebaseObjectsArray<>(this, tClass, parser); + } + @Override public boolean containsAll(Collection c) { return mSnapshots.containsAll(c); diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseObjectsArray.java b/database/src/main/java/com/firebase/ui/database/FirebaseObjectsArray.java index 85359d967..0fa4433e5 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseObjectsArray.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseObjectsArray.java @@ -11,15 +11,25 @@ /** * Acts as a bridge between a list of {@link DataSnapshot}s and a list of objects of type E. * - * @param the object representation of a {@link DataSnapshot} + * @param the model representation of a {@link DataSnapshot} */ -public class FirebaseObjectsArray extends ImmutableList { +public class FirebaseObjectsArray extends ImmutableList implements SnapshotParser { private List mSnapshots; private Class mEClass; + private SnapshotParser mParser; public FirebaseObjectsArray(List snapshots, Class eClass) { mSnapshots = snapshots; mEClass = eClass; + mParser = this; + } + + public FirebaseObjectsArray(List snapshots, + Class eClass, + SnapshotParser parser) { + mSnapshots = snapshots; + mEClass = eClass; + mParser = parser; } protected List getObjects() { @@ -30,6 +40,11 @@ protected List getObjects() { return objects; } + @Override + public E parseSnapshot(DataSnapshot snapshot) { + return snapshot.getValue(mEClass); + } + @Override public int size() { return mSnapshots.size(); @@ -72,7 +87,7 @@ public boolean containsAll(Collection c) { @Override public E get(int index) { - return mSnapshots.get(index).getValue(mEClass); + return mParser.parseSnapshot(mSnapshots.get(index)); } @Override diff --git a/database/src/main/java/com/firebase/ui/database/SnapshotParser.java b/database/src/main/java/com/firebase/ui/database/SnapshotParser.java new file mode 100644 index 000000000..8801f54f9 --- /dev/null +++ b/database/src/main/java/com/firebase/ui/database/SnapshotParser.java @@ -0,0 +1,13 @@ +package com.firebase.ui.database; + +import com.google.firebase.database.DataSnapshot; + +public interface SnapshotParser { + /** + * This method parses the DataSnapshot into the requested type. + * + * @param snapshot the DataSnapshot to extract the model from + * @return the model extracted from the DataSnapshot + */ + T parseSnapshot(DataSnapshot snapshot); +} diff --git a/database/src/main/java/com/firebase/ui/database/adapter/FirebaseRecyclerAdapter.java b/database/src/main/java/com/firebase/ui/database/adapter/FirebaseRecyclerAdapter.java index 92fab7897..46017b579 100644 --- a/database/src/main/java/com/firebase/ui/database/adapter/FirebaseRecyclerAdapter.java +++ b/database/src/main/java/com/firebase/ui/database/adapter/FirebaseRecyclerAdapter.java @@ -9,6 +9,7 @@ import com.firebase.ui.database.ChangeEventListener; import com.firebase.ui.database.FirebaseArray; +import com.firebase.ui.database.SnapshotParser; import com.google.firebase.database.DataSnapshot; import com.google.firebase.database.DatabaseError; import com.google.firebase.database.DatabaseReference; @@ -30,7 +31,7 @@ * is shown for each object. */ public abstract class FirebaseRecyclerAdapter - extends RecyclerView.Adapter implements ChangeEventListener { + extends RecyclerView.Adapter implements ChangeEventListener, SnapshotParser { private static final String TAG = "FirebaseRecyclerAdapter"; protected FirebaseArray mSnapshots; @@ -121,14 +122,8 @@ public T getItem(int position) { return parseSnapshot(mSnapshots.get(position)); } - /** - * This method parses the DataSnapshot into the requested type. You can override it in - * subclasses to do custom parsing. - * - * @param snapshot the DataSnapshot to extract the model from - * @return the model extracted from the DataSnapshot - */ - protected T parseSnapshot(DataSnapshot snapshot) { + @Override + public T parseSnapshot(DataSnapshot snapshot) { return snapshot.getValue(mModelClass); } From f2e862b5ff538c0c69101f85cf65295379b08185 Mon Sep 17 00:00:00 2001 From: Alex Saveau Date: Thu, 26 Jan 2017 16:46:30 -0800 Subject: [PATCH 40/93] Add back FirebaseListAdapter.java Signed-off-by: Alex Saveau --- .../ui/database/FirebaseIndexListAdapter.java | 3 +- .../ui/database/FirebaseListAdapter.java | 3 +- .../adapter/FirebaseIndexListAdapter.java | 26 +++ .../database/adapter/FirebaseListAdapter.java | 152 ++++++++++++++++++ 4 files changed, 180 insertions(+), 4 deletions(-) create mode 100644 database/src/main/java/com/firebase/ui/database/adapter/FirebaseIndexListAdapter.java create mode 100644 database/src/main/java/com/firebase/ui/database/adapter/FirebaseListAdapter.java diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseIndexListAdapter.java b/database/src/main/java/com/firebase/ui/database/FirebaseIndexListAdapter.java index bbb67469f..4d7810b0e 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseIndexListAdapter.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseIndexListAdapter.java @@ -34,8 +34,7 @@ * * @param The class type to use as a model for the data contained in the children of the given * Firebase location - * @deprecated ListView is no longer supported, use {@link com.firebase.ui.database.adapter.FirebaseIndexRecyclerAdapter} - * instead + * @deprecated use {@link com.firebase.ui.database.adapter.FirebaseIndexRecyclerAdapter} instead */ @Deprecated public abstract class FirebaseIndexListAdapter extends FirebaseListAdapter { diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseListAdapter.java b/database/src/main/java/com/firebase/ui/database/FirebaseListAdapter.java index 8e096ecde..a279b4c4d 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseListAdapter.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseListAdapter.java @@ -50,8 +50,7 @@ * * @param The class type to use as a model for the data contained in the children of the given * Firebase location - * @deprecated ListView is no longer supported, use {@link com.firebase.ui.database.adapter.FirebaseRecyclerAdapter} - * instead + * @deprecated use {@link com.firebase.ui.database.adapter.FirebaseRecyclerAdapter} instead */ @Deprecated public abstract class FirebaseListAdapter extends BaseAdapter { diff --git a/database/src/main/java/com/firebase/ui/database/adapter/FirebaseIndexListAdapter.java b/database/src/main/java/com/firebase/ui/database/adapter/FirebaseIndexListAdapter.java new file mode 100644 index 000000000..bc33fcefb --- /dev/null +++ b/database/src/main/java/com/firebase/ui/database/adapter/FirebaseIndexListAdapter.java @@ -0,0 +1,26 @@ +package com.firebase.ui.database.adapter; + +import android.app.Activity; +import android.support.annotation.LayoutRes; + +import com.firebase.ui.database.FirebaseArray; +import com.firebase.ui.database.FirebaseIndexArray; +import com.google.firebase.database.Query; + +public abstract class FirebaseIndexListAdapter extends FirebaseListAdapter { + /** + * @param keyRef 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 in {@code + * keyRef}'s location represents a list item in the {@code ListView}. + * @see FirebaseListAdapter#FirebaseListAdapter(Activity, FirebaseArray, Class, int) + */ + public FirebaseIndexListAdapter(Activity activity, + Class modelClass, + @LayoutRes int modelLayout, + Query keyRef, + Query dataRef) { + super(activity, new FirebaseIndexArray(keyRef, dataRef), modelClass, modelLayout); + } +} diff --git a/database/src/main/java/com/firebase/ui/database/adapter/FirebaseListAdapter.java b/database/src/main/java/com/firebase/ui/database/adapter/FirebaseListAdapter.java new file mode 100644 index 000000000..7437ef691 --- /dev/null +++ b/database/src/main/java/com/firebase/ui/database/adapter/FirebaseListAdapter.java @@ -0,0 +1,152 @@ +package com.firebase.ui.database.adapter; + +import android.app.Activity; +import android.support.annotation.LayoutRes; +import android.util.Log; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.ListView; + +import com.firebase.ui.database.ChangeEventListener; +import com.firebase.ui.database.FirebaseArray; +import com.firebase.ui.database.SnapshotParser; +import com.google.firebase.database.DataSnapshot; +import com.google.firebase.database.DatabaseError; +import com.google.firebase.database.DatabaseReference; +import com.google.firebase.database.Query; + +/** + * This class is a generic way of backing an Android {@link android.widget.ListView} with a Firebase + * location. It handles all of the child events at the given Firebase location. It marshals received + * data into the given class type. + *

+ * See the README + * for an in-depth tutorial on how to set up the FirebaseListAdapter. + * + * @param The class type to use as a model for the data contained in the children of the given + * Firebase location + */ +public abstract class FirebaseListAdapter extends BaseAdapter implements ChangeEventListener, SnapshotParser { + private static final String TAG = "FirebaseListAdapter"; + + protected Activity mActivity; + protected FirebaseArray mSnapshots; + protected Class mModelClass; + protected int mLayout; + + /** + * @param activity The {@link Activity} containing the {@link ListView} + * @param modelClass Firebase will marshall the data at a location into an instance of a class + * that you provide + * @param modelLayout This is the layout used to represent a single list item. You will be + * responsible for populating an instance of the corresponding view with the + * data from an instance of modelClass. + * @param snapshots The data used to populate the adapter + */ + public FirebaseListAdapter(Activity activity, + FirebaseArray snapshots, + Class modelClass, + @LayoutRes int modelLayout) { + mActivity = activity; + mSnapshots = snapshots; + mModelClass = modelClass; + mLayout = modelLayout; + + startListening(); + } + + /** + * @param ref The Firebase location to watch for data changes. Can also be a slice of a + * location, using some combination of {@code limit()}, {@code startAt()}, and {@code + * endAt()}. + * @see #FirebaseListAdapter(Activity, FirebaseArray, Class, int) + */ + public FirebaseListAdapter(Activity activity, + Class modelClass, + @LayoutRes int modelLayout, + Query ref) { + this(activity, new FirebaseArray(ref), modelClass, modelLayout); + } + + /** + * If you need to do some setup before we start listening for change events in the database + * (such as setting a custom {@link JoinResolver}), do so it here and then call {@code + * super.startListening()}. + */ + protected void startListening() { + if (!mSnapshots.isListening()) { + mSnapshots.addChangeEventListener(this); + } + } + + public void cleanup() { + mSnapshots.removeChangeEventListener(this); + } + + @Override + public void onChildChanged(ChangeEventListener.EventType type, int index, int oldIndex) { + notifyDataSetChanged(); + } + + @Override + public void onDataChanged() { + } + + @Override + public void onCancelled(DatabaseError error) { + Log.w(TAG, error.toException()); + } + + @Override + public T getItem(int position) { + return parseSnapshot(mSnapshots.get(position)); + } + + @Override + public T parseSnapshot(DataSnapshot snapshot) { + return snapshot.getValue(mModelClass); + } + + public DatabaseReference getRef(int position) { + return mSnapshots.get(position).getRef(); + } + + @Override + public int getCount() { + return mSnapshots.size(); + } + + @Override + public long getItemId(int i) { + // http://stackoverflow.com/questions/5100071/whats-the-purpose-of-item-ids-in-android-listview-adapter + return mSnapshots.get(i).getKey().hashCode(); + } + + @Override + public View getView(int position, View view, ViewGroup viewGroup) { + if (view == null) { + view = mActivity.getLayoutInflater().inflate(mLayout, viewGroup, false); + } + + T model = getItem(position); + + // Call out to subclass to marshall this model into the provided view + populateView(view, model, position); + return view; + } + + /** + * Each time the data at the given Firebase location changes, + * this method will be called for each item that needs to be displayed. + * The first two arguments correspond to the mLayout and mModelClass given to the constructor of + * this class. The third argument is the item's position in the list. + *

+ * Your implementation should populate the view using the data contained in the model. + * + * @param v The view to populate + * @param model The object containing the data used to populate the view + * @param position The position in the list of the view being populated + */ + protected abstract void populateView(View v, T model, int position); +} From ee1d2d2b46b272ffac8201e719b3f2f6aa8f7980 Mon Sep 17 00:00:00 2001 From: Alex Saveau Date: Fri, 27 Jan 2017 15:58:51 -0800 Subject: [PATCH 41/93] The cherry on top: add FirebaseAdapter.java interface Signed-off-by: Alex Saveau --- .../uidemo/database/ChatActivity.java | 2 +- .../ui/database/adapter/FirebaseAdapter.java | 24 +++++++++++++++++++ .../database/adapter/FirebaseListAdapter.java | 13 ++++------ .../adapter/FirebaseRecyclerAdapter.java | 14 +++++------ 4 files changed, 36 insertions(+), 17 deletions(-) create mode 100644 database/src/main/java/com/firebase/ui/database/adapter/FirebaseAdapter.java diff --git a/app/src/main/java/com/firebase/uidemo/database/ChatActivity.java b/app/src/main/java/com/firebase/uidemo/database/ChatActivity.java index b048e2d44..f1c0d4900 100644 --- a/app/src/main/java/com/firebase/uidemo/database/ChatActivity.java +++ b/app/src/main/java/com/firebase/uidemo/database/ChatActivity.java @@ -175,7 +175,7 @@ public void onChildChanged(EventType type, int index, int oldIndex) { @Override public void onDataChanged() { // if there are no chat messages, show a view that invites the user to add a message - mEmptyListView.setVisibility(mAdapter.getItemCount() == 0 ? View.VISIBLE : View.INVISIBLE); + mEmptyListView.setVisibility(getItemCount() == 0 ? View.VISIBLE : View.INVISIBLE); } }; diff --git a/database/src/main/java/com/firebase/ui/database/adapter/FirebaseAdapter.java b/database/src/main/java/com/firebase/ui/database/adapter/FirebaseAdapter.java new file mode 100644 index 000000000..d04b880f5 --- /dev/null +++ b/database/src/main/java/com/firebase/ui/database/adapter/FirebaseAdapter.java @@ -0,0 +1,24 @@ +package com.firebase.ui.database.adapter; + +import com.firebase.ui.database.ChangeEventListener; +import com.firebase.ui.database.FirebaseArray; +import com.firebase.ui.database.SnapshotParser; +import com.google.firebase.database.DatabaseReference; + +public interface FirebaseAdapter extends ChangeEventListener, SnapshotParser { + /** + * If you need to do some setup before the adapter starts listening for change events in the + * database (such as setting a custom {@link JoinResolver}), do so it here and then call {@code + * super.startListening()}. + */ + void startListening(); + + /** + * Removes listeners and clears all items in the backing {@link FirebaseArray}. + */ + void cleanup(); + + T getItem(int position); + + DatabaseReference getRef(int position); +} diff --git a/database/src/main/java/com/firebase/ui/database/adapter/FirebaseListAdapter.java b/database/src/main/java/com/firebase/ui/database/adapter/FirebaseListAdapter.java index 7437ef691..61b2a8531 100644 --- a/database/src/main/java/com/firebase/ui/database/adapter/FirebaseListAdapter.java +++ b/database/src/main/java/com/firebase/ui/database/adapter/FirebaseListAdapter.java @@ -10,7 +10,6 @@ import com.firebase.ui.database.ChangeEventListener; import com.firebase.ui.database.FirebaseArray; -import com.firebase.ui.database.SnapshotParser; import com.google.firebase.database.DataSnapshot; import com.google.firebase.database.DatabaseError; import com.google.firebase.database.DatabaseReference; @@ -27,7 +26,7 @@ * @param The class type to use as a model for the data contained in the children of the given * Firebase location */ -public abstract class FirebaseListAdapter extends BaseAdapter implements ChangeEventListener, SnapshotParser { +public abstract class FirebaseListAdapter extends BaseAdapter implements FirebaseAdapter { private static final String TAG = "FirebaseListAdapter"; protected Activity mActivity; @@ -69,17 +68,14 @@ public FirebaseListAdapter(Activity activity, this(activity, new FirebaseArray(ref), modelClass, modelLayout); } - /** - * If you need to do some setup before we start listening for change events in the database - * (such as setting a custom {@link JoinResolver}), do so it here and then call {@code - * super.startListening()}. - */ - protected void startListening() { + @Override + public void startListening() { if (!mSnapshots.isListening()) { mSnapshots.addChangeEventListener(this); } } + @Override public void cleanup() { mSnapshots.removeChangeEventListener(this); } @@ -108,6 +104,7 @@ public T parseSnapshot(DataSnapshot snapshot) { return snapshot.getValue(mModelClass); } + @Override public DatabaseReference getRef(int position) { return mSnapshots.get(position).getRef(); } diff --git a/database/src/main/java/com/firebase/ui/database/adapter/FirebaseRecyclerAdapter.java b/database/src/main/java/com/firebase/ui/database/adapter/FirebaseRecyclerAdapter.java index 46017b579..9565fd24e 100644 --- a/database/src/main/java/com/firebase/ui/database/adapter/FirebaseRecyclerAdapter.java +++ b/database/src/main/java/com/firebase/ui/database/adapter/FirebaseRecyclerAdapter.java @@ -9,7 +9,6 @@ import com.firebase.ui.database.ChangeEventListener; import com.firebase.ui.database.FirebaseArray; -import com.firebase.ui.database.SnapshotParser; import com.google.firebase.database.DataSnapshot; import com.google.firebase.database.DatabaseError; import com.google.firebase.database.DatabaseReference; @@ -31,7 +30,7 @@ * is shown for each object. */ public abstract class FirebaseRecyclerAdapter - extends RecyclerView.Adapter implements ChangeEventListener, SnapshotParser { + extends RecyclerView.Adapter implements FirebaseAdapter { private static final String TAG = "FirebaseRecyclerAdapter"; protected FirebaseArray mSnapshots; @@ -74,17 +73,14 @@ public FirebaseRecyclerAdapter(Class modelClass, this(new FirebaseArray(ref), modelClass, viewHolderClass, modelLayout); } - /** - * If you need to do some setup before we start listening for change events in the database - * (such as setting a custom {@link JoinResolver}), do so it here and then call {@code - * super.startListening()}. - */ - protected void startListening() { + @Override + public void startListening() { if (!mSnapshots.isListening()) { mSnapshots.addChangeEventListener(this); } } + @Override public void cleanup() { mSnapshots.removeChangeEventListener(this); } @@ -118,6 +114,7 @@ public void onCancelled(DatabaseError error) { Log.w(TAG, error.toException()); } + @Override public T getItem(int position) { return parseSnapshot(mSnapshots.get(position)); } @@ -127,6 +124,7 @@ public T parseSnapshot(DataSnapshot snapshot) { return snapshot.getValue(mModelClass); } + @Override public DatabaseReference getRef(int position) { return mSnapshots.get(position).getRef(); } From 54f5f7c7cf4d7ac9162b3cedac847d4d2295b87d Mon Sep 17 00:00:00 2001 From: Alex Saveau Date: Fri, 27 Jan 2017 16:23:57 -0800 Subject: [PATCH 42/93] Update documentation Signed-off-by: Alex Saveau --- database/README.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/database/README.md b/database/README.md index fb3d910f0..c97c506de 100644 --- a/database/README.md +++ b/database/README.md @@ -266,6 +266,26 @@ protected void onDestroy() { You're done! You now have a minimal, yet fully functional, chat app in about 30 lines of code. Not bad, right? +## Using FirebaseUI to populate a ListView + +ListView is the older, yet simpler way to handle lists of items. Using it is analogous to +using a `FirebaseRecyclerAdapter`, but with `FirebaseListAdapter` instead and no `ViewHolder`: +```java +ListView messagesView = (ListView) findViewById(R.id.messages_list); + +DatabaseReference ref = FirebaseDatabase.getInstance().getReference(); + +mAdapter = new FirebaseListAdapter(this, Chat.class, android.R.layout.two_line_list_item, ref) { + @Override + protected void populateView(View view, Chat chatMessage, int position) { + ((TextView) view.findViewById(android.R.id.text1)).setText(chatMessage.getName()); + ((TextView) view.findViewById(android.R.id.text2)).setText(chatMessage.getText()); + + } +}; +messagesView.setAdapter(mAdapter); +``` + ## Using FirebaseUI with indexed data If your data is [properly indexed](https://firebase.google.com/docs/database/android/structure-data#best_practices_for_data_structure), change your adapter initalization like so: From fff42e8e72cb72db6a3e7922ce99c78821d67469 Mon Sep 17 00:00:00 2001 From: Alex Saveau Date: Fri, 27 Jan 2017 18:30:48 -0800 Subject: [PATCH 43/93] Rename objects array: sounded too much like "FirebaseObjects array". Now it's like "FirebaseArray ofObjects" Signed-off-by: Alex Saveau --- .../com/firebase/ui/database/FirebaseArray.java | 4 ++-- ...jectsArray.java => FirebaseArrayOfObjects.java} | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) rename database/src/main/java/com/firebase/ui/database/{FirebaseObjectsArray.java => FirebaseArrayOfObjects.java} (86%) diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseArray.java b/database/src/main/java/com/firebase/ui/database/FirebaseArray.java index 1bf75e76e..5c76b2655 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseArray.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseArray.java @@ -222,7 +222,7 @@ public T[] toArray(T[] a) { * @return a list that represents the objects in this list of {@link DataSnapshot} */ public List toModelList(Class tClass) { - return new FirebaseObjectsArray<>(this, tClass); + return new FirebaseArrayOfObjects<>(this, tClass); } /** @@ -233,7 +233,7 @@ public List toModelList(Class tClass) { * @return a list that represents the objects in this list of {@link DataSnapshot} */ public List toModelList(Class tClass, SnapshotParser parser) { - return new FirebaseObjectsArray<>(this, tClass, parser); + return new FirebaseArrayOfObjects<>(this, tClass, parser); } @Override diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseObjectsArray.java b/database/src/main/java/com/firebase/ui/database/FirebaseArrayOfObjects.java similarity index 86% rename from database/src/main/java/com/firebase/ui/database/FirebaseObjectsArray.java rename to database/src/main/java/com/firebase/ui/database/FirebaseArrayOfObjects.java index 0fa4433e5..e4ab951d7 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseObjectsArray.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseArrayOfObjects.java @@ -13,20 +13,20 @@ * * @param the model representation of a {@link DataSnapshot} */ -public class FirebaseObjectsArray extends ImmutableList implements SnapshotParser { +public class FirebaseArrayOfObjects extends ImmutableList implements SnapshotParser { private List mSnapshots; private Class mEClass; private SnapshotParser mParser; - public FirebaseObjectsArray(List snapshots, Class eClass) { + public FirebaseArrayOfObjects(List snapshots, Class eClass) { mSnapshots = snapshots; mEClass = eClass; mParser = this; } - public FirebaseObjectsArray(List snapshots, - Class eClass, - SnapshotParser parser) { + public FirebaseArrayOfObjects(List snapshots, + Class eClass, + SnapshotParser parser) { mSnapshots = snapshots; mEClass = eClass; mParser = parser; @@ -125,7 +125,7 @@ public boolean equals(Object obj) { if (this == obj) return true; if (obj == null || getClass() != obj.getClass()) return false; - FirebaseObjectsArray array = (FirebaseObjectsArray) obj; + FirebaseArrayOfObjects array = (FirebaseArrayOfObjects) obj; return mSnapshots.equals(array.mSnapshots) && mEClass.equals(array.mEClass); } @@ -139,7 +139,7 @@ public int hashCode() { @Override public String toString() { - return "FirebaseObjectsArray{" + + return "FirebaseArrayOfObjects{" + "mSnapshots=" + mSnapshots + '}'; } From 189d35575837c5277a4af99c731d4700c3f1fc45 Mon Sep 17 00:00:00 2001 From: Alex Saveau Date: Fri, 27 Jan 2017 18:40:19 -0800 Subject: [PATCH 44/93] Add getSnapshots method to FirebaseArrayOfObjects.java and update api for FirebaseArray.java Signed-off-by: Alex Saveau --- .../src/main/java/com/firebase/ui/database/FirebaseArray.java | 4 ++-- .../java/com/firebase/ui/database/FirebaseArrayOfObjects.java | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseArray.java b/database/src/main/java/com/firebase/ui/database/FirebaseArray.java index 5c76b2655..a4686f7f1 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseArray.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseArray.java @@ -221,7 +221,7 @@ public T[] toArray(T[] a) { * @param the model representation of a {@link DataSnapshot} * @return a list that represents the objects in this list of {@link DataSnapshot} */ - public List toModelList(Class tClass) { + public List toObjectsList(Class tClass) { return new FirebaseArrayOfObjects<>(this, tClass); } @@ -232,7 +232,7 @@ public List toModelList(Class tClass) { * @param the model representation of a {@link DataSnapshot} * @return a list that represents the objects in this list of {@link DataSnapshot} */ - public List toModelList(Class tClass, SnapshotParser parser) { + public List toObjectsList(Class tClass, SnapshotParser parser) { return new FirebaseArrayOfObjects<>(this, tClass, parser); } diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseArrayOfObjects.java b/database/src/main/java/com/firebase/ui/database/FirebaseArrayOfObjects.java index e4ab951d7..13ec0aad9 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseArrayOfObjects.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseArrayOfObjects.java @@ -32,6 +32,10 @@ public FirebaseArrayOfObjects(List snapshots, mParser = parser; } + public List getSnapshots() { + return mSnapshots; + } + protected List getObjects() { List objects = new ArrayList<>(mSnapshots.size()); for (int i = 0; i < mSnapshots.size(); i++) { From 16d654d627c693ef192c13ecd2d55a1014f5d306 Mon Sep 17 00:00:00 2001 From: Alex Saveau Date: Sun, 29 Jan 2017 11:48:45 -0800 Subject: [PATCH 45/93] Update api so you can't pass in a query for index stuff Signed-off-by: Alex Saveau --- .../ui/database/FirebaseIndexArray.java | 19 +++++++++---------- .../adapter/FirebaseIndexListAdapter.java | 17 +++++++++-------- .../adapter/FirebaseIndexRecyclerAdapter.java | 17 +++++++++-------- .../database/adapter/FirebaseListAdapter.java | 10 +++++----- .../adapter/FirebaseRecyclerAdapter.java | 10 +++++----- 5 files changed, 37 insertions(+), 36 deletions(-) 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 d652a700b..991a5de28 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseIndexArray.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseIndexArray.java @@ -19,6 +19,7 @@ import com.google.firebase.database.DataSnapshot; import com.google.firebase.database.DatabaseError; +import com.google.firebase.database.DatabaseReference; import com.google.firebase.database.Query; import com.google.firebase.database.ValueEventListener; @@ -33,13 +34,13 @@ public class FirebaseIndexArray extends FirebaseArray { private static final String TAG = "FirebaseIndexArray"; - private Query mDataQuery; + private DatabaseReference mDataRef; private Map mRefs = new HashMap<>(); private List mDataSnapshots = new ArrayList<>(); - public FirebaseIndexArray(Query keyQuery, Query dataQuery) { + public FirebaseIndexArray(Query keyQuery, DatabaseReference dataRef) { super(keyQuery); - mDataQuery = dataQuery; + mDataRef = dataRef; } @Override @@ -78,7 +79,7 @@ public void onChildAdded(DataSnapshot keySnapshot, String previousChildKey) { super.onChildAdded(keySnapshot, previousChildKey); setShouldNotifyListeners(true); - Query ref = mDataQuery.getRef().child(keySnapshot.getKey()); + Query ref = mDataRef.child(keySnapshot.getKey()); mRefs.put(ref, ref.addValueEventListener(new DataRefListener())); } @@ -93,9 +94,7 @@ public void onChildChanged(DataSnapshot snapshot, String previousChildKey) { public void onChildRemoved(DataSnapshot keySnapshot) { String key = keySnapshot.getKey(); int index = getIndexForKey(key); - mDataQuery.getRef() - .child(key) - .removeEventListener(mRefs.remove(mDataQuery.getRef().child(key))); + mDataRef.child(key).removeEventListener(mRefs.remove(mDataRef.getRef().child(key))); setShouldNotifyListeners(false); super.onChildRemoved(keySnapshot); @@ -228,13 +227,13 @@ public boolean equals(Object obj) { FirebaseIndexArray array = (FirebaseIndexArray) obj; - return mDataQuery.equals(array.mDataQuery) && mDataSnapshots.equals(array.mDataSnapshots); + return mDataRef.equals(array.mDataRef) && mDataSnapshots.equals(array.mDataSnapshots); } @Override public int hashCode() { int result = super.hashCode(); - result = 31 * result + mDataQuery.hashCode(); + result = 31 * result + mDataRef.hashCode(); result = 31 * result + mDataSnapshots.hashCode(); return result; } @@ -243,7 +242,7 @@ public int hashCode() { public String toString() { return "FirebaseIndexArray{" + "mIsListening=" + isListening() + - ", mDataQuery=" + mDataQuery + + ", mDataRef=" + mDataRef + ", mDataSnapshots=" + mDataSnapshots + '}'; } diff --git a/database/src/main/java/com/firebase/ui/database/adapter/FirebaseIndexListAdapter.java b/database/src/main/java/com/firebase/ui/database/adapter/FirebaseIndexListAdapter.java index bc33fcefb..a60872c2a 100644 --- a/database/src/main/java/com/firebase/ui/database/adapter/FirebaseIndexListAdapter.java +++ b/database/src/main/java/com/firebase/ui/database/adapter/FirebaseIndexListAdapter.java @@ -5,22 +5,23 @@ import com.firebase.ui.database.FirebaseArray; import com.firebase.ui.database.FirebaseIndexArray; +import com.google.firebase.database.DatabaseReference; import com.google.firebase.database.Query; public abstract class FirebaseIndexListAdapter extends FirebaseListAdapter { /** - * @param keyRef 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 in {@code - * keyRef}'s location represents a list item in the {@code ListView}. + * @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 in {@code + * keyQuery}'s location represents a list item in the {@code ListView}. * @see FirebaseListAdapter#FirebaseListAdapter(Activity, FirebaseArray, Class, int) */ public FirebaseIndexListAdapter(Activity activity, Class modelClass, @LayoutRes int modelLayout, - Query keyRef, - Query dataRef) { - super(activity, new FirebaseIndexArray(keyRef, dataRef), modelClass, modelLayout); + Query keyQuery, + DatabaseReference dataRef) { + super(activity, new FirebaseIndexArray(keyQuery, dataRef), modelClass, modelLayout); } } diff --git a/database/src/main/java/com/firebase/ui/database/adapter/FirebaseIndexRecyclerAdapter.java b/database/src/main/java/com/firebase/ui/database/adapter/FirebaseIndexRecyclerAdapter.java index c3f25bf70..cdc4b58af 100644 --- a/database/src/main/java/com/firebase/ui/database/adapter/FirebaseIndexRecyclerAdapter.java +++ b/database/src/main/java/com/firebase/ui/database/adapter/FirebaseIndexRecyclerAdapter.java @@ -5,23 +5,24 @@ import com.firebase.ui.database.FirebaseArray; import com.firebase.ui.database.FirebaseIndexArray; +import com.google.firebase.database.DatabaseReference; import com.google.firebase.database.Query; public abstract class FirebaseIndexRecyclerAdapter extends FirebaseRecyclerAdapter { /** - * @param keyRef 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 - * keyRef}'s location represents a list item in the {@link RecyclerView}. + * @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 FirebaseRecyclerAdapter#FirebaseRecyclerAdapter(FirebaseArray, Class, Class, int) */ public FirebaseIndexRecyclerAdapter(Class modelClass, Class viewHolderClass, @LayoutRes int modelLayout, - Query keyRef, - Query dataRef) { - super(new FirebaseIndexArray(keyRef, dataRef), modelClass, viewHolderClass, modelLayout); + Query keyQuery, + DatabaseReference dataRef) { + super(new FirebaseIndexArray(keyQuery, dataRef), modelClass, viewHolderClass, modelLayout); } } diff --git a/database/src/main/java/com/firebase/ui/database/adapter/FirebaseListAdapter.java b/database/src/main/java/com/firebase/ui/database/adapter/FirebaseListAdapter.java index 61b2a8531..0f151cf39 100644 --- a/database/src/main/java/com/firebase/ui/database/adapter/FirebaseListAdapter.java +++ b/database/src/main/java/com/firebase/ui/database/adapter/FirebaseListAdapter.java @@ -56,16 +56,16 @@ public FirebaseListAdapter(Activity activity, } /** - * @param ref The Firebase location to watch for data changes. Can also be a slice of a - * location, using some combination of {@code limit()}, {@code startAt()}, and {@code - * endAt()}. + * @param query The Firebase location to watch for data changes. Can also be a slice of a + * location, using some combination of {@code limit()}, {@code startAt()}, and + * {@code endAt()}. * @see #FirebaseListAdapter(Activity, FirebaseArray, Class, int) */ public FirebaseListAdapter(Activity activity, Class modelClass, @LayoutRes int modelLayout, - Query ref) { - this(activity, new FirebaseArray(ref), modelClass, modelLayout); + Query query) { + this(activity, new FirebaseArray(query), modelClass, modelLayout); } @Override diff --git a/database/src/main/java/com/firebase/ui/database/adapter/FirebaseRecyclerAdapter.java b/database/src/main/java/com/firebase/ui/database/adapter/FirebaseRecyclerAdapter.java index 9565fd24e..eecf3e82b 100644 --- a/database/src/main/java/com/firebase/ui/database/adapter/FirebaseRecyclerAdapter.java +++ b/database/src/main/java/com/firebase/ui/database/adapter/FirebaseRecyclerAdapter.java @@ -61,16 +61,16 @@ public FirebaseRecyclerAdapter(FirebaseArray snapshots, } /** - * @param ref The Firebase location to watch for data changes. Can also be a slice of a - * location, using some combination of {@code limit()}, {@code startAt()}, and {@code - * endAt()}. + * @param query The Firebase location to watch for data changes. Can also be a slice of a + * location, using some combination of {@code limit()}, {@code startAt()}, and + * {@code endAt()}. * @see #FirebaseRecyclerAdapter(FirebaseArray, Class, Class, int) */ public FirebaseRecyclerAdapter(Class modelClass, Class viewHolderClass, @LayoutRes int modelLayout, - Query ref) { - this(new FirebaseArray(ref), modelClass, viewHolderClass, modelLayout); + Query query) { + this(new FirebaseArray(query), modelClass, viewHolderClass, modelLayout); } @Override From a14e3edd4d10286df90b5fffacb688e303c8d998 Mon Sep 17 00:00:00 2001 From: Alex Saveau Date: Sun, 29 Jan 2017 11:54:52 -0800 Subject: [PATCH 46/93] Woops, fix compile errors for old adapters Signed-off-by: Alex Saveau --- .../com/firebase/ui/database/FirebaseIndexListAdapter.java | 2 +- .../firebase/ui/database/FirebaseIndexRecyclerAdapter.java | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseIndexListAdapter.java b/database/src/main/java/com/firebase/ui/database/FirebaseIndexListAdapter.java index 4d7810b0e..22c1cae58 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseIndexListAdapter.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseIndexListAdapter.java @@ -56,6 +56,6 @@ public FirebaseIndexListAdapter(Activity activity, @LayoutRes int modelLayout, Query keyRef, Query dataRef) { - super(activity, modelClass, modelLayout, new FirebaseIndexArray(keyRef, dataRef)); + super(activity, modelClass, modelLayout, new FirebaseIndexArray(keyRef, dataRef.getRef())); } } diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseIndexRecyclerAdapter.java b/database/src/main/java/com/firebase/ui/database/FirebaseIndexRecyclerAdapter.java index cbe05e75f..b422b10dd 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseIndexRecyclerAdapter.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseIndexRecyclerAdapter.java @@ -88,6 +88,9 @@ public FirebaseIndexRecyclerAdapter(Class modelClass, Class viewHolderClass, Query keyRef, Query dataRef) { - super(modelClass, modelLayout, viewHolderClass, new FirebaseIndexArray(keyRef, dataRef)); + super(modelClass, + modelLayout, + viewHolderClass, + new FirebaseIndexArray(keyRef, dataRef.getRef())); } } From 75aa4f48c4c97fd7a38ea85c7469e24d801b567e Mon Sep 17 00:00:00 2001 From: Alex Saveau Date: Mon, 30 Jan 2017 16:40:51 -0800 Subject: [PATCH 47/93] Update documentation Signed-off-by: Alex Saveau --- .../java/com/firebase/ui/database/FirebaseArray.java | 5 +++++ .../com/firebase/ui/database/FirebaseArrayOfObjects.java | 9 +++++++++ .../com/firebase/ui/database/FirebaseIndexArray.java | 8 ++++++++ 3 files changed, 22 insertions(+) diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseArray.java b/database/src/main/java/com/firebase/ui/database/FirebaseArray.java index a4686f7f1..178d34e71 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseArray.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseArray.java @@ -37,6 +37,11 @@ public class FirebaseArray extends ImmutableList implements ChildE private List mListeners = new ArrayList<>(); private List mSnapshots = new ArrayList<>(); + /** + * @param query The Firebase location to watch for data changes. Can also be a slice of a + * location, using some combination of {@code limit()}, {@code startAt()}, and + * {@code endAt()}. + */ public FirebaseArray(Query query) { mQuery = query; } diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseArrayOfObjects.java b/database/src/main/java/com/firebase/ui/database/FirebaseArrayOfObjects.java index 13ec0aad9..1a7ac82ee 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseArrayOfObjects.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseArrayOfObjects.java @@ -18,12 +18,21 @@ public class FirebaseArrayOfObjects extends ImmutableList implements Snaps private Class mEClass; private SnapshotParser mParser; + /** + * @param snapshots a list of {@link DataSnapshot}s to be converted to a model type + * @param eClass the model representation of a {@link DataSnapshot} + */ public FirebaseArrayOfObjects(List snapshots, Class eClass) { mSnapshots = snapshots; mEClass = eClass; mParser = this; } + /** + * @param parser a custom {@link SnapshotParser} to manually convert each {@link DataSnapshot} + * to its model type + * @see #FirebaseArrayOfObjects(List, Class) + */ public FirebaseArrayOfObjects(List snapshots, Class eClass, SnapshotParser parser) { 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 991a5de28..5627662fe 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseIndexArray.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseIndexArray.java @@ -15,6 +15,7 @@ package com.firebase.ui.database; import android.support.annotation.NonNull; +import android.support.v7.widget.RecyclerView; import android.util.Log; import com.google.firebase.database.DataSnapshot; @@ -38,6 +39,13 @@ public class FirebaseIndexArray extends FirebaseArray { private Map mRefs = new HashMap<>(); private List mDataSnapshots = new ArrayList<>(); + /** + * @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}. + */ public FirebaseIndexArray(Query keyQuery, DatabaseReference dataRef) { super(keyQuery); mDataRef = dataRef; From d286df5f594ddfc367edb67b947fa6b54651833b Mon Sep 17 00:00:00 2001 From: Alex Saveau Date: Tue, 31 Jan 2017 16:00:02 -0800 Subject: [PATCH 48/93] Optimize FirebaseArrayOfObjects.java Signed-off-by: Alex Saveau --- .../firebase/ui/database/FirebaseArray.java | 58 +++++++-- .../ui/database/FirebaseArrayOfObjects.java | 120 +++++++++++++++--- .../database/SubscriptionEventListener.java | 14 ++ 3 files changed, 157 insertions(+), 35 deletions(-) create mode 100644 database/src/main/java/com/firebase/ui/database/SubscriptionEventListener.java diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseArray.java b/database/src/main/java/com/firebase/ui/database/FirebaseArray.java index 178d34e71..7961555dd 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseArray.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseArray.java @@ -35,6 +35,7 @@ public class FirebaseArray extends ImmutableList implements ChildE private Query mQuery; private boolean mNotifyListeners = true; private List mListeners = new ArrayList<>(); + private List mSubscribers = new ArrayList<>(); private List mSnapshots = new ArrayList<>(); /** @@ -58,6 +59,7 @@ public ChangeEventListener addChangeEventListener(@NonNull ChangeEventListener l checkNotNull(listener); mListeners.add(listener); + notifySubscriptionEventListeners(SubscriptionEventListener.EventType.ADDED); if (mListeners.size() <= 1) { mQuery.addChildEventListener(this); mQuery.addValueEventListener(this); @@ -67,16 +69,14 @@ public ChangeEventListener addChangeEventListener(@NonNull ChangeEventListener l } /** - * Remove the {@link ChangeEventListener} from the location provided in {@link + * Remove a {@link ChangeEventListener} from the location provided in {@link * #FirebaseArray(Query)}. The list will be empty after this call returns. * * @param listener the listener to remove - * @throws IllegalArgumentException if the listener is null */ public void removeChangeEventListener(@NonNull ChangeEventListener listener) { - checkNotNull(listener); - mListeners.remove(listener); + notifySubscriptionEventListeners(SubscriptionEventListener.EventType.REMOVED); if (mListeners.isEmpty()) { mQuery.removeEventListener((ValueEventListener) this); mQuery.removeEventListener((ChildEventListener) this); @@ -84,12 +84,42 @@ public void removeChangeEventListener(@NonNull ChangeEventListener listener) { } } - private static void checkNotNull(ChangeEventListener listener) { - if (listener == null) { - throw new IllegalArgumentException("ChangeEventListener cannot be null."); + /** + * Add a listener for subscription events eg additions/removals of {@link ChangeEventListener}s. + * + * @param listener the listener to be called with changes + * @return a reference to the listener provided. Save this to remove the listener later + * @throws IllegalArgumentException if the listener is null + */ + public SubscriptionEventListener addSubscriptionEventListener(@NonNull SubscriptionEventListener listener) { + checkNotNull(listener); + mSubscribers.add(listener); + return listener; + } + + /** + * Remove a {@link SubscriptionEventListener} for this {@link FirebaseArray}. + * + * @param listener the listener to remove + */ + public void removeSubscriptionEventListener(@NonNull SubscriptionEventListener listener) { + mSubscribers.remove(listener); + } + + protected void notifySubscriptionEventListeners(@SubscriptionEventListener.EventType int eventType) { + for (SubscriptionEventListener listener : mSubscribers) { + if (eventType == SubscriptionEventListener.EventType.ADDED) { + listener.onSubscriptionAdded(); + } else if (eventType == SubscriptionEventListener.EventType.REMOVED) { + listener.onSubscriptionRemoved(); + } } } + private static void checkNotNull(Object o) { + if (o == null) throw new IllegalArgumentException("Listener cannot be null."); + } + /** * @return true if {@link FirebaseArray} is listening for change events from the Firebase * database, false otherwise @@ -223,22 +253,22 @@ public T[] toArray(T[] a) { * Get a continually updated list of objects representing the {@link DataSnapshot}s in this * list. * - * @param the model representation of a {@link DataSnapshot} + * @param modelClass the model representation of a {@link DataSnapshot} * @return a list that represents the objects in this list of {@link DataSnapshot} */ - public List toObjectsList(Class tClass) { - return new FirebaseArrayOfObjects<>(this, tClass); + public List toObjectsList(Class modelClass) { + return FirebaseArrayOfObjects.newInstance(this, modelClass); } /** * Get a continually updated list of objects representing the {@link DataSnapshot}s in this * list. * - * @param the model representation of a {@link DataSnapshot} - * @return a list that represents the objects in this list of {@link DataSnapshot} + * @param parser a custom {@link SnapshotParser} to manually convert each {@link DataSnapshot} + * to its model type */ - public List toObjectsList(Class tClass, SnapshotParser parser) { - return new FirebaseArrayOfObjects<>(this, tClass, parser); + public List toObjectsList(Class modelClass, SnapshotParser parser) { + return FirebaseArrayOfObjects.newInstance(this, modelClass, parser); } @Override diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseArrayOfObjects.java b/database/src/main/java/com/firebase/ui/database/FirebaseArrayOfObjects.java index 1a7ac82ee..5ca194a2f 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseArrayOfObjects.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseArrayOfObjects.java @@ -1,6 +1,9 @@ package com.firebase.ui.database; +import android.util.Pair; + import com.google.firebase.database.DataSnapshot; +import com.google.firebase.database.DatabaseError; import java.util.ArrayList; import java.util.Collection; @@ -13,32 +16,50 @@ * * @param the model representation of a {@link DataSnapshot} */ -public class FirebaseArrayOfObjects extends ImmutableList implements SnapshotParser { - private List mSnapshots; - private Class mEClass; - private SnapshotParser mParser; +public class FirebaseArrayOfObjects extends ImmutableList { + protected List mSnapshots; + protected Class mEClass; + protected SnapshotParser mParser; /** - * @param snapshots a list of {@link DataSnapshot}s to be converted to a model type - * @param eClass the model representation of a {@link DataSnapshot} + * @param snapshots a list of {@link DataSnapshot}s to be converted to a model type + * @param modelClass the model representation of a {@link DataSnapshot} */ - public FirebaseArrayOfObjects(List snapshots, Class eClass) { + public FirebaseArrayOfObjects(List snapshots, Class modelClass) { mSnapshots = snapshots; - mEClass = eClass; - mParser = this; + mEClass = modelClass; + mParser = new SnapshotParser() { + @Override + public E parseSnapshot(DataSnapshot snapshot) { + return snapshot.getValue(mEClass); + } + }; + } + + /** + * @param snapshots a list of {@link DataSnapshot}s to be converted to a model type + * @param modelClass the model representation of a {@link DataSnapshot} + */ + public static FirebaseArrayOfObjects newInstance(List snapshots, + Class modelClass) { + if (snapshots instanceof FirebaseArray) { + return new FirebaseArrayOfObjectsOptimized<>((FirebaseArray) snapshots, modelClass); + } else { + return new FirebaseArrayOfObjects<>(snapshots, modelClass); + } } /** * @param parser a custom {@link SnapshotParser} to manually convert each {@link DataSnapshot} * to its model type - * @see #FirebaseArrayOfObjects(List, Class) + * @see #newInstance(List, Class) */ - public FirebaseArrayOfObjects(List snapshots, - Class eClass, - SnapshotParser parser) { - mSnapshots = snapshots; - mEClass = eClass; - mParser = parser; + public static FirebaseArrayOfObjects newInstance(List snapshots, + Class modelClass, + SnapshotParser parser) { + FirebaseArrayOfObjects array = newInstance(snapshots, modelClass); + array.mParser = parser; + return array; } public List getSnapshots() { @@ -53,11 +74,6 @@ protected List getObjects() { return objects; } - @Override - public E parseSnapshot(DataSnapshot snapshot) { - return snapshot.getValue(mEClass); - } - @Override public int size() { return mSnapshots.size(); @@ -156,4 +172,66 @@ public String toString() { "mSnapshots=" + mSnapshots + '}'; } + + protected static class FirebaseArrayOfObjectsOptimized extends FirebaseArrayOfObjects + implements ChangeEventListener, SubscriptionEventListener { + protected List mObjects = new ArrayList<>(); + protected Pair mIsListening$AddedListener = new Pair<>(true, false); + + public FirebaseArrayOfObjectsOptimized(FirebaseArray snapshots, Class modelClass) { + super(snapshots, modelClass); + snapshots.addChangeEventListener(this); + snapshots.addSubscriptionEventListener(this); + } + + @Override + protected List getObjects() { + return mObjects; + } + + @Override + public void onChildChanged(ChangeEventListener.EventType type, int index, int oldIndex) { + switch (type) { + case ADDED: + mObjects.add(get(index)); + break; + case CHANGED: + mObjects.set(index, get(index)); + break; + case REMOVED: + mObjects.remove(index); + break; + case MOVED: + mObjects.add(index, mObjects.remove(oldIndex)); + break; + } + } + + @Override + public void onSubscriptionRemoved() { + FirebaseArray snapshots = (FirebaseArray) mSnapshots; + if (!snapshots.isListening()) { + snapshots.removeChangeEventListener(this); + mIsListening$AddedListener = new Pair<>(false, false); + } + } + + @Override + public void onSubscriptionAdded() { + if (mIsListening$AddedListener.second) { + mIsListening$AddedListener = new Pair<>(true, false); + } else if (!mIsListening$AddedListener.first) { + ((FirebaseArray) mSnapshots).addChangeEventListener(this); + mIsListening$AddedListener = new Pair<>(true, true); + } + } + + @Override + public void onDataChanged() { + } + + @Override + public void onCancelled(DatabaseError error) { + } + } } diff --git a/database/src/main/java/com/firebase/ui/database/SubscriptionEventListener.java b/database/src/main/java/com/firebase/ui/database/SubscriptionEventListener.java new file mode 100644 index 000000000..8ca955d42 --- /dev/null +++ b/database/src/main/java/com/firebase/ui/database/SubscriptionEventListener.java @@ -0,0 +1,14 @@ +package com.firebase.ui.database; + +import android.support.annotation.IntDef; + +public interface SubscriptionEventListener { + @IntDef({EventType.ADDED, EventType.REMOVED}) + @interface EventType { + int ADDED = 0, REMOVED = 1; + } + + void onSubscriptionAdded(); + + void onSubscriptionRemoved(); +} From 9316ed956d256b7687bd3635e57302aa1c02ef18 Mon Sep 17 00:00:00 2001 From: Alex Saveau Date: Tue, 31 Jan 2017 16:02:38 -0800 Subject: [PATCH 49/93] Cleanup Signed-off-by: Alex Saveau --- .../ui/database/ChangeEventListener.java | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/database/src/main/java/com/firebase/ui/database/ChangeEventListener.java b/database/src/main/java/com/firebase/ui/database/ChangeEventListener.java index a8028dc47..7703ba11b 100644 --- a/database/src/main/java/com/firebase/ui/database/ChangeEventListener.java +++ b/database/src/main/java/com/firebase/ui/database/ChangeEventListener.java @@ -40,18 +40,20 @@ enum EventType { * * @param type The type of event received * @param index The index at which the change occurred - * @param oldIndex If {@code type} is a moved event, the previous index of the moved child. - * For any other event, {@code oldIndex} will be -1. + * @param oldIndex If {@code type} is a moved event, the previous index of the moved child. For + * any other event, {@code oldIndex} will be -1. */ void onChildChanged(EventType type, int index, int oldIndex); - /** This method will be triggered each time updates from the database have been completely processed. - * So the first time this method is called, the initial data has been loaded - including the case - * when no data at all is available. Each next time the method is called, a complete update (potentially - * consisting of updates to multiple child items) has been completed. + /** + * This method will be triggered each time updates from the database have been completely + * processed. So the first time this method is called, the initial data has been loaded - + * including the case when no data at all is available. Each next time the method is called, a + * complete update (potentially consisting of updates to multiple child items) has been + * completed. *

- * You would typically override this method to hide a loading indicator (after the initial load) or - * to complete a batch update to a UI element. + * You would typically override this method to hide a loading indicator (after the initial load) + * or to complete a batch update to a UI element. */ void onDataChanged(); From c98d2f9aaae3ae83466b98ed523b8630064dc037 Mon Sep 17 00:00:00 2001 From: Alex Saveau Date: Tue, 31 Jan 2017 22:23:20 -0800 Subject: [PATCH 50/93] Remove all non-critical changes Signed-off-by: Alex Saveau --- .../uidemo/database/ChatActivity.java | 36 ++-- database/README.md | 181 +++++++++--------- .../ui/database/ChangeEventListener.java | 18 +- .../ui/database/FirebaseIndexListAdapter.java | 32 ++-- .../FirebaseIndexRecyclerAdapter.java | 33 ++-- .../ui/database/FirebaseListAdapter.java | 30 ++- .../ui/database/FirebaseRecyclerAdapter.java | 27 ++- 7 files changed, 167 insertions(+), 190 deletions(-) diff --git a/app/src/main/java/com/firebase/uidemo/database/ChatActivity.java b/app/src/main/java/com/firebase/uidemo/database/ChatActivity.java index db4e0a17b..96426eb71 100644 --- a/app/src/main/java/com/firebase/uidemo/database/ChatActivity.java +++ b/app/src/main/java/com/firebase/uidemo/database/ChatActivity.java @@ -26,8 +26,7 @@ import android.widget.TextView; import android.widget.Toast; -import com.firebase.ui.database.adapter.FirebaseIndexRecyclerAdapter; -import com.firebase.ui.database.adapter.FirebaseRecyclerAdapter; +import com.firebase.ui.database.FirebaseRecyclerAdapter; import com.firebase.uidemo.R; import com.firebase.uidemo.util.SignInResultNotifier; import com.google.android.gms.tasks.OnSuccessListener; @@ -37,12 +36,13 @@ import com.google.firebase.database.DatabaseError; import com.google.firebase.database.DatabaseReference; import com.google.firebase.database.FirebaseDatabase; +import com.google.firebase.database.Query; public class ChatActivity extends AppCompatActivity implements FirebaseAuth.AuthStateListener { private static final String TAG = "RecyclerViewDemo"; private FirebaseAuth mAuth; - private DatabaseReference mChatIndicesRef; + private DatabaseReference mRef; private DatabaseReference mChatRef; private Button mSendButton; private EditText mMessageEdit; @@ -64,9 +64,8 @@ protected void onCreate(Bundle savedInstanceState) { mMessageEdit = (EditText) findViewById(R.id.messageEdit); mEmptyListMessage = (TextView) findViewById(R.id.emptyTextView); - DatabaseReference ref = FirebaseDatabase.getInstance().getReference(); - mChatIndicesRef = ref.child("chatIndices"); - mChatRef = ref.child("chats"); + mRef = FirebaseDatabase.getInstance().getReference(); + mChatRef = mRef.child("chats"); mSendButton.setOnClickListener(new View.OnClickListener() { @Override @@ -75,9 +74,7 @@ public void onClick(View v) { String name = "User " + uid.substring(0, 6); Chat chat = new Chat(name, mMessageEdit.getText().toString(), uid); - DatabaseReference chatRef = mChatRef.push(); - mChatIndicesRef.child(chatRef.getKey()).setValue(true); - chatRef.setValue(chat, new DatabaseReference.CompletionListener() { + mChatRef.push().setValue(chat, new DatabaseReference.CompletionListener() { @Override public void onComplete(DatabaseError error, DatabaseReference reference) { if (error != null) { @@ -134,12 +131,9 @@ public void onAuthStateChanged(@NonNull FirebaseAuth firebaseAuth) { } private void attachRecyclerViewAdapter() { - mAdapter = new FirebaseIndexRecyclerAdapter( - Chat.class, - ChatHolder.class, - R.layout.message, - mChatIndicesRef.limitToLast(50), - mChatRef) { + Query lastFifty = mChatRef.limitToLast(50); + mAdapter = new FirebaseRecyclerAdapter( + Chat.class, R.layout.message, ChatHolder.class, lastFifty) { @Override public void populateViewHolder(ChatHolder holder, Chat chat, int position) { holder.setName(chat.getName()); @@ -154,17 +148,9 @@ public void populateViewHolder(ChatHolder holder, Chat chat, int position) { } @Override - public void onChildChanged(EventType type, int index, int oldIndex) { - super.onChildChanged(type, index, oldIndex); - - // TODO temporary fix for https://github.com/firebase/FirebaseUI-Android/issues/546 - onDataChanged(); - } - - @Override - public void onDataChanged() { + protected void onDataChanged() { // If there are no chat messages, show a view that invites the user to add a message. - mEmptyListMessage.setVisibility(getItemCount() == 0 ? View.VISIBLE : View.GONE); + mEmptyListMessage.setVisibility(mAdapter.getItemCount() == 0 ? View.VISIBLE : View.GONE); } }; diff --git a/database/README.md b/database/README.md index c97c506de..0b572b71b 100644 --- a/database/README.md +++ b/database/README.md @@ -1,15 +1,15 @@ # firebase-ui-database -## Using FirebaseUI to Populate a RecyclerView +## Using FirebaseUI to Populate a ListView To use the FirebaseUI to display Firebase data, we need a few things: 1. A Java class that represents our database objects - 1. A custom recycler view adapter to map from a collection from Firebase to Android + 1. A custom list adapter to map from a collection from Firebase to Android ### Creating a model class -In your app, create a class that represents the data from Firebase that you want to show in the RecyclerView. +In your app, create a class that represents the data from Firebase that you want to show in the ListView. So say we have these chat messages in our Firebase database: @@ -96,11 +96,11 @@ we get the `Chat` objects from the `DataSnapshot` with `getValue(Chat.class)`. T then read the properties that it got from the database and map them to the fields of our `Chat` class. But when we build our app using FirebaseUI, we often won't need to register our own EventListener. The -`FirebaseRecyclerAdapter` takes care of that for us. +`FirebaseListAdapter` takes care of that for us. -### Find the RecyclerView +### Find the ListView -We'll assume you've already added a `RecyclerVview` to your layout and have looked it up in the `onCreate` method of your activity: +We'll assume you've already added a `ListView` to your layout and have looked it up in the `onCreate` method of your activity: ```java @Override @@ -108,40 +108,10 @@ protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); - RecyclerView messages = (RecyclerView) findViewById(R.id.messages); - messages.setLayoutManager(new LinearLayoutManager(this)); + ListView messagesView = (ListView) findViewById(R.id.messages_list); } ``` -### Create a custom ViewHolder - -A ViewHolder is similar to container of a ViewGroup that allows simple lookup of the sub-views of the group. -If we use the same layout as before (`android.R.layout.two_line_list_item`), there are two `TextView`s in there. -We can wrap that in a ViewHolder with: - -```java -public static class ChatHolder extends RecyclerView.ViewHolder { - private final TextView mNameField; - private final TextView mTextField; - - public ChatHolder(View itemView) { - super(itemView); - mNameField = (TextView) itemView.findViewById(android.R.id.text1); - mTextField = (TextView) itemView.findViewById(android.R.id.text2); - } - - public void setName(String name) { - mNameField.setText(name); - } - - public void setText(String text) { - mTextField.setText(text); - } -} -``` - -There's nothing magical going on here; we're just mapping numeric IDs and casts into a nice, type-safe contract. - ### Connect to Firebase First we'll set up a reference to the database of chat messages: @@ -152,17 +122,15 @@ protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); - RecyclerView messages = (RecyclerView) findViewById(R.id.messages); - messages.setLayoutManager(new LinearLayoutManager(this)); + ListView messagesView = (ListView) findViewById(R.id.messages_list); DatabaseReference ref = FirebaseDatabase.getInstance().getReference(); } ``` -### Create custom FirebaseRecyclerAdapter subclass +### Create custom FirebaseListAdapter subclass -Next, we need to create a subclass of the `FirebaseRecyclerAdapter` with the correct parameters -and implement its `populateViewHolder` method: +Next, we need to create a subclass of the `FirebaseListAdapter` with the correct parameters and implement its `populateView` method: ```java @Override @@ -170,36 +138,31 @@ protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); - RecyclerView messages = (RecyclerView) findViewById(R.id.messages); - messages.setLayoutManager(new LinearLayoutManager(this)); + ListView messagesView = (ListView) findViewById(R.id.messages_list); DatabaseReference ref = FirebaseDatabase.getInstance().getReference(); - - mAdapter = new FirebaseRecyclerAdapter( - Chat.class, - ChatHolder.class, - R.layout.message, - ref) { + + mAdapter = new FirebaseListAdapter(this, Chat.class, android.R.layout.two_line_list_item, ref) { @Override - public void populateViewHolder(ChatHolder holder, Chat chat, int position) { - holder.setName(chat.getName()); - holder.setText(chat.getMessage()); + protected void populateView(View view, Chat chatMessage, int position) { + ((TextView)view.findViewById(android.R.id.text1)).setText(chatMessage.getName()); + ((TextView)view.findViewById(android.R.id.text2)).setText(chatMessage.getText()); + } }; - - messages.setAdapter(mAdapter); + messagesView.setAdapter(mAdapter); } ``` -In this last snippet we create a subclass of `FirebaseRecyclerAdapter`. +In this last snippet we create a subclass of `FirebaseListAdapter`. We tell is that it is of type ``, so that it is a type-safe collection. We also tell it to use `Chat.class` when reading messages from the database. Next we say that each message will be displayed in a `android.R.layout.two_line_list_item`, which is a built-in layout in Android that has two `TextView` elements under each other. Then we say that the adapter belongs to `this` activity and that it needs to monitor the data location in `ref`. -We also have to override the `populateViewHolder` method, from the `FirebaseRecyclerAdapter`. The -`FirebaseRecyclerAdapter` will call our `populateViewHolder` method for each `Chat` it finds in the database. +We also have to override the `populateView()` method, from the `FirebaseListAdapter`. The +`FirebaseListAdapter` will call our `populateView` method for each `Chat` it finds in the database. It passes us the `Chat` and a `View`, which is an instance of the `android.R.layout.two_line_list_item` we specified in the constructor. So what we do in our subclass is map the fields from `chatMessage` to the correct `TextView` controls from the `view`. The code is a bit verbose, but hey... that's Java and Android for you. @@ -207,7 +170,7 @@ correct `TextView` controls from the `view`. The code is a bit verbose, but hey. ### Clean up When the Activity is Destroyed Finally, we need to clean up after ourselves. When the activity is destroyed, we need to call `cleanup()` -on the adapter so that it can stop listening for changes in the Firebase database. +on the `ListAdapter` so that it can stop listening for changes in the Firebase database. ```java @Override @@ -228,31 +191,25 @@ protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); - RecyclerView messages = (RecyclerView) findViewById(R.id.messages); - messages.setLayoutManager(new LinearLayoutManager(this)); + ListView messagesView = (ListView) findViewById(R.id.messages_list); DatabaseReference ref = FirebaseDatabase.getInstance().getReference(); - mAdapter = new FirebaseRecyclerAdapter( - Chat.class, - ChatHolder.class, - R.layout.message, - ref) { + mAdapter = new FirebaseListAdapter(this, Chat.class, android.R.layout.two_line_list_item, ref) { @Override - public void populateViewHolder(ChatHolder holder, Chat chat, int position) { - holder.setName(chat.getName()); - holder.setText(chat.getMessage()); + protected void populateView(View view, Chat chatMessage, int position) { + ((TextView)view.findViewById(android.R.id.text1)).setText(chatMessage.getName()); + ((TextView)view.findViewById(android.R.id.text2)).setText(chatMessage.getText()); } }; + messagesView.setAdapter(mAdapter); - messages.setAdapter(mAdapter); - - final EditText message = (EditText) findViewById(R.id.message_text); + final EditText mMessage = (EditText) findViewById(R.id.message_text); findViewById(R.id.send_button).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { ref.push().setValue(new Chat("puf", "1234", mMessage.getText().toString())); - message.setText(""); + mMessage.setText(""); } }); } @@ -266,38 +223,82 @@ protected void onDestroy() { You're done! You now have a minimal, yet fully functional, chat app in about 30 lines of code. Not bad, right? -## Using FirebaseUI to populate a ListView +## Using FirebaseUI to Populate a RecyclerView + +RecyclerView is the new preferred way to handle potentially long lists of items. Since Firebase collections +can contain many items, there is an `FirebaseRecyclerAdapter` too. Here's how you use it: + +1. Create a custom ViewHolder class +2. Create a custom subclass FirebaseRecyclerAdapter + +The rest of the steps is the same as for the `FirebaseListAdapter` above, so be sure to read that first. + +### Create a custom ViewHolder + +A ViewHolder is similar to container of a ViewGroup that allows simple lookup of the sub-views of the group. +If we use the same layout as before (`android.R.layout.two_line_list_item`), there are two `TextView`s in there. +We can wrap that in a ViewHolder with: -ListView is the older, yet simpler way to handle lists of items. Using it is analogous to -using a `FirebaseRecyclerAdapter`, but with `FirebaseListAdapter` instead and no `ViewHolder`: ```java -ListView messagesView = (ListView) findViewById(R.id.messages_list); +public static class ChatHolder extends RecyclerView.ViewHolder { + private final TextView mNameField; + private final TextView mTextField; -DatabaseReference ref = FirebaseDatabase.getInstance().getReference(); + public ChatHolder(View itemView) { + super(itemView); + mNameField = (TextView) itemView.findViewById(android.R.id.text1); + mTextField = (TextView) itemView.findViewById(android.R.id.text2); + } -mAdapter = new FirebaseListAdapter(this, Chat.class, android.R.layout.two_line_list_item, ref) { - @Override - protected void populateView(View view, Chat chatMessage, int position) { - ((TextView) view.findViewById(android.R.id.text1)).setText(chatMessage.getName()); - ((TextView) view.findViewById(android.R.id.text2)).setText(chatMessage.getText()); + public void setName(String name) { + mNameField.setText(name); + } + + public void setText(String text) { + mTextField.setText(text); + } +} +``` + +There's nothing magical going on here; we're just mapping numeric IDs and casts into a nice, type-safe contract. +### Create a custom FirebaseRecyclerAdapter + +Just like we did for `FirebaseListAdapter`, we'll create an anonymous subclass for our Chats, but this time we'll use `FirebaseRecyclerAdapter`: + +```java +RecyclerView recycler = (RecyclerView) findViewById(R.id.messages_recycler); +recycler.setHasFixedSize(true); +recycler.setLayoutManager(new LinearLayoutManager(this)); + +mAdapter = new FirebaseRecyclerAdapter(Chat.class, android.R.layout.two_line_list_item, ChatHolder.class, mRef) { + @Override + public void populateViewHolder(ChatHolder chatMessageViewHolder, Chat chatMessage, int position) { + chatMessageViewHolder.setName(chatMessage.getName()); + chatMessageViewHolder.setText(chatMessage.getText()); } }; -messagesView.setAdapter(mAdapter); +recycler.setAdapter(mAdapter); ``` +Like before, we get a custom RecyclerView populated with data from Firebase by setting the properties to the correct fields. + ## Using FirebaseUI with indexed data If your data is [properly indexed](https://firebase.google.com/docs/database/android/structure-data#best_practices_for_data_structure), change your adapter initalization like so: For a `RecyclerView`, use `FirebaseIndexRecyclerAdapter` instead of `FirebaseRecyclerAdapter`: ```java -new FirebaseIndexRecyclerAdapter( - Chat.class, - android.R.layout.two_line_list_item, - ChatHolder.class, - keyRef, // The Firebase location containing the list of keys to be found in dataRef. - dataRef) //The Firebase location to watch for data changes. Each key key found at keyRef's location represents a list item in the RecyclerView. +new FirebaseIndexRecyclerAdapter(Chat.class, + android.R.layout.two_line_list_item, + ChatHolder.class, + keyRef, // The Firebase location containing the list of keys to be found in dataRef. + dataRef) //The Firebase location to watch for data changes. Each key key found at keyRef's location represents a list item in the RecyclerView. +``` + +And for a `ListView`, use `FirebaseIndexListAdapter`; +```java +new FirebaseIndexListAdapter(this, Chat.class, android.R.layout.two_line_list_item, keyRef, dataRef) ``` `keyRef` is the location of your keys, and `dataRef` is the location of your data. diff --git a/database/src/main/java/com/firebase/ui/database/ChangeEventListener.java b/database/src/main/java/com/firebase/ui/database/ChangeEventListener.java index 7703ba11b..a8028dc47 100644 --- a/database/src/main/java/com/firebase/ui/database/ChangeEventListener.java +++ b/database/src/main/java/com/firebase/ui/database/ChangeEventListener.java @@ -40,20 +40,18 @@ enum EventType { * * @param type The type of event received * @param index The index at which the change occurred - * @param oldIndex If {@code type} is a moved event, the previous index of the moved child. For - * any other event, {@code oldIndex} will be -1. + * @param oldIndex If {@code type} is a moved event, the previous index of the moved child. + * For any other event, {@code oldIndex} will be -1. */ void onChildChanged(EventType type, int index, int oldIndex); - /** - * This method will be triggered each time updates from the database have been completely - * processed. So the first time this method is called, the initial data has been loaded - - * including the case when no data at all is available. Each next time the method is called, a - * complete update (potentially consisting of updates to multiple child items) has been - * completed. + /** This method will be triggered each time updates from the database have been completely processed. + * So the first time this method is called, the initial data has been loaded - including the case + * when no data at all is available. Each next time the method is called, a complete update (potentially + * consisting of updates to multiple child items) has been completed. *

- * You would typically override this method to hide a loading indicator (after the initial load) - * or to complete a batch update to a UI element. + * You would typically override this method to hide a loading indicator (after the initial load) or + * to complete a batch update to a UI element. */ void onDataChanged(); diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseIndexListAdapter.java b/database/src/main/java/com/firebase/ui/database/FirebaseIndexListAdapter.java index 22c1cae58..dc1529347 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseIndexListAdapter.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseIndexListAdapter.java @@ -6,12 +6,11 @@ import com.google.firebase.database.Query; /** - * This class is a generic way of backing an Android ListView with a Firebase location. It handles - * all of the child events at the given Firebase location. It marshals received data into the given - * class type. Extend this class and provide an implementation of {@code populateView}, which will - * be given an instance of your list item mLayout and an instance your class that holds your data. - * Simply populate the view however you like and this class will handle updating the list as the - * data changes. + * This class is a generic way of backing an Android ListView with a Firebase location. + * It handles all of the child events at the given Firebase location. It marshals received data into the given + * class type. Extend this class and provide an implementation of {@code populateView}, which will be given an + * instance of your list item mLayout and an instance your class that holds your data. + * Simply populate the view however you like and this class will handle updating the list as the data changes. *

* If your data is not indexed: *

@@ -40,16 +39,17 @@
 public abstract class FirebaseIndexListAdapter extends FirebaseListAdapter {
     /**
      * @param activity    The activity containing the ListView
-     * @param modelClass  Firebase will marshall the data at a location into an instance of a class
-     *                    that you provide
-     * @param modelLayout This is the layout used to represent a single list item. You will be
-     *                    responsible for populating an instance of the corresponding view with the
-     *                    data from an instance of modelClass.
-     * @param keyRef      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 in
-     *                    {@code keyRef}'s location represents a list item in the {@code ListView}.
+     * @param modelClass  Firebase will marshall the data at a location into
+     *                    an instance of a class that you provide
+     * @param modelLayout This is the layout used to represent a single list item.
+     *                    You will be responsible for populating an instance of the corresponding
+     *                    view with the data from an instance of modelClass.
+     * @param keyRef      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 in {@code keyRef}'s location represents
+     *                    a list item in the {@code ListView}.
      */
     public FirebaseIndexListAdapter(Activity activity,
                                     Class modelClass,
diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseIndexRecyclerAdapter.java b/database/src/main/java/com/firebase/ui/database/FirebaseIndexRecyclerAdapter.java
index b422b10dd..1277c5c0d 100644
--- a/database/src/main/java/com/firebase/ui/database/FirebaseIndexRecyclerAdapter.java
+++ b/database/src/main/java/com/firebase/ui/database/FirebaseIndexRecyclerAdapter.java
@@ -20,8 +20,8 @@
 import com.google.firebase.database.Query;
 
 /**
- * This class is a generic way of backing an RecyclerView with a Firebase location. It handles all
- * of the child events at the given Firebase location. It marshals received data into the given
+ * This class is a generic way of backing an RecyclerView with a Firebase location.
+ * It handles all of the child events at the given Firebase location. It marshals received data into the given
  * class type.
  * 

* To use this class in your app, subclass it passing in all required parameters and implement the @@ -47,8 +47,7 @@ * recycler.setLayoutManager(new LinearLayoutManager(this)); * * adapter = new FirebaseIndexRecyclerAdapter( - * ChatMessage.class, android.R.layout.two_line_list_item, ChatMessageViewHolder.class, - * keyRef, dataRef) { + * ChatMessage.class, android.R.layout.two_line_list_item, ChatMessageViewHolder.class, keyRef, dataRef) { * public void populateViewHolder(ChatMessageViewHolder chatMessageViewHolder, * ChatMessage chatMessage, * int position) { @@ -68,20 +67,18 @@ public abstract class FirebaseIndexRecyclerAdapter extends FirebaseRecyclerAdapter { /** - * @param modelClass Firebase will marshall the data at a location into an instance of a - * class that you provide - * @param modelLayout This is the layout used to represent a single item in the list. You - * will be responsible for populating an instance of the corresponding - * view with the data from an instance of modelClass. - * @param viewHolderClass The class that hold references to all sub-views in an instance - * modelLayout. - * @param keyRef 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 keyRef}'s location represents a list item in the {@code - * RecyclerView}. + * @param modelClass Firebase will marshall the data at a location into an instance + * of a class that you provide + * @param modelLayout This is the layout used to represent a single item in the list. + * You will be responsible for populating an + * instance of the corresponding view with the data from an instance of modelClass. + * @param viewHolderClass The class that hold references to all sub-views in an instance modelLayout. + * @param keyRef 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 keyRef}'s location represents + * a list item in the {@code RecyclerView}. */ public FirebaseIndexRecyclerAdapter(Class modelClass, @LayoutRes int modelLayout, diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseListAdapter.java b/database/src/main/java/com/firebase/ui/database/FirebaseListAdapter.java index a279b4c4d..dc66704bd 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseListAdapter.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseListAdapter.java @@ -27,12 +27,11 @@ import com.google.firebase.database.Query; /** - * This class is a generic way of backing an Android ListView with a Firebase location. It handles - * all of the child events at the given Firebase location. It marshals received data into the given - * class type. Extend this class and provide an implementation of {@code populateView}, which will - * be given an instance of your list item mLayout and an instance your class that holds your data. - * Simply populate the view however you like and this class will handle updating the list as the - * data changes. + * This class is a generic way of backing an Android ListView with a Firebase location. + * It handles all of the child events at the given Firebase location. It marshals received data into the given + * class type. Extend this class and provide an implementation of {@code populateView}, which will be given an + * instance of your list item mLayout and an instance your class that holds your data. + * Simply populate the view however you like and this class will handle updating the list as the data changes. *

*

  *     DatabaseReference ref = FirebaseDatabase.getInstance().getReference();
@@ -91,14 +90,13 @@ public void onCancelled(DatabaseError error) {
 
     /**
      * @param activity    The activity containing the ListView
-     * @param modelClass  Firebase will marshall the data at a location into an instance of a class
-     *                    that you provide
-     * @param modelLayout This is the layout used to represent a single list item. You will be
-     *                    responsible for populating an instance of the corresponding view with the
-     *                    data from an instance of modelClass.
-     * @param ref         The Firebase location to watch for data changes. Can also be a slice of a
-     *                    location, using some combination of {@code limit()}, {@code startAt()},
-     *                    and {@code endAt()}.
+     * @param modelClass  Firebase will marshall the data at a location into
+     *                    an instance of a class that you provide
+     * @param modelLayout This is the layout used to represent a single list item.
+     *                    You will be responsible for populating an instance of the corresponding
+     *                    view with the data from an instance of modelClass.
+     * @param ref         The Firebase location to watch for data changes. Can also be a slice of a location,
+     *                    using some combination of {@code limit()}, {@code startAt()}, and {@code endAt()}.
      */
     public FirebaseListAdapter(Activity activity,
                                Class modelClass,
@@ -122,8 +120,8 @@ public T getItem(int position) {
     }
 
     /**
-     * This method parses the DataSnapshot into the requested type. You can override it in
-     * subclasses to do custom parsing.
+     * This method parses the DataSnapshot into the requested type. You can override it in subclasses
+     * to do custom parsing.
      *
      * @param snapshot the DataSnapshot to extract the model from
      * @return the model extracted from the DataSnapshot
diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseRecyclerAdapter.java b/database/src/main/java/com/firebase/ui/database/FirebaseRecyclerAdapter.java
index c7980f7c8..8b6118b82 100644
--- a/database/src/main/java/com/firebase/ui/database/FirebaseRecyclerAdapter.java
+++ b/database/src/main/java/com/firebase/ui/database/FirebaseRecyclerAdapter.java
@@ -30,8 +30,8 @@
 import java.lang.reflect.InvocationTargetException;
 
 /**
- * This class is a generic way of backing an RecyclerView with a Firebase location. It handles all
- * of the child events at the given Firebase location. It marshals received data into the given
+ * This class is a generic way of backing an RecyclerView with a Firebase location.
+ * It handles all of the child events at the given Firebase location. It marshals received data into the given
  * class type.
  * 

* To use this class in your app, subclass it passing in all required parameters and implement the @@ -57,8 +57,7 @@ * recycler.setLayoutManager(new LinearLayoutManager(this)); * * adapter = new FirebaseRecyclerAdapter( - * ChatMessage.class, android.R.layout.two_line_list_item, ChatMessageViewHolder.class, - * ref) { + * ChatMessage.class, android.R.layout.two_line_list_item, ChatMessageViewHolder.class, ref) { * public void populateViewHolder(ChatMessageViewHolder chatMessageViewHolder, * ChatMessage chatMessage, * int position) { @@ -113,16 +112,14 @@ public void onCancelled(DatabaseError error) { } /** - * @param modelClass Firebase will marshall the data at a location into an instance of a - * class that you provide - * @param modelLayout This is the layout used to represent a single item in the list. You - * will be responsible for populating an instance of the corresponding + * @param modelClass Firebase will marshall the data at a location into + * an instance of a class that you provide + * @param modelLayout This is the layout used to represent a single item in the list. + * You will be responsible for populating an instance of the corresponding * view with the data from an instance of modelClass. - * @param viewHolderClass The class that hold references to all sub-views in an instance - * modelLayout. - * @param ref The Firebase location to watch for data changes. Can also be a slice - * of a location, using some combination of {@code limit()}, {@code - * startAt()}, and {@code endAt()}. + * @param viewHolderClass The class that hold references to all sub-views in an instance modelLayout. + * @param ref The Firebase location to watch for data changes. Can also be a slice of a location, + * using some combination of {@code limit()}, {@code startAt()}, and {@code endAt()}. */ public FirebaseRecyclerAdapter(Class modelClass, int modelLayout, @@ -145,8 +142,8 @@ public T getItem(int position) { } /** - * This method parses the DataSnapshot into the requested type. You can override it in - * subclasses to do custom parsing. + * This method parses the DataSnapshot into the requested type. You can override it in subclasses + * to do custom parsing. * * @param snapshot the DataSnapshot to extract the model from * @return the model extracted from the DataSnapshot From 91bf3cce8bb39d739c41c270cae24155a85c7b19 Mon Sep 17 00:00:00 2001 From: Alex Saveau Date: Wed, 1 Feb 2017 12:27:43 -0800 Subject: [PATCH 51/93] Make listener logic clearer Signed-off-by: Alex Saveau --- .../src/main/java/com/firebase/ui/database/FirebaseArray.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseArray.java b/database/src/main/java/com/firebase/ui/database/FirebaseArray.java index 7961555dd..99481ebed 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseArray.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseArray.java @@ -60,7 +60,7 @@ public ChangeEventListener addChangeEventListener(@NonNull ChangeEventListener l mListeners.add(listener); notifySubscriptionEventListeners(SubscriptionEventListener.EventType.ADDED); - if (mListeners.size() <= 1) { + if (mListeners.size() == 1) { // Only start listening when the first listener is added mQuery.addChildEventListener(this); mQuery.addValueEventListener(this); } From 23dba3232a9249efefb1e0d2f706e59ae7da37b4 Mon Sep 17 00:00:00 2001 From: Alex Saveau Date: Wed, 1 Feb 2017 12:29:56 -0800 Subject: [PATCH 52/93] Fix refactoring mistake Signed-off-by: Alex Saveau --- .../java/com/firebase/ui/database/FirebaseArrayOfObjects.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseArrayOfObjects.java b/database/src/main/java/com/firebase/ui/database/FirebaseArrayOfObjects.java index 5ca194a2f..22d7ed395 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseArrayOfObjects.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseArrayOfObjects.java @@ -25,7 +25,7 @@ public class FirebaseArrayOfObjects extends ImmutableList { * @param snapshots a list of {@link DataSnapshot}s to be converted to a model type * @param modelClass the model representation of a {@link DataSnapshot} */ - public FirebaseArrayOfObjects(List snapshots, Class modelClass) { + protected FirebaseArrayOfObjects(List snapshots, Class modelClass) { mSnapshots = snapshots; mEClass = modelClass; mParser = new SnapshotParser() { From 1d3ffd250eec703e92e8fb52e47541fe7adf8528 Mon Sep 17 00:00:00 2001 From: Alex Saveau Date: Wed, 1 Feb 2017 12:32:47 -0800 Subject: [PATCH 53/93] Cleanup FirebaseArrayOfObjectsOptimized.java variables Signed-off-by: Alex Saveau --- .../ui/database/FirebaseArrayOfObjects.java | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseArrayOfObjects.java b/database/src/main/java/com/firebase/ui/database/FirebaseArrayOfObjects.java index 22d7ed395..6b5e948f2 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseArrayOfObjects.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseArrayOfObjects.java @@ -1,7 +1,5 @@ package com.firebase.ui.database; -import android.util.Pair; - import com.google.firebase.database.DataSnapshot; import com.google.firebase.database.DatabaseError; @@ -176,7 +174,8 @@ public String toString() { protected static class FirebaseArrayOfObjectsOptimized extends FirebaseArrayOfObjects implements ChangeEventListener, SubscriptionEventListener { protected List mObjects = new ArrayList<>(); - protected Pair mIsListening$AddedListener = new Pair<>(true, false); + protected boolean mIsListening = true; + protected boolean mAddedListener = false; public FirebaseArrayOfObjectsOptimized(FirebaseArray snapshots, Class modelClass) { super(snapshots, modelClass); @@ -212,17 +211,20 @@ public void onSubscriptionRemoved() { FirebaseArray snapshots = (FirebaseArray) mSnapshots; if (!snapshots.isListening()) { snapshots.removeChangeEventListener(this); - mIsListening$AddedListener = new Pair<>(false, false); + mIsListening = false; + mAddedListener = false; } } @Override public void onSubscriptionAdded() { - if (mIsListening$AddedListener.second) { - mIsListening$AddedListener = new Pair<>(true, false); - } else if (!mIsListening$AddedListener.first) { + if (mAddedListener) { + mIsListening = true; + mAddedListener = false; + } else if (!mIsListening) { ((FirebaseArray) mSnapshots).addChangeEventListener(this); - mIsListening$AddedListener = new Pair<>(true, true); + mIsListening = true; + mAddedListener = true; } } From e29cfb882614d43b465342bab27d219b95ccad56 Mon Sep 17 00:00:00 2001 From: Alex Saveau Date: Wed, 1 Feb 2017 15:42:54 -0800 Subject: [PATCH 54/93] Bug fixes Signed-off-by: Alex Saveau --- .../com/firebase/ui/database/SubscriptionEventListener.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/database/src/main/java/com/firebase/ui/database/SubscriptionEventListener.java b/database/src/main/java/com/firebase/ui/database/SubscriptionEventListener.java index 8ca955d42..024298270 100644 --- a/database/src/main/java/com/firebase/ui/database/SubscriptionEventListener.java +++ b/database/src/main/java/com/firebase/ui/database/SubscriptionEventListener.java @@ -2,7 +2,11 @@ import android.support.annotation.IntDef; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + public interface SubscriptionEventListener { + @Retention(RetentionPolicy.SOURCE) @IntDef({EventType.ADDED, EventType.REMOVED}) @interface EventType { int ADDED = 0, REMOVED = 1; From 8496ad1189f29c9a239dadf704abfb10ef0901a4 Mon Sep 17 00:00:00 2001 From: Alex Saveau Date: Thu, 2 Feb 2017 10:36:16 -0800 Subject: [PATCH 55/93] Fix permuted arguments Signed-off-by: Alex Saveau --- .../java/com/firebase/ui/database/FirebaseArray.java | 2 +- .../adapter/FirebaseIndexRecyclerAdapter.java | 6 +++--- .../ui/database/adapter/FirebaseRecyclerAdapter.java | 12 ++++++------ 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseArray.java b/database/src/main/java/com/firebase/ui/database/FirebaseArray.java index 99481ebed..a6469d218 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseArray.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseArray.java @@ -124,7 +124,7 @@ private static void checkNotNull(Object o) { * @return true if {@link FirebaseArray} is listening for change events from the Firebase * database, false otherwise */ - public boolean isListening() { + public synchronized boolean isListening() { return !mListeners.isEmpty(); } diff --git a/database/src/main/java/com/firebase/ui/database/adapter/FirebaseIndexRecyclerAdapter.java b/database/src/main/java/com/firebase/ui/database/adapter/FirebaseIndexRecyclerAdapter.java index cdc4b58af..de8a4773d 100644 --- a/database/src/main/java/com/firebase/ui/database/adapter/FirebaseIndexRecyclerAdapter.java +++ b/database/src/main/java/com/firebase/ui/database/adapter/FirebaseIndexRecyclerAdapter.java @@ -16,13 +16,13 @@ public abstract class FirebaseIndexRecyclerAdapter modelClass, - Class viewHolderClass, @LayoutRes int modelLayout, + Class viewHolderClass, Query keyQuery, DatabaseReference dataRef) { - super(new FirebaseIndexArray(keyQuery, dataRef), modelClass, viewHolderClass, modelLayout); + super(new FirebaseIndexArray(keyQuery, dataRef), modelClass, modelLayout, viewHolderClass); } } diff --git a/database/src/main/java/com/firebase/ui/database/adapter/FirebaseRecyclerAdapter.java b/database/src/main/java/com/firebase/ui/database/adapter/FirebaseRecyclerAdapter.java index eecf3e82b..7148d846b 100644 --- a/database/src/main/java/com/firebase/ui/database/adapter/FirebaseRecyclerAdapter.java +++ b/database/src/main/java/com/firebase/ui/database/adapter/FirebaseRecyclerAdapter.java @@ -39,6 +39,7 @@ public abstract class FirebaseRecyclerAdapter modelClass, - Class viewHolderClass, - @LayoutRes int modelLayout) { + @LayoutRes int modelLayout, + Class viewHolderClass) { mSnapshots = snapshots; mModelClass = modelClass; mViewHolderClass = viewHolderClass; @@ -64,13 +64,13 @@ public FirebaseRecyclerAdapter(FirebaseArray snapshots, * @param query The Firebase location to watch for data changes. Can also be a slice of a * location, using some combination of {@code limit()}, {@code startAt()}, and * {@code endAt()}. - * @see #FirebaseRecyclerAdapter(FirebaseArray, Class, Class, int) + * @see #FirebaseRecyclerAdapter(FirebaseArray, Class, int, Class) */ public FirebaseRecyclerAdapter(Class modelClass, - Class viewHolderClass, @LayoutRes int modelLayout, + Class viewHolderClass, Query query) { - this(new FirebaseArray(query), modelClass, viewHolderClass, modelLayout); + this(new FirebaseArray(query), modelClass, modelLayout, viewHolderClass); } @Override From 788727f41ac36e03968a90c39ec92bb8c7ae3c75 Mon Sep 17 00:00:00 2001 From: Alex Saveau Date: Thu, 2 Feb 2017 12:37:51 -0800 Subject: [PATCH 56/93] Lock mListeners Signed-off-by: Alex Saveau --- .../firebase/ui/database/FirebaseArray.java | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseArray.java b/database/src/main/java/com/firebase/ui/database/FirebaseArray.java index a6469d218..135fdce60 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseArray.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseArray.java @@ -34,7 +34,7 @@ public class FirebaseArray extends ImmutableList implements ChildEventListener, ValueEventListener { private Query mQuery; private boolean mNotifyListeners = true; - private List mListeners = new ArrayList<>(); + private final List mListeners = new ArrayList<>(); private List mSubscribers = new ArrayList<>(); private List mSnapshots = new ArrayList<>(); @@ -58,11 +58,13 @@ public FirebaseArray(Query query) { public ChangeEventListener addChangeEventListener(@NonNull ChangeEventListener listener) { checkNotNull(listener); - mListeners.add(listener); - notifySubscriptionEventListeners(SubscriptionEventListener.EventType.ADDED); - if (mListeners.size() == 1) { // Only start listening when the first listener is added - mQuery.addChildEventListener(this); - mQuery.addValueEventListener(this); + synchronized (mListeners) { + mListeners.add(listener); + notifySubscriptionEventListeners(SubscriptionEventListener.EventType.ADDED); + if (mListeners.size() == 1) { // Only start listening when the first listener is added + mQuery.addChildEventListener(this); + mQuery.addValueEventListener(this); + } } return listener; @@ -75,12 +77,14 @@ public ChangeEventListener addChangeEventListener(@NonNull ChangeEventListener l * @param listener the listener to remove */ public void removeChangeEventListener(@NonNull ChangeEventListener listener) { - mListeners.remove(listener); - notifySubscriptionEventListeners(SubscriptionEventListener.EventType.REMOVED); - if (mListeners.isEmpty()) { - mQuery.removeEventListener((ValueEventListener) this); - mQuery.removeEventListener((ChildEventListener) this); - mSnapshots.clear(); + synchronized (mListeners) { + mListeners.remove(listener); + notifySubscriptionEventListeners(SubscriptionEventListener.EventType.REMOVED); + if (mListeners.isEmpty()) { + mQuery.removeEventListener((ValueEventListener) this); + mQuery.removeEventListener((ChildEventListener) this); + mSnapshots.clear(); + } } } From 3cfe46f1f56c582fe057c1ce8cab051129c3ef89 Mon Sep 17 00:00:00 2001 From: Alex Saveau Date: Sun, 5 Feb 2017 00:37:53 -0800 Subject: [PATCH 57/93] Add back changes that got deleted because of merging Signed-off-by: Alex Saveau --- .../uidemo/database/ChatActivity.java | 36 ++-- database/README.md | 183 +++++++++--------- .../ui/database/ChangeEventListener.java | 18 +- .../ui/database/FirebaseIndexListAdapter.java | 32 +-- .../FirebaseIndexRecyclerAdapter.java | 33 ++-- .../ui/database/FirebaseListAdapter.java | 30 +-- .../ui/database/FirebaseRecyclerAdapter.java | 27 +-- 7 files changed, 191 insertions(+), 168 deletions(-) diff --git a/app/src/main/java/com/firebase/uidemo/database/ChatActivity.java b/app/src/main/java/com/firebase/uidemo/database/ChatActivity.java index 96426eb71..5b8c63603 100644 --- a/app/src/main/java/com/firebase/uidemo/database/ChatActivity.java +++ b/app/src/main/java/com/firebase/uidemo/database/ChatActivity.java @@ -26,7 +26,8 @@ import android.widget.TextView; import android.widget.Toast; -import com.firebase.ui.database.FirebaseRecyclerAdapter; +import com.firebase.ui.database.adapter.FirebaseIndexRecyclerAdapter; +import com.firebase.ui.database.adapter.FirebaseRecyclerAdapter; import com.firebase.uidemo.R; import com.firebase.uidemo.util.SignInResultNotifier; import com.google.android.gms.tasks.OnSuccessListener; @@ -36,13 +37,12 @@ import com.google.firebase.database.DatabaseError; import com.google.firebase.database.DatabaseReference; import com.google.firebase.database.FirebaseDatabase; -import com.google.firebase.database.Query; public class ChatActivity extends AppCompatActivity implements FirebaseAuth.AuthStateListener { private static final String TAG = "RecyclerViewDemo"; private FirebaseAuth mAuth; - private DatabaseReference mRef; + private DatabaseReference mChatIndicesRef; private DatabaseReference mChatRef; private Button mSendButton; private EditText mMessageEdit; @@ -64,8 +64,9 @@ protected void onCreate(Bundle savedInstanceState) { mMessageEdit = (EditText) findViewById(R.id.messageEdit); mEmptyListMessage = (TextView) findViewById(R.id.emptyTextView); - mRef = FirebaseDatabase.getInstance().getReference(); - mChatRef = mRef.child("chats"); + DatabaseReference ref = FirebaseDatabase.getInstance().getReference(); + mChatIndicesRef = ref.child("chatIndices"); + mChatRef = ref.child("chats"); mSendButton.setOnClickListener(new View.OnClickListener() { @Override @@ -74,7 +75,9 @@ public void onClick(View v) { String name = "User " + uid.substring(0, 6); Chat chat = new Chat(name, mMessageEdit.getText().toString(), uid); - mChatRef.push().setValue(chat, new DatabaseReference.CompletionListener() { + DatabaseReference chatRef = mChatRef.push(); + mChatIndicesRef.child(chatRef.getKey()).setValue(true); + chatRef.setValue(chat, new DatabaseReference.CompletionListener() { @Override public void onComplete(DatabaseError error, DatabaseReference reference) { if (error != null) { @@ -131,9 +134,12 @@ public void onAuthStateChanged(@NonNull FirebaseAuth firebaseAuth) { } private void attachRecyclerViewAdapter() { - Query lastFifty = mChatRef.limitToLast(50); - mAdapter = new FirebaseRecyclerAdapter( - Chat.class, R.layout.message, ChatHolder.class, lastFifty) { + mAdapter = new FirebaseIndexRecyclerAdapter( + Chat.class, + R.layout.message, + ChatHolder.class, + mChatIndicesRef.limitToLast(50), + mChatRef) { @Override public void populateViewHolder(ChatHolder holder, Chat chat, int position) { holder.setName(chat.getName()); @@ -148,9 +154,17 @@ public void populateViewHolder(ChatHolder holder, Chat chat, int position) { } @Override - protected void onDataChanged() { + public void onChildChanged(EventType type, int index, int oldIndex) { + super.onChildChanged(type, index, oldIndex); + + // TODO temporary fix for https://github.com/firebase/FirebaseUI-Android/issues/546 + onDataChanged(); + } + + @Override + public void onDataChanged() { // If there are no chat messages, show a view that invites the user to add a message. - mEmptyListMessage.setVisibility(mAdapter.getItemCount() == 0 ? View.VISIBLE : View.GONE); + mEmptyListMessage.setVisibility(getItemCount() == 0 ? View.VISIBLE : View.GONE); } }; diff --git a/database/README.md b/database/README.md index 0b572b71b..7898894ec 100644 --- a/database/README.md +++ b/database/README.md @@ -1,15 +1,15 @@ -# firebase-ui-database +# FirebaseUI Database -## Using FirebaseUI to Populate a ListView +## Using FirebaseUI to Populate a RecyclerView To use the FirebaseUI to display Firebase data, we need a few things: 1. A Java class that represents our database objects - 1. A custom list adapter to map from a collection from Firebase to Android + 1. A custom recycler view adapter to map from a collection from Firebase to Android ### Creating a model class -In your app, create a class that represents the data from Firebase that you want to show in the ListView. +In your app, create a class that represents the data from Firebase that you want to show in the RecyclerView. So say we have these chat messages in our Firebase database: @@ -96,11 +96,11 @@ we get the `Chat` objects from the `DataSnapshot` with `getValue(Chat.class)`. T then read the properties that it got from the database and map them to the fields of our `Chat` class. But when we build our app using FirebaseUI, we often won't need to register our own EventListener. The -`FirebaseListAdapter` takes care of that for us. +`FirebaseRecyclerAdapter` takes care of that for us. -### Find the ListView +### Find the RecyclerView -We'll assume you've already added a `ListView` to your layout and have looked it up in the `onCreate` method of your activity: +We'll assume you've already added a `RecyclerVview` to your layout and have looked it up in the `onCreate` method of your activity: ```java @Override @@ -108,10 +108,40 @@ protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); - ListView messagesView = (ListView) findViewById(R.id.messages_list); + RecyclerView messages = (RecyclerView) findViewById(R.id.messages); + messages.setLayoutManager(new LinearLayoutManager(this)); } ``` +### Create a custom ViewHolder + +A ViewHolder is similar to container of a ViewGroup that allows simple lookup of the sub-views of the group. +If we use the same layout as before (`android.R.layout.two_line_list_item`), there are two `TextView`s in there. +We can wrap that in a ViewHolder with: + +```java +public static class ChatHolder extends RecyclerView.ViewHolder { + private final TextView mNameField; + private final TextView mTextField; + + public ChatHolder(View itemView) { + super(itemView); + mNameField = (TextView) itemView.findViewById(android.R.id.text1); + mTextField = (TextView) itemView.findViewById(android.R.id.text2); + } + + public void setName(String name) { + mNameField.setText(name); + } + + public void setText(String text) { + mTextField.setText(text); + } +} +``` + +There's nothing magical going on here; we're just mapping numeric IDs and casts into a nice, type-safe contract. + ### Connect to Firebase First we'll set up a reference to the database of chat messages: @@ -122,15 +152,17 @@ protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); - ListView messagesView = (ListView) findViewById(R.id.messages_list); + RecyclerView messages = (RecyclerView) findViewById(R.id.messages); + messages.setLayoutManager(new LinearLayoutManager(this)); DatabaseReference ref = FirebaseDatabase.getInstance().getReference(); } ``` -### Create custom FirebaseListAdapter subclass +### Create custom FirebaseRecyclerAdapter subclass -Next, we need to create a subclass of the `FirebaseListAdapter` with the correct parameters and implement its `populateView` method: +Next, we need to create a subclass of the `FirebaseRecyclerAdapter` with the correct parameters +and implement its `populateViewHolder` method: ```java @Override @@ -138,31 +170,36 @@ protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); - ListView messagesView = (ListView) findViewById(R.id.messages_list); + RecyclerView messages = (RecyclerView) findViewById(R.id.messages); + messages.setLayoutManager(new LinearLayoutManager(this)); DatabaseReference ref = FirebaseDatabase.getInstance().getReference(); - - mAdapter = new FirebaseListAdapter(this, Chat.class, android.R.layout.two_line_list_item, ref) { - @Override - protected void populateView(View view, Chat chatMessage, int position) { - ((TextView)view.findViewById(android.R.id.text1)).setText(chatMessage.getName()); - ((TextView)view.findViewById(android.R.id.text2)).setText(chatMessage.getText()); + mAdapter = new FirebaseRecyclerAdapter( + Chat.class, + ChatHolder.class, + R.layout.message, + ref) { + @Override + public void populateViewHolder(ChatHolder holder, Chat chat, int position) { + holder.setName(chat.getName()); + holder.setText(chat.getMessage()); } }; - messagesView.setAdapter(mAdapter); + + messages.setAdapter(mAdapter); } ``` -In this last snippet we create a subclass of `FirebaseListAdapter`. +In this last snippet we create a subclass of `FirebaseRecyclerAdapter`. We tell is that it is of type ``, so that it is a type-safe collection. We also tell it to use `Chat.class` when reading messages from the database. Next we say that each message will be displayed in a `android.R.layout.two_line_list_item`, which is a built-in layout in Android that has two `TextView` elements under each other. Then we say that the adapter belongs to `this` activity and that it needs to monitor the data location in `ref`. -We also have to override the `populateView()` method, from the `FirebaseListAdapter`. The -`FirebaseListAdapter` will call our `populateView` method for each `Chat` it finds in the database. +We also have to override the `populateViewHolder` method, from the `FirebaseRecyclerAdapter`. The +`FirebaseRecyclerAdapter` will call our `populateViewHolder` method for each `Chat` it finds in the database. It passes us the `Chat` and a `View`, which is an instance of the `android.R.layout.two_line_list_item` we specified in the constructor. So what we do in our subclass is map the fields from `chatMessage` to the correct `TextView` controls from the `view`. The code is a bit verbose, but hey... that's Java and Android for you. @@ -170,7 +207,7 @@ correct `TextView` controls from the `view`. The code is a bit verbose, but hey. ### Clean up When the Activity is Destroyed Finally, we need to clean up after ourselves. When the activity is destroyed, we need to call `cleanup()` -on the `ListAdapter` so that it can stop listening for changes in the Firebase database. +on the adapter so that it can stop listening for changes in the Firebase database. ```java @Override @@ -191,25 +228,31 @@ protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); - ListView messagesView = (ListView) findViewById(R.id.messages_list); + RecyclerView messages = (RecyclerView) findViewById(R.id.messages); + messages.setLayoutManager(new LinearLayoutManager(this)); DatabaseReference ref = FirebaseDatabase.getInstance().getReference(); - mAdapter = new FirebaseListAdapter(this, Chat.class, android.R.layout.two_line_list_item, ref) { + mAdapter = new FirebaseRecyclerAdapter( + Chat.class, + ChatHolder.class, + R.layout.message, + ref) { @Override - protected void populateView(View view, Chat chatMessage, int position) { - ((TextView)view.findViewById(android.R.id.text1)).setText(chatMessage.getName()); - ((TextView)view.findViewById(android.R.id.text2)).setText(chatMessage.getText()); + public void populateViewHolder(ChatHolder holder, Chat chat, int position) { + holder.setName(chat.getName()); + holder.setText(chat.getMessage()); } }; - messagesView.setAdapter(mAdapter); - final EditText mMessage = (EditText) findViewById(R.id.message_text); + messages.setAdapter(mAdapter); + + final EditText message = (EditText) findViewById(R.id.message_text); findViewById(R.id.send_button).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { ref.push().setValue(new Chat("puf", "1234", mMessage.getText().toString())); - mMessage.setText(""); + message.setText(""); } }); } @@ -223,82 +266,38 @@ protected void onDestroy() { You're done! You now have a minimal, yet fully functional, chat app in about 30 lines of code. Not bad, right? -## Using FirebaseUI to Populate a RecyclerView - -RecyclerView is the new preferred way to handle potentially long lists of items. Since Firebase collections -can contain many items, there is an `FirebaseRecyclerAdapter` too. Here's how you use it: - -1. Create a custom ViewHolder class -2. Create a custom subclass FirebaseRecyclerAdapter - -The rest of the steps is the same as for the `FirebaseListAdapter` above, so be sure to read that first. - -### Create a custom ViewHolder - -A ViewHolder is similar to container of a ViewGroup that allows simple lookup of the sub-views of the group. -If we use the same layout as before (`android.R.layout.two_line_list_item`), there are two `TextView`s in there. -We can wrap that in a ViewHolder with: +## Using FirebaseUI to populate a ListView +ListView is the older, yet simpler way to handle lists of items. Using it is analogous to +using a `FirebaseRecyclerAdapter`, but with `FirebaseListAdapter` instead and no `ViewHolder`: ```java -public static class ChatHolder extends RecyclerView.ViewHolder { - private final TextView mNameField; - private final TextView mTextField; +ListView messagesView = (ListView) findViewById(R.id.messages_list); - public ChatHolder(View itemView) { - super(itemView); - mNameField = (TextView) itemView.findViewById(android.R.id.text1); - mTextField = (TextView) itemView.findViewById(android.R.id.text2); - } - - public void setName(String name) { - mNameField.setText(name); - } - - public void setText(String text) { - mTextField.setText(text); - } -} -``` - -There's nothing magical going on here; we're just mapping numeric IDs and casts into a nice, type-safe contract. - -### Create a custom FirebaseRecyclerAdapter - -Just like we did for `FirebaseListAdapter`, we'll create an anonymous subclass for our Chats, but this time we'll use `FirebaseRecyclerAdapter`: - -```java -RecyclerView recycler = (RecyclerView) findViewById(R.id.messages_recycler); -recycler.setHasFixedSize(true); -recycler.setLayoutManager(new LinearLayoutManager(this)); +DatabaseReference ref = FirebaseDatabase.getInstance().getReference(); -mAdapter = new FirebaseRecyclerAdapter(Chat.class, android.R.layout.two_line_list_item, ChatHolder.class, mRef) { +mAdapter = new FirebaseListAdapter(this, Chat.class, android.R.layout.two_line_list_item, ref) { @Override - public void populateViewHolder(ChatHolder chatMessageViewHolder, Chat chatMessage, int position) { - chatMessageViewHolder.setName(chatMessage.getName()); - chatMessageViewHolder.setText(chatMessage.getText()); + protected void populateView(View view, Chat chatMessage, int position) { + ((TextView) view.findViewById(android.R.id.text1)).setText(chatMessage.getName()); + ((TextView) view.findViewById(android.R.id.text2)).setText(chatMessage.getText()); + } }; -recycler.setAdapter(mAdapter); +messagesView.setAdapter(mAdapter); ``` -Like before, we get a custom RecyclerView populated with data from Firebase by setting the properties to the correct fields. - ## Using FirebaseUI with indexed data If your data is [properly indexed](https://firebase.google.com/docs/database/android/structure-data#best_practices_for_data_structure), change your adapter initalization like so: For a `RecyclerView`, use `FirebaseIndexRecyclerAdapter` instead of `FirebaseRecyclerAdapter`: ```java -new FirebaseIndexRecyclerAdapter(Chat.class, - android.R.layout.two_line_list_item, - ChatHolder.class, - keyRef, // The Firebase location containing the list of keys to be found in dataRef. - dataRef) //The Firebase location to watch for data changes. Each key key found at keyRef's location represents a list item in the RecyclerView. -``` - -And for a `ListView`, use `FirebaseIndexListAdapter`; -```java -new FirebaseIndexListAdapter(this, Chat.class, android.R.layout.two_line_list_item, keyRef, dataRef) +new FirebaseIndexRecyclerAdapter( + Chat.class, + android.R.layout.two_line_list_item, + ChatHolder.class, + keyRef, // The Firebase location containing the list of keys to be found in dataRef. + dataRef) //The Firebase location to watch for data changes. Each key key found at keyRef's location represents a list item in the RecyclerView. ``` `keyRef` is the location of your keys, and `dataRef` is the location of your data. diff --git a/database/src/main/java/com/firebase/ui/database/ChangeEventListener.java b/database/src/main/java/com/firebase/ui/database/ChangeEventListener.java index a8028dc47..7703ba11b 100644 --- a/database/src/main/java/com/firebase/ui/database/ChangeEventListener.java +++ b/database/src/main/java/com/firebase/ui/database/ChangeEventListener.java @@ -40,18 +40,20 @@ enum EventType { * * @param type The type of event received * @param index The index at which the change occurred - * @param oldIndex If {@code type} is a moved event, the previous index of the moved child. - * For any other event, {@code oldIndex} will be -1. + * @param oldIndex If {@code type} is a moved event, the previous index of the moved child. For + * any other event, {@code oldIndex} will be -1. */ void onChildChanged(EventType type, int index, int oldIndex); - /** This method will be triggered each time updates from the database have been completely processed. - * So the first time this method is called, the initial data has been loaded - including the case - * when no data at all is available. Each next time the method is called, a complete update (potentially - * consisting of updates to multiple child items) has been completed. + /** + * This method will be triggered each time updates from the database have been completely + * processed. So the first time this method is called, the initial data has been loaded - + * including the case when no data at all is available. Each next time the method is called, a + * complete update (potentially consisting of updates to multiple child items) has been + * completed. *

- * You would typically override this method to hide a loading indicator (after the initial load) or - * to complete a batch update to a UI element. + * You would typically override this method to hide a loading indicator (after the initial load) + * or to complete a batch update to a UI element. */ void onDataChanged(); diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseIndexListAdapter.java b/database/src/main/java/com/firebase/ui/database/FirebaseIndexListAdapter.java index dc1529347..22c1cae58 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseIndexListAdapter.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseIndexListAdapter.java @@ -6,11 +6,12 @@ import com.google.firebase.database.Query; /** - * This class is a generic way of backing an Android ListView with a Firebase location. - * It handles all of the child events at the given Firebase location. It marshals received data into the given - * class type. Extend this class and provide an implementation of {@code populateView}, which will be given an - * instance of your list item mLayout and an instance your class that holds your data. - * Simply populate the view however you like and this class will handle updating the list as the data changes. + * This class is a generic way of backing an Android ListView with a Firebase location. It handles + * all of the child events at the given Firebase location. It marshals received data into the given + * class type. Extend this class and provide an implementation of {@code populateView}, which will + * be given an instance of your list item mLayout and an instance your class that holds your data. + * Simply populate the view however you like and this class will handle updating the list as the + * data changes. *

* If your data is not indexed: *

@@ -39,17 +40,16 @@
 public abstract class FirebaseIndexListAdapter extends FirebaseListAdapter {
     /**
      * @param activity    The activity containing the ListView
-     * @param modelClass  Firebase will marshall the data at a location into
-     *                    an instance of a class that you provide
-     * @param modelLayout This is the layout used to represent a single list item.
-     *                    You will be responsible for populating an instance of the corresponding
-     *                    view with the data from an instance of modelClass.
-     * @param keyRef      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 in {@code keyRef}'s location represents
-     *                    a list item in the {@code ListView}.
+     * @param modelClass  Firebase will marshall the data at a location into an instance of a class
+     *                    that you provide
+     * @param modelLayout This is the layout used to represent a single list item. You will be
+     *                    responsible for populating an instance of the corresponding view with the
+     *                    data from an instance of modelClass.
+     * @param keyRef      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 in
+     *                    {@code keyRef}'s location represents a list item in the {@code ListView}.
      */
     public FirebaseIndexListAdapter(Activity activity,
                                     Class modelClass,
diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseIndexRecyclerAdapter.java b/database/src/main/java/com/firebase/ui/database/FirebaseIndexRecyclerAdapter.java
index 1277c5c0d..b422b10dd 100644
--- a/database/src/main/java/com/firebase/ui/database/FirebaseIndexRecyclerAdapter.java
+++ b/database/src/main/java/com/firebase/ui/database/FirebaseIndexRecyclerAdapter.java
@@ -20,8 +20,8 @@
 import com.google.firebase.database.Query;
 
 /**
- * This class is a generic way of backing an RecyclerView with a Firebase location.
- * It handles all of the child events at the given Firebase location. It marshals received data into the given
+ * This class is a generic way of backing an RecyclerView with a Firebase location. It handles all
+ * of the child events at the given Firebase location. It marshals received data into the given
  * class type.
  * 

* To use this class in your app, subclass it passing in all required parameters and implement the @@ -47,7 +47,8 @@ * recycler.setLayoutManager(new LinearLayoutManager(this)); * * adapter = new FirebaseIndexRecyclerAdapter( - * ChatMessage.class, android.R.layout.two_line_list_item, ChatMessageViewHolder.class, keyRef, dataRef) { + * ChatMessage.class, android.R.layout.two_line_list_item, ChatMessageViewHolder.class, + * keyRef, dataRef) { * public void populateViewHolder(ChatMessageViewHolder chatMessageViewHolder, * ChatMessage chatMessage, * int position) { @@ -67,18 +68,20 @@ public abstract class FirebaseIndexRecyclerAdapter extends FirebaseRecyclerAdapter { /** - * @param modelClass Firebase will marshall the data at a location into an instance - * of a class that you provide - * @param modelLayout This is the layout used to represent a single item in the list. - * You will be responsible for populating an - * instance of the corresponding view with the data from an instance of modelClass. - * @param viewHolderClass The class that hold references to all sub-views in an instance modelLayout. - * @param keyRef 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 keyRef}'s location represents - * a list item in the {@code RecyclerView}. + * @param modelClass Firebase will marshall the data at a location into an instance of a + * class that you provide + * @param modelLayout This is the layout used to represent a single item in the list. You + * will be responsible for populating an instance of the corresponding + * view with the data from an instance of modelClass. + * @param viewHolderClass The class that hold references to all sub-views in an instance + * modelLayout. + * @param keyRef 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 keyRef}'s location represents a list item in the {@code + * RecyclerView}. */ public FirebaseIndexRecyclerAdapter(Class modelClass, @LayoutRes int modelLayout, diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseListAdapter.java b/database/src/main/java/com/firebase/ui/database/FirebaseListAdapter.java index dc66704bd..a279b4c4d 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseListAdapter.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseListAdapter.java @@ -27,11 +27,12 @@ import com.google.firebase.database.Query; /** - * This class is a generic way of backing an Android ListView with a Firebase location. - * It handles all of the child events at the given Firebase location. It marshals received data into the given - * class type. Extend this class and provide an implementation of {@code populateView}, which will be given an - * instance of your list item mLayout and an instance your class that holds your data. - * Simply populate the view however you like and this class will handle updating the list as the data changes. + * This class is a generic way of backing an Android ListView with a Firebase location. It handles + * all of the child events at the given Firebase location. It marshals received data into the given + * class type. Extend this class and provide an implementation of {@code populateView}, which will + * be given an instance of your list item mLayout and an instance your class that holds your data. + * Simply populate the view however you like and this class will handle updating the list as the + * data changes. *

*

  *     DatabaseReference ref = FirebaseDatabase.getInstance().getReference();
@@ -90,13 +91,14 @@ public void onCancelled(DatabaseError error) {
 
     /**
      * @param activity    The activity containing the ListView
-     * @param modelClass  Firebase will marshall the data at a location into
-     *                    an instance of a class that you provide
-     * @param modelLayout This is the layout used to represent a single list item.
-     *                    You will be responsible for populating an instance of the corresponding
-     *                    view with the data from an instance of modelClass.
-     * @param ref         The Firebase location to watch for data changes. Can also be a slice of a location,
-     *                    using some combination of {@code limit()}, {@code startAt()}, and {@code endAt()}.
+     * @param modelClass  Firebase will marshall the data at a location into an instance of a class
+     *                    that you provide
+     * @param modelLayout This is the layout used to represent a single list item. You will be
+     *                    responsible for populating an instance of the corresponding view with the
+     *                    data from an instance of modelClass.
+     * @param ref         The Firebase location to watch for data changes. Can also be a slice of a
+     *                    location, using some combination of {@code limit()}, {@code startAt()},
+     *                    and {@code endAt()}.
      */
     public FirebaseListAdapter(Activity activity,
                                Class modelClass,
@@ -120,8 +122,8 @@ public T getItem(int position) {
     }
 
     /**
-     * This method parses the DataSnapshot into the requested type. You can override it in subclasses
-     * to do custom parsing.
+     * This method parses the DataSnapshot into the requested type. You can override it in
+     * subclasses to do custom parsing.
      *
      * @param snapshot the DataSnapshot to extract the model from
      * @return the model extracted from the DataSnapshot
diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseRecyclerAdapter.java b/database/src/main/java/com/firebase/ui/database/FirebaseRecyclerAdapter.java
index 8b6118b82..c7980f7c8 100644
--- a/database/src/main/java/com/firebase/ui/database/FirebaseRecyclerAdapter.java
+++ b/database/src/main/java/com/firebase/ui/database/FirebaseRecyclerAdapter.java
@@ -30,8 +30,8 @@
 import java.lang.reflect.InvocationTargetException;
 
 /**
- * This class is a generic way of backing an RecyclerView with a Firebase location.
- * It handles all of the child events at the given Firebase location. It marshals received data into the given
+ * This class is a generic way of backing an RecyclerView with a Firebase location. It handles all
+ * of the child events at the given Firebase location. It marshals received data into the given
  * class type.
  * 

* To use this class in your app, subclass it passing in all required parameters and implement the @@ -57,7 +57,8 @@ * recycler.setLayoutManager(new LinearLayoutManager(this)); * * adapter = new FirebaseRecyclerAdapter( - * ChatMessage.class, android.R.layout.two_line_list_item, ChatMessageViewHolder.class, ref) { + * ChatMessage.class, android.R.layout.two_line_list_item, ChatMessageViewHolder.class, + * ref) { * public void populateViewHolder(ChatMessageViewHolder chatMessageViewHolder, * ChatMessage chatMessage, * int position) { @@ -112,14 +113,16 @@ public void onCancelled(DatabaseError error) { } /** - * @param modelClass Firebase will marshall the data at a location into - * an instance of a class that you provide - * @param modelLayout This is the layout used to represent a single item in the list. - * You will be responsible for populating an instance of the corresponding + * @param modelClass Firebase will marshall the data at a location into an instance of a + * class that you provide + * @param modelLayout This is the layout used to represent a single item in the list. You + * will be responsible for populating an instance of the corresponding * view with the data from an instance of modelClass. - * @param viewHolderClass The class that hold references to all sub-views in an instance modelLayout. - * @param ref The Firebase location to watch for data changes. Can also be a slice of a location, - * using some combination of {@code limit()}, {@code startAt()}, and {@code endAt()}. + * @param viewHolderClass The class that hold references to all sub-views in an instance + * modelLayout. + * @param ref The Firebase location to watch for data changes. Can also be a slice + * of a location, using some combination of {@code limit()}, {@code + * startAt()}, and {@code endAt()}. */ public FirebaseRecyclerAdapter(Class modelClass, int modelLayout, @@ -142,8 +145,8 @@ public T getItem(int position) { } /** - * This method parses the DataSnapshot into the requested type. You can override it in subclasses - * to do custom parsing. + * This method parses the DataSnapshot into the requested type. You can override it in + * subclasses to do custom parsing. * * @param snapshot the DataSnapshot to extract the model from * @return the model extracted from the DataSnapshot From 8dbdfa8a1c570f3f2355df78d20fa9bcebe2c7eb Mon Sep 17 00:00:00 2001 From: Alex Saveau Date: Sun, 5 Feb 2017 00:50:40 -0800 Subject: [PATCH 58/93] Fix proguard saying SubscriptionEventListener.EventType doesn't exist Signed-off-by: Alex Saveau --- .../com/firebase/ui/database/SubscriptionEventListener.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/database/src/main/java/com/firebase/ui/database/SubscriptionEventListener.java b/database/src/main/java/com/firebase/ui/database/SubscriptionEventListener.java index 024298270..efc7a7182 100644 --- a/database/src/main/java/com/firebase/ui/database/SubscriptionEventListener.java +++ b/database/src/main/java/com/firebase/ui/database/SubscriptionEventListener.java @@ -1,11 +1,13 @@ package com.firebase.ui.database; import android.support.annotation.IntDef; +import android.support.annotation.Keep; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; public interface SubscriptionEventListener { + @Keep @Retention(RetentionPolicy.SOURCE) @IntDef({EventType.ADDED, EventType.REMOVED}) @interface EventType { From 28163141299ce12fb09ce389ca4726db06d6a3fa Mon Sep 17 00:00:00 2001 From: Alex Saveau Date: Sun, 5 Feb 2017 01:02:36 -0800 Subject: [PATCH 59/93] Fix proguard saying SubscriptionEventListener.EventType doesn't exist Signed-off-by: Alex Saveau --- database/proguard-rules.pro | 18 +----------------- .../ui/database/SubscriptionEventListener.java | 2 -- 2 files changed, 1 insertion(+), 19 deletions(-) diff --git a/database/proguard-rules.pro b/database/proguard-rules.pro index a5afa4992..28b88591f 100644 --- a/database/proguard-rules.pro +++ b/database/proguard-rules.pro @@ -1,17 +1 @@ -# Add project specific ProGuard rules here. -# By default, the flags in this file are appended to flags specified -# in /usr/local/google/home/samstern/android-sdk-linux/tools/proguard/proguard-android.txt -# You can edit the include path and order by changing the proguardFiles -# directive in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# Add any project specific keep options here: - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} +-dontwarn com.firebase.ui.database.SubscriptionEventListener.EventType diff --git a/database/src/main/java/com/firebase/ui/database/SubscriptionEventListener.java b/database/src/main/java/com/firebase/ui/database/SubscriptionEventListener.java index efc7a7182..024298270 100644 --- a/database/src/main/java/com/firebase/ui/database/SubscriptionEventListener.java +++ b/database/src/main/java/com/firebase/ui/database/SubscriptionEventListener.java @@ -1,13 +1,11 @@ package com.firebase.ui.database; import android.support.annotation.IntDef; -import android.support.annotation.Keep; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; public interface SubscriptionEventListener { - @Keep @Retention(RetentionPolicy.SOURCE) @IntDef({EventType.ADDED, EventType.REMOVED}) @interface EventType { From 2bbb8c974bd129eec271337f645f9ae87fdffb8c Mon Sep 17 00:00:00 2001 From: Alex Saveau Date: Sun, 5 Feb 2017 01:41:54 -0800 Subject: [PATCH 60/93] Fix proguard saying SubscriptionEventListener.EventType doesn't exist Signed-off-by: Alex Saveau --- app/proguard-rules.pro | 2 ++ database/proguard-rules.pro | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index f59df819a..202021bdb 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -21,6 +21,8 @@ -keepattributes Signature -keepattributes *Annotation* +-dontwarn com.firebase.ui.database.SubscriptionEventListener$EventType + # See: # storage/README.md -assumenosideeffects class android.util.Log { diff --git a/database/proguard-rules.pro b/database/proguard-rules.pro index 28b88591f..e69de29bb 100644 --- a/database/proguard-rules.pro +++ b/database/proguard-rules.pro @@ -1 +0,0 @@ --dontwarn com.firebase.ui.database.SubscriptionEventListener.EventType From 31c56276a43bc202354ec9a5d7bb1b7a68256f85 Mon Sep 17 00:00:00 2001 From: Alex Saveau Date: Sun, 5 Feb 2017 02:49:20 -0800 Subject: [PATCH 61/93] Fix proguard Signed-off-by: Alex Saveau --- library/consumer-proguard-rules.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/library/consumer-proguard-rules.txt b/library/consumer-proguard-rules.txt index bcbb48d08..d26a6dab9 100644 --- a/library/consumer-proguard-rules.txt +++ b/library/consumer-proguard-rules.txt @@ -6,3 +6,6 @@ -dontwarn retrofit.** -dontwarn retrofit2.** -dontwarn okio.** + +# Don't warn about custom @IntDef annotations +-dontwarn com.firebase.ui.database.SubscriptionEventListener$EventType From 49cafe8f74e776e756688c192bb2d3943483ec88 Mon Sep 17 00:00:00 2001 From: Alex Saveau Date: Sun, 5 Feb 2017 03:00:37 -0800 Subject: [PATCH 62/93] Didn't work, just use an enum instead Signed-off-by: Alex Saveau --- app/proguard-rules.pro | 2 -- .../ui/database/SubscriptionEventListener.java | 12 +++--------- library/consumer-proguard-rules.txt | 3 --- 3 files changed, 3 insertions(+), 14 deletions(-) diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 202021bdb..f59df819a 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -21,8 +21,6 @@ -keepattributes Signature -keepattributes *Annotation* --dontwarn com.firebase.ui.database.SubscriptionEventListener$EventType - # See: # storage/README.md -assumenosideeffects class android.util.Log { diff --git a/database/src/main/java/com/firebase/ui/database/SubscriptionEventListener.java b/database/src/main/java/com/firebase/ui/database/SubscriptionEventListener.java index 024298270..0b9035ea9 100644 --- a/database/src/main/java/com/firebase/ui/database/SubscriptionEventListener.java +++ b/database/src/main/java/com/firebase/ui/database/SubscriptionEventListener.java @@ -1,15 +1,9 @@ package com.firebase.ui.database; -import android.support.annotation.IntDef; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - public interface SubscriptionEventListener { - @Retention(RetentionPolicy.SOURCE) - @IntDef({EventType.ADDED, EventType.REMOVED}) - @interface EventType { - int ADDED = 0, REMOVED = 1; + enum EventType { + ADDED, + REMOVED } void onSubscriptionAdded(); diff --git a/library/consumer-proguard-rules.txt b/library/consumer-proguard-rules.txt index d26a6dab9..bcbb48d08 100644 --- a/library/consumer-proguard-rules.txt +++ b/library/consumer-proguard-rules.txt @@ -6,6 +6,3 @@ -dontwarn retrofit.** -dontwarn retrofit2.** -dontwarn okio.** - -# Don't warn about custom @IntDef annotations --dontwarn com.firebase.ui.database.SubscriptionEventListener$EventType From 6e0031a50f279baa773e6e6191131e4fb30aa007 Mon Sep 17 00:00:00 2001 From: Alex Saveau Date: Sun, 5 Feb 2017 03:03:45 -0800 Subject: [PATCH 63/93] Fix compile errors Signed-off-by: Alex Saveau --- .../src/main/java/com/firebase/ui/database/FirebaseArray.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseArray.java b/database/src/main/java/com/firebase/ui/database/FirebaseArray.java index 135fdce60..0902e40fc 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseArray.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseArray.java @@ -110,7 +110,7 @@ public void removeSubscriptionEventListener(@NonNull SubscriptionEventListener l mSubscribers.remove(listener); } - protected void notifySubscriptionEventListeners(@SubscriptionEventListener.EventType int eventType) { + protected void notifySubscriptionEventListeners(SubscriptionEventListener.EventType eventType) { for (SubscriptionEventListener listener : mSubscribers) { if (eventType == SubscriptionEventListener.EventType.ADDED) { listener.onSubscriptionAdded(); From 5e01b4598f6d46c7f72798f80af3dd335322d7bc Mon Sep 17 00:00:00 2001 From: Alex Saveau Date: Mon, 6 Feb 2017 16:46:25 -0800 Subject: [PATCH 64/93] Make everything that shouldn't be extended final Signed-off-by: Alex Saveau --- .../firebase/ui/database/FirebaseArray.java | 22 +++++++++++----- .../ui/database/FirebaseIndexArray.java | 9 ++----- .../firebase/ui/database/ImmutableList.java | 26 +++++++++---------- 3 files changed, 30 insertions(+), 27 deletions(-) diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseArray.java b/database/src/main/java/com/firebase/ui/database/FirebaseArray.java index 0902e40fc..caa331efa 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseArray.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseArray.java @@ -15,6 +15,7 @@ package com.firebase.ui.database; import android.support.annotation.NonNull; +import android.support.annotation.RestrictTo; import com.google.firebase.database.ChildEventListener; import com.google.firebase.database.DataSnapshot; @@ -244,13 +245,8 @@ public Iterator iterator() { } @Override - public Object[] toArray() { - return mSnapshots.toArray(); - } - - @Override - public T[] toArray(T[] a) { - return mSnapshots.toArray(a); + public DataSnapshot[] toArray() { + return mSnapshots.toArray(new DataSnapshot[mSnapshots.size()]); } /** @@ -340,4 +336,16 @@ public String toString() { ", mSnapshots=" + mSnapshots + '}'; } + + /** + * Guaranteed to throw an exception. Use {@link #toArray()} instead to get an array of {@link + * DataSnapshot}s. + * + * @throws UnsupportedOperationException always + */ + @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) + @Override + public final T[] toArray(T[] a) { + throw new UnsupportedOperationException(); + } } 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 5627662fe..0cd8f861a 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseIndexArray.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseIndexArray.java @@ -188,13 +188,8 @@ public Iterator iterator() { } @Override - public Object[] toArray() { - return mDataSnapshots.toArray(); - } - - @Override - public T[] toArray(T[] a) { - return mDataSnapshots.toArray(a); + public DataSnapshot[] toArray() { + return mDataSnapshots.toArray(new DataSnapshot[mDataSnapshots.size()]); } @Override diff --git a/database/src/main/java/com/firebase/ui/database/ImmutableList.java b/database/src/main/java/com/firebase/ui/database/ImmutableList.java index 9449eb1f2..103174d0a 100644 --- a/database/src/main/java/com/firebase/ui/database/ImmutableList.java +++ b/database/src/main/java/com/firebase/ui/database/ImmutableList.java @@ -15,7 +15,7 @@ public abstract class ImmutableList implements List { */ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) @Override - public boolean add(E element) { + public final boolean add(E element) { throw new UnsupportedOperationException(); } @@ -26,7 +26,7 @@ public boolean add(E element) { */ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) @Override - public boolean remove(Object o) { + public final boolean remove(Object o) { throw new UnsupportedOperationException(); } @@ -37,7 +37,7 @@ public boolean remove(Object o) { */ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) @Override - public boolean addAll(Collection c) { + public final boolean addAll(Collection c) { throw new UnsupportedOperationException(); } @@ -48,7 +48,7 @@ public boolean addAll(Collection c) { */ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) @Override - public boolean addAll(int index, Collection c) { + public final boolean addAll(int index, Collection c) { throw new UnsupportedOperationException(); } @@ -59,7 +59,7 @@ public boolean addAll(int index, Collection c) { */ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) @Override - public boolean removeAll(Collection c) { + public final boolean removeAll(Collection c) { throw new UnsupportedOperationException(); } @@ -70,7 +70,7 @@ public boolean removeAll(Collection c) { */ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) @Override - public boolean retainAll(Collection c) { + public final boolean retainAll(Collection c) { throw new UnsupportedOperationException(); } @@ -81,7 +81,7 @@ public boolean retainAll(Collection c) { */ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) @Override - public void clear() { + public final void clear() { throw new UnsupportedOperationException(); } @@ -92,7 +92,7 @@ public void clear() { */ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) @Override - public E set(int index, E element) { + public final E set(int index, E element) { throw new UnsupportedOperationException(); } @@ -103,7 +103,7 @@ public E set(int index, E element) { */ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) @Override - public void add(int index, E element) { + public final void add(int index, E element) { throw new UnsupportedOperationException(); } @@ -114,7 +114,7 @@ public void add(int index, E element) { */ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) @Override - public E remove(int index) { + public final E remove(int index) { throw new UnsupportedOperationException(); } @@ -125,11 +125,11 @@ public E remove(int index) { */ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) @Override - public List subList(int fromIndex, int toIndex) { + public final List subList(int fromIndex, int toIndex) { throw new UnsupportedOperationException(); } - protected class ImmutableIterator implements Iterator { + protected final class ImmutableIterator implements Iterator { protected Iterator mIterator; public ImmutableIterator(Iterator iterator) { @@ -147,7 +147,7 @@ public E next() { } } - protected class ImmutableListIterator implements ListIterator { + protected final class ImmutableListIterator implements ListIterator { protected ListIterator mListIterator; public ImmutableListIterator(ListIterator listIterator) { From 7d463395428ef4b3b55d3701085415145932e0ca Mon Sep 17 00:00:00 2001 From: Alex Saveau Date: Mon, 6 Feb 2017 17:08:19 -0800 Subject: [PATCH 65/93] Notify of existing listeners/items for new listeners in `addFooListener` Signed-off-by: Alex Saveau --- .../java/com/firebase/ui/database/FirebaseArray.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseArray.java b/database/src/main/java/com/firebase/ui/database/FirebaseArray.java index caa331efa..111b0637f 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseArray.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseArray.java @@ -62,9 +62,13 @@ public ChangeEventListener addChangeEventListener(@NonNull ChangeEventListener l synchronized (mListeners) { mListeners.add(listener); notifySubscriptionEventListeners(SubscriptionEventListener.EventType.ADDED); - if (mListeners.size() == 1) { // Only start listening when the first listener is added + if (mListeners.size() <= 1) { // Only start listening when the first listener is added mQuery.addChildEventListener(this); mQuery.addValueEventListener(this); + } else { + for (int i = 0; i < size(); i++) { + listener.onChildChanged(ChangeEventListener.EventType.ADDED, i, -1); + } } } @@ -98,6 +102,11 @@ public void removeChangeEventListener(@NonNull ChangeEventListener listener) { */ public SubscriptionEventListener addSubscriptionEventListener(@NonNull SubscriptionEventListener listener) { checkNotNull(listener); + + for (SubscriptionEventListener ignored : mSubscribers) { + listener.onSubscriptionAdded(); + } + mSubscribers.add(listener); return listener; } From 327d1828912ace383187df554e60a15dfea7117a Mon Sep 17 00:00:00 2001 From: Alex Saveau Date: Mon, 6 Feb 2017 18:06:48 -0800 Subject: [PATCH 66/93] Fix `concurrentmodificationexception`s Signed-off-by: Alex Saveau --- .../java/com/firebase/ui/database/FirebaseArray.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseArray.java b/database/src/main/java/com/firebase/ui/database/FirebaseArray.java index 111b0637f..8f8ec8193 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseArray.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseArray.java @@ -28,6 +28,7 @@ import java.util.Iterator; import java.util.List; import java.util.ListIterator; +import java.util.concurrent.CopyOnWriteArrayList; /** * This class implements a collection on top of a Firebase location. @@ -35,7 +36,7 @@ public class FirebaseArray extends ImmutableList implements ChildEventListener, ValueEventListener { private Query mQuery; private boolean mNotifyListeners = true; - private final List mListeners = new ArrayList<>(); + private final List mListeners = new CopyOnWriteArrayList<>(); private List mSubscribers = new ArrayList<>(); private List mSnapshots = new ArrayList<>(); @@ -138,8 +139,10 @@ private static void checkNotNull(Object o) { * @return true if {@link FirebaseArray} is listening for change events from the Firebase * database, false otherwise */ - public synchronized boolean isListening() { - return !mListeners.isEmpty(); + public boolean isListening() { + synchronized (mListeners) { + return !mListeners.isEmpty(); + } } @Override From fda54177423b57c267b0208ffea280a6a385a564 Mon Sep 17 00:00:00 2001 From: Alex Saveau Date: Mon, 6 Feb 2017 18:42:18 -0800 Subject: [PATCH 67/93] Fix FirebaseArrayOfObjects$FirebaseArrayOfObjectsOptimized.java being incomprehensible and bugs related to adapters not listening for changes if FirebaseArray.java already had listeners by adding a new method to check if an object is listening for changes. Signed-off-by: Alex Saveau --- .../com/firebase/ui/database/FirebaseArray.java | 9 +++++++++ .../ui/database/FirebaseArrayOfObjects.java | 14 +++----------- .../ui/database/adapter/FirebaseListAdapter.java | 2 +- .../database/adapter/FirebaseRecyclerAdapter.java | 2 +- 4 files changed, 14 insertions(+), 13 deletions(-) diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseArray.java b/database/src/main/java/com/firebase/ui/database/FirebaseArray.java index 8f8ec8193..13b07933a 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseArray.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseArray.java @@ -145,6 +145,15 @@ public boolean isListening() { } } + /** + * @return true if the provided {@link ChangeEventListener} is listening for changes + */ + public boolean isListening(ChangeEventListener listener) { + synchronized (mListeners) { + return mListeners.contains(listener); + } + } + @Override public void onChildAdded(DataSnapshot snapshot, String previousChildKey) { int index = 0; diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseArrayOfObjects.java b/database/src/main/java/com/firebase/ui/database/FirebaseArrayOfObjects.java index 6b5e948f2..4e4fe064c 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseArrayOfObjects.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseArrayOfObjects.java @@ -174,8 +174,6 @@ public String toString() { protected static class FirebaseArrayOfObjectsOptimized extends FirebaseArrayOfObjects implements ChangeEventListener, SubscriptionEventListener { protected List mObjects = new ArrayList<>(); - protected boolean mIsListening = true; - protected boolean mAddedListener = false; public FirebaseArrayOfObjectsOptimized(FirebaseArray snapshots, Class modelClass) { super(snapshots, modelClass); @@ -211,20 +209,14 @@ public void onSubscriptionRemoved() { FirebaseArray snapshots = (FirebaseArray) mSnapshots; if (!snapshots.isListening()) { snapshots.removeChangeEventListener(this); - mIsListening = false; - mAddedListener = false; } } @Override public void onSubscriptionAdded() { - if (mAddedListener) { - mIsListening = true; - mAddedListener = false; - } else if (!mIsListening) { - ((FirebaseArray) mSnapshots).addChangeEventListener(this); - mIsListening = true; - mAddedListener = true; + FirebaseArray snapshots = (FirebaseArray) mSnapshots; + if (!snapshots.isListening(this)) { + snapshots.addChangeEventListener(this); } } diff --git a/database/src/main/java/com/firebase/ui/database/adapter/FirebaseListAdapter.java b/database/src/main/java/com/firebase/ui/database/adapter/FirebaseListAdapter.java index 0f151cf39..8be36d1ee 100644 --- a/database/src/main/java/com/firebase/ui/database/adapter/FirebaseListAdapter.java +++ b/database/src/main/java/com/firebase/ui/database/adapter/FirebaseListAdapter.java @@ -70,7 +70,7 @@ public FirebaseListAdapter(Activity activity, @Override public void startListening() { - if (!mSnapshots.isListening()) { + if (!mSnapshots.isListening(this)) { mSnapshots.addChangeEventListener(this); } } diff --git a/database/src/main/java/com/firebase/ui/database/adapter/FirebaseRecyclerAdapter.java b/database/src/main/java/com/firebase/ui/database/adapter/FirebaseRecyclerAdapter.java index 7148d846b..52992454a 100644 --- a/database/src/main/java/com/firebase/ui/database/adapter/FirebaseRecyclerAdapter.java +++ b/database/src/main/java/com/firebase/ui/database/adapter/FirebaseRecyclerAdapter.java @@ -75,7 +75,7 @@ public FirebaseRecyclerAdapter(Class modelClass, @Override public void startListening() { - if (!mSnapshots.isListening()) { + if (!mSnapshots.isListening(this)) { mSnapshots.addChangeEventListener(this); } } From ad67210942ad58ea5098de06ca3371977a37b3c3 Mon Sep 17 00:00:00 2001 From: Alex Saveau Date: Mon, 6 Feb 2017 19:05:01 -0800 Subject: [PATCH 68/93] Remove synchronized since we're using `CopyOnWriteArrayList` Signed-off-by: Alex Saveau --- .../firebase/ui/database/FirebaseArray.java | 40 ++++++++----------- 1 file changed, 16 insertions(+), 24 deletions(-) diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseArray.java b/database/src/main/java/com/firebase/ui/database/FirebaseArray.java index 13b07933a..d25d0e945 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseArray.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseArray.java @@ -60,16 +60,14 @@ public FirebaseArray(Query query) { public ChangeEventListener addChangeEventListener(@NonNull ChangeEventListener listener) { checkNotNull(listener); - synchronized (mListeners) { - mListeners.add(listener); - notifySubscriptionEventListeners(SubscriptionEventListener.EventType.ADDED); - if (mListeners.size() <= 1) { // Only start listening when the first listener is added - mQuery.addChildEventListener(this); - mQuery.addValueEventListener(this); - } else { - for (int i = 0; i < size(); i++) { - listener.onChildChanged(ChangeEventListener.EventType.ADDED, i, -1); - } + mListeners.add(listener); + notifySubscriptionEventListeners(SubscriptionEventListener.EventType.ADDED); + if (mListeners.size() <= 1) { // Only start listening when the first listener is added + mQuery.addChildEventListener(this); + mQuery.addValueEventListener(this); + } else { + for (int i = 0; i < size(); i++) { + listener.onChildChanged(ChangeEventListener.EventType.ADDED, i, -1); } } @@ -83,14 +81,12 @@ public ChangeEventListener addChangeEventListener(@NonNull ChangeEventListener l * @param listener the listener to remove */ public void removeChangeEventListener(@NonNull ChangeEventListener listener) { - synchronized (mListeners) { - mListeners.remove(listener); - notifySubscriptionEventListeners(SubscriptionEventListener.EventType.REMOVED); - if (mListeners.isEmpty()) { - mQuery.removeEventListener((ValueEventListener) this); - mQuery.removeEventListener((ChildEventListener) this); - mSnapshots.clear(); - } + mListeners.remove(listener); + notifySubscriptionEventListeners(SubscriptionEventListener.EventType.REMOVED); + if (mListeners.isEmpty()) { + mQuery.removeEventListener((ValueEventListener) this); + mQuery.removeEventListener((ChildEventListener) this); + mSnapshots.clear(); } } @@ -140,18 +136,14 @@ private static void checkNotNull(Object o) { * database, false otherwise */ public boolean isListening() { - synchronized (mListeners) { - return !mListeners.isEmpty(); - } + return !mListeners.isEmpty(); } /** * @return true if the provided {@link ChangeEventListener} is listening for changes */ public boolean isListening(ChangeEventListener listener) { - synchronized (mListeners) { - return mListeners.contains(listener); - } + return mListeners.contains(listener); } @Override From 6f1f1a8135c1e3d6573416e17ea709d6806656ee Mon Sep 17 00:00:00 2001 From: Alex Saveau Date: Mon, 6 Feb 2017 22:34:57 -0800 Subject: [PATCH 69/93] Fix typos Signed-off-by: Alex Saveau --- database/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/database/README.md b/database/README.md index 7898894ec..47f8f2fb5 100644 --- a/database/README.md +++ b/database/README.md @@ -61,7 +61,7 @@ public static class Chat { A few things to note here: * The fields have the exact same name as the properties in Firebase. This allows Firebase to automatically map the properties to these fields. - * There is a default (parameterless constructor) that is necessary for Firebase to be able to create a new instance of this class. + * There is a default (parameter-less constructor) that is necessary for Firebase to be able to create a new instance of this class. * There is a convenience constructor that takes the member fields, so that we easily create a fully initialized `Chat` in our app * the `getText`, `getUid`, and `getName` methods are so-called getters and follow a JavaBean pattern @@ -100,7 +100,7 @@ But when we build our app using FirebaseUI, we often won't need to register our ### Find the RecyclerView -We'll assume you've already added a `RecyclerVview` to your layout and have looked it up in the `onCreate` method of your activity: +We'll assume you've already added a `RecyclerView` to your layout and have looked it up in the `onCreate` method of your activity: ```java @Override @@ -288,7 +288,7 @@ messagesView.setAdapter(mAdapter); ## Using FirebaseUI with indexed data -If your data is [properly indexed](https://firebase.google.com/docs/database/android/structure-data#best_practices_for_data_structure), change your adapter initalization like so: +If your data is [properly indexed](https://firebase.google.com/docs/database/android/structure-data#best_practices_for_data_structure), change your adapter initialization like so: For a `RecyclerView`, use `FirebaseIndexRecyclerAdapter` instead of `FirebaseRecyclerAdapter`: ```java From 5d695d44b28eb19df5c9f85178bdcacb1d014601 Mon Sep 17 00:00:00 2001 From: Alex Saveau Date: Mon, 6 Feb 2017 22:38:57 -0800 Subject: [PATCH 70/93] Update links in new adapters Signed-off-by: Alex Saveau --- database/README.md | 4 ++-- .../com/firebase/ui/database/adapter/FirebaseListAdapter.java | 2 +- .../firebase/ui/database/adapter/FirebaseRecyclerAdapter.java | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/database/README.md b/database/README.md index 47f8f2fb5..1a1b16f09 100644 --- a/database/README.md +++ b/database/README.md @@ -1,6 +1,6 @@ # FirebaseUI Database -## Using FirebaseUI to Populate a RecyclerView +## Using FirebaseUI to populate a `RecyclerView` To use the FirebaseUI to display Firebase data, we need a few things: @@ -266,7 +266,7 @@ protected void onDestroy() { You're done! You now have a minimal, yet fully functional, chat app in about 30 lines of code. Not bad, right? -## Using FirebaseUI to populate a ListView +## Using FirebaseUI to populate a `ListView` ListView is the older, yet simpler way to handle lists of items. Using it is analogous to using a `FirebaseRecyclerAdapter`, but with `FirebaseListAdapter` instead and no `ViewHolder`: diff --git a/database/src/main/java/com/firebase/ui/database/adapter/FirebaseListAdapter.java b/database/src/main/java/com/firebase/ui/database/adapter/FirebaseListAdapter.java index 8be36d1ee..e146e5033 100644 --- a/database/src/main/java/com/firebase/ui/database/adapter/FirebaseListAdapter.java +++ b/database/src/main/java/com/firebase/ui/database/adapter/FirebaseListAdapter.java @@ -20,7 +20,7 @@ * location. It handles all of the child events at the given Firebase location. It marshals received * data into the given class type. *

- * See the README + * See the README * for an in-depth tutorial on how to set up the FirebaseListAdapter. * * @param The class type to use as a model for the data contained in the children of the given diff --git a/database/src/main/java/com/firebase/ui/database/adapter/FirebaseRecyclerAdapter.java b/database/src/main/java/com/firebase/ui/database/adapter/FirebaseRecyclerAdapter.java index 52992454a..d42d2ab95 100644 --- a/database/src/main/java/com/firebase/ui/database/adapter/FirebaseRecyclerAdapter.java +++ b/database/src/main/java/com/firebase/ui/database/adapter/FirebaseRecyclerAdapter.java @@ -22,7 +22,7 @@ * handles all of the child events at the given Firebase location and marshals received data into * the given class type. *

- * See the README + * See the README * for an in-depth tutorial on how to set up the FirebaseRecyclerAdapter. * * @param The Java class that maps to the type of objects stored in the Firebase location. From 34652db70b911d0891f63550e055f188285a7d88 Mon Sep 17 00:00:00 2001 From: Alex Saveau Date: Thu, 9 Feb 2017 14:51:58 -0800 Subject: [PATCH 71/93] Fix merge mistakes Signed-off-by: Alex Saveau --- .../java/com/firebase/ui/database/FirebaseArrayTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/database/src/androidTest/java/com/firebase/ui/database/FirebaseArrayTest.java b/database/src/androidTest/java/com/firebase/ui/database/FirebaseArrayTest.java index 951b3d392..adfe0a843 100644 --- a/database/src/androidTest/java/com/firebase/ui/database/FirebaseArrayTest.java +++ b/database/src/androidTest/java/com/firebase/ui/database/FirebaseArrayTest.java @@ -132,7 +132,7 @@ public void testChangePriorityFrontToBack() throws Exception { runAndWaitUntil(mArray, new Runnable() { @Override public void run() { - mArray.getItem(0).getRef().setPriority(4); + mArray.get(0).getRef().setPriority(4); } }, new Callable() { @Override From de7e7e8270aabbd2ad803b0998af9e8a8600ad69 Mon Sep 17 00:00:00 2001 From: Alex Saveau Date: Sun, 12 Feb 2017 16:53:38 -0800 Subject: [PATCH 72/93] Add ability to delete items Signed-off-by: Alex Saveau --- .../uidemo/database/ChatActivity.java | 33 ++++++++++++------- .../firebase/uidemo/database/ChatHolder.java | 28 +++++++++++----- 2 files changed, 41 insertions(+), 20 deletions(-) diff --git a/app/src/main/java/com/firebase/uidemo/database/ChatActivity.java b/app/src/main/java/com/firebase/uidemo/database/ChatActivity.java index 5b8c63603..6c535ff1c 100644 --- a/app/src/main/java/com/firebase/uidemo/database/ChatActivity.java +++ b/app/src/main/java/com/firebase/uidemo/database/ChatActivity.java @@ -20,6 +20,8 @@ import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.util.Log; +import android.view.ContextMenu; +import android.view.MenuItem; import android.view.View; import android.widget.Button; import android.widget.EditText; @@ -33,7 +35,6 @@ import com.google.android.gms.tasks.OnSuccessListener; import com.google.firebase.auth.AuthResult; import com.google.firebase.auth.FirebaseAuth; -import com.google.firebase.auth.FirebaseUser; import com.google.firebase.database.DatabaseError; import com.google.firebase.database.DatabaseReference; import com.google.firebase.database.FirebaseDatabase; @@ -141,16 +142,26 @@ private void attachRecyclerViewAdapter() { mChatIndicesRef.limitToLast(50), mChatRef) { @Override - public void populateViewHolder(ChatHolder holder, Chat chat, int position) { - holder.setName(chat.getName()); - holder.setText(chat.getMessage()); - - FirebaseUser currentUser = mAuth.getCurrentUser(); - if (currentUser != null && chat.getUid().equals(currentUser.getUid())) { - holder.setIsSender(true); - } else { - holder.setIsSender(false); - } + public void populateViewHolder(final ChatHolder holder, Chat chat, int position) { + holder.bind(chat); + + holder.itemView.setOnCreateContextMenuListener(new View.OnCreateContextMenuListener() { + @Override + public void onCreateContextMenu(ContextMenu menu, + View v, + ContextMenu.ContextMenuInfo menuInfo) { + menu.add("Delete") + .setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem item) { + DatabaseReference ref = getRef(holder.getAdapterPosition()); + mChatIndicesRef.child(ref.getKey()).removeValue(); + ref.removeValue(); + return true; + } + }); + } + }); } @Override diff --git a/app/src/main/java/com/firebase/uidemo/database/ChatHolder.java b/app/src/main/java/com/firebase/uidemo/database/ChatHolder.java index 7c95dccdb..ce3be5248 100644 --- a/app/src/main/java/com/firebase/uidemo/database/ChatHolder.java +++ b/app/src/main/java/com/firebase/uidemo/database/ChatHolder.java @@ -13,6 +13,8 @@ import android.widget.TextView; import com.firebase.uidemo.R; +import com.google.firebase.auth.FirebaseAuth; +import com.google.firebase.auth.FirebaseUser; public class ChatHolder extends RecyclerView.ViewHolder { private final TextView mNameField; @@ -36,7 +38,23 @@ public ChatHolder(View itemView) { mGray300 = ContextCompat.getColor(itemView.getContext(), R.color.material_gray_300); } - public void setIsSender(boolean isSender) { + public void bind(Chat chat) { + setName(chat.getName()); + setText(chat.getMessage()); + + FirebaseUser currentUser = FirebaseAuth.getInstance().getCurrentUser(); + setIsSender(currentUser != null && chat.getUid().equals(currentUser.getUid())); + } + + private void setName(String name) { + mNameField.setText(name); + } + + private void setText(String text) { + mTextField.setText(text); + } + + private void setIsSender(boolean isSender) { final int color; if (isSender) { color = mGreen300; @@ -56,12 +74,4 @@ public void setIsSender(boolean isSender) { ((RotateDrawable) mRightArrow.getBackground()).getDrawable() .setColorFilter(color, PorterDuff.Mode.SRC); } - - public void setName(String name) { - mNameField.setText(name); - } - - public void setText(String text) { - mTextField.setText(text); - } } From 074af0c75147a9a00192c268b52ac9a24f196aa2 Mon Sep 17 00:00:00 2001 From: Alex Saveau Date: Wed, 15 Feb 2017 20:03:08 -0800 Subject: [PATCH 73/93] Replace old adapters with new ones Signed-off-by: Alex Saveau --- .../uidemo/database/ChatActivity.java | 2 +- .../{adapter => }/FirebaseAdapter.java | 5 +- .../ui/database/FirebaseIndexListAdapter.java | 56 +---- .../FirebaseIndexRecyclerAdapter.java | 87 +------ .../ui/database/FirebaseListAdapter.java | 160 +++++-------- .../ui/database/FirebaseRecyclerAdapter.java | 223 +++++++----------- .../adapter/FirebaseIndexListAdapter.java | 27 --- .../adapter/FirebaseIndexRecyclerAdapter.java | 28 --- .../database/adapter/FirebaseListAdapter.java | 149 ------------ .../adapter/FirebaseRecyclerAdapter.java | 184 --------------- 10 files changed, 163 insertions(+), 758 deletions(-) rename database/src/main/java/com/firebase/ui/database/{adapter => }/FirebaseAdapter.java (76%) delete mode 100644 database/src/main/java/com/firebase/ui/database/adapter/FirebaseIndexListAdapter.java delete mode 100644 database/src/main/java/com/firebase/ui/database/adapter/FirebaseIndexRecyclerAdapter.java delete mode 100644 database/src/main/java/com/firebase/ui/database/adapter/FirebaseListAdapter.java delete mode 100644 database/src/main/java/com/firebase/ui/database/adapter/FirebaseRecyclerAdapter.java diff --git a/app/src/main/java/com/firebase/uidemo/database/ChatActivity.java b/app/src/main/java/com/firebase/uidemo/database/ChatActivity.java index 96426eb71..ac3e97359 100644 --- a/app/src/main/java/com/firebase/uidemo/database/ChatActivity.java +++ b/app/src/main/java/com/firebase/uidemo/database/ChatActivity.java @@ -148,7 +148,7 @@ public void populateViewHolder(ChatHolder holder, Chat chat, int position) { } @Override - protected void onDataChanged() { + public void onDataChanged() { // If there are no chat messages, show a view that invites the user to add a message. mEmptyListMessage.setVisibility(mAdapter.getItemCount() == 0 ? View.VISIBLE : View.GONE); } diff --git a/database/src/main/java/com/firebase/ui/database/adapter/FirebaseAdapter.java b/database/src/main/java/com/firebase/ui/database/FirebaseAdapter.java similarity index 76% rename from database/src/main/java/com/firebase/ui/database/adapter/FirebaseAdapter.java rename to database/src/main/java/com/firebase/ui/database/FirebaseAdapter.java index d04b880f5..1f28424f4 100644 --- a/database/src/main/java/com/firebase/ui/database/adapter/FirebaseAdapter.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseAdapter.java @@ -1,8 +1,5 @@ -package com.firebase.ui.database.adapter; +package com.firebase.ui.database; -import com.firebase.ui.database.ChangeEventListener; -import com.firebase.ui.database.FirebaseArray; -import com.firebase.ui.database.SnapshotParser; import com.google.firebase.database.DatabaseReference; public interface FirebaseAdapter extends ChangeEventListener, SnapshotParser { diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseIndexListAdapter.java b/database/src/main/java/com/firebase/ui/database/FirebaseIndexListAdapter.java index dc1529347..92f3b34d7 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseIndexListAdapter.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseIndexListAdapter.java @@ -3,59 +3,23 @@ import android.app.Activity; import android.support.annotation.LayoutRes; +import com.google.firebase.database.DatabaseReference; import com.google.firebase.database.Query; -/** - * This class is a generic way of backing an Android ListView with a Firebase location. - * It handles all of the child events at the given Firebase location. It marshals received data into the given - * class type. Extend this class and provide an implementation of {@code populateView}, which will be given an - * instance of your list item mLayout and an instance your class that holds your data. - * Simply populate the view however you like and this class will handle updating the list as the data changes. - *

- * If your data is not indexed: - *

- *     DatabaseReference ref = FirebaseDatabase.getInstance().getReference();
- *     ListAdapter adapter = new FirebaseListAdapter(
- *             this,
- *             ChatMessage.class,
- *             android.R.layout.two_line_list_item,
- *             keyRef,
- *             dataRef)
- *     {
- *         protected void populateView(View view, ChatMessage chatMessage, int position)
- *         {
- *             ((TextView)view.findViewById(android.R.id.text1)).setText(chatMessage.getName());
- *             ((TextView)view.findViewById(android.R.id.text2)).setText(chatMessage.getMessage());
- *         }
- *     };
- *     listView.setListAdapter(adapter);
- * 
- * - * @param The class type to use as a model for the data contained in the children of the given - * Firebase location - * @deprecated use {@link com.firebase.ui.database.adapter.FirebaseIndexRecyclerAdapter} instead - */ -@Deprecated public abstract class FirebaseIndexListAdapter extends FirebaseListAdapter { /** - * @param activity The activity containing the ListView - * @param modelClass Firebase will marshall the data at a location into - * an instance of a class that you provide - * @param modelLayout This is the layout used to represent a single list item. - * You will be responsible for populating an instance of the corresponding - * view with the data from an instance of modelClass. - * @param keyRef 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 in {@code keyRef}'s location represents - * a list item in the {@code ListView}. + * @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 in {@code + * keyQuery}'s location represents a list item in the {@code ListView}. + * @see FirebaseListAdapter#FirebaseListAdapter(Activity, FirebaseArray, Class, int) */ public FirebaseIndexListAdapter(Activity activity, Class modelClass, @LayoutRes int modelLayout, - Query keyRef, - Query dataRef) { - super(activity, modelClass, modelLayout, new FirebaseIndexArray(keyRef, dataRef.getRef())); + Query keyQuery, + DatabaseReference dataRef) { + super(activity, new FirebaseIndexArray(keyQuery, dataRef), modelClass, modelLayout); } } diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseIndexRecyclerAdapter.java b/database/src/main/java/com/firebase/ui/database/FirebaseIndexRecyclerAdapter.java index 1277c5c0d..dab13cb91 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseIndexRecyclerAdapter.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseIndexRecyclerAdapter.java @@ -1,93 +1,26 @@ -/* - * Copyright 2016 Google Inc. All Rights Reserved. - * - * 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.firebase.ui.database; import android.support.annotation.LayoutRes; import android.support.v7.widget.RecyclerView; +import com.google.firebase.database.DatabaseReference; import com.google.firebase.database.Query; -/** - * This class is a generic way of backing an RecyclerView with a Firebase location. - * It handles all of the child events at the given Firebase location. It marshals received data into the given - * class type. - *

- * To use this class in your app, subclass it passing in all required parameters and implement the - * populateViewHolder method. - *

- *

- *     private static class ChatMessageViewHolder extends RecyclerView.ViewHolder {
- *         TextView messageText;
- *         TextView nameText;
- *
- *         public ChatMessageViewHolder(View itemView) {
- *             super(itemView);
- *             nameText = (TextView)itemView.findViewById(android.R.id.text1);
- *             messageText = (TextView) itemView.findViewById(android.R.id.text2);
- *         }
- *     }
- *
- *     FirebaseIndexRecyclerAdapter adapter;
- *     DatabaseReference ref = FirebaseDatabase.getInstance().getReference();
- *
- *     RecyclerView recycler = (RecyclerView) findViewById(R.id.messages_recycler);
- *     recycler.setHasFixedSize(true);
- *     recycler.setLayoutManager(new LinearLayoutManager(this));
- *
- *     adapter = new FirebaseIndexRecyclerAdapter(
- *          ChatMessage.class, android.R.layout.two_line_list_item, ChatMessageViewHolder.class, keyRef, dataRef) {
- *         public void populateViewHolder(ChatMessageViewHolder chatMessageViewHolder,
- *                                        ChatMessage chatMessage,
- *                                        int position) {
- *             chatMessageViewHolder.nameText.setText(chatMessage.getName());
- *             chatMessageViewHolder.messageText.setText(chatMessage.getMessage());
- *         }
- *     };
- *     recycler.setAdapter(mAdapter);
- * 
- * - * @param The Java class that maps to the type of objects stored in the Firebase location. - * @param The ViewHolder class that contains the Views in the layout that is shown for each - * object. - * @deprecated use {@link com.firebase.ui.database.adapter.FirebaseIndexRecyclerAdapter} instead - */ -@Deprecated public abstract class FirebaseIndexRecyclerAdapter extends FirebaseRecyclerAdapter { /** - * @param modelClass Firebase will marshall the data at a location into an instance - * of a class that you provide - * @param modelLayout This is the layout used to represent a single item in the list. - * You will be responsible for populating an - * instance of the corresponding view with the data from an instance of modelClass. - * @param viewHolderClass The class that hold references to all sub-views in an instance modelLayout. - * @param keyRef 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 keyRef}'s location represents - * a list item in the {@code RecyclerView}. + * @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 FirebaseRecyclerAdapter#FirebaseRecyclerAdapter(FirebaseArray, Class, int, Class) */ public FirebaseIndexRecyclerAdapter(Class modelClass, @LayoutRes int modelLayout, Class viewHolderClass, - Query keyRef, - Query dataRef) { - super(modelClass, - modelLayout, - viewHolderClass, - new FirebaseIndexArray(keyRef, dataRef.getRef())); + Query keyQuery, + DatabaseReference dataRef) { + super(new FirebaseIndexArray(keyQuery, dataRef), modelClass, modelLayout, viewHolderClass); } } diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseListAdapter.java b/database/src/main/java/com/firebase/ui/database/FirebaseListAdapter.java index dc66704bd..f5b44bfb7 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseListAdapter.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseListAdapter.java @@ -1,17 +1,3 @@ -/* - * Copyright 2016 Google Inc. All Rights Reserved. - * - * 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.firebase.ui.database; import android.app.Activity; @@ -20,6 +6,7 @@ import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; +import android.widget.ListView; import com.google.firebase.database.DataSnapshot; import com.google.firebase.database.DatabaseError; @@ -27,91 +14,82 @@ import com.google.firebase.database.Query; /** - * This class is a generic way of backing an Android ListView with a Firebase location. - * It handles all of the child events at the given Firebase location. It marshals received data into the given - * class type. Extend this class and provide an implementation of {@code populateView}, which will be given an - * instance of your list item mLayout and an instance your class that holds your data. - * Simply populate the view however you like and this class will handle updating the list as the data changes. + * This class is a generic way of backing an Android {@link android.widget.ListView} with a Firebase + * location. It handles all of the child events at the given Firebase location. It marshals received + * data into the given class type. *

- *

- *     DatabaseReference ref = FirebaseDatabase.getInstance().getReference();
- *     ListAdapter adapter = new FirebaseListAdapter(
- *              this, ChatMessage.class, android.R.layout.two_line_list_item, ref)
- *     {
- *         protected void populateView(View view, ChatMessage chatMessage, int position)
- *         {
- *             ((TextView)view.findViewById(android.R.id.text1)).setText(chatMessage.getName());
- *             ((TextView)view.findViewById(android.R.id.text2)).setText(chatMessage.getMessage());
- *         }
- *     };
- *     listView.setListAdapter(adapter);
- * 
+ * See the README + * for an in-depth tutorial on how to set up the FirebaseListAdapter. * * @param The class type to use as a model for the data contained in the children of the given * Firebase location - * @deprecated use {@link com.firebase.ui.database.adapter.FirebaseRecyclerAdapter} instead */ -@Deprecated -public abstract class FirebaseListAdapter extends BaseAdapter { +public abstract class FirebaseListAdapter extends BaseAdapter implements FirebaseAdapter { private static final String TAG = "FirebaseListAdapter"; - private FirebaseArray mSnapshots; - private final Class mModelClass; protected Activity mActivity; + protected FirebaseArray mSnapshots; + protected Class mModelClass; protected int mLayout; - private ChangeEventListener mListener; - FirebaseListAdapter(Activity activity, - Class modelClass, - @LayoutRes int modelLayout, - FirebaseArray snapshots) { + /** + * @param activity The {@link Activity} containing the {@link ListView} + * @param modelClass Firebase will marshall the data at a location into an instance of a class + * that you provide + * @param modelLayout This is the layout used to represent a single list item. You will be + * responsible for populating an instance of the corresponding view with the + * data from an instance of modelClass. + * @param snapshots The data used to populate the adapter + */ + public FirebaseListAdapter(Activity activity, + FirebaseArray snapshots, + Class modelClass, + @LayoutRes int modelLayout) { mActivity = activity; + mSnapshots = snapshots; mModelClass = modelClass; mLayout = modelLayout; - mSnapshots = snapshots; - mListener = mSnapshots.addChangeEventListener(new ChangeEventListener() { - @Override - public void onChildChanged(EventType type, int index, int oldIndex) { - FirebaseListAdapter.this.onChildChanged(type, index, oldIndex); - } - - @Override - public void onDataChanged() { - FirebaseListAdapter.this.onDataChanged(); - } - - @Override - public void onCancelled(DatabaseError error) { - FirebaseListAdapter.this.onCancelled(error); - } - }); + startListening(); } /** - * @param activity The activity containing the ListView - * @param modelClass Firebase will marshall the data at a location into - * an instance of a class that you provide - * @param modelLayout This is the layout used to represent a single list item. - * You will be responsible for populating an instance of the corresponding - * view with the data from an instance of modelClass. - * @param ref The Firebase location to watch for data changes. Can also be a slice of a location, - * using some combination of {@code limit()}, {@code startAt()}, and {@code endAt()}. + * @param query The Firebase location to watch for data changes. Can also be a slice of a + * location, using some combination of {@code limit()}, {@code startAt()}, and + * {@code endAt()}. + * @see #FirebaseListAdapter(Activity, FirebaseArray, Class, int) */ public FirebaseListAdapter(Activity activity, Class modelClass, - int modelLayout, - Query ref) { - this(activity, modelClass, modelLayout, new FirebaseArray(ref)); + @LayoutRes int modelLayout, + Query query) { + this(activity, new FirebaseArray(query), modelClass, modelLayout); } + @Override + public void startListening() { + if (!mSnapshots.isListening(this)) { + mSnapshots.addChangeEventListener(this); + } + } + + @Override public void cleanup() { - mSnapshots.removeChangeEventListener(mListener); + mSnapshots.removeChangeEventListener(this); } @Override - public int getCount() { - return mSnapshots.size(); + public void onChildChanged(ChangeEventListener.EventType type, int index, int oldIndex) { + notifyDataSetChanged(); + } + + @Override + public void onDataChanged() { + } + + @Override + public void onCancelled(DatabaseError error) { + Log.w(TAG, error.toException()); } @Override @@ -119,21 +97,21 @@ public T getItem(int position) { return parseSnapshot(mSnapshots.get(position)); } - /** - * This method parses the DataSnapshot into the requested type. You can override it in subclasses - * to do custom parsing. - * - * @param snapshot the DataSnapshot to extract the model from - * @return the model extracted from the DataSnapshot - */ - protected T parseSnapshot(DataSnapshot snapshot) { + @Override + public T parseSnapshot(DataSnapshot snapshot) { return snapshot.getValue(mModelClass); } + @Override public DatabaseReference getRef(int position) { return mSnapshots.get(position).getRef(); } + @Override + public int getCount() { + return mSnapshots.size(); + } + @Override public long getItemId(int i) { // http://stackoverflow.com/questions/5100071/whats-the-purpose-of-item-ids-in-android-listview-adapter @@ -153,26 +131,6 @@ public View getView(int position, View view, ViewGroup viewGroup) { return view; } - /** - * @see ChangeEventListener#onChildChanged(ChangeEventListener.EventType, int, int) - */ - protected void onChildChanged(ChangeEventListener.EventType type, int index, int oldIndex) { - notifyDataSetChanged(); - } - - /** - * @see ChangeEventListener#onDataChanged() - */ - protected void onDataChanged() { - } - - /** - * @see ChangeEventListener#onCancelled(DatabaseError) - */ - protected void onCancelled(DatabaseError error) { - Log.w(TAG, error.toException()); - } - /** * Each time the data at the given Firebase location changes, * this method will be called for each item that needs to be displayed. diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseRecyclerAdapter.java b/database/src/main/java/com/firebase/ui/database/FirebaseRecyclerAdapter.java index 8b6118b82..46598b7f7 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseRecyclerAdapter.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseRecyclerAdapter.java @@ -1,17 +1,3 @@ -/* - * Copyright 2016 Google Inc. All Rights Reserved. - * - * 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.firebase.ui.database; import android.support.annotation.LayoutRes; @@ -30,132 +16,122 @@ import java.lang.reflect.InvocationTargetException; /** - * This class is a generic way of backing an RecyclerView with a Firebase location. - * It handles all of the child events at the given Firebase location. It marshals received data into the given - * class type. - *

- * To use this class in your app, subclass it passing in all required parameters and implement the - * populateViewHolder method. + * This class is a generic way of backing a {@link RecyclerView} with a Firebase location. It + * handles all of the child events at the given Firebase location and marshals received data into + * the given class type. *

- *

- *     private static class ChatMessageViewHolder extends RecyclerView.ViewHolder {
- *         TextView messageText;
- *         TextView nameText;
- *
- *         public ChatMessageViewHolder(View itemView) {
- *             super(itemView);
- *             nameText = (TextView)itemView.findViewById(android.R.id.text1);
- *             messageText = (TextView) itemView.findViewById(android.R.id.text2);
- *         }
- *     }
- *
- *     FirebaseRecyclerAdapter adapter;
- *     DatabaseReference ref = FirebaseDatabase.getInstance().getReference();
- *
- *     RecyclerView recycler = (RecyclerView) findViewById(R.id.messages_recycler);
- *     recycler.setHasFixedSize(true);
- *     recycler.setLayoutManager(new LinearLayoutManager(this));
- *
- *     adapter = new FirebaseRecyclerAdapter(
- *           ChatMessage.class, android.R.layout.two_line_list_item, ChatMessageViewHolder.class, ref) {
- *         public void populateViewHolder(ChatMessageViewHolder chatMessageViewHolder,
- *                                        ChatMessage chatMessage,
- *                                        int position) {
- *             chatMessageViewHolder.nameText.setText(chatMessage.getName());
- *             chatMessageViewHolder.messageText.setText(chatMessage.getMessage());
- *         }
- *     };
- *     recycler.setAdapter(mAdapter);
- * 
+ * See the README + * for an in-depth tutorial on how to set up the FirebaseRecyclerAdapter. * * @param The Java class that maps to the type of objects stored in the Firebase location. - * @param The ViewHolder class that contains the Views in the layout that is shown for each - * object. - * @deprecated use {@link com.firebase.ui.database.adapter.FirebaseRecyclerAdapter} instead + * @param The {@link RecyclerView.ViewHolder} class that contains the Views in the layout that + * is shown for each object. */ -@Deprecated public abstract class FirebaseRecyclerAdapter - extends RecyclerView.Adapter { + extends RecyclerView.Adapter implements FirebaseAdapter { private static final String TAG = "FirebaseRecyclerAdapter"; - private FirebaseArray mSnapshots; - private Class mModelClass; + protected FirebaseArray mSnapshots; + protected Class mModelClass; protected Class mViewHolderClass; protected int mModelLayout; - private ChangeEventListener mListener; - FirebaseRecyclerAdapter(Class modelClass, - @LayoutRes int modelLayout, - Class viewHolderClass, - FirebaseArray snapshots) { + /** + * @param snapshots The data used to populate the adapter + * @param modelClass Firebase will marshall the data at a location into an instance of a + * class that you provide + * @param modelLayout This is the layout used to represent a single item in the list. You + * will be responsible for populating an instance of the corresponding + * view with the data from an instance of modelClass. + * @param viewHolderClass The class that hold references to all sub-views in an instance + * modelLayout. + */ + public FirebaseRecyclerAdapter(FirebaseArray snapshots, + Class modelClass, + @LayoutRes int modelLayout, + Class viewHolderClass) { + mSnapshots = snapshots; mModelClass = modelClass; - mModelLayout = modelLayout; mViewHolderClass = viewHolderClass; - mSnapshots = snapshots; + mModelLayout = modelLayout; - mListener = mSnapshots.addChangeEventListener(new ChangeEventListener() { - @Override - public void onChildChanged(EventType type, int index, int oldIndex) { - FirebaseRecyclerAdapter.this.onChildChanged(type, index, oldIndex); - } - - @Override - public void onDataChanged() { - FirebaseRecyclerAdapter.this.onDataChanged(); - } - - @Override - public void onCancelled(DatabaseError error) { - FirebaseRecyclerAdapter.this.onCancelled(error); - } - }); + startListening(); } /** - * @param modelClass Firebase will marshall the data at a location into - * an instance of a class that you provide - * @param modelLayout This is the layout used to represent a single item in the list. - * You will be responsible for populating an instance of the corresponding - * view with the data from an instance of modelClass. - * @param viewHolderClass The class that hold references to all sub-views in an instance modelLayout. - * @param ref The Firebase location to watch for data changes. Can also be a slice of a location, - * using some combination of {@code limit()}, {@code startAt()}, and {@code endAt()}. + * @param query The Firebase location to watch for data changes. Can also be a slice of a + * location, using some combination of {@code limit()}, {@code startAt()}, and + * {@code endAt()}. + * @see #FirebaseRecyclerAdapter(FirebaseArray, Class, int, Class) */ public FirebaseRecyclerAdapter(Class modelClass, - int modelLayout, + @LayoutRes int modelLayout, Class viewHolderClass, - Query ref) { - this(modelClass, modelLayout, viewHolderClass, new FirebaseArray(ref)); + Query query) { + this(new FirebaseArray(query), modelClass, modelLayout, viewHolderClass); } + @Override + public void startListening() { + if (!mSnapshots.isListening(this)) { + mSnapshots.addChangeEventListener(this); + } + } + + @Override public void cleanup() { - mSnapshots.removeChangeEventListener(mListener); + mSnapshots.removeChangeEventListener(this); } @Override - public int getItemCount() { - return mSnapshots.size(); + public void onChildChanged(ChangeEventListener.EventType type, int index, int oldIndex) { + switch (type) { + case ADDED: + notifyItemInserted(index); + break; + case CHANGED: + notifyItemChanged(index); + break; + case REMOVED: + notifyItemRemoved(index); + break; + case MOVED: + notifyItemMoved(oldIndex, index); + break; + default: + throw new IllegalStateException("Incomplete case statement"); + } + } + + @Override + public void onDataChanged() { + } + + @Override + public void onCancelled(DatabaseError error) { + Log.w(TAG, error.toException()); } + @Override public T getItem(int position) { return parseSnapshot(mSnapshots.get(position)); } - /** - * This method parses the DataSnapshot into the requested type. You can override it in subclasses - * to do custom parsing. - * - * @param snapshot the DataSnapshot to extract the model from - * @return the model extracted from the DataSnapshot - */ - protected T parseSnapshot(DataSnapshot snapshot) { + @Override + public T parseSnapshot(DataSnapshot snapshot) { return snapshot.getValue(mModelClass); } + @Override public DatabaseReference getRef(int position) { return mSnapshots.get(position).getRef(); } + @Override + public int getItemCount() { + return mSnapshots.size(); + } + @Override public long getItemId(int position) { // http://stackoverflow.com/questions/5100071/whats-the-purpose-of-item-ids-in-android-listview-adapter @@ -179,50 +155,15 @@ public VH onCreateViewHolder(ViewGroup parent, int viewType) { } } - @Override - public void onBindViewHolder(VH viewHolder, int position) { - T model = getItem(position); - populateViewHolder(viewHolder, model, position); - } - @Override public int getItemViewType(int position) { return mModelLayout; } - /** - * @see ChangeEventListener#onChildChanged(ChangeEventListener.EventType, int, int) - */ - protected void onChildChanged(ChangeEventListener.EventType type, int index, int oldIndex) { - switch (type) { - case ADDED: - notifyItemInserted(index); - break; - case CHANGED: - notifyItemChanged(index); - break; - case REMOVED: - notifyItemRemoved(index); - break; - case MOVED: - notifyItemMoved(oldIndex, index); - break; - default: - throw new IllegalStateException("Incomplete case statement"); - } - } - - /** - * @see ChangeEventListener#onDataChanged() - */ - protected void onDataChanged() { - } - - /** - * @see ChangeEventListener#onCancelled(DatabaseError) - */ - protected void onCancelled(DatabaseError error) { - Log.w(TAG, error.toException()); + @Override + public void onBindViewHolder(VH viewHolder, int position) { + T model = getItem(position); + populateViewHolder(viewHolder, model, position); } /** diff --git a/database/src/main/java/com/firebase/ui/database/adapter/FirebaseIndexListAdapter.java b/database/src/main/java/com/firebase/ui/database/adapter/FirebaseIndexListAdapter.java deleted file mode 100644 index a60872c2a..000000000 --- a/database/src/main/java/com/firebase/ui/database/adapter/FirebaseIndexListAdapter.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.firebase.ui.database.adapter; - -import android.app.Activity; -import android.support.annotation.LayoutRes; - -import com.firebase.ui.database.FirebaseArray; -import com.firebase.ui.database.FirebaseIndexArray; -import com.google.firebase.database.DatabaseReference; -import com.google.firebase.database.Query; - -public abstract class FirebaseIndexListAdapter extends FirebaseListAdapter { - /** - * @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 in {@code - * keyQuery}'s location represents a list item in the {@code ListView}. - * @see FirebaseListAdapter#FirebaseListAdapter(Activity, FirebaseArray, Class, int) - */ - public FirebaseIndexListAdapter(Activity activity, - Class modelClass, - @LayoutRes int modelLayout, - Query keyQuery, - DatabaseReference dataRef) { - super(activity, new FirebaseIndexArray(keyQuery, dataRef), modelClass, modelLayout); - } -} diff --git a/database/src/main/java/com/firebase/ui/database/adapter/FirebaseIndexRecyclerAdapter.java b/database/src/main/java/com/firebase/ui/database/adapter/FirebaseIndexRecyclerAdapter.java deleted file mode 100644 index de8a4773d..000000000 --- a/database/src/main/java/com/firebase/ui/database/adapter/FirebaseIndexRecyclerAdapter.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.firebase.ui.database.adapter; - -import android.support.annotation.LayoutRes; -import android.support.v7.widget.RecyclerView; - -import com.firebase.ui.database.FirebaseArray; -import com.firebase.ui.database.FirebaseIndexArray; -import com.google.firebase.database.DatabaseReference; -import com.google.firebase.database.Query; - -public abstract class FirebaseIndexRecyclerAdapter - extends FirebaseRecyclerAdapter { - /** - * @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 FirebaseRecyclerAdapter#FirebaseRecyclerAdapter(FirebaseArray, Class, int, Class) - */ - public FirebaseIndexRecyclerAdapter(Class modelClass, - @LayoutRes int modelLayout, - Class viewHolderClass, - Query keyQuery, - DatabaseReference dataRef) { - super(new FirebaseIndexArray(keyQuery, dataRef), modelClass, modelLayout, viewHolderClass); - } -} diff --git a/database/src/main/java/com/firebase/ui/database/adapter/FirebaseListAdapter.java b/database/src/main/java/com/firebase/ui/database/adapter/FirebaseListAdapter.java deleted file mode 100644 index 8be36d1ee..000000000 --- a/database/src/main/java/com/firebase/ui/database/adapter/FirebaseListAdapter.java +++ /dev/null @@ -1,149 +0,0 @@ -package com.firebase.ui.database.adapter; - -import android.app.Activity; -import android.support.annotation.LayoutRes; -import android.util.Log; -import android.view.View; -import android.view.ViewGroup; -import android.widget.BaseAdapter; -import android.widget.ListView; - -import com.firebase.ui.database.ChangeEventListener; -import com.firebase.ui.database.FirebaseArray; -import com.google.firebase.database.DataSnapshot; -import com.google.firebase.database.DatabaseError; -import com.google.firebase.database.DatabaseReference; -import com.google.firebase.database.Query; - -/** - * This class is a generic way of backing an Android {@link android.widget.ListView} with a Firebase - * location. It handles all of the child events at the given Firebase location. It marshals received - * data into the given class type. - *

- * See the README - * for an in-depth tutorial on how to set up the FirebaseListAdapter. - * - * @param The class type to use as a model for the data contained in the children of the given - * Firebase location - */ -public abstract class FirebaseListAdapter extends BaseAdapter implements FirebaseAdapter { - private static final String TAG = "FirebaseListAdapter"; - - protected Activity mActivity; - protected FirebaseArray mSnapshots; - protected Class mModelClass; - protected int mLayout; - - /** - * @param activity The {@link Activity} containing the {@link ListView} - * @param modelClass Firebase will marshall the data at a location into an instance of a class - * that you provide - * @param modelLayout This is the layout used to represent a single list item. You will be - * responsible for populating an instance of the corresponding view with the - * data from an instance of modelClass. - * @param snapshots The data used to populate the adapter - */ - public FirebaseListAdapter(Activity activity, - FirebaseArray snapshots, - Class modelClass, - @LayoutRes int modelLayout) { - mActivity = activity; - mSnapshots = snapshots; - mModelClass = modelClass; - mLayout = modelLayout; - - startListening(); - } - - /** - * @param query The Firebase location to watch for data changes. Can also be a slice of a - * location, using some combination of {@code limit()}, {@code startAt()}, and - * {@code endAt()}. - * @see #FirebaseListAdapter(Activity, FirebaseArray, Class, int) - */ - public FirebaseListAdapter(Activity activity, - Class modelClass, - @LayoutRes int modelLayout, - Query query) { - this(activity, new FirebaseArray(query), modelClass, modelLayout); - } - - @Override - public void startListening() { - if (!mSnapshots.isListening(this)) { - mSnapshots.addChangeEventListener(this); - } - } - - @Override - public void cleanup() { - mSnapshots.removeChangeEventListener(this); - } - - @Override - public void onChildChanged(ChangeEventListener.EventType type, int index, int oldIndex) { - notifyDataSetChanged(); - } - - @Override - public void onDataChanged() { - } - - @Override - public void onCancelled(DatabaseError error) { - Log.w(TAG, error.toException()); - } - - @Override - public T getItem(int position) { - return parseSnapshot(mSnapshots.get(position)); - } - - @Override - public T parseSnapshot(DataSnapshot snapshot) { - return snapshot.getValue(mModelClass); - } - - @Override - public DatabaseReference getRef(int position) { - return mSnapshots.get(position).getRef(); - } - - @Override - public int getCount() { - return mSnapshots.size(); - } - - @Override - public long getItemId(int i) { - // http://stackoverflow.com/questions/5100071/whats-the-purpose-of-item-ids-in-android-listview-adapter - return mSnapshots.get(i).getKey().hashCode(); - } - - @Override - public View getView(int position, View view, ViewGroup viewGroup) { - if (view == null) { - view = mActivity.getLayoutInflater().inflate(mLayout, viewGroup, false); - } - - T model = getItem(position); - - // Call out to subclass to marshall this model into the provided view - populateView(view, model, position); - return view; - } - - /** - * Each time the data at the given Firebase location changes, - * this method will be called for each item that needs to be displayed. - * The first two arguments correspond to the mLayout and mModelClass given to the constructor of - * this class. The third argument is the item's position in the list. - *

- * Your implementation should populate the view using the data contained in the model. - * - * @param v The view to populate - * @param model The object containing the data used to populate the view - * @param position The position in the list of the view being populated - */ - protected abstract void populateView(View v, T model, int position); -} diff --git a/database/src/main/java/com/firebase/ui/database/adapter/FirebaseRecyclerAdapter.java b/database/src/main/java/com/firebase/ui/database/adapter/FirebaseRecyclerAdapter.java deleted file mode 100644 index 52992454a..000000000 --- a/database/src/main/java/com/firebase/ui/database/adapter/FirebaseRecyclerAdapter.java +++ /dev/null @@ -1,184 +0,0 @@ -package com.firebase.ui.database.adapter; - -import android.support.annotation.LayoutRes; -import android.support.v7.widget.RecyclerView; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import com.firebase.ui.database.ChangeEventListener; -import com.firebase.ui.database.FirebaseArray; -import com.google.firebase.database.DataSnapshot; -import com.google.firebase.database.DatabaseError; -import com.google.firebase.database.DatabaseReference; -import com.google.firebase.database.Query; - -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; - -/** - * This class is a generic way of backing a {@link RecyclerView} with a Firebase location. It - * handles all of the child events at the given Firebase location and marshals received data into - * the given class type. - *

- * See the README - * for an in-depth tutorial on how to set up the FirebaseRecyclerAdapter. - * - * @param The Java class that maps to the type of objects stored in the Firebase location. - * @param The {@link RecyclerView.ViewHolder} class that contains the Views in the layout that - * is shown for each object. - */ -public abstract class FirebaseRecyclerAdapter - extends RecyclerView.Adapter implements FirebaseAdapter { - private static final String TAG = "FirebaseRecyclerAdapter"; - - protected FirebaseArray mSnapshots; - protected Class mModelClass; - protected Class mViewHolderClass; - protected int mModelLayout; - - /** - * @param snapshots The data used to populate the adapter - * @param modelClass Firebase will marshall the data at a location into an instance of a - * class that you provide - * @param modelLayout This is the layout used to represent a single item in the list. You - * will be responsible for populating an instance of the corresponding - * view with the data from an instance of modelClass. - * @param viewHolderClass The class that hold references to all sub-views in an instance - * modelLayout. - */ - public FirebaseRecyclerAdapter(FirebaseArray snapshots, - Class modelClass, - @LayoutRes int modelLayout, - Class viewHolderClass) { - mSnapshots = snapshots; - mModelClass = modelClass; - mViewHolderClass = viewHolderClass; - mModelLayout = modelLayout; - - startListening(); - } - - /** - * @param query The Firebase location to watch for data changes. Can also be a slice of a - * location, using some combination of {@code limit()}, {@code startAt()}, and - * {@code endAt()}. - * @see #FirebaseRecyclerAdapter(FirebaseArray, Class, int, Class) - */ - public FirebaseRecyclerAdapter(Class modelClass, - @LayoutRes int modelLayout, - Class viewHolderClass, - Query query) { - this(new FirebaseArray(query), modelClass, modelLayout, viewHolderClass); - } - - @Override - public void startListening() { - if (!mSnapshots.isListening(this)) { - mSnapshots.addChangeEventListener(this); - } - } - - @Override - public void cleanup() { - mSnapshots.removeChangeEventListener(this); - } - - @Override - public void onChildChanged(ChangeEventListener.EventType type, int index, int oldIndex) { - switch (type) { - case ADDED: - notifyItemInserted(index); - break; - case CHANGED: - notifyItemChanged(index); - break; - case REMOVED: - notifyItemRemoved(index); - break; - case MOVED: - notifyItemMoved(oldIndex, index); - break; - default: - throw new IllegalStateException("Incomplete case statement"); - } - } - - @Override - public void onDataChanged() { - } - - @Override - public void onCancelled(DatabaseError error) { - Log.w(TAG, error.toException()); - } - - @Override - public T getItem(int position) { - return parseSnapshot(mSnapshots.get(position)); - } - - @Override - public T parseSnapshot(DataSnapshot snapshot) { - return snapshot.getValue(mModelClass); - } - - @Override - public DatabaseReference getRef(int position) { - return mSnapshots.get(position).getRef(); - } - - @Override - public int getItemCount() { - return mSnapshots.size(); - } - - @Override - public long getItemId(int position) { - // http://stackoverflow.com/questions/5100071/whats-the-purpose-of-item-ids-in-android-listview-adapter - return mSnapshots.get(position).getKey().hashCode(); - } - - @Override - public VH onCreateViewHolder(ViewGroup parent, int viewType) { - View view = LayoutInflater.from(parent.getContext()).inflate(viewType, parent, false); - try { - Constructor constructor = mViewHolderClass.getConstructor(View.class); - return constructor.newInstance(view); - } catch (NoSuchMethodException e) { - throw new RuntimeException(e); - } catch (InvocationTargetException e) { - throw new RuntimeException(e); - } catch (InstantiationException e) { - throw new RuntimeException(e); - } catch (IllegalAccessException e) { - throw new RuntimeException(e); - } - } - - @Override - public int getItemViewType(int position) { - return mModelLayout; - } - - @Override - public void onBindViewHolder(VH viewHolder, int position) { - T model = getItem(position); - populateViewHolder(viewHolder, model, position); - } - - /** - * Each time the data at the given Firebase location changes, - * this method will be called for each item that needs to be displayed. - * The first two arguments correspond to the mLayout and mModelClass given to the constructor of - * this class. The third argument is the item's position in the list. - *

- * Your implementation should populate the view using the data contained in the model. - * - * @param viewHolder The view to populate - * @param model The object containing the data used to populate the view - * @param position The position in the list of the view being populated - */ - protected abstract void populateViewHolder(VH viewHolder, T model, int position); -} From cd0b0f3a7e0c1a5e46cd597da48ebff809d9cc32 Mon Sep 17 00:00:00 2001 From: Alex Saveau Date: Wed, 15 Feb 2017 20:15:08 -0800 Subject: [PATCH 74/93] Cleanup Signed-off-by: Alex Saveau --- .../ui/database/ChangeEventListener.java | 18 ++++++++++-------- .../firebase/ui/database/FirebaseArray.java | 13 +++++++------ .../ui/database/FirebaseIndexArray.java | 3 ++- 3 files changed, 19 insertions(+), 15 deletions(-) diff --git a/database/src/main/java/com/firebase/ui/database/ChangeEventListener.java b/database/src/main/java/com/firebase/ui/database/ChangeEventListener.java index a8028dc47..7703ba11b 100644 --- a/database/src/main/java/com/firebase/ui/database/ChangeEventListener.java +++ b/database/src/main/java/com/firebase/ui/database/ChangeEventListener.java @@ -40,18 +40,20 @@ enum EventType { * * @param type The type of event received * @param index The index at which the change occurred - * @param oldIndex If {@code type} is a moved event, the previous index of the moved child. - * For any other event, {@code oldIndex} will be -1. + * @param oldIndex If {@code type} is a moved event, the previous index of the moved child. For + * any other event, {@code oldIndex} will be -1. */ void onChildChanged(EventType type, int index, int oldIndex); - /** This method will be triggered each time updates from the database have been completely processed. - * So the first time this method is called, the initial data has been loaded - including the case - * when no data at all is available. Each next time the method is called, a complete update (potentially - * consisting of updates to multiple child items) has been completed. + /** + * This method will be triggered each time updates from the database have been completely + * processed. So the first time this method is called, the initial data has been loaded - + * including the case when no data at all is available. Each next time the method is called, a + * complete update (potentially consisting of updates to multiple child items) has been + * completed. *

- * You would typically override this method to hide a loading indicator (after the initial load) or - * to complete a batch update to a UI element. + * You would typically override this method to hide a loading indicator (after the initial load) + * or to complete a batch update to a UI element. */ void onDataChanged(); diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseArray.java b/database/src/main/java/com/firebase/ui/database/FirebaseArray.java index d25d0e945..3074a3660 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseArray.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseArray.java @@ -34,10 +34,11 @@ * This class implements a collection on top of a Firebase location. */ public class FirebaseArray extends ImmutableList implements ChildEventListener, ValueEventListener { - private Query mQuery; - private boolean mNotifyListeners = true; private final List mListeners = new CopyOnWriteArrayList<>(); private List mSubscribers = new ArrayList<>(); + + private Query mQuery; + private boolean mNotifyListeners = true; private List mSnapshots = new ArrayList<>(); /** @@ -49,6 +50,10 @@ public FirebaseArray(Query query) { mQuery = query; } + private static void checkNotNull(Object o) { + if (o == null) throw new IllegalArgumentException("Listener cannot be null."); + } + /** * Add a listener for change events and errors occurring at the location provided in {@link * #FirebaseArray(Query)}. @@ -127,10 +132,6 @@ protected void notifySubscriptionEventListeners(SubscriptionEventListener.EventT } } - private static void checkNotNull(Object o) { - if (o == null) throw new IllegalArgumentException("Listener cannot be null."); - } - /** * @return true if {@link FirebaseArray} is listening for change events from the Firebase * database, false otherwise 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 27be089ad..2fd653823 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseIndexArray.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseIndexArray.java @@ -133,7 +133,8 @@ public void onChildMoved(DataSnapshot keySnapshot, String previousChildKey) { @Override public void onCancelled(DatabaseError error) { - Log.e(TAG, "A fatal error occurred retrieving the necessary keys to populate your adapter."); + Log.e(TAG, + "A fatal error occurred retrieving the necessary keys to populate your adapter."); super.onCancelled(error); } From bcc8de19c571c17b7e11205a848b086e87318e76 Mon Sep 17 00:00:00 2001 From: Alex Saveau Date: Thu, 16 Feb 2017 18:23:43 -0800 Subject: [PATCH 75/93] Fix compile errors Signed-off-by: Alex Saveau --- .../main/java/com/firebase/uidemo/database/ChatActivity.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/firebase/uidemo/database/ChatActivity.java b/app/src/main/java/com/firebase/uidemo/database/ChatActivity.java index 6c535ff1c..32d5b210a 100644 --- a/app/src/main/java/com/firebase/uidemo/database/ChatActivity.java +++ b/app/src/main/java/com/firebase/uidemo/database/ChatActivity.java @@ -28,8 +28,8 @@ import android.widget.TextView; import android.widget.Toast; -import com.firebase.ui.database.adapter.FirebaseIndexRecyclerAdapter; -import com.firebase.ui.database.adapter.FirebaseRecyclerAdapter; +import com.firebase.ui.database.FirebaseIndexRecyclerAdapter; +import com.firebase.ui.database.FirebaseRecyclerAdapter; import com.firebase.uidemo.R; import com.firebase.uidemo.util.SignInResultNotifier; import com.google.android.gms.tasks.OnSuccessListener; From fc615564f712156e4de0c185023b2aa4fd4690f5 Mon Sep 17 00:00:00 2001 From: Alex Saveau Date: Fri, 17 Feb 2017 19:00:26 -0800 Subject: [PATCH 76/93] Fix SubscriptionEventListener.java mess Signed-off-by: Alex Saveau --- .../firebase/ui/database/FirebaseArray.java | 41 ++++--------------- .../ui/database/FirebaseArrayOfObjects.java | 23 +---------- .../database/SubscriptionEventListener.java | 12 ------ 3 files changed, 9 insertions(+), 67 deletions(-) delete mode 100644 database/src/main/java/com/firebase/ui/database/SubscriptionEventListener.java diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseArray.java b/database/src/main/java/com/firebase/ui/database/FirebaseArray.java index 3074a3660..d3feba24e 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseArray.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseArray.java @@ -35,7 +35,6 @@ */ public class FirebaseArray extends ImmutableList implements ChildEventListener, ValueEventListener { private final List mListeners = new CopyOnWriteArrayList<>(); - private List mSubscribers = new ArrayList<>(); private Query mQuery; private boolean mNotifyListeners = true; @@ -66,7 +65,6 @@ public ChangeEventListener addChangeEventListener(@NonNull ChangeEventListener l checkNotNull(listener); mListeners.add(listener); - notifySubscriptionEventListeners(SubscriptionEventListener.EventType.ADDED); if (mListeners.size() <= 1) { // Only start listening when the first listener is added mQuery.addChildEventListener(this); mQuery.addValueEventListener(this); @@ -84,10 +82,10 @@ public ChangeEventListener addChangeEventListener(@NonNull ChangeEventListener l * #FirebaseArray(Query)}. The list will be empty after this call returns. * * @param listener the listener to remove + * @see #removeAllListeners() */ public void removeChangeEventListener(@NonNull ChangeEventListener listener) { mListeners.remove(listener); - notifySubscriptionEventListeners(SubscriptionEventListener.EventType.REMOVED); if (mListeners.isEmpty()) { mQuery.removeEventListener((ValueEventListener) this); mQuery.removeEventListener((ChildEventListener) this); @@ -96,39 +94,13 @@ public void removeChangeEventListener(@NonNull ChangeEventListener listener) { } /** - * Add a listener for subscription events eg additions/removals of {@link ChangeEventListener}s. + * Removes all {@link ChangeEventListener}s. The list will be empty after this call returns. * - * @param listener the listener to be called with changes - * @return a reference to the listener provided. Save this to remove the listener later - * @throws IllegalArgumentException if the listener is null - */ - public SubscriptionEventListener addSubscriptionEventListener(@NonNull SubscriptionEventListener listener) { - checkNotNull(listener); - - for (SubscriptionEventListener ignored : mSubscribers) { - listener.onSubscriptionAdded(); - } - - mSubscribers.add(listener); - return listener; - } - - /** - * Remove a {@link SubscriptionEventListener} for this {@link FirebaseArray}. - * - * @param listener the listener to remove + * @see #removeChangeEventListener(ChangeEventListener) */ - public void removeSubscriptionEventListener(@NonNull SubscriptionEventListener listener) { - mSubscribers.remove(listener); - } - - protected void notifySubscriptionEventListeners(SubscriptionEventListener.EventType eventType) { - for (SubscriptionEventListener listener : mSubscribers) { - if (eventType == SubscriptionEventListener.EventType.ADDED) { - listener.onSubscriptionAdded(); - } else if (eventType == SubscriptionEventListener.EventType.REMOVED) { - listener.onSubscriptionRemoved(); - } + public void removeAllListeners() { + for (ChangeEventListener listener : mListeners) { + removeChangeEventListener(listener); } } @@ -280,6 +252,7 @@ public List toObjectsList(Class modelClass) { * * @param parser a custom {@link SnapshotParser} to manually convert each {@link DataSnapshot} * to its model type + * @see #toObjectsList(Class) */ public List toObjectsList(Class modelClass, SnapshotParser parser) { return FirebaseArrayOfObjects.newInstance(this, modelClass, parser); diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseArrayOfObjects.java b/database/src/main/java/com/firebase/ui/database/FirebaseArrayOfObjects.java index 4e4fe064c..12de32040 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseArrayOfObjects.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseArrayOfObjects.java @@ -166,19 +166,16 @@ public int hashCode() { @Override public String toString() { - return "FirebaseArrayOfObjects{" + - "mSnapshots=" + mSnapshots + - '}'; + return mSnapshots.toString(); } protected static class FirebaseArrayOfObjectsOptimized extends FirebaseArrayOfObjects - implements ChangeEventListener, SubscriptionEventListener { + implements ChangeEventListener { protected List mObjects = new ArrayList<>(); public FirebaseArrayOfObjectsOptimized(FirebaseArray snapshots, Class modelClass) { super(snapshots, modelClass); snapshots.addChangeEventListener(this); - snapshots.addSubscriptionEventListener(this); } @Override @@ -204,22 +201,6 @@ public void onChildChanged(ChangeEventListener.EventType type, int index, int ol } } - @Override - public void onSubscriptionRemoved() { - FirebaseArray snapshots = (FirebaseArray) mSnapshots; - if (!snapshots.isListening()) { - snapshots.removeChangeEventListener(this); - } - } - - @Override - public void onSubscriptionAdded() { - FirebaseArray snapshots = (FirebaseArray) mSnapshots; - if (!snapshots.isListening(this)) { - snapshots.addChangeEventListener(this); - } - } - @Override public void onDataChanged() { } diff --git a/database/src/main/java/com/firebase/ui/database/SubscriptionEventListener.java b/database/src/main/java/com/firebase/ui/database/SubscriptionEventListener.java deleted file mode 100644 index 0b9035ea9..000000000 --- a/database/src/main/java/com/firebase/ui/database/SubscriptionEventListener.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.firebase.ui.database; - -public interface SubscriptionEventListener { - enum EventType { - ADDED, - REMOVED - } - - void onSubscriptionAdded(); - - void onSubscriptionRemoved(); -} From 2417e2a106f8daecb640aebac6ff15175b4b6e98 Mon Sep 17 00:00:00 2001 From: Alex Saveau Date: Mon, 20 Feb 2017 11:01:03 -0800 Subject: [PATCH 77/93] Make toStrings nicer Signed-off-by: Alex Saveau --- .../java/com/firebase/ui/database/FirebaseArray.java | 10 +++++----- .../com/firebase/ui/database/FirebaseIndexArray.java | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseArray.java b/database/src/main/java/com/firebase/ui/database/FirebaseArray.java index d3feba24e..eddbe6ec7 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseArray.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseArray.java @@ -317,11 +317,11 @@ public int hashCode() { @Override public String toString() { - return "FirebaseArray{" + - "mIsListening=" + isListening() + - ", mQuery=" + mQuery + - ", mSnapshots=" + mSnapshots + - '}'; + if (isListening()) { + return "FirebaseArray is listening at " + mQuery + ":\n" + mSnapshots; + } else { + return "FirebaseArray is inactive"; + } } /** 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 2fd653823..1d9c36faa 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseIndexArray.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseIndexArray.java @@ -244,10 +244,10 @@ public int hashCode() { @Override public String toString() { - return "FirebaseIndexArray{" + - "mIsListening=" + isListening() + - ", mDataRef=" + mDataRef + - ", mDataSnapshots=" + mDataSnapshots + - '}'; + if (isListening()) { + return "FirebaseIndexArray is listening at " + mDataRef + ":\n" + mDataSnapshots; + } else { + return "FirebaseIndexArray is inactive"; + } } } From 1948ece083120124661ed25d3b07e72c277fa958 Mon Sep 17 00:00:00 2001 From: Alex Saveau Date: Mon, 20 Feb 2017 11:08:44 -0800 Subject: [PATCH 78/93] Add annotations and make extending FirebaseArray.java sticker Signed-off-by: Alex Saveau --- .../firebase/ui/database/FirebaseArray.java | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseArray.java b/database/src/main/java/com/firebase/ui/database/FirebaseArray.java index eddbe6ec7..b9fae66a6 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseArray.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseArray.java @@ -14,6 +14,7 @@ package com.firebase.ui.database; +import android.support.annotation.CallSuper; import android.support.annotation.NonNull; import android.support.annotation.RestrictTo; @@ -61,6 +62,7 @@ private static void checkNotNull(Object o) { * @return a reference to the listener provided. Save this to remove the listener later * @throws IllegalArgumentException if the listener is null */ + @CallSuper public ChangeEventListener addChangeEventListener(@NonNull ChangeEventListener listener) { checkNotNull(listener); @@ -84,6 +86,7 @@ public ChangeEventListener addChangeEventListener(@NonNull ChangeEventListener l * @param listener the listener to remove * @see #removeAllListeners() */ + @CallSuper public void removeChangeEventListener(@NonNull ChangeEventListener listener) { mListeners.remove(listener); if (mListeners.isEmpty()) { @@ -98,6 +101,7 @@ public void removeChangeEventListener(@NonNull ChangeEventListener listener) { * * @see #removeChangeEventListener(ChangeEventListener) */ + @CallSuper public void removeAllListeners() { for (ChangeEventListener listener : mListeners) { removeChangeEventListener(listener); @@ -108,14 +112,14 @@ public void removeAllListeners() { * @return true if {@link FirebaseArray} is listening for change events from the Firebase * database, false otherwise */ - public boolean isListening() { + public final boolean isListening() { return !mListeners.isEmpty(); } /** * @return true if the provided {@link ChangeEventListener} is listening for changes */ - public boolean isListening(ChangeEventListener listener) { + public final boolean isListening(ChangeEventListener listener) { return mListeners.contains(listener); } @@ -178,27 +182,27 @@ protected void setShouldNotifyListeners(boolean notifyListeners) { mNotifyListeners = notifyListeners; } - protected void notifyChangeEventListeners(ChangeEventListener.EventType type, int index) { + protected final void notifyChangeEventListeners(ChangeEventListener.EventType type, int index) { notifyChangeEventListeners(type, index, -1); } - protected void notifyChangeEventListeners(ChangeEventListener.EventType type, - int index, - int oldIndex) { + protected final void notifyChangeEventListeners(ChangeEventListener.EventType type, + int index, + int oldIndex) { if (!mNotifyListeners) return; for (ChangeEventListener listener : mListeners) { listener.onChildChanged(type, index, oldIndex); } } - protected void notifyListenersOnDataChanged() { + protected final void notifyListenersOnDataChanged() { if (!mNotifyListeners) return; for (ChangeEventListener listener : mListeners) { listener.onDataChanged(); } } - protected void notifyListenersOnCancelled(DatabaseError error) { + protected final void notifyListenersOnCancelled(DatabaseError error) { if (!mNotifyListeners) return; for (ChangeEventListener listener : mListeners) { listener.onCancelled(error); From 1c31e1623030879b3b730afd6c1f249a815cf6ea Mon Sep 17 00:00:00 2001 From: Alex Saveau Date: Tue, 21 Feb 2017 17:02:54 -0800 Subject: [PATCH 79/93] Fix bug where custom snapshot parser was not applied in FirebaseArrayOfObjects.java if FirebaseArray.java already had data Signed-off-by: Alex Saveau --- .../ui/database/FirebaseArrayOfObjects.java | 45 +++++++++++-------- 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseArrayOfObjects.java b/database/src/main/java/com/firebase/ui/database/FirebaseArrayOfObjects.java index 12de32040..6f8ac8898 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseArrayOfObjects.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseArrayOfObjects.java @@ -23,15 +23,12 @@ public class FirebaseArrayOfObjects extends ImmutableList { * @param snapshots a list of {@link DataSnapshot}s to be converted to a model type * @param modelClass the model representation of a {@link DataSnapshot} */ - protected FirebaseArrayOfObjects(List snapshots, Class modelClass) { + protected FirebaseArrayOfObjects(List snapshots, + Class modelClass, + SnapshotParser parser) { mSnapshots = snapshots; mEClass = modelClass; - mParser = new SnapshotParser() { - @Override - public E parseSnapshot(DataSnapshot snapshot) { - return snapshot.getValue(mEClass); - } - }; + mParser = parser; } /** @@ -39,12 +36,13 @@ public E parseSnapshot(DataSnapshot snapshot) { * @param modelClass the model representation of a {@link DataSnapshot} */ public static FirebaseArrayOfObjects newInstance(List snapshots, - Class modelClass) { - if (snapshots instanceof FirebaseArray) { - return new FirebaseArrayOfObjectsOptimized<>((FirebaseArray) snapshots, modelClass); - } else { - return new FirebaseArrayOfObjects<>(snapshots, modelClass); - } + final Class modelClass) { + return getArray(snapshots, modelClass, new SnapshotParser() { + @Override + public T parseSnapshot(DataSnapshot snapshot) { + return snapshot.getValue(modelClass); + } + }); } /** @@ -55,9 +53,18 @@ public static FirebaseArrayOfObjects newInstance(List snaps public static FirebaseArrayOfObjects newInstance(List snapshots, Class modelClass, SnapshotParser parser) { - FirebaseArrayOfObjects array = newInstance(snapshots, modelClass); - array.mParser = parser; - return array; + return getArray(snapshots, modelClass, parser); + } + + private static FirebaseArrayOfObjects getArray(List snapshots, + Class modelClass, + SnapshotParser parser) { + if (snapshots instanceof FirebaseArray) { + return new FirebaseArrayOfObjectsOptimized<>( + (FirebaseArray) snapshots, modelClass, parser); + } else { + return new FirebaseArrayOfObjects<>(snapshots, modelClass, parser); + } } public List getSnapshots() { @@ -173,8 +180,10 @@ protected static class FirebaseArrayOfObjectsOptimized extends FirebaseArrayO implements ChangeEventListener { protected List mObjects = new ArrayList<>(); - public FirebaseArrayOfObjectsOptimized(FirebaseArray snapshots, Class modelClass) { - super(snapshots, modelClass); + public FirebaseArrayOfObjectsOptimized(FirebaseArray snapshots, + Class modelClass, + SnapshotParser parser) { + super(snapshots, modelClass, parser); snapshots.addChangeEventListener(this); } From bdeb6e2b821b44e671d4add25d0add0564aa3731 Mon Sep 17 00:00:00 2001 From: Alex Saveau Date: Tue, 21 Feb 2017 17:13:04 -0800 Subject: [PATCH 80/93] Update optimized array name Signed-off-by: Alex Saveau --- .../firebase/ui/database/FirebaseArrayOfObjects.java | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseArrayOfObjects.java b/database/src/main/java/com/firebase/ui/database/FirebaseArrayOfObjects.java index 6f8ac8898..91d7f398e 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseArrayOfObjects.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseArrayOfObjects.java @@ -60,8 +60,7 @@ private static FirebaseArrayOfObjects getArray(List snapsho Class modelClass, SnapshotParser parser) { if (snapshots instanceof FirebaseArray) { - return new FirebaseArrayOfObjectsOptimized<>( - (FirebaseArray) snapshots, modelClass, parser); + return new Optimized<>((FirebaseArray) snapshots, modelClass, parser); } else { return new FirebaseArrayOfObjects<>(snapshots, modelClass, parser); } @@ -176,13 +175,10 @@ public String toString() { return mSnapshots.toString(); } - protected static class FirebaseArrayOfObjectsOptimized extends FirebaseArrayOfObjects - implements ChangeEventListener { + protected static class Optimized extends FirebaseArrayOfObjects implements ChangeEventListener { protected List mObjects = new ArrayList<>(); - public FirebaseArrayOfObjectsOptimized(FirebaseArray snapshots, - Class modelClass, - SnapshotParser parser) { + public Optimized(FirebaseArray snapshots, Class modelClass, SnapshotParser parser) { super(snapshots, modelClass, parser); snapshots.addChangeEventListener(this); } From fdf9be8eeb8f789ca38c32f789fedb425e2074ea Mon Sep 17 00:00:00 2001 From: Sam Stern Date: Fri, 24 Feb 2017 18:13:32 -0800 Subject: [PATCH 81/93] Initial commit of refactor. Far from done, but enough that the sample works Change-Id: I6a62e69a58d0023c5feffb0984613e8e95be2ecc --- .../FirebaseIndexArrayOfObjectsTest.java | 4 +- .../com/firebase/ui/database/TestUtils.java | 2 + .../ui/database/ChangeEventListener.java | 4 +- .../firebase/ui/database/FirebaseArray.java | 173 +++++--------- .../ui/database/FirebaseArrayOfObjects.java | 217 ------------------ .../ui/database/FirebaseIndexArray.java | 147 ++++++++---- .../ui/database/FirebaseListAdapter.java | 19 +- .../ui/database/FirebaseRecyclerAdapter.java | 21 +- .../ui/database/ObservableSnapshotArray.java | 113 +++++++++ .../firebase/ui/database/Preconditions.java | 12 + 10 files changed, 316 insertions(+), 396 deletions(-) delete mode 100644 database/src/main/java/com/firebase/ui/database/FirebaseArrayOfObjects.java create mode 100644 database/src/main/java/com/firebase/ui/database/ObservableSnapshotArray.java create mode 100644 database/src/main/java/com/firebase/ui/database/Preconditions.java diff --git a/database/src/androidTest/java/com/firebase/ui/database/FirebaseIndexArrayOfObjectsTest.java b/database/src/androidTest/java/com/firebase/ui/database/FirebaseIndexArrayOfObjectsTest.java index 0017f49ac..07bab7b5f 100644 --- a/database/src/androidTest/java/com/firebase/ui/database/FirebaseIndexArrayOfObjectsTest.java +++ b/database/src/androidTest/java/com/firebase/ui/database/FirebaseIndexArrayOfObjectsTest.java @@ -25,6 +25,7 @@ import org.junit.Test; import org.junit.runner.RunWith; +import java.util.Objects; import java.util.concurrent.Callable; import static com.firebase.ui.database.TestUtils.getAppInstance; @@ -37,7 +38,7 @@ public class FirebaseIndexArrayOfObjectsTest { private DatabaseReference mRef; private DatabaseReference mKeyRef; - private FirebaseArray mArray; + private ObservableSnapshotArray mArray; private ChangeEventListener mListener; @Before @@ -47,6 +48,7 @@ public void setUp() throws Exception { mRef = databaseInstance.getReference().child("firebasearray").child("objects"); mKeyRef = databaseInstance.getReference().child("firebaseindexarray").child("objects"); + // TODO(samstern): FIX ALL DIS mArray = new FirebaseIndexArray(mKeyRef, mRef); mRef.removeValue(); mKeyRef.removeValue(); diff --git a/database/src/androidTest/java/com/firebase/ui/database/TestUtils.java b/database/src/androidTest/java/com/firebase/ui/database/TestUtils.java index de2f24fbf..8d87fcdcf 100644 --- a/database/src/androidTest/java/com/firebase/ui/database/TestUtils.java +++ b/database/src/androidTest/java/com/firebase/ui/database/TestUtils.java @@ -4,6 +4,7 @@ import com.google.firebase.FirebaseApp; import com.google.firebase.FirebaseOptions; +import com.google.firebase.database.DataSnapshot; import com.google.firebase.database.DatabaseError; import com.google.firebase.database.DatabaseReference; @@ -40,6 +41,7 @@ public static ChangeEventListener runAndWaitUntil(FirebaseArray array, ChangeEventListener listener = array.addChangeEventListener(new ChangeEventListener() { @Override public void onChildChanged(ChangeEventListener.EventType type, + DataSnapshot snapshot, int index, int oldIndex) { semaphore.release(); diff --git a/database/src/main/java/com/firebase/ui/database/ChangeEventListener.java b/database/src/main/java/com/firebase/ui/database/ChangeEventListener.java index 7703ba11b..b1045c865 100644 --- a/database/src/main/java/com/firebase/ui/database/ChangeEventListener.java +++ b/database/src/main/java/com/firebase/ui/database/ChangeEventListener.java @@ -5,6 +5,7 @@ import com.google.firebase.database.DatabaseError; public interface ChangeEventListener { + /** * The type of event received when a child has been updated. */ @@ -36,6 +37,7 @@ enum EventType { } /** + * TODO(samstern): Docs * A callback for when a child has changed in FirebaseArray. * * @param type The type of event received @@ -43,7 +45,7 @@ enum EventType { * @param oldIndex If {@code type} is a moved event, the previous index of the moved child. For * any other event, {@code oldIndex} will be -1. */ - void onChildChanged(EventType type, int index, int oldIndex); + void onChildChanged(EventType type, DataSnapshot snapshot, int index, int oldIndex); /** * This method will be triggered each time updates from the database have been completely diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseArray.java b/database/src/main/java/com/firebase/ui/database/FirebaseArray.java index b9fae66a6..66f8cf556 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseArray.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseArray.java @@ -14,7 +14,6 @@ package com.firebase.ui.database; -import android.support.annotation.CallSuper; import android.support.annotation.NonNull; import android.support.annotation.RestrictTo; @@ -29,16 +28,13 @@ import java.util.Iterator; import java.util.List; import java.util.ListIterator; -import java.util.concurrent.CopyOnWriteArrayList; /** * This class implements a collection on top of a Firebase location. */ -public class FirebaseArray extends ImmutableList implements ChildEventListener, ValueEventListener { - private final List mListeners = new CopyOnWriteArrayList<>(); +public class FirebaseArray extends ObservableSnapshotArray implements ChildEventListener, ValueEventListener { - private Query mQuery; - private boolean mNotifyListeners = true; + protected final Query mQuery; private List mSnapshots = new ArrayList<>(); /** @@ -47,113 +43,104 @@ public class FirebaseArray extends ImmutableList implements ChildE * {@code endAt()}. */ public FirebaseArray(Query query) { + // TODO: Instead of default parser, just fail on getObject if no parser is set + this(query, new SnapshotParser() { + @Override + public T parseSnapshot(DataSnapshot snapshot) { + // This must mean that is DataSnapshot, or it will explode. + return (T) snapshot; + } + }); + } + + public FirebaseArray(Query query, SnapshotParser parser) { + super(parser); mQuery = query; } - private static void checkNotNull(Object o) { - if (o == null) throw new IllegalArgumentException("Listener cannot be null."); + @Override + public T getObject(int index) { + Preconditions.checkNotNull(mParser); + + // TODO: Cache this! + return mParser.parseSnapshot(get(index)); } - /** - * Add a listener for change events and errors occurring at the location provided in {@link - * #FirebaseArray(Query)}. - * - * @param listener the listener to be called with changes - * @return a reference to the listener provided. Save this to remove the listener later - * @throws IllegalArgumentException if the listener is null - */ - @CallSuper + @Override + public T getObject(String key) { + Preconditions.checkNotNull(mParser); + + // TODO: Implement and cache this! + return null; + } + + @Override public ChangeEventListener addChangeEventListener(@NonNull ChangeEventListener listener) { - checkNotNull(listener); + boolean wasListening = isListening(); + super.addChangeEventListener(listener); - mListeners.add(listener); - if (mListeners.size() <= 1) { // Only start listening when the first listener is added + // Only start listening when the first listener is added + if (!wasListening) { mQuery.addChildEventListener(this); mQuery.addValueEventListener(this); - } else { - for (int i = 0; i < size(); i++) { - listener.onChildChanged(ChangeEventListener.EventType.ADDED, i, -1); - } } return listener; } - /** - * Remove a {@link ChangeEventListener} from the location provided in {@link - * #FirebaseArray(Query)}. The list will be empty after this call returns. - * - * @param listener the listener to remove - * @see #removeAllListeners() - */ - @CallSuper + @Override public void removeChangeEventListener(@NonNull ChangeEventListener listener) { - mListeners.remove(listener); - if (mListeners.isEmpty()) { + super.removeChangeEventListener(listener); + + // Clear data when all listeners are removed + if (!isListening()) { mQuery.removeEventListener((ValueEventListener) this); mQuery.removeEventListener((ChildEventListener) this); - mSnapshots.clear(); - } - } - /** - * Removes all {@link ChangeEventListener}s. The list will be empty after this call returns. - * - * @see #removeChangeEventListener(ChangeEventListener) - */ - @CallSuper - public void removeAllListeners() { - for (ChangeEventListener listener : mListeners) { - removeChangeEventListener(listener); + mSnapshots.clear(); } } - /** - * @return true if {@link FirebaseArray} is listening for change events from the Firebase - * database, false otherwise - */ - public final boolean isListening() { - return !mListeners.isEmpty(); - } - - /** - * @return true if the provided {@link ChangeEventListener} is listening for changes - */ - public final boolean isListening(ChangeEventListener listener) { - return mListeners.contains(listener); - } - @Override public void onChildAdded(DataSnapshot snapshot, String previousChildKey) { int index = 0; if (previousChildKey != null) { index = getIndexForKey(previousChildKey) + 1; } + mSnapshots.add(index, snapshot); - notifyChangeEventListeners(ChangeEventListener.EventType.ADDED, index); + + notifyChangeEventListeners(ChangeEventListener.EventType.ADDED, snapshot, index); } @Override public void onChildChanged(DataSnapshot snapshot, String previousChildKey) { int index = getIndexForKey(snapshot.getKey()); + mSnapshots.set(index, snapshot); - notifyChangeEventListeners(ChangeEventListener.EventType.CHANGED, index); + + notifyChangeEventListeners(ChangeEventListener.EventType.CHANGED, snapshot, index); } @Override public void onChildRemoved(DataSnapshot snapshot) { int index = getIndexForKey(snapshot.getKey()); + mSnapshots.remove(index); - notifyChangeEventListeners(ChangeEventListener.EventType.REMOVED, index); + + notifyChangeEventListeners(ChangeEventListener.EventType.REMOVED, snapshot, index); } @Override public void onChildMoved(DataSnapshot snapshot, String previousChildKey) { int oldIndex = getIndexForKey(snapshot.getKey()); mSnapshots.remove(oldIndex); + int newIndex = previousChildKey == null ? 0 : (getIndexForKey(previousChildKey) + 1); mSnapshots.add(newIndex, snapshot); - notifyChangeEventListeners(ChangeEventListener.EventType.MOVED, newIndex, oldIndex); + + notifyChangeEventListeners(ChangeEventListener.EventType.MOVED, snapshot, + newIndex, oldIndex); } @Override @@ -178,35 +165,8 @@ private int getIndexForKey(String key) { throw new IllegalArgumentException("Key not found"); } - protected void setShouldNotifyListeners(boolean notifyListeners) { - mNotifyListeners = notifyListeners; - } - - protected final void notifyChangeEventListeners(ChangeEventListener.EventType type, int index) { - notifyChangeEventListeners(type, index, -1); - } - - protected final void notifyChangeEventListeners(ChangeEventListener.EventType type, - int index, - int oldIndex) { - if (!mNotifyListeners) return; - for (ChangeEventListener listener : mListeners) { - listener.onChildChanged(type, index, oldIndex); - } - } - - protected final void notifyListenersOnDataChanged() { - if (!mNotifyListeners) return; - for (ChangeEventListener listener : mListeners) { - listener.onDataChanged(); - } - } - - protected final void notifyListenersOnCancelled(DatabaseError error) { - if (!mNotifyListeners) return; - for (ChangeEventListener listener : mListeners) { - listener.onCancelled(error); - } + public DataSnapshot getSnapshot(int index) { + return mSnapshots.get(index); } @Override @@ -219,6 +179,7 @@ public boolean isEmpty() { return mSnapshots.isEmpty(); } + // TODO: Maybe a containsObject? @Override public boolean contains(Object o) { return mSnapshots.contains(o); @@ -234,34 +195,12 @@ public Iterator iterator() { return new ImmutableIterator(mSnapshots.iterator()); } + // TODO(samstern): probably needs to be killed @Override public DataSnapshot[] toArray() { return mSnapshots.toArray(new DataSnapshot[mSnapshots.size()]); } - /** - * Get a continually updated list of objects representing the {@link DataSnapshot}s in this - * list. - * - * @param modelClass the model representation of a {@link DataSnapshot} - * @return a list that represents the objects in this list of {@link DataSnapshot} - */ - public List toObjectsList(Class modelClass) { - return FirebaseArrayOfObjects.newInstance(this, modelClass); - } - - /** - * Get a continually updated list of objects representing the {@link DataSnapshot}s in this - * list. - * - * @param parser a custom {@link SnapshotParser} to manually convert each {@link DataSnapshot} - * to its model type - * @see #toObjectsList(Class) - */ - public List toObjectsList(Class modelClass, SnapshotParser parser) { - return FirebaseArrayOfObjects.newInstance(this, modelClass, parser); - } - @Override public boolean containsAll(Collection c) { return mSnapshots.containsAll(c); diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseArrayOfObjects.java b/database/src/main/java/com/firebase/ui/database/FirebaseArrayOfObjects.java deleted file mode 100644 index 91d7f398e..000000000 --- a/database/src/main/java/com/firebase/ui/database/FirebaseArrayOfObjects.java +++ /dev/null @@ -1,217 +0,0 @@ -package com.firebase.ui.database; - -import com.google.firebase.database.DataSnapshot; -import com.google.firebase.database.DatabaseError; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Iterator; -import java.util.List; -import java.util.ListIterator; - -/** - * Acts as a bridge between a list of {@link DataSnapshot}s and a list of objects of type E. - * - * @param the model representation of a {@link DataSnapshot} - */ -public class FirebaseArrayOfObjects extends ImmutableList { - protected List mSnapshots; - protected Class mEClass; - protected SnapshotParser mParser; - - /** - * @param snapshots a list of {@link DataSnapshot}s to be converted to a model type - * @param modelClass the model representation of a {@link DataSnapshot} - */ - protected FirebaseArrayOfObjects(List snapshots, - Class modelClass, - SnapshotParser parser) { - mSnapshots = snapshots; - mEClass = modelClass; - mParser = parser; - } - - /** - * @param snapshots a list of {@link DataSnapshot}s to be converted to a model type - * @param modelClass the model representation of a {@link DataSnapshot} - */ - public static FirebaseArrayOfObjects newInstance(List snapshots, - final Class modelClass) { - return getArray(snapshots, modelClass, new SnapshotParser() { - @Override - public T parseSnapshot(DataSnapshot snapshot) { - return snapshot.getValue(modelClass); - } - }); - } - - /** - * @param parser a custom {@link SnapshotParser} to manually convert each {@link DataSnapshot} - * to its model type - * @see #newInstance(List, Class) - */ - public static FirebaseArrayOfObjects newInstance(List snapshots, - Class modelClass, - SnapshotParser parser) { - return getArray(snapshots, modelClass, parser); - } - - private static FirebaseArrayOfObjects getArray(List snapshots, - Class modelClass, - SnapshotParser parser) { - if (snapshots instanceof FirebaseArray) { - return new Optimized<>((FirebaseArray) snapshots, modelClass, parser); - } else { - return new FirebaseArrayOfObjects<>(snapshots, modelClass, parser); - } - } - - public List getSnapshots() { - return mSnapshots; - } - - protected List getObjects() { - List objects = new ArrayList<>(mSnapshots.size()); - for (int i = 0; i < mSnapshots.size(); i++) { - objects.add(get(i)); - } - return objects; - } - - @Override - public int size() { - return mSnapshots.size(); - } - - @Override - public boolean isEmpty() { - return mSnapshots.isEmpty(); - } - - @Override - public boolean contains(Object o) { - return indexOf(o) >= 0; - } - - /** - * {@inheritDoc} - * - * @return an immutable iterator - */ - @Override - public Iterator iterator() { - return new ImmutableIterator(getObjects().iterator()); - } - - @Override - public Object[] toArray() { - return getObjects().toArray(); - } - - @Override - public T[] toArray(T[] a) { - return getObjects().toArray(a); - } - - @Override - public boolean containsAll(Collection c) { - return getObjects().containsAll(c); - } - - @Override - public E get(int index) { - return mParser.parseSnapshot(mSnapshots.get(index)); - } - - @Override - public int indexOf(Object o) { - return getObjects().indexOf(o); - } - - @Override - public int lastIndexOf(Object o) { - return getObjects().lastIndexOf(o); - } - - /** - * {@inheritDoc} - * - * @return an immutable list iterator - */ - @Override - public ListIterator listIterator() { - return new ImmutableListIterator(getObjects().listIterator()); - } - - /** - * {@inheritDoc} - * - * @return an immutable list iterator - */ - @Override - public ListIterator listIterator(int index) { - return new ImmutableListIterator(getObjects().listIterator(index)); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - if (obj == null || getClass() != obj.getClass()) return false; - - FirebaseArrayOfObjects array = (FirebaseArrayOfObjects) obj; - - return mSnapshots.equals(array.mSnapshots) && mEClass.equals(array.mEClass); - } - - @Override - public int hashCode() { - int result = mSnapshots.hashCode(); - result = 31 * result + mEClass.hashCode(); - return result; - } - - @Override - public String toString() { - return mSnapshots.toString(); - } - - protected static class Optimized extends FirebaseArrayOfObjects implements ChangeEventListener { - protected List mObjects = new ArrayList<>(); - - public Optimized(FirebaseArray snapshots, Class modelClass, SnapshotParser parser) { - super(snapshots, modelClass, parser); - snapshots.addChangeEventListener(this); - } - - @Override - protected List getObjects() { - return mObjects; - } - - @Override - public void onChildChanged(ChangeEventListener.EventType type, int index, int oldIndex) { - switch (type) { - case ADDED: - mObjects.add(get(index)); - break; - case CHANGED: - mObjects.set(index, get(index)); - break; - case REMOVED: - mObjects.remove(index); - break; - case MOVED: - mObjects.add(index, mObjects.remove(oldIndex)); - break; - } - } - - @Override - public void onDataChanged() { - } - - @Override - public void onCancelled(DatabaseError error) { - } - } -} 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 1d9c36faa..1e15ee846 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseIndexArray.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseIndexArray.java @@ -32,13 +32,19 @@ import java.util.ListIterator; import java.util.Map; -public class FirebaseIndexArray extends FirebaseArray { +public class FirebaseIndexArray extends ObservableSnapshotArray implements ChangeEventListener { private static final String TAG = "FirebaseIndexArray"; private DatabaseReference mDataRef; private Map mRefs = new HashMap<>(); + + private FirebaseArray mKeySnapshots; + + // TODO private List mDataSnapshots = new ArrayList<>(); + public FirebaseIndexArray() {} + /** * @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 @@ -47,10 +53,48 @@ public class FirebaseIndexArray extends FirebaseArray { * keyQuery}'s location represents a list item in the {@link RecyclerView}. */ public FirebaseIndexArray(Query keyQuery, DatabaseReference dataRef) { - super(keyQuery); + mKeySnapshots = new FirebaseArray<>(keyQuery); mDataRef = dataRef; + + mKeySnapshots.addChangeEventListener(this); + } + + // TODO(samstern): These comments suck + /** ===================== Start ChangeEventListener ===================================== **/ + + @Override + public void onChildChanged(EventType type, DataSnapshot snapshot, int index, int oldIndex) { + switch (type) { + case ADDED: + // TODO + onKeyAdded(index); + break; + case MOVED: + // TODO + onKeyMoved(index, oldIndex); + break; + case CHANGED: + // TODO: Can this be a no-op? + break; + case REMOVED: + // TODO + onKeyRemoved(index, snapshot); + break; + } } + @Override + public void onDataChanged() { + // TODO: Anything? + } + + @Override + public void onCancelled(DatabaseError error) { + Log.e(TAG, "A fatal error occurred retrieving the necessary keys to populate your adapter."); + } + + /** ============================= End ChangeEventListener =============================== **/ + @Override public void removeChangeEventListener(@NonNull ChangeEventListener listener) { super.removeChangeEventListener(listener); @@ -63,11 +107,24 @@ public void removeChangeEventListener(@NonNull ChangeEventListener listener) { } } + @Override + public T getObject(int index) { + // TODO + return null; + } + + @Override + public T getObject(String key) { + // TODO + return null; + } + + // TODO(samstern): Figure out what's going on here private int getIndexForKey(String key) { int dataCount = size(); int index = 0; for (int keyIndex = 0; index < dataCount; keyIndex++) { - String superKey = super.get(keyIndex).getKey(); + String superKey = mKeySnapshots.get(keyIndex).getKey(); if (key.equals(superKey)) { break; } else if (mDataSnapshots.get(index).getKey().equals(superKey)) { @@ -77,67 +134,50 @@ private int getIndexForKey(String key) { return index; } + /** + * Determines if a DataSnapshot with the given key is present at the given index. + */ private boolean isKeyAtIndex(String key, int index) { return index >= 0 && index < size() && mDataSnapshots.get(index).getKey().equals(key); } - @Override - public void onChildAdded(DataSnapshot keySnapshot, String previousChildKey) { - setShouldNotifyListeners(false); - super.onChildAdded(keySnapshot, previousChildKey); - setShouldNotifyListeners(true); + // TODO(samstern): Should this take a string? + protected void onKeyAdded(int index) { + String key = mKeySnapshots.get(index).getKey(); + Query ref = mDataRef.child(key); - Query ref = mDataRef.child(keySnapshot.getKey()); + // Start listening mRefs.put(ref, ref.addValueEventListener(new DataRefListener())); - } - - @Override - public void onChildChanged(DataSnapshot snapshot, String previousChildKey) { - setShouldNotifyListeners(false); - super.onChildChanged(snapshot, previousChildKey); - setShouldNotifyListeners(true); - } - @Override - public void onChildRemoved(DataSnapshot keySnapshot) { - String key = keySnapshot.getKey(); - int index = getIndexForKey(key); - mDataRef.child(key).removeEventListener(mRefs.remove(mDataRef.getRef().child(key))); - - setShouldNotifyListeners(false); - super.onChildRemoved(keySnapshot); - setShouldNotifyListeners(true); - - if (isKeyAtIndex(key, index)) { - mDataSnapshots.remove(index); - notifyChangeEventListeners(ChangeEventListener.EventType.REMOVED, index); - } + // TODO(samstern): Who do notify and how? } - @Override - public void onChildMoved(DataSnapshot keySnapshot, String previousChildKey) { - String key = keySnapshot.getKey(); - int oldIndex = getIndexForKey(key); - - setShouldNotifyListeners(false); - super.onChildMoved(keySnapshot, previousChildKey); - setShouldNotifyListeners(true); + protected void onKeyMoved(int index, int oldIndex) { + String key = mKeySnapshots.get(index).getKey(); if (isKeyAtIndex(key, oldIndex)) { DataSnapshot snapshot = mDataSnapshots.remove(oldIndex); int newIndex = getIndexForKey(key); mDataSnapshots.add(newIndex, snapshot); - notifyChangeEventListeners(ChangeEventListener.EventType.MOVED, newIndex, oldIndex); + notifyChangeEventListeners(ChangeEventListener.EventType.MOVED, snapshot, newIndex, oldIndex); } } - @Override - public void onCancelled(DatabaseError error) { - Log.e(TAG, - "A fatal error occurred retrieving the necessary keys to populate your adapter."); - super.onCancelled(error); + protected void onKeyRemoved(int index, DataSnapshot data) { + // TODO(samstern): How to get the removed data? + + String key = data.getKey(); + mDataRef.child(key).removeEventListener(mRefs.remove(mDataRef.getRef().child(key))); + + if (isKeyAtIndex(key, index)) { + DataSnapshot snapshot = mDataSnapshots.remove(index); + notifyChangeEventListeners(ChangeEventListener.EventType.REMOVED, snapshot, index); + } } + /** + * A ValueEventListener attached to the joined child data. + */ protected class DataRefListener implements ValueEventListener { @Override public void onDataChange(DataSnapshot snapshot) { @@ -146,17 +186,22 @@ public void onDataChange(DataSnapshot snapshot) { if (snapshot.getValue() != null) { if (!isKeyAtIndex(key, index)) { + // We don't already know about this data, add it mDataSnapshots.add(index, snapshot); - notifyChangeEventListeners(ChangeEventListener.EventType.ADDED, index); + // TODO(samstern): notifyChangeEventListeners needs to move to the base class + notifyChangeEventListeners(ChangeEventListener.EventType.ADDED, snapshot, index); } else { + // We already know about this data, just update it mDataSnapshots.set(index, snapshot); - notifyChangeEventListeners(ChangeEventListener.EventType.CHANGED, index); + notifyChangeEventListeners(ChangeEventListener.EventType.CHANGED, snapshot, index); } } else { if (isKeyAtIndex(key, index)) { + // This data has disappeared, remove it mDataSnapshots.remove(index); - notifyChangeEventListeners(ChangeEventListener.EventType.REMOVED, index); + notifyChangeEventListeners(ChangeEventListener.EventType.REMOVED, snapshot, index); } else { + // Data we never knew about has disappeared Log.w(TAG, "Key not found at ref: " + snapshot.getRef()); } } @@ -193,6 +238,12 @@ public DataSnapshot[] toArray() { return mDataSnapshots.toArray(new DataSnapshot[mDataSnapshots.size()]); } + @NonNull + @Override + public T[] toArray(@NonNull T[] ts) { + return null; + } + @Override public boolean containsAll(Collection c) { return mDataSnapshots.containsAll(c); diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseListAdapter.java b/database/src/main/java/com/firebase/ui/database/FirebaseListAdapter.java index f5b44bfb7..bfe6e4fe9 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseListAdapter.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseListAdapter.java @@ -28,7 +28,7 @@ public abstract class FirebaseListAdapter extends BaseAdapter implements Fire private static final String TAG = "FirebaseListAdapter"; protected Activity mActivity; - protected FirebaseArray mSnapshots; + protected ObservableSnapshotArray mSnapshots; protected Class mModelClass; protected int mLayout; @@ -42,11 +42,11 @@ public abstract class FirebaseListAdapter extends BaseAdapter implements Fire * @param snapshots The data used to populate the adapter */ public FirebaseListAdapter(Activity activity, - FirebaseArray snapshots, + ObservableSnapshotArray snapshots, Class modelClass, @LayoutRes int modelLayout) { - mActivity = activity; mSnapshots = snapshots; + mActivity = activity; mModelClass = modelClass; mLayout = modelLayout; @@ -63,7 +63,13 @@ public FirebaseListAdapter(Activity activity, Class modelClass, @LayoutRes int modelLayout, Query query) { - this(activity, new FirebaseArray(query), modelClass, modelLayout); + + mSnapshots = new FirebaseArray(query, this); + mActivity = activity; + mModelClass = modelClass; + mLayout = modelLayout; + + startListening(); } @Override @@ -79,7 +85,8 @@ public void cleanup() { } @Override - public void onChildChanged(ChangeEventListener.EventType type, int index, int oldIndex) { + public void onChildChanged(ChangeEventListener.EventType type, DataSnapshot snapshot, + int index, int oldIndex) { notifyDataSetChanged(); } @@ -94,7 +101,7 @@ public void onCancelled(DatabaseError error) { @Override public T getItem(int position) { - return parseSnapshot(mSnapshots.get(position)); + return mSnapshots.getObject(position); } @Override diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseRecyclerAdapter.java b/database/src/main/java/com/firebase/ui/database/FirebaseRecyclerAdapter.java index 46598b7f7..240440470 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseRecyclerAdapter.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseRecyclerAdapter.java @@ -29,9 +29,10 @@ */ public abstract class FirebaseRecyclerAdapter extends RecyclerView.Adapter implements FirebaseAdapter { + private static final String TAG = "FirebaseRecyclerAdapter"; - protected FirebaseArray mSnapshots; + protected ObservableSnapshotArray mSnapshots; protected Class mModelClass; protected Class mViewHolderClass; protected int mModelLayout; @@ -46,7 +47,7 @@ public abstract class FirebaseRecyclerAdapter snapshots, Class modelClass, @LayoutRes int modelLayout, Class viewHolderClass) { @@ -62,13 +63,20 @@ public FirebaseRecyclerAdapter(FirebaseArray snapshots, * @param query The Firebase location to watch for data changes. Can also be a slice of a * location, using some combination of {@code limit()}, {@code startAt()}, and * {@code endAt()}. - * @see #FirebaseRecyclerAdapter(FirebaseArray, Class, int, Class) + * @see #FirebaseRecyclerAdapter(ObservableSnapshotArray, Class, int, Class) */ public FirebaseRecyclerAdapter(Class modelClass, @LayoutRes int modelLayout, Class viewHolderClass, Query query) { - this(new FirebaseArray(query), modelClass, modelLayout, viewHolderClass); + + // TODO(samstern): DRY with above constructor + mSnapshots = new FirebaseArray(query, this); + mModelClass = modelClass; + mViewHolderClass = viewHolderClass; + mModelLayout = modelLayout; + + startListening(); } @Override @@ -84,7 +92,8 @@ public void cleanup() { } @Override - public void onChildChanged(ChangeEventListener.EventType type, int index, int oldIndex) { + public void onChildChanged(ChangeEventListener.EventType type, DataSnapshot snapshot, + int index, int oldIndex) { switch (type) { case ADDED: notifyItemInserted(index); @@ -114,7 +123,7 @@ public void onCancelled(DatabaseError error) { @Override public T getItem(int position) { - return parseSnapshot(mSnapshots.get(position)); + return mSnapshots.getObject(position); } @Override diff --git a/database/src/main/java/com/firebase/ui/database/ObservableSnapshotArray.java b/database/src/main/java/com/firebase/ui/database/ObservableSnapshotArray.java new file mode 100644 index 000000000..f6c652116 --- /dev/null +++ b/database/src/main/java/com/firebase/ui/database/ObservableSnapshotArray.java @@ -0,0 +1,113 @@ +package com.firebase.ui.database; + +import android.support.annotation.CallSuper; +import android.support.annotation.NonNull; + +import com.google.firebase.database.DataSnapshot; +import com.google.firebase.database.DatabaseError; + +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +/** + * TODO(samstern): Document and document all methods + */ +public abstract class ObservableSnapshotArray extends ImmutableList { + + protected final List mListeners = new CopyOnWriteArrayList<>(); + + // TODO(samstern):Probably none of this should be in the super class! + protected final SnapshotParser mParser; + + public ObservableSnapshotArray() { + mParser = null; + } + + public ObservableSnapshotArray(SnapshotParser parser) { + mParser = parser; + } + + /** + * TODO(samstern): Document. + */ + @CallSuper + public ChangeEventListener addChangeEventListener(@NonNull ChangeEventListener listener) { + Preconditions.checkNotNull(listener); + + mListeners.add(listener); + for (int i = 0; i < size(); i++) { + listener.onChildChanged(ChangeEventListener.EventType.ADDED, get(i), i, -1); + } + + return listener; + } + + /** + * TODO(samstern): Document. + */ + @CallSuper + public void removeChangeEventListener(@NonNull ChangeEventListener listener) { + mListeners.remove(listener); + } + + /** + * Removes all {@link ChangeEventListener}s. The list will be empty after this call returns. + * + * @see #removeChangeEventListener(ChangeEventListener) + */ + @CallSuper + public void removeAllListeners() { + for (ChangeEventListener listener : mListeners) { + removeChangeEventListener(listener); + } + } + + protected final void notifyChangeEventListeners(ChangeEventListener.EventType type, + DataSnapshot snapshot, + int index) { + notifyChangeEventListeners(type, snapshot, index, -1); + } + + protected final void notifyChangeEventListeners(ChangeEventListener.EventType type, + DataSnapshot snapshot, + int index, + int oldIndex) { + for (ChangeEventListener listener : mListeners) { + listener.onChildChanged(type, snapshot, index, oldIndex); + } + } + + protected final void notifyListenersOnDataChanged() { + for (ChangeEventListener listener : mListeners) { + listener.onDataChanged(); + } + } + + protected final void notifyListenersOnCancelled(DatabaseError error) { + for (ChangeEventListener listener : mListeners) { + listener.onCancelled(error); + } + } + + /** + * @return true if {@link FirebaseArray} is listening for change events from the Firebase + * database, false otherwise + */ + public final boolean isListening() { + return !mListeners.isEmpty(); + } + + /** + * @return true if the provided {@link ChangeEventListener} is listening for changes + */ + public final boolean isListening(ChangeEventListener listener) { + return mListeners.contains(listener); + } + + // TODO(samstern): Document + public abstract T getObject(int index); + + // TODO: Do we need this method at all? + public abstract T getObject(String key); + +} diff --git a/database/src/main/java/com/firebase/ui/database/Preconditions.java b/database/src/main/java/com/firebase/ui/database/Preconditions.java new file mode 100644 index 000000000..09edf519b --- /dev/null +++ b/database/src/main/java/com/firebase/ui/database/Preconditions.java @@ -0,0 +1,12 @@ +package com.firebase.ui.database; + +/** + * TODO(samstern): Document + */ +public class Preconditions { + + public static void checkNotNull(Object o) { + if (o == null) throw new IllegalArgumentException("Listener cannot be null."); + } + +} From 03d6bf40cc2957ba8f7c7239108d4c46a814c9bd Mon Sep 17 00:00:00 2001 From: Sam Stern Date: Fri, 24 Feb 2017 18:30:14 -0800 Subject: [PATCH 82/93] Clean up some TODOs, make the tests compile, some tests pass. Change-Id: Ia72a6942c947464ade5070aa07fade1e133c0f8d --- .../FirebaseIndexArrayOfObjectsTest.java | 4 +- .../ui/database/FirebaseIndexArrayTest.java | 2 +- .../com/firebase/ui/database/TestUtils.java | 6 +-- .../ui/database/ChangeEventListener.java | 2 +- .../firebase/ui/database/FirebaseArray.java | 23 +-------- .../ui/database/FirebaseIndexArray.java | 48 ++++++------------- .../ui/database/ObservableSnapshotArray.java | 10 ++-- 7 files changed, 27 insertions(+), 68 deletions(-) diff --git a/database/src/androidTest/java/com/firebase/ui/database/FirebaseIndexArrayOfObjectsTest.java b/database/src/androidTest/java/com/firebase/ui/database/FirebaseIndexArrayOfObjectsTest.java index 07bab7b5f..94c4468ce 100644 --- a/database/src/androidTest/java/com/firebase/ui/database/FirebaseIndexArrayOfObjectsTest.java +++ b/database/src/androidTest/java/com/firebase/ui/database/FirebaseIndexArrayOfObjectsTest.java @@ -38,7 +38,7 @@ public class FirebaseIndexArrayOfObjectsTest { private DatabaseReference mRef; private DatabaseReference mKeyRef; - private ObservableSnapshotArray mArray; + private ObservableSnapshotArray mArray; private ChangeEventListener mListener; @Before @@ -49,7 +49,7 @@ public void setUp() throws Exception { mKeyRef = databaseInstance.getReference().child("firebaseindexarray").child("objects"); // TODO(samstern): FIX ALL DIS - mArray = new FirebaseIndexArray(mKeyRef, mRef); + mArray = new FirebaseIndexArray<>(mKeyRef, mRef); mRef.removeValue(); mKeyRef.removeValue(); diff --git a/database/src/androidTest/java/com/firebase/ui/database/FirebaseIndexArrayTest.java b/database/src/androidTest/java/com/firebase/ui/database/FirebaseIndexArrayTest.java index 9e9003084..493c00b3e 100644 --- a/database/src/androidTest/java/com/firebase/ui/database/FirebaseIndexArrayTest.java +++ b/database/src/androidTest/java/com/firebase/ui/database/FirebaseIndexArrayTest.java @@ -37,7 +37,7 @@ public class FirebaseIndexArrayTest { private DatabaseReference mRef; private DatabaseReference mKeyRef; - private FirebaseArray mArray; + private ObservableSnapshotArray mArray; private ChangeEventListener mListener; @Before diff --git a/database/src/androidTest/java/com/firebase/ui/database/TestUtils.java b/database/src/androidTest/java/com/firebase/ui/database/TestUtils.java index 8d87fcdcf..65695ad6a 100644 --- a/database/src/androidTest/java/com/firebase/ui/database/TestUtils.java +++ b/database/src/androidTest/java/com/firebase/ui/database/TestUtils.java @@ -34,7 +34,7 @@ private static FirebaseApp initializeApp(Context context) { .build(), APP_NAME); } - public static ChangeEventListener runAndWaitUntil(FirebaseArray array, + public static ChangeEventListener runAndWaitUntil(ObservableSnapshotArray array, Runnable task, Callable done) throws InterruptedException { final Semaphore semaphore = new Semaphore(0); @@ -74,7 +74,7 @@ public void onCancelled(DatabaseError error) { return listener; } - public static boolean isValuesEqual(FirebaseArray array, int[] expected) { + public static boolean isValuesEqual(ObservableSnapshotArray array, int[] expected) { if (array.size() != expected.length) return false; for (int i = 0; i < array.size(); i++) { if (!array.get(i).getValue(Integer.class).equals(expected[i])) { @@ -84,7 +84,7 @@ public static boolean isValuesEqual(FirebaseArray array, int[] expected) { return true; } - public static Bean getBean(FirebaseArray array, int index) { + public static Bean getBean(ObservableSnapshotArray array, int index) { return array.get(index).getValue(Bean.class); } diff --git a/database/src/main/java/com/firebase/ui/database/ChangeEventListener.java b/database/src/main/java/com/firebase/ui/database/ChangeEventListener.java index b1045c865..c75b5957a 100644 --- a/database/src/main/java/com/firebase/ui/database/ChangeEventListener.java +++ b/database/src/main/java/com/firebase/ui/database/ChangeEventListener.java @@ -37,7 +37,7 @@ enum EventType { } /** - * TODO(samstern): Docs + * TODO(samstern): Document. * A callback for when a child has changed in FirebaseArray. * * @param type The type of event received diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseArray.java b/database/src/main/java/com/firebase/ui/database/FirebaseArray.java index 66f8cf556..3a4f9119c 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseArray.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseArray.java @@ -43,14 +43,7 @@ public class FirebaseArray extends ObservableSnapshotArray implements Chil * {@code endAt()}. */ public FirebaseArray(Query query) { - // TODO: Instead of default parser, just fail on getObject if no parser is set - this(query, new SnapshotParser() { - @Override - public T parseSnapshot(DataSnapshot snapshot) { - // This must mean that is DataSnapshot, or it will explode. - return (T) snapshot; - } - }); + this(query, null); } public FirebaseArray(Query query, SnapshotParser parser) { @@ -60,18 +53,8 @@ public FirebaseArray(Query query, SnapshotParser parser) { @Override public T getObject(int index) { - Preconditions.checkNotNull(mParser); - // TODO: Cache this! - return mParser.parseSnapshot(get(index)); - } - - @Override - public T getObject(String key) { - Preconditions.checkNotNull(mParser); - - // TODO: Implement and cache this! - return null; + return super.getObject(index); } @Override @@ -179,7 +162,6 @@ public boolean isEmpty() { return mSnapshots.isEmpty(); } - // TODO: Maybe a containsObject? @Override public boolean contains(Object o) { return mSnapshots.contains(o); @@ -195,7 +177,6 @@ public Iterator iterator() { return new ImmutableIterator(mSnapshots.iterator()); } - // TODO(samstern): probably needs to be killed @Override public DataSnapshot[] toArray() { return mSnapshots.toArray(new DataSnapshot[mSnapshots.size()]); 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 1e15ee846..a8e1f1fc8 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseIndexArray.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseIndexArray.java @@ -39,10 +39,9 @@ public class FirebaseIndexArray extends ObservableSnapshotArray implements private Map mRefs = new HashMap<>(); private FirebaseArray mKeySnapshots; - - // TODO private List mDataSnapshots = new ArrayList<>(); + // TODO(samstern): Need to pass in a SnapshotParser. public FirebaseIndexArray() {} /** @@ -59,26 +58,20 @@ public FirebaseIndexArray(Query keyQuery, DatabaseReference dataRef) { mKeySnapshots.addChangeEventListener(this); } - // TODO(samstern): These comments suck - /** ===================== Start ChangeEventListener ===================================== **/ - @Override public void onChildChanged(EventType type, DataSnapshot snapshot, int index, int oldIndex) { switch (type) { case ADDED: - // TODO - onKeyAdded(index); + onKeyAdded(snapshot); break; case MOVED: - // TODO - onKeyMoved(index, oldIndex); + onKeyMoved(snapshot, index, oldIndex); break; case CHANGED: - // TODO: Can this be a no-op? + // TODO(samstern): Can this be a no-op? break; case REMOVED: - // TODO - onKeyRemoved(index, snapshot); + onKeyRemoved(snapshot, index); break; } } @@ -93,8 +86,6 @@ public void onCancelled(DatabaseError error) { Log.e(TAG, "A fatal error occurred retrieving the necessary keys to populate your adapter."); } - /** ============================= End ChangeEventListener =============================== **/ - @Override public void removeChangeEventListener(@NonNull ChangeEventListener listener) { super.removeChangeEventListener(listener); @@ -109,17 +100,11 @@ public void removeChangeEventListener(@NonNull ChangeEventListener listener) { @Override public T getObject(int index) { - // TODO - return null; - } - - @Override - public T getObject(String key) { - // TODO - return null; + // TODO: Cache this! + return super.getObject(index); } - // TODO(samstern): Figure out what's going on here + // TODO(samstern): Figure out what's going on here, make sure it's still right private int getIndexForKey(String key) { int dataCount = size(); int index = 0; @@ -141,20 +126,18 @@ private boolean isKeyAtIndex(String key, int index) { return index >= 0 && index < size() && mDataSnapshots.get(index).getKey().equals(key); } - // TODO(samstern): Should this take a string? - protected void onKeyAdded(int index) { - String key = mKeySnapshots.get(index).getKey(); + protected void onKeyAdded(DataSnapshot data) { + String key = data.getKey(); Query ref = mDataRef.child(key); // Start listening mRefs.put(ref, ref.addValueEventListener(new DataRefListener())); - - // TODO(samstern): Who do notify and how? } - protected void onKeyMoved(int index, int oldIndex) { - String key = mKeySnapshots.get(index).getKey(); + protected void onKeyMoved(DataSnapshot data, int index, int oldIndex) { + String key = data.getKey(); + // TODO(samstern): I don't think this block is correct anymore if (isKeyAtIndex(key, oldIndex)) { DataSnapshot snapshot = mDataSnapshots.remove(oldIndex); int newIndex = getIndexForKey(key); @@ -163,9 +146,7 @@ protected void onKeyMoved(int index, int oldIndex) { } } - protected void onKeyRemoved(int index, DataSnapshot data) { - // TODO(samstern): How to get the removed data? - + protected void onKeyRemoved(DataSnapshot data, int index) { String key = data.getKey(); mDataRef.child(key).removeEventListener(mRefs.remove(mDataRef.getRef().child(key))); @@ -188,7 +169,6 @@ public void onDataChange(DataSnapshot snapshot) { if (!isKeyAtIndex(key, index)) { // We don't already know about this data, add it mDataSnapshots.add(index, snapshot); - // TODO(samstern): notifyChangeEventListeners needs to move to the base class notifyChangeEventListeners(ChangeEventListener.EventType.ADDED, snapshot, index); } else { // We already know about this data, just update it diff --git a/database/src/main/java/com/firebase/ui/database/ObservableSnapshotArray.java b/database/src/main/java/com/firebase/ui/database/ObservableSnapshotArray.java index f6c652116..4a5a1395e 100644 --- a/database/src/main/java/com/firebase/ui/database/ObservableSnapshotArray.java +++ b/database/src/main/java/com/firebase/ui/database/ObservableSnapshotArray.java @@ -15,8 +15,6 @@ public abstract class ObservableSnapshotArray extends ImmutableList { protected final List mListeners = new CopyOnWriteArrayList<>(); - - // TODO(samstern):Probably none of this should be in the super class! protected final SnapshotParser mParser; public ObservableSnapshotArray() { @@ -105,9 +103,9 @@ public final boolean isListening(ChangeEventListener listener) { } // TODO(samstern): Document - public abstract T getObject(int index); - - // TODO: Do we need this method at all? - public abstract T getObject(String key); + public T getObject(int index) { + Preconditions.checkNotNull(mParser); + return mParser.parseSnapshot(get(index)); + } } From a38c1b29fd6f9e44b587c884f7e2b9ea01e53b56 Mon Sep 17 00:00:00 2001 From: Sam Stern Date: Mon, 27 Feb 2017 10:56:40 -0800 Subject: [PATCH 83/93] Tests pass, TODOs resolved, code made DRY Change-Id: I831fd0bae52970ff7db6f4ab1ae2fdb7aa04200a --- build.gradle | 2 +- .../java/com/firebase/ui/database/Bean.java | 12 ++ .../ui/database/ClassSnapshotParser.java | 25 ++++ .../database/FirebaseArrayOfObjectsTest.java | 49 ++++++-- .../FirebaseIndexArrayOfObjectsTest.java | 18 ++- .../com/firebase/ui/database/TestUtils.java | 4 - .../CachingObservableSnapshotArray.java | 57 +++++++++ .../ui/database/ChangeEventListener.java | 2 +- .../firebase/ui/database/FirebaseArray.java | 105 ++-------------- .../ui/database/FirebaseIndexArray.java | 118 ++++++------------ .../ui/database/FirebaseListAdapter.java | 3 +- .../ui/database/FirebaseRecyclerAdapter.java | 2 - .../ui/database/ObservableSnapshotArray.java | 89 ++++++++++++- .../firebase/ui/database/Preconditions.java | 9 +- 14 files changed, 279 insertions(+), 216 deletions(-) create mode 100644 database/src/androidTest/java/com/firebase/ui/database/ClassSnapshotParser.java create mode 100644 database/src/main/java/com/firebase/ui/database/CachingObservableSnapshotArray.java diff --git a/build.gradle b/build.gradle index d0e775857..33e11e125 100644 --- a/build.gradle +++ b/build.gradle @@ -8,7 +8,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:2.3.0-rc1' + classpath 'com.android.tools.build:gradle:2.3.0' classpath 'com.google.gms:google-services:3.0.0' classpath 'io.fabric.tools:gradle:1.+' classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' diff --git a/database/src/androidTest/java/com/firebase/ui/database/Bean.java b/database/src/androidTest/java/com/firebase/ui/database/Bean.java index faf5ccbcb..7146b2617 100644 --- a/database/src/androidTest/java/com/firebase/ui/database/Bean.java +++ b/database/src/androidTest/java/com/firebase/ui/database/Bean.java @@ -30,4 +30,16 @@ public String getText() { public boolean isBool() { return mBool; } + + public void setNumber(int number) { + this.mNumber = number; + } + + public void setText(String text) { + this.mText = text; + } + + public void setBool(boolean bool) { + this.mBool = bool; + } } diff --git a/database/src/androidTest/java/com/firebase/ui/database/ClassSnapshotParser.java b/database/src/androidTest/java/com/firebase/ui/database/ClassSnapshotParser.java new file mode 100644 index 000000000..5137451e3 --- /dev/null +++ b/database/src/androidTest/java/com/firebase/ui/database/ClassSnapshotParser.java @@ -0,0 +1,25 @@ +package com.firebase.ui.database; + +import android.support.annotation.NonNull; + +import com.google.firebase.database.DataSnapshot; + +/** + * A convenience implementation of {@link SnapshotParser} that converts a {@link DataSnapshot} to + * the parameterized class via {@link DataSnapshot#getValue(Class)}. + * @param the POJO class to create from snapshots. + */ +public class ClassSnapshotParser implements SnapshotParser { + + private Class mClass; + + public ClassSnapshotParser(@NonNull Class clazz) { + Preconditions.checkNotNull(clazz); + mClass = clazz; + } + + @Override + public T parseSnapshot(DataSnapshot snapshot) { + return snapshot.getValue(mClass); + } +} diff --git a/database/src/androidTest/java/com/firebase/ui/database/FirebaseArrayOfObjectsTest.java b/database/src/androidTest/java/com/firebase/ui/database/FirebaseArrayOfObjectsTest.java index b34badf7b..13240c107 100644 --- a/database/src/androidTest/java/com/firebase/ui/database/FirebaseArrayOfObjectsTest.java +++ b/database/src/androidTest/java/com/firebase/ui/database/FirebaseArrayOfObjectsTest.java @@ -29,7 +29,6 @@ import java.util.concurrent.Callable; import static com.firebase.ui.database.TestUtils.getAppInstance; -import static com.firebase.ui.database.TestUtils.getBean; import static com.firebase.ui.database.TestUtils.runAndWaitUntil; @RunWith(AndroidJUnit4.class) @@ -37,7 +36,7 @@ public class FirebaseArrayOfObjectsTest { private static final int INITIAL_SIZE = 3; private DatabaseReference mRef; - private FirebaseArray mArray; + private FirebaseArray mArray; private ChangeEventListener mListener; @Before @@ -47,7 +46,7 @@ public void setUp() throws Exception { .getReference() .child("firebasearray") .child("objects"); - mArray = new FirebaseArray(mRef); + mArray = new FirebaseArray<>(mRef, new ClassSnapshotParser<>(Bean.class)); mRef.removeValue(); mListener = runAndWaitUntil(mArray, new Runnable() { @Override @@ -95,7 +94,7 @@ public void run() { }, new Callable() { @Override public Boolean call() throws Exception { - return mArray.get(3).getValue(Bean.class).getNumber() == 4; + return mArray.getObject(3).getNumber() == 4; } }); } @@ -110,9 +109,8 @@ public void run() { }, new Callable() { @Override public Boolean call() throws Exception { - return mArray.get(3).getValue(Bean.class).getNumber() == 3 && mArray.get(0) - .getValue(Bean.class) - .getNumber() == 4; + return mArray.getObject(3).getNumber() == 3 && + mArray.getObject(0).getNumber() == 4; } }); } @@ -127,11 +125,42 @@ public void run() { }, new Callable() { @Override public Boolean call() throws Exception { - return getBean(mArray, 0).getNumber() == 3 - && getBean(mArray, 1).getNumber() == 1 - && getBean(mArray, 2).getNumber() == 2; + return mArray.getObject(0).getNumber() == 3 + && mArray.getObject(1).getNumber() == 1 + && mArray.getObject(2).getNumber() == 2; //return isValuesEqual(mArray, new int[]{3, 1, 2}); } }); } + + @Test + public void testCacheInvalidates() throws Exception { + final DatabaseReference pushRef = mRef.push(); + + // Set initial value to "5" + runAndWaitUntil(mArray, new Runnable() { + @Override + public void run() { + pushRef.setValue(new Bean(5), 100); + } + }, new Callable() { + @Override + public Boolean call() throws Exception { + return mArray.getObject(3).getNumber() == 5; + } + }); + + // Change the value to "6" and ensure that the change is propagated + runAndWaitUntil(mArray, new Runnable() { + @Override + public void run() { + pushRef.setValue(new Bean(6), 100); + } + }, new Callable() { + @Override + public Boolean call() throws Exception { + return mArray.getObject(3).getNumber() == 6; + } + }); + } } diff --git a/database/src/androidTest/java/com/firebase/ui/database/FirebaseIndexArrayOfObjectsTest.java b/database/src/androidTest/java/com/firebase/ui/database/FirebaseIndexArrayOfObjectsTest.java index 94c4468ce..bc59e0ba2 100644 --- a/database/src/androidTest/java/com/firebase/ui/database/FirebaseIndexArrayOfObjectsTest.java +++ b/database/src/androidTest/java/com/firebase/ui/database/FirebaseIndexArrayOfObjectsTest.java @@ -25,11 +25,9 @@ import org.junit.Test; import org.junit.runner.RunWith; -import java.util.Objects; import java.util.concurrent.Callable; import static com.firebase.ui.database.TestUtils.getAppInstance; -import static com.firebase.ui.database.TestUtils.getBean; import static com.firebase.ui.database.TestUtils.runAndWaitUntil; @RunWith(AndroidJUnit4.class) @@ -48,8 +46,7 @@ public void setUp() throws Exception { mRef = databaseInstance.getReference().child("firebasearray").child("objects"); mKeyRef = databaseInstance.getReference().child("firebaseindexarray").child("objects"); - // TODO(samstern): FIX ALL DIS - mArray = new FirebaseIndexArray<>(mKeyRef, mRef); + mArray = new FirebaseIndexArray<>(mKeyRef, mRef, new ClassSnapshotParser<>(Bean.class)); mRef.removeValue(); mKeyRef.removeValue(); @@ -99,7 +96,7 @@ public void run() { }, new Callable() { @Override public Boolean call() throws Exception { - return mArray.get(3).getValue(Bean.class).getNumber() == 4; + return mArray.getObject(3).getNumber() == 4; } }); } @@ -114,9 +111,8 @@ public void run() { }, new Callable() { @Override public Boolean call() throws Exception { - return mArray.get(3).getValue(Bean.class).getNumber() == 3 && mArray.get(0) - .getValue(Bean.class) - .getNumber() == 4; + return mArray.getObject(3).getNumber() == 3 && + mArray.getObject(0).getNumber() == 4; } }); } @@ -131,9 +127,9 @@ public void run() { }, new Callable() { @Override public Boolean call() throws Exception { - return getBean(mArray, 0).getNumber() == 3 - && getBean(mArray, 1).getNumber() == 1 - && getBean(mArray, 2).getNumber() == 2; + return mArray.getObject(0).getNumber() == 3 + && mArray.getObject(1).getNumber() == 1 + && mArray.getObject(2).getNumber() == 2; //return isValuesEqual(mArray, new int[]{3, 1, 2}); } }); diff --git a/database/src/androidTest/java/com/firebase/ui/database/TestUtils.java b/database/src/androidTest/java/com/firebase/ui/database/TestUtils.java index 65695ad6a..88dc0b693 100644 --- a/database/src/androidTest/java/com/firebase/ui/database/TestUtils.java +++ b/database/src/androidTest/java/com/firebase/ui/database/TestUtils.java @@ -84,10 +84,6 @@ public static boolean isValuesEqual(ObservableSnapshotArray array, int[] expe return true; } - public static Bean getBean(ObservableSnapshotArray array, int index) { - return array.get(index).getValue(Bean.class); - } - public static void pushValue(DatabaseReference keyRef, DatabaseReference ref, Object value, diff --git a/database/src/main/java/com/firebase/ui/database/CachingObservableSnapshotArray.java b/database/src/main/java/com/firebase/ui/database/CachingObservableSnapshotArray.java new file mode 100644 index 000000000..8f9f17297 --- /dev/null +++ b/database/src/main/java/com/firebase/ui/database/CachingObservableSnapshotArray.java @@ -0,0 +1,57 @@ +package com.firebase.ui.database; + +import com.google.firebase.database.DataSnapshot; + +import java.util.HashMap; +import java.util.Map; + +/** + * TODO(samstern): Document + */ +public abstract class CachingObservableSnapshotArray extends ObservableSnapshotArray { + + private Map mObjectCache = new HashMap<>(); + + public CachingObservableSnapshotArray() { + super(); + } + + public CachingObservableSnapshotArray(SnapshotParser parser) { + super(parser); + } + + @Override + public T getObject(int index) { + String key = get(index).getKey(); + + // Return from the cache if possible, otherwise populate the cache and return + if (mObjectCache.containsKey(key)) { + return mObjectCache.get(key); + } else { + T object = super.getObject(index); + mObjectCache.put(key, object); + return object; + } + } + + protected void clearData() { + getSnapshots().clear(); + mObjectCache.clear(); + } + + protected DataSnapshot removeData(int index) { + DataSnapshot snapshot = getSnapshots().remove(index); + if (snapshot != null) { + mObjectCache.remove(snapshot.getKey()); + } + + return snapshot; + } + + protected void updateData(int index, DataSnapshot snapshot) { + getSnapshots().set(index, snapshot); + mObjectCache.remove(snapshot.getKey()); + } + + +} diff --git a/database/src/main/java/com/firebase/ui/database/ChangeEventListener.java b/database/src/main/java/com/firebase/ui/database/ChangeEventListener.java index c75b5957a..843f027e1 100644 --- a/database/src/main/java/com/firebase/ui/database/ChangeEventListener.java +++ b/database/src/main/java/com/firebase/ui/database/ChangeEventListener.java @@ -37,10 +37,10 @@ enum EventType { } /** - * TODO(samstern): Document. * A callback for when a child has changed in FirebaseArray. * * @param type The type of event received + * @param snapshot the {@link DataSnapshot} of the changed child. * @param index The index at which the change occurred * @param oldIndex If {@code type} is a moved event, the previous index of the moved child. For * any other event, {@code oldIndex} will be -1. diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseArray.java b/database/src/main/java/com/firebase/ui/database/FirebaseArray.java index 3a4f9119c..c24104043 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseArray.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseArray.java @@ -15,7 +15,6 @@ package com.firebase.ui.database; import android.support.annotation.NonNull; -import android.support.annotation.RestrictTo; import com.google.firebase.database.ChildEventListener; import com.google.firebase.database.DataSnapshot; @@ -24,19 +23,20 @@ import com.google.firebase.database.ValueEventListener; import java.util.ArrayList; -import java.util.Collection; -import java.util.Iterator; +import java.util.HashMap; import java.util.List; -import java.util.ListIterator; +import java.util.Map; /** * This class implements a collection on top of a Firebase location. */ -public class FirebaseArray extends ObservableSnapshotArray implements ChildEventListener, ValueEventListener { +public class FirebaseArray extends CachingObservableSnapshotArray implements ChildEventListener, ValueEventListener { protected final Query mQuery; private List mSnapshots = new ArrayList<>(); + private Map mObjectCache = new HashMap<>(); + /** * @param query The Firebase location to watch for data changes. Can also be a slice of a * location, using some combination of {@code limit()}, {@code startAt()}, and @@ -52,9 +52,8 @@ public FirebaseArray(Query query, SnapshotParser parser) { } @Override - public T getObject(int index) { - // TODO: Cache this! - return super.getObject(index); + protected List getSnapshots() { + return mSnapshots; } @Override @@ -80,7 +79,7 @@ public void removeChangeEventListener(@NonNull ChangeEventListener listener) { mQuery.removeEventListener((ValueEventListener) this); mQuery.removeEventListener((ChildEventListener) this); - mSnapshots.clear(); + clearData(); } } @@ -100,8 +99,7 @@ public void onChildAdded(DataSnapshot snapshot, String previousChildKey) { public void onChildChanged(DataSnapshot snapshot, String previousChildKey) { int index = getIndexForKey(snapshot.getKey()); - mSnapshots.set(index, snapshot); - + updateData(index, snapshot); notifyChangeEventListeners(ChangeEventListener.EventType.CHANGED, snapshot, index); } @@ -109,8 +107,7 @@ public void onChildChanged(DataSnapshot snapshot, String previousChildKey) { public void onChildRemoved(DataSnapshot snapshot) { int index = getIndexForKey(snapshot.getKey()); - mSnapshots.remove(index); - + removeData(index); notifyChangeEventListeners(ChangeEventListener.EventType.REMOVED, snapshot, index); } @@ -152,76 +149,6 @@ public DataSnapshot getSnapshot(int index) { return mSnapshots.get(index); } - @Override - public int size() { - return mSnapshots.size(); - } - - @Override - public boolean isEmpty() { - return mSnapshots.isEmpty(); - } - - @Override - public boolean contains(Object o) { - return mSnapshots.contains(o); - } - - /** - * {@inheritDoc} - * - * @return an immutable iterator - */ - @Override - public Iterator iterator() { - return new ImmutableIterator(mSnapshots.iterator()); - } - - @Override - public DataSnapshot[] toArray() { - return mSnapshots.toArray(new DataSnapshot[mSnapshots.size()]); - } - - @Override - public boolean containsAll(Collection c) { - return mSnapshots.containsAll(c); - } - - @Override - public DataSnapshot get(int index) { - return mSnapshots.get(index); - } - - @Override - public int indexOf(Object o) { - return mSnapshots.indexOf(o); - } - - @Override - public int lastIndexOf(Object o) { - return mSnapshots.lastIndexOf(o); - } - - /** - * {@inheritDoc} - * - * @return an immutable list iterator - */ - @Override - public ListIterator listIterator() { - return new ImmutableListIterator(mSnapshots.listIterator()); - } - - /** - * {@inheritDoc} - * - * @return an immutable list iterator - */ - @Override - public ListIterator listIterator(int index) { - return new ImmutableListIterator(mSnapshots.listIterator(index)); - } - @Override public boolean equals(Object obj) { if (this == obj) return true; @@ -247,16 +174,4 @@ public String toString() { return "FirebaseArray is inactive"; } } - - /** - * Guaranteed to throw an exception. Use {@link #toArray()} instead to get an array of {@link - * DataSnapshot}s. - * - * @throws UnsupportedOperationException always - */ - @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) - @Override - public final T[] toArray(T[] a) { - throw new UnsupportedOperationException(); - } } 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 a8e1f1fc8..8ff359721 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseIndexArray.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseIndexArray.java @@ -25,24 +25,20 @@ import com.google.firebase.database.ValueEventListener; import java.util.ArrayList; -import java.util.Collection; import java.util.HashMap; -import java.util.Iterator; import java.util.List; -import java.util.ListIterator; import java.util.Map; -public class FirebaseIndexArray extends ObservableSnapshotArray implements ChangeEventListener { +public class FirebaseIndexArray extends CachingObservableSnapshotArray implements ChangeEventListener { private static final String TAG = "FirebaseIndexArray"; private DatabaseReference mDataRef; private Map mRefs = new HashMap<>(); - private FirebaseArray mKeySnapshots; + private FirebaseArray mKeySnapshots; private List mDataSnapshots = new ArrayList<>(); - // TODO(samstern): Need to pass in a SnapshotParser. - public FirebaseIndexArray() {} + private Map mObjectCache = new HashMap<>(); /** * @param keyQuery The Firebase location containing the list of keys to be found in {@code @@ -52,7 +48,19 @@ public FirebaseIndexArray() {} * keyQuery}'s location represents a list item in the {@link RecyclerView}. */ public FirebaseIndexArray(Query keyQuery, DatabaseReference dataRef) { - mKeySnapshots = new FirebaseArray<>(keyQuery); + this(keyQuery, dataRef, null); + } + + public FirebaseIndexArray(Query keyQuery, DatabaseReference dataRef, SnapshotParser parser) { + super(parser); + + mKeySnapshots = new FirebaseArray<>(keyQuery, new SnapshotParser() { + @Override + public String parseSnapshot(DataSnapshot snapshot) { + return snapshot.getKey(); + } + }); + mDataRef = dataRef; mKeySnapshots.addChangeEventListener(this); @@ -68,7 +76,8 @@ public void onChildChanged(EventType type, DataSnapshot snapshot, int index, int onKeyMoved(snapshot, index, oldIndex); break; case CHANGED: - // TODO(samstern): Can this be a no-op? + // This is a no-op, we don't care when a key 'changes' since that should not + // be a supported operation break; case REMOVED: onKeyRemoved(snapshot, index); @@ -78,7 +87,7 @@ public void onChildChanged(EventType type, DataSnapshot snapshot, int index, int @Override public void onDataChanged() { - // TODO: Anything? + // No-op, we don't listen to batch events for the key ref } @Override @@ -93,23 +102,27 @@ public void removeChangeEventListener(@NonNull ChangeEventListener listener) { for (Query query : mRefs.keySet()) { query.removeEventListener(mRefs.get(query)); } - mRefs.clear(); - mDataSnapshots.clear(); + + clearData(); } } @Override - public T getObject(int index) { - // TODO: Cache this! - return super.getObject(index); + protected List getSnapshots() { + return mDataSnapshots; + } + + @Override + protected void clearData() { + super.clearData(); + mRefs.clear(); } - // TODO(samstern): Figure out what's going on here, make sure it's still right private int getIndexForKey(String key) { int dataCount = size(); int index = 0; for (int keyIndex = 0; index < dataCount; keyIndex++) { - String superKey = mKeySnapshots.get(keyIndex).getKey(); + String superKey = mKeySnapshots.getObject(keyIndex); if (key.equals(superKey)) { break; } else if (mDataSnapshots.get(index).getKey().equals(superKey)) { @@ -137,9 +150,8 @@ protected void onKeyAdded(DataSnapshot data) { protected void onKeyMoved(DataSnapshot data, int index, int oldIndex) { String key = data.getKey(); - // TODO(samstern): I don't think this block is correct anymore if (isKeyAtIndex(key, oldIndex)) { - DataSnapshot snapshot = mDataSnapshots.remove(oldIndex); + DataSnapshot snapshot = removeData(oldIndex); int newIndex = getIndexForKey(key); mDataSnapshots.add(newIndex, snapshot); notifyChangeEventListeners(ChangeEventListener.EventType.MOVED, snapshot, newIndex, oldIndex); @@ -151,7 +163,7 @@ protected void onKeyRemoved(DataSnapshot data, int index) { mDataRef.child(key).removeEventListener(mRefs.remove(mDataRef.getRef().child(key))); if (isKeyAtIndex(key, index)) { - DataSnapshot snapshot = mDataSnapshots.remove(index); + DataSnapshot snapshot = removeData(index); notifyChangeEventListeners(ChangeEventListener.EventType.REMOVED, snapshot, index); } } @@ -172,13 +184,13 @@ public void onDataChange(DataSnapshot snapshot) { notifyChangeEventListeners(ChangeEventListener.EventType.ADDED, snapshot, index); } else { // We already know about this data, just update it - mDataSnapshots.set(index, snapshot); + updateData(index, snapshot); notifyChangeEventListeners(ChangeEventListener.EventType.CHANGED, snapshot, index); } } else { if (isKeyAtIndex(key, index)) { // This data has disappeared, remove it - mDataSnapshots.remove(index); + removeData(index); notifyChangeEventListeners(ChangeEventListener.EventType.REMOVED, snapshot, index); } else { // Data we never knew about has disappeared @@ -193,67 +205,6 @@ public void onCancelled(DatabaseError error) { } } - @Override - public int size() { - return mDataSnapshots.size(); - } - - @Override - public boolean isEmpty() { - return mDataSnapshots.isEmpty(); - } - - @Override - public boolean contains(Object o) { - return mDataSnapshots.contains(o); - } - - @Override - public Iterator iterator() { - return new ImmutableIterator(mDataSnapshots.iterator()); - } - - @Override - public DataSnapshot[] toArray() { - return mDataSnapshots.toArray(new DataSnapshot[mDataSnapshots.size()]); - } - - @NonNull - @Override - public T[] toArray(@NonNull T[] ts) { - return null; - } - - @Override - public boolean containsAll(Collection c) { - return mDataSnapshots.containsAll(c); - } - - @Override - public DataSnapshot get(int index) { - return mDataSnapshots.get(index); - } - - @Override - public int indexOf(Object o) { - return mDataSnapshots.indexOf(o); - } - - @Override - public int lastIndexOf(Object o) { - return mDataSnapshots.lastIndexOf(o); - } - - @Override - public ListIterator listIterator() { - return new ImmutableListIterator(mDataSnapshots.listIterator()); - } - - @Override - public ListIterator listIterator(int index) { - return new ImmutableListIterator(mDataSnapshots.listIterator(index)); - } - @Override public boolean equals(Object obj) { if (this == obj) return true; @@ -281,4 +232,5 @@ public String toString() { return "FirebaseIndexArray is inactive"; } } + } diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseListAdapter.java b/database/src/main/java/com/firebase/ui/database/FirebaseListAdapter.java index bfe6e4fe9..c9f3bd0df 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseListAdapter.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseListAdapter.java @@ -57,13 +57,12 @@ public FirebaseListAdapter(Activity activity, * @param query The Firebase location to watch for data changes. Can also be a slice of a * location, using some combination of {@code limit()}, {@code startAt()}, and * {@code endAt()}. - * @see #FirebaseListAdapter(Activity, FirebaseArray, Class, int) + * @see #FirebaseListAdapter(Activity, ObservableSnapshotArray, Class, int) */ public FirebaseListAdapter(Activity activity, Class modelClass, @LayoutRes int modelLayout, Query query) { - mSnapshots = new FirebaseArray(query, this); mActivity = activity; mModelClass = modelClass; diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseRecyclerAdapter.java b/database/src/main/java/com/firebase/ui/database/FirebaseRecyclerAdapter.java index 240440470..92c9f972d 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseRecyclerAdapter.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseRecyclerAdapter.java @@ -69,8 +69,6 @@ public FirebaseRecyclerAdapter(Class modelClass, @LayoutRes int modelLayout, Class viewHolderClass, Query query) { - - // TODO(samstern): DRY with above constructor mSnapshots = new FirebaseArray(query, this); mModelClass = modelClass; mViewHolderClass = viewHolderClass; diff --git a/database/src/main/java/com/firebase/ui/database/ObservableSnapshotArray.java b/database/src/main/java/com/firebase/ui/database/ObservableSnapshotArray.java index 4a5a1395e..1722f6e66 100644 --- a/database/src/main/java/com/firebase/ui/database/ObservableSnapshotArray.java +++ b/database/src/main/java/com/firebase/ui/database/ObservableSnapshotArray.java @@ -2,15 +2,22 @@ import android.support.annotation.CallSuper; import android.support.annotation.NonNull; +import android.support.annotation.RestrictTo; import com.google.firebase.database.DataSnapshot; import com.google.firebase.database.DatabaseError; +import java.util.Collection; +import java.util.Iterator; import java.util.List; +import java.util.ListIterator; import java.util.concurrent.CopyOnWriteArrayList; /** - * TODO(samstern): Document and document all methods + * Exposes a collection of items in Firebase as a {@link List} of {@link DataSnapshot}. To observe + * the list attach a {@link com.google.firebase.database.ChildEventListener}. + * + * @param a POJO class to which the DataSnapshots can be converted. */ public abstract class ObservableSnapshotArray extends ImmutableList { @@ -26,7 +33,9 @@ public ObservableSnapshotArray(SnapshotParser parser) { } /** - * TODO(samstern): Document. + * Attach a {@link ChangeEventListener} to this array. The listener will receive one + * 'ADDED' event for each item that exists in the array at the time of attachment, and then + * receive all future chld events. */ @CallSuper public ChangeEventListener addChangeEventListener(@NonNull ChangeEventListener listener) { @@ -41,7 +50,7 @@ public ChangeEventListener addChangeEventListener(@NonNull ChangeEventListener l } /** - * TODO(samstern): Document. + * Detach a {@link com.google.firebase.database.ChildEventListener} from this array. */ @CallSuper public void removeChangeEventListener(@NonNull ChangeEventListener listener) { @@ -60,6 +69,8 @@ public void removeAllListeners() { } } + protected abstract List getSnapshots(); + protected final void notifyChangeEventListeners(ChangeEventListener.EventType type, DataSnapshot snapshot, int index) { @@ -102,10 +113,80 @@ public final boolean isListening(ChangeEventListener listener) { return mListeners.contains(listener); } - // TODO(samstern): Document + /** + * Get the {@link DataSnapshot} at a given position converted to an object of the parameterized + * type. This uses the {@link SnapshotParser} passed to the constructor. If the parser was not + * initialized this will throw an unchecked exception. + */ public T getObject(int index) { Preconditions.checkNotNull(mParser); return mParser.parseSnapshot(get(index)); } + @Override + public int size() { + return getSnapshots().size(); + } + + @Override + public boolean isEmpty() { + return getSnapshots().isEmpty(); + } + + @Override + public boolean contains(Object o) { + return getSnapshots().contains(o); + } + + @Override + public Iterator iterator() { + return new ImmutableIterator(getSnapshots().iterator()); + } + + @Override + public DataSnapshot[] toArray() { + return getSnapshots().toArray(new DataSnapshot[getSnapshots().size()]); + } + + @Override + public boolean containsAll(Collection c) { + return getSnapshots().containsAll(c); + } + + @Override + public DataSnapshot get(int index) { + return getSnapshots().get(index); + } + + @Override + public int indexOf(Object o) { + return getSnapshots().indexOf(o); + } + + @Override + public int lastIndexOf(Object o) { + return getSnapshots().lastIndexOf(o); + } + + @Override + public ListIterator listIterator() { + return new ImmutableListIterator(getSnapshots().listIterator()); + } + + @Override + public ListIterator listIterator(int index) { + return new ImmutableListIterator(getSnapshots().listIterator(index)); + } + + /** + * Guaranteed to throw an exception. Use {@link #toArray()} instead to get an array of {@link + * DataSnapshot}s. + * + * @throws UnsupportedOperationException always + */ + @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) + @Override + public final T[] toArray(T[] a) { + throw new UnsupportedOperationException(); + } } diff --git a/database/src/main/java/com/firebase/ui/database/Preconditions.java b/database/src/main/java/com/firebase/ui/database/Preconditions.java index 09edf519b..c16095532 100644 --- a/database/src/main/java/com/firebase/ui/database/Preconditions.java +++ b/database/src/main/java/com/firebase/ui/database/Preconditions.java @@ -1,12 +1,15 @@ package com.firebase.ui.database; +import android.support.annotation.RestrictTo; + /** - * TODO(samstern): Document + * Convenience class for checking argument conditions. */ -public class Preconditions { +@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) +class Preconditions { public static void checkNotNull(Object o) { - if (o == null) throw new IllegalArgumentException("Listener cannot be null."); + if (o == null) throw new IllegalArgumentException("Argument cannot be null."); } } From a87a0d9568d423e5f895ff5896a01539bcf70569 Mon Sep 17 00:00:00 2001 From: Sam Date: Mon, 27 Feb 2017 11:20:48 -0800 Subject: [PATCH 84/93] Fix build and docs --- build.gradle | 2 +- .../firebase/ui/database/CachingObservableSnapshotArray.java | 4 +++- .../main/java/com/firebase/ui/database/FirebaseAdapter.java | 4 ++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/build.gradle b/build.gradle index 33e11e125..d0e775857 100644 --- a/build.gradle +++ b/build.gradle @@ -8,7 +8,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:2.3.0' + classpath 'com.android.tools.build:gradle:2.3.0-rc1' classpath 'com.google.gms:google-services:3.0.0' classpath 'io.fabric.tools:gradle:1.+' classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' diff --git a/database/src/main/java/com/firebase/ui/database/CachingObservableSnapshotArray.java b/database/src/main/java/com/firebase/ui/database/CachingObservableSnapshotArray.java index 8f9f17297..d2c9e598a 100644 --- a/database/src/main/java/com/firebase/ui/database/CachingObservableSnapshotArray.java +++ b/database/src/main/java/com/firebase/ui/database/CachingObservableSnapshotArray.java @@ -6,7 +6,9 @@ import java.util.Map; /** - * TODO(samstern): Document + * An extension of {@link ObservableSnapshotArray} that caches the result of {@link #getObject(int)} + * so that repeated calls for the same key are not expensive (unless the underlying snapshot + * has changed). */ public abstract class CachingObservableSnapshotArray extends ObservableSnapshotArray { diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseAdapter.java b/database/src/main/java/com/firebase/ui/database/FirebaseAdapter.java index 1f28424f4..5e4e045ae 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseAdapter.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseAdapter.java @@ -3,10 +3,10 @@ import com.google.firebase.database.DatabaseReference; public interface FirebaseAdapter extends ChangeEventListener, SnapshotParser { + /** * If you need to do some setup before the adapter starts listening for change events in the - * database (such as setting a custom {@link JoinResolver}), do so it here and then call {@code - * super.startListening()}. + * database, do so it here and then call {@code super.startListening()}. */ void startListening(); From 51b05e632baf31dfa6ace5f1fbadb79645a238e0 Mon Sep 17 00:00:00 2001 From: Sam Date: Wed, 1 Mar 2017 09:51:05 -0800 Subject: [PATCH 85/93] Respond to review comments --- .../java/com/firebase/ui/database/Bean.java | 6 ++-- .../database/FirebaseArrayOfObjectsTest.java | 2 +- .../FirebaseIndexArrayOfObjectsTest.java | 2 +- .../CachingObservableSnapshotArray.java | 17 +++++++++- .../ui/database/ClassSnapshotParser.java | 0 .../firebase/ui/database/FirebaseArray.java | 33 +++++++++++++++---- .../ui/database/FirebaseIndexArray.java | 30 ++++++++++++++--- .../ui/database/FirebaseListAdapter.java | 14 ++++---- .../ui/database/FirebaseRecyclerAdapter.java | 14 ++++---- .../ui/database/ObservableSnapshotArray.java | 19 ++++++++++- 10 files changed, 108 insertions(+), 29 deletions(-) rename database/src/{androidTest => main}/java/com/firebase/ui/database/ClassSnapshotParser.java (100%) diff --git a/database/src/androidTest/java/com/firebase/ui/database/Bean.java b/database/src/androidTest/java/com/firebase/ui/database/Bean.java index 7146b2617..600497f0b 100644 --- a/database/src/androidTest/java/com/firebase/ui/database/Bean.java +++ b/database/src/androidTest/java/com/firebase/ui/database/Bean.java @@ -32,14 +32,14 @@ public boolean isBool() { } public void setNumber(int number) { - this.mNumber = number; + mNumber = number; } public void setText(String text) { - this.mText = text; + mText = text; } public void setBool(boolean bool) { - this.mBool = bool; + mBool = bool; } } diff --git a/database/src/androidTest/java/com/firebase/ui/database/FirebaseArrayOfObjectsTest.java b/database/src/androidTest/java/com/firebase/ui/database/FirebaseArrayOfObjectsTest.java index 13240c107..d61f411f6 100644 --- a/database/src/androidTest/java/com/firebase/ui/database/FirebaseArrayOfObjectsTest.java +++ b/database/src/androidTest/java/com/firebase/ui/database/FirebaseArrayOfObjectsTest.java @@ -46,7 +46,7 @@ public void setUp() throws Exception { .getReference() .child("firebasearray") .child("objects"); - mArray = new FirebaseArray<>(mRef, new ClassSnapshotParser<>(Bean.class)); + mArray = new FirebaseArray<>(mRef, Bean.class); mRef.removeValue(); mListener = runAndWaitUntil(mArray, new Runnable() { @Override diff --git a/database/src/androidTest/java/com/firebase/ui/database/FirebaseIndexArrayOfObjectsTest.java b/database/src/androidTest/java/com/firebase/ui/database/FirebaseIndexArrayOfObjectsTest.java index bc59e0ba2..e54d805d6 100644 --- a/database/src/androidTest/java/com/firebase/ui/database/FirebaseIndexArrayOfObjectsTest.java +++ b/database/src/androidTest/java/com/firebase/ui/database/FirebaseIndexArrayOfObjectsTest.java @@ -46,7 +46,7 @@ public void setUp() throws Exception { mRef = databaseInstance.getReference().child("firebasearray").child("objects"); mKeyRef = databaseInstance.getReference().child("firebaseindexarray").child("objects"); - mArray = new FirebaseIndexArray<>(mKeyRef, mRef, new ClassSnapshotParser<>(Bean.class)); + mArray = new FirebaseIndexArray<>(mKeyRef, mRef, Bean.class); mRef.removeValue(); mKeyRef.removeValue(); diff --git a/database/src/main/java/com/firebase/ui/database/CachingObservableSnapshotArray.java b/database/src/main/java/com/firebase/ui/database/CachingObservableSnapshotArray.java index d2c9e598a..db3ef0ba0 100644 --- a/database/src/main/java/com/firebase/ui/database/CachingObservableSnapshotArray.java +++ b/database/src/main/java/com/firebase/ui/database/CachingObservableSnapshotArray.java @@ -1,5 +1,7 @@ package com.firebase.ui.database; +import android.support.annotation.NonNull; + import com.google.firebase.database.DataSnapshot; import java.util.HashMap; @@ -14,11 +16,24 @@ public abstract class CachingObservableSnapshotArray extends ObservableSnapsh private Map mObjectCache = new HashMap<>(); + /** + * See {@link ObservableSnapshotArray#ObservableSnapshotArray()}. + */ public CachingObservableSnapshotArray() { super(); } - public CachingObservableSnapshotArray(SnapshotParser parser) { + /** + * See {@link ObservableSnapshotArray#ObservableSnapshotArray(Class)}. + */ + public CachingObservableSnapshotArray(@NonNull Class tClass) { + super(tClass); + } + + /** + * See {@link ObservableSnapshotArray#ObservableSnapshotArray(SnapshotParser)}. + */ + public CachingObservableSnapshotArray(@NonNull SnapshotParser parser) { super(parser); } diff --git a/database/src/androidTest/java/com/firebase/ui/database/ClassSnapshotParser.java b/database/src/main/java/com/firebase/ui/database/ClassSnapshotParser.java similarity index 100% rename from database/src/androidTest/java/com/firebase/ui/database/ClassSnapshotParser.java rename to database/src/main/java/com/firebase/ui/database/ClassSnapshotParser.java diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseArray.java b/database/src/main/java/com/firebase/ui/database/FirebaseArray.java index c24104043..d2bc7182e 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseArray.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseArray.java @@ -23,31 +23,52 @@ import com.google.firebase.database.ValueEventListener; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; -import java.util.Map; /** * This class implements a collection on top of a Firebase location. */ public class FirebaseArray extends CachingObservableSnapshotArray implements ChildEventListener, ValueEventListener { - protected final Query mQuery; + private Query mQuery; private List mSnapshots = new ArrayList<>(); - private Map mObjectCache = new HashMap<>(); - /** + * Create a new FirebaseArray with no {@link SnapshotParser}. Calls to + * {@link #getObject(int)} will fail. + * * @param query The Firebase location to watch for data changes. Can also be a slice of a * location, using some combination of {@code limit()}, {@code startAt()}, and * {@code endAt()}. */ public FirebaseArray(Query query) { - this(query, null); + super(); + init(query); + } + + /** + * Create a new FirebaseArray that parses snapshots as members of a given class. + * + * See {@link ObservableSnapshotArray#ObservableSnapshotArray(Class)}. + * See {@link #FirebaseArray(Query)}. + */ + public FirebaseArray(Query query, Class tClass) { + super(tClass); + init(query); } + /** + * Create a new FirebaseArray with a custom {@link SnapshotParser}. + * + * See {@link ObservableSnapshotArray#ObservableSnapshotArray(SnapshotParser)}. + * See {@link #FirebaseArray(Query)}. + */ public FirebaseArray(Query query, SnapshotParser parser) { super(parser); + init(query); + } + + private void init(Query query) { mQuery = query; } 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 8ff359721..ab6b89010 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseIndexArray.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseIndexArray.java @@ -38,9 +38,10 @@ public class FirebaseIndexArray extends CachingObservableSnapshotArray imp private FirebaseArray mKeySnapshots; private List mDataSnapshots = new ArrayList<>(); - private Map mObjectCache = new HashMap<>(); - /** + * Create a new FirebaseIndexArray without a {@link SnapshotParser}. + * Calls to {@link #getObject(int)} will fail. + * * @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()}. @@ -48,12 +49,33 @@ public class FirebaseIndexArray extends CachingObservableSnapshotArray imp * keyQuery}'s location represents a list item in the {@link RecyclerView}. */ public FirebaseIndexArray(Query keyQuery, DatabaseReference dataRef) { - this(keyQuery, dataRef, null); + super(); + init(keyQuery, dataRef); + } + + /** + * Create a new FirebaseIndexArray that parses snapshots as members of a given class. + * + * See {@link ObservableSnapshotArray#ObservableSnapshotArray(Class)}. + * See {@link #FirebaseIndexArray(Query, DatabaseReference)}. + */ + public FirebaseIndexArray(Query keyQuery, DatabaseReference dataRef, Class tClass) { + super(tClass); + init(keyQuery, dataRef); } + /** + * Create a new FirebaseIndexArray with a custom {@link SnapshotParser}. + * + * See {@link ObservableSnapshotArray#ObservableSnapshotArray(SnapshotParser)}. + * See {@link #FirebaseIndexArray(Query, DatabaseReference)}. + */ public FirebaseIndexArray(Query keyQuery, DatabaseReference dataRef, SnapshotParser parser) { super(parser); + init(keyQuery, dataRef); + } + private void init(Query keyQuery, DatabaseReference dataRef) { mKeySnapshots = new FirebaseArray<>(keyQuery, new SnapshotParser() { @Override public String parseSnapshot(DataSnapshot snapshot) { @@ -193,7 +215,7 @@ public void onDataChange(DataSnapshot snapshot) { removeData(index); notifyChangeEventListeners(ChangeEventListener.EventType.REMOVED, snapshot, index); } else { - // Data we never knew about has disappeared + // Data does not exist Log.w(TAG, "Key not found at ref: " + snapshot.getRef()); } } diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseListAdapter.java b/database/src/main/java/com/firebase/ui/database/FirebaseListAdapter.java index c9f3bd0df..f958748e8 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseListAdapter.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseListAdapter.java @@ -45,12 +45,8 @@ public FirebaseListAdapter(Activity activity, ObservableSnapshotArray snapshots, Class modelClass, @LayoutRes int modelLayout) { - mSnapshots = snapshots; - mActivity = activity; - mModelClass = modelClass; - mLayout = modelLayout; - startListening(); + init(activity, snapshots, modelClass, modelLayout); } /** @@ -63,8 +59,14 @@ public FirebaseListAdapter(Activity activity, Class modelClass, @LayoutRes int modelLayout, Query query) { - mSnapshots = new FirebaseArray(query, this); + + init(activity, new FirebaseArray(query, this), modelClass, modelLayout); + } + + private void init(Activity activity, ObservableSnapshotArray snapshots, + Class modelClass, @LayoutRes int modelLayout) { mActivity = activity; + mSnapshots = snapshots; mModelClass = modelClass; mLayout = modelLayout; diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseRecyclerAdapter.java b/database/src/main/java/com/firebase/ui/database/FirebaseRecyclerAdapter.java index 92c9f972d..a97fccc9a 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseRecyclerAdapter.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseRecyclerAdapter.java @@ -51,12 +51,8 @@ public FirebaseRecyclerAdapter(ObservableSnapshotArray snapshots, Class modelClass, @LayoutRes int modelLayout, Class viewHolderClass) { - mSnapshots = snapshots; - mModelClass = modelClass; - mViewHolderClass = viewHolderClass; - mModelLayout = modelLayout; - startListening(); + init(snapshots, modelClass, modelLayout, viewHolderClass); } /** @@ -69,7 +65,13 @@ public FirebaseRecyclerAdapter(Class modelClass, @LayoutRes int modelLayout, Class viewHolderClass, Query query) { - mSnapshots = new FirebaseArray(query, this); + + init(new FirebaseArray<>(query, this), modelClass, modelLayout, viewHolderClass); + } + + private void init(ObservableSnapshotArray snapshots, Class modelClass, + @LayoutRes int modelLayout, Class viewHolderClass) { + mSnapshots = snapshots; mModelClass = modelClass; mViewHolderClass = viewHolderClass; mModelLayout = modelLayout; diff --git a/database/src/main/java/com/firebase/ui/database/ObservableSnapshotArray.java b/database/src/main/java/com/firebase/ui/database/ObservableSnapshotArray.java index 1722f6e66..0ae1c0b34 100644 --- a/database/src/main/java/com/firebase/ui/database/ObservableSnapshotArray.java +++ b/database/src/main/java/com/firebase/ui/database/ObservableSnapshotArray.java @@ -24,11 +24,28 @@ public abstract class ObservableSnapshotArray extends ImmutableList mListeners = new CopyOnWriteArrayList<>(); protected final SnapshotParser mParser; + /** + * Create an ObservableSnapshotArray without a {@link SnapshotParser}. Calls to + * {@link #getObject(int)} will fail. + */ public ObservableSnapshotArray() { mParser = null; } - public ObservableSnapshotArray(SnapshotParser parser) { + /** + * Create an ObservableSnapshotArray where snapshots are parsed as objects of a particular + * class. See {@link ClassSnapshotParser}. + * @param tClass the class as which DataSnapshots should be parsed. + */ + public ObservableSnapshotArray(@NonNull Class tClass) { + this(new ClassSnapshotParser<>(tClass)); + } + + /** + * Create an ObservableSnapshotArray with a custom {@link SnapshotParser}. + * @param parser the {@link SnapshotParser to use}. + */ + public ObservableSnapshotArray(@NonNull SnapshotParser parser) { mParser = parser; } From be09c80795f185333f03d0d41a32cb95e7b749cc Mon Sep 17 00:00:00 2001 From: Sam Date: Wed, 1 Mar 2017 10:44:24 -0800 Subject: [PATCH 86/93] Make FirebaseAdapter protected --- .../main/java/com/firebase/ui/database/FirebaseAdapter.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseAdapter.java b/database/src/main/java/com/firebase/ui/database/FirebaseAdapter.java index 5e4e045ae..c5539eb65 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseAdapter.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseAdapter.java @@ -1,8 +1,11 @@ package com.firebase.ui.database; +import android.support.annotation.RestrictTo; + import com.google.firebase.database.DatabaseReference; -public interface FirebaseAdapter extends ChangeEventListener, SnapshotParser { +@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) +interface FirebaseAdapter extends ChangeEventListener, SnapshotParser { /** * If you need to do some setup before the adapter starts listening for change events in the From ce0302d4d93626e740e049dede8b2fc31fae3bc7 Mon Sep 17 00:00:00 2001 From: Alex Saveau Date: Wed, 1 Mar 2017 12:38:00 -0800 Subject: [PATCH 87/93] Cleanup, fix documentation typos, make preconditions return passed in object, change type name to `E` in ObservableSnapshotArray to unhide type T in toArray method, remove getSnapshots(int) method from FirebaseArray since there's already get(int), use `index` instead of `newIndex` in onKeyMove in FirebaseIndexArray Signed-off-by: Alex Saveau --- .../database/FirebaseArrayOfObjectsTest.java | 4 +- .../FirebaseIndexArrayOfObjectsTest.java | 4 +- .../CachingObservableSnapshotArray.java | 7 +- .../ui/database/ChangeEventListener.java | 1 - .../ui/database/ClassSnapshotParser.java | 7 +- .../firebase/ui/database/FirebaseAdapter.java | 1 - .../firebase/ui/database/FirebaseArray.java | 23 +++--- .../ui/database/FirebaseIndexArray.java | 79 +++++++++---------- .../ui/database/FirebaseListAdapter.java | 16 ++-- .../ui/database/FirebaseRecyclerAdapter.java | 15 ++-- .../ui/database/ObservableSnapshotArray.java | 26 +++--- .../firebase/ui/database/Preconditions.java | 5 +- 12 files changed, 91 insertions(+), 97 deletions(-) diff --git a/database/src/androidTest/java/com/firebase/ui/database/FirebaseArrayOfObjectsTest.java b/database/src/androidTest/java/com/firebase/ui/database/FirebaseArrayOfObjectsTest.java index d61f411f6..4d8c9ea4f 100644 --- a/database/src/androidTest/java/com/firebase/ui/database/FirebaseArrayOfObjectsTest.java +++ b/database/src/androidTest/java/com/firebase/ui/database/FirebaseArrayOfObjectsTest.java @@ -109,8 +109,8 @@ public void run() { }, new Callable() { @Override public Boolean call() throws Exception { - return mArray.getObject(3).getNumber() == 3 && - mArray.getObject(0).getNumber() == 4; + return mArray.getObject(3).getNumber() == 3 + && mArray.getObject(0).getNumber() == 4; } }); } diff --git a/database/src/androidTest/java/com/firebase/ui/database/FirebaseIndexArrayOfObjectsTest.java b/database/src/androidTest/java/com/firebase/ui/database/FirebaseIndexArrayOfObjectsTest.java index e54d805d6..3377d5c56 100644 --- a/database/src/androidTest/java/com/firebase/ui/database/FirebaseIndexArrayOfObjectsTest.java +++ b/database/src/androidTest/java/com/firebase/ui/database/FirebaseIndexArrayOfObjectsTest.java @@ -111,8 +111,8 @@ public void run() { }, new Callable() { @Override public Boolean call() throws Exception { - return mArray.getObject(3).getNumber() == 3 && - mArray.getObject(0).getNumber() == 4; + return mArray.getObject(3).getNumber() == 3 + && mArray.getObject(0).getNumber() == 4; } }); } diff --git a/database/src/main/java/com/firebase/ui/database/CachingObservableSnapshotArray.java b/database/src/main/java/com/firebase/ui/database/CachingObservableSnapshotArray.java index db3ef0ba0..84f947720 100644 --- a/database/src/main/java/com/firebase/ui/database/CachingObservableSnapshotArray.java +++ b/database/src/main/java/com/firebase/ui/database/CachingObservableSnapshotArray.java @@ -9,11 +9,10 @@ /** * An extension of {@link ObservableSnapshotArray} that caches the result of {@link #getObject(int)} - * so that repeated calls for the same key are not expensive (unless the underlying snapshot - * has changed). + * so that repeated calls for the same key are not expensive (unless the underlying snapshot has + * changed). */ public abstract class CachingObservableSnapshotArray extends ObservableSnapshotArray { - private Map mObjectCache = new HashMap<>(); /** @@ -69,6 +68,4 @@ protected void updateData(int index, DataSnapshot snapshot) { getSnapshots().set(index, snapshot); mObjectCache.remove(snapshot.getKey()); } - - } diff --git a/database/src/main/java/com/firebase/ui/database/ChangeEventListener.java b/database/src/main/java/com/firebase/ui/database/ChangeEventListener.java index 843f027e1..626773d67 100644 --- a/database/src/main/java/com/firebase/ui/database/ChangeEventListener.java +++ b/database/src/main/java/com/firebase/ui/database/ChangeEventListener.java @@ -5,7 +5,6 @@ import com.google.firebase.database.DatabaseError; public interface ChangeEventListener { - /** * The type of event received when a child has been updated. */ diff --git a/database/src/main/java/com/firebase/ui/database/ClassSnapshotParser.java b/database/src/main/java/com/firebase/ui/database/ClassSnapshotParser.java index 5137451e3..215a78972 100644 --- a/database/src/main/java/com/firebase/ui/database/ClassSnapshotParser.java +++ b/database/src/main/java/com/firebase/ui/database/ClassSnapshotParser.java @@ -6,16 +6,15 @@ /** * A convenience implementation of {@link SnapshotParser} that converts a {@link DataSnapshot} to - * the parameterized class via {@link DataSnapshot#getValue(Class)}. + * the parametrized class via {@link DataSnapshot#getValue(Class)}. + * * @param the POJO class to create from snapshots. */ public class ClassSnapshotParser implements SnapshotParser { - private Class mClass; public ClassSnapshotParser(@NonNull Class clazz) { - Preconditions.checkNotNull(clazz); - mClass = clazz; + mClass = Preconditions.checkNotNull(clazz); } @Override diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseAdapter.java b/database/src/main/java/com/firebase/ui/database/FirebaseAdapter.java index c5539eb65..bad980ec7 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseAdapter.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseAdapter.java @@ -6,7 +6,6 @@ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) interface FirebaseAdapter extends ChangeEventListener, SnapshotParser { - /** * If you need to do some setup before the adapter starts listening for change events in the * database, do so it here and then call {@code super.startListening()}. diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseArray.java b/database/src/main/java/com/firebase/ui/database/FirebaseArray.java index d2bc7182e..ed30abe33 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseArray.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseArray.java @@ -29,13 +29,12 @@ * This class implements a collection on top of a Firebase location. */ public class FirebaseArray extends CachingObservableSnapshotArray implements ChildEventListener, ValueEventListener { - private Query mQuery; private List mSnapshots = new ArrayList<>(); /** - * Create a new FirebaseArray with no {@link SnapshotParser}. Calls to - * {@link #getObject(int)} will fail. + * Create a new FirebaseArray with no {@link SnapshotParser}. Calls to {@link #getObject(int)} + * will fail. * * @param query The Firebase location to watch for data changes. Can also be a slice of a * location, using some combination of {@code limit()}, {@code startAt()}, and @@ -49,8 +48,8 @@ public FirebaseArray(Query query) { /** * Create a new FirebaseArray that parses snapshots as members of a given class. * - * See {@link ObservableSnapshotArray#ObservableSnapshotArray(Class)}. - * See {@link #FirebaseArray(Query)}. + * @see ObservableSnapshotArray#ObservableSnapshotArray(Class) + * @see FirebaseArray#FirebaseArray(Query) */ public FirebaseArray(Query query, Class tClass) { super(tClass); @@ -60,8 +59,8 @@ public FirebaseArray(Query query, Class tClass) { /** * Create a new FirebaseArray with a custom {@link SnapshotParser}. * - * See {@link ObservableSnapshotArray#ObservableSnapshotArray(SnapshotParser)}. - * See {@link #FirebaseArray(Query)}. + * @see ObservableSnapshotArray#ObservableSnapshotArray(SnapshotParser) + * @see FirebaseArray#FirebaseArray(Query) */ public FirebaseArray(Query query, SnapshotParser parser) { super(parser); @@ -140,8 +139,10 @@ public void onChildMoved(DataSnapshot snapshot, String previousChildKey) { int newIndex = previousChildKey == null ? 0 : (getIndexForKey(previousChildKey) + 1); mSnapshots.add(newIndex, snapshot); - notifyChangeEventListeners(ChangeEventListener.EventType.MOVED, snapshot, - newIndex, oldIndex); + notifyChangeEventListeners(ChangeEventListener.EventType.MOVED, + snapshot, + newIndex, + oldIndex); } @Override @@ -166,10 +167,6 @@ private int getIndexForKey(String key) { throw new IllegalArgumentException("Key not found"); } - public DataSnapshot getSnapshot(int index) { - return mSnapshots.get(index); - } - @Override public boolean equals(Object obj) { if (this == obj) return true; 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 ab6b89010..262acd048 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseIndexArray.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseIndexArray.java @@ -56,8 +56,8 @@ public FirebaseIndexArray(Query keyQuery, DatabaseReference dataRef) { /** * Create a new FirebaseIndexArray that parses snapshots as members of a given class. * - * See {@link ObservableSnapshotArray#ObservableSnapshotArray(Class)}. - * See {@link #FirebaseIndexArray(Query, DatabaseReference)}. + * @see ObservableSnapshotArray#ObservableSnapshotArray(Class) + * @see FirebaseIndexArray#FirebaseIndexArray(Query, DatabaseReference) */ public FirebaseIndexArray(Query keyQuery, DatabaseReference dataRef, Class tClass) { super(tClass); @@ -67,8 +67,8 @@ public FirebaseIndexArray(Query keyQuery, DatabaseReference dataRef, Class tC /** * Create a new FirebaseIndexArray with a custom {@link SnapshotParser}. * - * See {@link ObservableSnapshotArray#ObservableSnapshotArray(SnapshotParser)}. - * See {@link #FirebaseIndexArray(Query, DatabaseReference)}. + * @see ObservableSnapshotArray#ObservableSnapshotArray(SnapshotParser) + * @see FirebaseIndexArray#FirebaseIndexArray(Query, DatabaseReference) */ public FirebaseIndexArray(Query keyQuery, DatabaseReference dataRef, SnapshotParser parser) { super(parser); @@ -76,6 +76,7 @@ public FirebaseIndexArray(Query keyQuery, DatabaseReference dataRef, SnapshotPar } private void init(Query keyQuery, DatabaseReference dataRef) { + mDataRef = dataRef; mKeySnapshots = new FirebaseArray<>(keyQuery, new SnapshotParser() { @Override public String parseSnapshot(DataSnapshot snapshot) { @@ -83,8 +84,6 @@ public String parseSnapshot(DataSnapshot snapshot) { } }); - mDataRef = dataRef; - mKeySnapshots.addChangeEventListener(this); } @@ -162,8 +161,7 @@ private boolean isKeyAtIndex(String key, int index) { } protected void onKeyAdded(DataSnapshot data) { - String key = data.getKey(); - Query ref = mDataRef.child(key); + Query ref = mDataRef.child(data.getKey()); // Start listening mRefs.put(ref, ref.addValueEventListener(new DataRefListener())); @@ -174,9 +172,11 @@ protected void onKeyMoved(DataSnapshot data, int index, int oldIndex) { if (isKeyAtIndex(key, oldIndex)) { DataSnapshot snapshot = removeData(oldIndex); - int newIndex = getIndexForKey(key); - mDataSnapshots.add(newIndex, snapshot); - notifyChangeEventListeners(ChangeEventListener.EventType.MOVED, snapshot, newIndex, oldIndex); + mDataSnapshots.add(index, snapshot); + notifyChangeEventListeners(ChangeEventListener.EventType.MOVED, + snapshot, + index, + oldIndex); } } @@ -190,6 +190,34 @@ protected void onKeyRemoved(DataSnapshot data, int index) { } } + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null || getClass() != obj.getClass()) return false; + if (!super.equals(obj)) return false; + + FirebaseIndexArray array = (FirebaseIndexArray) obj; + + return mDataRef.equals(array.mDataRef) && mDataSnapshots.equals(array.mDataSnapshots); + } + + @Override + public int hashCode() { + int result = super.hashCode(); + result = 31 * result + mDataRef.hashCode(); + result = 31 * result + mDataSnapshots.hashCode(); + return result; + } + + @Override + public String toString() { + if (isListening()) { + return "FirebaseIndexArray is listening at " + mDataRef + ":\n" + mDataSnapshots; + } else { + return "FirebaseIndexArray is inactive"; + } + } + /** * A ValueEventListener attached to the joined child data. */ @@ -226,33 +254,4 @@ public void onCancelled(DatabaseError error) { notifyListenersOnCancelled(error); } } - - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - if (obj == null || getClass() != obj.getClass()) return false; - if (!super.equals(obj)) return false; - - FirebaseIndexArray array = (FirebaseIndexArray) obj; - - return mDataRef.equals(array.mDataRef) && mDataSnapshots.equals(array.mDataSnapshots); - } - - @Override - public int hashCode() { - int result = super.hashCode(); - result = 31 * result + mDataRef.hashCode(); - result = 31 * result + mDataSnapshots.hashCode(); - return result; - } - - @Override - public String toString() { - if (isListening()) { - return "FirebaseIndexArray is listening at " + mDataRef + ":\n" + mDataSnapshots; - } else { - return "FirebaseIndexArray is inactive"; - } - } - } diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseListAdapter.java b/database/src/main/java/com/firebase/ui/database/FirebaseListAdapter.java index f958748e8..3ac6b76e7 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseListAdapter.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseListAdapter.java @@ -45,7 +45,6 @@ public FirebaseListAdapter(Activity activity, ObservableSnapshotArray snapshots, Class modelClass, @LayoutRes int modelLayout) { - init(activity, snapshots, modelClass, modelLayout); } @@ -59,12 +58,13 @@ public FirebaseListAdapter(Activity activity, Class modelClass, @LayoutRes int modelLayout, Query query) { - - init(activity, new FirebaseArray(query, this), modelClass, modelLayout); + init(activity, new FirebaseArray<>(query, this), modelClass, modelLayout); } - private void init(Activity activity, ObservableSnapshotArray snapshots, - Class modelClass, @LayoutRes int modelLayout) { + private void init(Activity activity, + ObservableSnapshotArray snapshots, + Class modelClass, + @LayoutRes int modelLayout) { mActivity = activity; mSnapshots = snapshots; mModelClass = modelClass; @@ -86,8 +86,10 @@ public void cleanup() { } @Override - public void onChildChanged(ChangeEventListener.EventType type, DataSnapshot snapshot, - int index, int oldIndex) { + public void onChildChanged(ChangeEventListener.EventType type, + DataSnapshot snapshot, + int index, + int oldIndex) { notifyDataSetChanged(); } diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseRecyclerAdapter.java b/database/src/main/java/com/firebase/ui/database/FirebaseRecyclerAdapter.java index a97fccc9a..1dd69a3c7 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseRecyclerAdapter.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseRecyclerAdapter.java @@ -29,7 +29,6 @@ */ public abstract class FirebaseRecyclerAdapter extends RecyclerView.Adapter implements FirebaseAdapter { - private static final String TAG = "FirebaseRecyclerAdapter"; protected ObservableSnapshotArray mSnapshots; @@ -51,7 +50,6 @@ public FirebaseRecyclerAdapter(ObservableSnapshotArray snapshots, Class modelClass, @LayoutRes int modelLayout, Class viewHolderClass) { - init(snapshots, modelClass, modelLayout, viewHolderClass); } @@ -65,12 +63,13 @@ public FirebaseRecyclerAdapter(Class modelClass, @LayoutRes int modelLayout, Class viewHolderClass, Query query) { - init(new FirebaseArray<>(query, this), modelClass, modelLayout, viewHolderClass); } - private void init(ObservableSnapshotArray snapshots, Class modelClass, - @LayoutRes int modelLayout, Class viewHolderClass) { + private void init(ObservableSnapshotArray snapshots, + Class modelClass, + @LayoutRes int modelLayout, + Class viewHolderClass) { mSnapshots = snapshots; mModelClass = modelClass; mViewHolderClass = viewHolderClass; @@ -92,8 +91,10 @@ public void cleanup() { } @Override - public void onChildChanged(ChangeEventListener.EventType type, DataSnapshot snapshot, - int index, int oldIndex) { + public void onChildChanged(ChangeEventListener.EventType type, + DataSnapshot snapshot, + int index, + int oldIndex) { switch (type) { case ADDED: notifyItemInserted(index); diff --git a/database/src/main/java/com/firebase/ui/database/ObservableSnapshotArray.java b/database/src/main/java/com/firebase/ui/database/ObservableSnapshotArray.java index 0ae1c0b34..6ee58b359 100644 --- a/database/src/main/java/com/firebase/ui/database/ObservableSnapshotArray.java +++ b/database/src/main/java/com/firebase/ui/database/ObservableSnapshotArray.java @@ -17,12 +17,11 @@ * Exposes a collection of items in Firebase as a {@link List} of {@link DataSnapshot}. To observe * the list attach a {@link com.google.firebase.database.ChildEventListener}. * - * @param a POJO class to which the DataSnapshots can be converted. + * @param a POJO class to which the DataSnapshots can be converted. */ -public abstract class ObservableSnapshotArray extends ImmutableList { - +public abstract class ObservableSnapshotArray extends ImmutableList { protected final List mListeners = new CopyOnWriteArrayList<>(); - protected final SnapshotParser mParser; + protected final SnapshotParser mParser; /** * Create an ObservableSnapshotArray without a {@link SnapshotParser}. Calls to @@ -34,25 +33,28 @@ public ObservableSnapshotArray() { /** * Create an ObservableSnapshotArray where snapshots are parsed as objects of a particular - * class. See {@link ClassSnapshotParser}. + * class. + * * @param tClass the class as which DataSnapshots should be parsed. + * @see ClassSnapshotParser */ - public ObservableSnapshotArray(@NonNull Class tClass) { + public ObservableSnapshotArray(@NonNull Class tClass) { this(new ClassSnapshotParser<>(tClass)); } /** * Create an ObservableSnapshotArray with a custom {@link SnapshotParser}. + * * @param parser the {@link SnapshotParser to use}. */ - public ObservableSnapshotArray(@NonNull SnapshotParser parser) { + public ObservableSnapshotArray(@NonNull SnapshotParser parser) { mParser = parser; } /** - * Attach a {@link ChangeEventListener} to this array. The listener will receive one - * 'ADDED' event for each item that exists in the array at the time of attachment, and then - * receive all future chld events. + * Attach a {@link ChangeEventListener} to this array. The listener will receive one {@link + * ChangeEventListener.EventType#ADDED} event for each item that already exists in the array at + * the time of attachment, and then receive all future child events. */ @CallSuper public ChangeEventListener addChangeEventListener(@NonNull ChangeEventListener listener) { @@ -131,11 +133,11 @@ public final boolean isListening(ChangeEventListener listener) { } /** - * Get the {@link DataSnapshot} at a given position converted to an object of the parameterized + * Get the {@link DataSnapshot} at a given position converted to an object of the parametrized * type. This uses the {@link SnapshotParser} passed to the constructor. If the parser was not * initialized this will throw an unchecked exception. */ - public T getObject(int index) { + public E getObject(int index) { Preconditions.checkNotNull(mParser); return mParser.parseSnapshot(get(index)); } diff --git a/database/src/main/java/com/firebase/ui/database/Preconditions.java b/database/src/main/java/com/firebase/ui/database/Preconditions.java index c16095532..0fb15b2fa 100644 --- a/database/src/main/java/com/firebase/ui/database/Preconditions.java +++ b/database/src/main/java/com/firebase/ui/database/Preconditions.java @@ -7,9 +7,8 @@ */ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) class Preconditions { - - public static void checkNotNull(Object o) { + public static T checkNotNull(T o) { if (o == null) throw new IllegalArgumentException("Argument cannot be null."); + return o; } - } From 8d63cfb26106c0d87813e3dbfbb19b2ff97906d6 Mon Sep 17 00:00:00 2001 From: Alex Saveau Date: Wed, 1 Mar 2017 12:55:42 -0800 Subject: [PATCH 88/93] Fix compile errors Signed-off-by: Alex Saveau --- .../java/com/firebase/uidemo/database/ChatActivity.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/firebase/uidemo/database/ChatActivity.java b/app/src/main/java/com/firebase/uidemo/database/ChatActivity.java index 32d5b210a..c2d894757 100644 --- a/app/src/main/java/com/firebase/uidemo/database/ChatActivity.java +++ b/app/src/main/java/com/firebase/uidemo/database/ChatActivity.java @@ -35,6 +35,7 @@ import com.google.android.gms.tasks.OnSuccessListener; import com.google.firebase.auth.AuthResult; import com.google.firebase.auth.FirebaseAuth; +import com.google.firebase.database.DataSnapshot; import com.google.firebase.database.DatabaseError; import com.google.firebase.database.DatabaseReference; import com.google.firebase.database.FirebaseDatabase; @@ -165,8 +166,11 @@ public boolean onMenuItemClick(MenuItem item) { } @Override - public void onChildChanged(EventType type, int index, int oldIndex) { - super.onChildChanged(type, index, oldIndex); + public void onChildChanged(EventType type, + DataSnapshot snapshot, + int index, + int oldIndex) { + super.onChildChanged(type, snapshot, index, oldIndex); // TODO temporary fix for https://github.com/firebase/FirebaseUI-Android/issues/546 onDataChanged(); From 7601ded43b104d7de0b6529a9401579af9a8fe9f Mon Sep 17 00:00:00 2001 From: Alex Saveau Date: Wed, 1 Mar 2017 13:35:39 -0800 Subject: [PATCH 89/93] Remove null parser possibility and fix broken FirebaseIndexAdapters Signed-off-by: Alex Saveau --- .../CachingObservableSnapshotArray.java | 11 ++--------- .../firebase/ui/database/FirebaseArray.java | 15 ++------------- .../ui/database/FirebaseIndexArray.java | 15 ++------------- .../ui/database/FirebaseIndexListAdapter.java | 4 ++-- .../FirebaseIndexRecyclerAdapter.java | 8 ++++++-- .../ui/database/FirebaseListAdapter.java | 17 +++++++++++++---- .../ui/database/FirebaseRecyclerAdapter.java | 17 +++++++++++++---- .../ui/database/ObservableSnapshotArray.java | 19 +++++-------------- 8 files changed, 45 insertions(+), 61 deletions(-) diff --git a/database/src/main/java/com/firebase/ui/database/CachingObservableSnapshotArray.java b/database/src/main/java/com/firebase/ui/database/CachingObservableSnapshotArray.java index 84f947720..1a564f65e 100644 --- a/database/src/main/java/com/firebase/ui/database/CachingObservableSnapshotArray.java +++ b/database/src/main/java/com/firebase/ui/database/CachingObservableSnapshotArray.java @@ -16,21 +16,14 @@ public abstract class CachingObservableSnapshotArray extends ObservableSnapsh private Map mObjectCache = new HashMap<>(); /** - * See {@link ObservableSnapshotArray#ObservableSnapshotArray()}. - */ - public CachingObservableSnapshotArray() { - super(); - } - - /** - * See {@link ObservableSnapshotArray#ObservableSnapshotArray(Class)}. + * @see ObservableSnapshotArray#ObservableSnapshotArray(Class) */ public CachingObservableSnapshotArray(@NonNull Class tClass) { super(tClass); } /** - * See {@link ObservableSnapshotArray#ObservableSnapshotArray(SnapshotParser)}. + * @see ObservableSnapshotArray#ObservableSnapshotArray(SnapshotParser) */ public CachingObservableSnapshotArray(@NonNull SnapshotParser parser) { super(parser); diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseArray.java b/database/src/main/java/com/firebase/ui/database/FirebaseArray.java index ed30abe33..e26620372 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseArray.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseArray.java @@ -33,23 +33,12 @@ public class FirebaseArray extends CachingObservableSnapshotArray implemen private List mSnapshots = new ArrayList<>(); /** - * Create a new FirebaseArray with no {@link SnapshotParser}. Calls to {@link #getObject(int)} - * will fail. + * Create a new FirebaseArray that parses snapshots as members of a given class. * * @param query The Firebase location to watch for data changes. Can also be a slice of a * location, using some combination of {@code limit()}, {@code startAt()}, and * {@code endAt()}. - */ - public FirebaseArray(Query query) { - super(); - init(query); - } - - /** - * Create a new FirebaseArray that parses snapshots as members of a given class. - * * @see ObservableSnapshotArray#ObservableSnapshotArray(Class) - * @see FirebaseArray#FirebaseArray(Query) */ public FirebaseArray(Query query, Class tClass) { super(tClass); @@ -60,7 +49,7 @@ public FirebaseArray(Query query, Class tClass) { * Create a new FirebaseArray with a custom {@link SnapshotParser}. * * @see ObservableSnapshotArray#ObservableSnapshotArray(SnapshotParser) - * @see FirebaseArray#FirebaseArray(Query) + * @see FirebaseArray#FirebaseArray(Query, Class) */ public FirebaseArray(Query query, SnapshotParser parser) { super(parser); 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 262acd048..e7b700cf2 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseIndexArray.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseIndexArray.java @@ -39,25 +39,14 @@ public class FirebaseIndexArray extends CachingObservableSnapshotArray imp private List mDataSnapshots = new ArrayList<>(); /** - * Create a new FirebaseIndexArray without a {@link SnapshotParser}. - * Calls to {@link #getObject(int)} will fail. + * Create a new FirebaseIndexArray that parses snapshots as members of a given class. * * @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}. - */ - public FirebaseIndexArray(Query keyQuery, DatabaseReference dataRef) { - super(); - init(keyQuery, dataRef); - } - - /** - * Create a new FirebaseIndexArray that parses snapshots as members of a given class. - * * @see ObservableSnapshotArray#ObservableSnapshotArray(Class) - * @see FirebaseIndexArray#FirebaseIndexArray(Query, DatabaseReference) */ public FirebaseIndexArray(Query keyQuery, DatabaseReference dataRef, Class tClass) { super(tClass); @@ -68,7 +57,7 @@ public FirebaseIndexArray(Query keyQuery, DatabaseReference dataRef, Class tC * Create a new FirebaseIndexArray with a custom {@link SnapshotParser}. * * @see ObservableSnapshotArray#ObservableSnapshotArray(SnapshotParser) - * @see FirebaseIndexArray#FirebaseIndexArray(Query, DatabaseReference) + * @see FirebaseIndexArray#FirebaseIndexArray(Query, DatabaseReference, Class) */ public FirebaseIndexArray(Query keyQuery, DatabaseReference dataRef, SnapshotParser parser) { super(parser); diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseIndexListAdapter.java b/database/src/main/java/com/firebase/ui/database/FirebaseIndexListAdapter.java index 92f3b34d7..ed6c64cb0 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseIndexListAdapter.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseIndexListAdapter.java @@ -13,13 +13,13 @@ public abstract class FirebaseIndexListAdapter extends FirebaseListAdapter * limit()}, {@code startAt()}, and {@code endAt()}. * @param dataRef The Firebase location to watch for data changes. Each key key found in {@code * keyQuery}'s location represents a list item in the {@code ListView}. - * @see FirebaseListAdapter#FirebaseListAdapter(Activity, FirebaseArray, Class, int) + * @see FirebaseListAdapter#FirebaseListAdapter(Activity, ObservableSnapshotArray, Class, int) */ public FirebaseIndexListAdapter(Activity activity, Class modelClass, @LayoutRes int modelLayout, Query keyQuery, DatabaseReference dataRef) { - super(activity, new FirebaseIndexArray(keyQuery, dataRef), modelClass, modelLayout); + init(activity, new FirebaseIndexArray<>(keyQuery, dataRef, this), modelClass, modelLayout); } } diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseIndexRecyclerAdapter.java b/database/src/main/java/com/firebase/ui/database/FirebaseIndexRecyclerAdapter.java index dab13cb91..9bd4daaa3 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseIndexRecyclerAdapter.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseIndexRecyclerAdapter.java @@ -14,13 +14,17 @@ public abstract class FirebaseIndexRecyclerAdapter modelClass, @LayoutRes int modelLayout, Class viewHolderClass, Query keyQuery, DatabaseReference dataRef) { - super(new FirebaseIndexArray(keyQuery, dataRef), modelClass, modelLayout, viewHolderClass); + init(new FirebaseIndexArray<>(keyQuery, dataRef, this), + modelClass, + modelLayout, + viewHolderClass); } } diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseListAdapter.java b/database/src/main/java/com/firebase/ui/database/FirebaseListAdapter.java index 3ac6b76e7..ec765e596 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseListAdapter.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseListAdapter.java @@ -32,6 +32,15 @@ public abstract class FirebaseListAdapter extends BaseAdapter implements Fire protected Class mModelClass; protected int mLayout; + /** + * Internal constructor that does nothing. Can be used as a workaround to pass `this` into a + * constructor argument. + * + * @see #init(Activity, ObservableSnapshotArray, Class, int) + */ + protected FirebaseListAdapter() { + } + /** * @param activity The {@link Activity} containing the {@link ListView} * @param modelClass Firebase will marshall the data at a location into an instance of a class @@ -61,10 +70,10 @@ public FirebaseListAdapter(Activity activity, init(activity, new FirebaseArray<>(query, this), modelClass, modelLayout); } - private void init(Activity activity, - ObservableSnapshotArray snapshots, - Class modelClass, - @LayoutRes int modelLayout) { + protected void init(Activity activity, + ObservableSnapshotArray snapshots, + Class modelClass, + @LayoutRes int modelLayout) { mActivity = activity; mSnapshots = snapshots; mModelClass = modelClass; diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseRecyclerAdapter.java b/database/src/main/java/com/firebase/ui/database/FirebaseRecyclerAdapter.java index 1dd69a3c7..ee029ae49 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseRecyclerAdapter.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseRecyclerAdapter.java @@ -36,6 +36,15 @@ public abstract class FirebaseRecyclerAdapter mViewHolderClass; protected int mModelLayout; + /** + * Internal constructor that does nothing. Can be used as a workaround to pass `this` into a + * constructor argument. + * + * @see #init(ObservableSnapshotArray, Class, int, Class) + */ + protected FirebaseRecyclerAdapter() { + } + /** * @param snapshots The data used to populate the adapter * @param modelClass Firebase will marshall the data at a location into an instance of a @@ -66,10 +75,10 @@ public FirebaseRecyclerAdapter(Class modelClass, init(new FirebaseArray<>(query, this), modelClass, modelLayout, viewHolderClass); } - private void init(ObservableSnapshotArray snapshots, - Class modelClass, - @LayoutRes int modelLayout, - Class viewHolderClass) { + protected void init(ObservableSnapshotArray snapshots, + Class modelClass, + @LayoutRes int modelLayout, + Class viewHolderClass) { mSnapshots = snapshots; mModelClass = modelClass; mViewHolderClass = viewHolderClass; diff --git a/database/src/main/java/com/firebase/ui/database/ObservableSnapshotArray.java b/database/src/main/java/com/firebase/ui/database/ObservableSnapshotArray.java index 6ee58b359..2918103d6 100644 --- a/database/src/main/java/com/firebase/ui/database/ObservableSnapshotArray.java +++ b/database/src/main/java/com/firebase/ui/database/ObservableSnapshotArray.java @@ -23,32 +23,24 @@ public abstract class ObservableSnapshotArray extends ImmutableList mListeners = new CopyOnWriteArrayList<>(); protected final SnapshotParser mParser; - /** - * Create an ObservableSnapshotArray without a {@link SnapshotParser}. Calls to - * {@link #getObject(int)} will fail. - */ - public ObservableSnapshotArray() { - mParser = null; - } - /** * Create an ObservableSnapshotArray where snapshots are parsed as objects of a particular * class. * - * @param tClass the class as which DataSnapshots should be parsed. + * @param clazz the class as which DataSnapshots should be parsed. * @see ClassSnapshotParser */ - public ObservableSnapshotArray(@NonNull Class tClass) { - this(new ClassSnapshotParser<>(tClass)); + public ObservableSnapshotArray(@NonNull Class clazz) { + this(new ClassSnapshotParser<>(clazz)); } /** * Create an ObservableSnapshotArray with a custom {@link SnapshotParser}. * - * @param parser the {@link SnapshotParser to use}. + * @param parser the {@link SnapshotParser} to use */ public ObservableSnapshotArray(@NonNull SnapshotParser parser) { - mParser = parser; + mParser = Preconditions.checkNotNull(parser); } /** @@ -138,7 +130,6 @@ public final boolean isListening(ChangeEventListener listener) { * initialized this will throw an unchecked exception. */ public E getObject(int index) { - Preconditions.checkNotNull(mParser); return mParser.parseSnapshot(get(index)); } From 42c639a7afe3c3edc0b3f0a9692d6fb9257a7301 Mon Sep 17 00:00:00 2001 From: Alex Saveau Date: Fri, 3 Mar 2017 19:07:05 -0800 Subject: [PATCH 90/93] Fix circular SnapshotParser dependency in adapters Signed-off-by: Alex Saveau --- .../firebase/ui/database/FirebaseAdapter.java | 2 +- .../ui/database/FirebaseIndexListAdapter.java | 24 ++++++-- .../FirebaseIndexRecyclerAdapter.java | 24 ++++++-- .../ui/database/FirebaseListAdapter.java | 56 +++++++------------ .../ui/database/FirebaseRecyclerAdapter.java | 56 +++++++------------ 5 files changed, 82 insertions(+), 80 deletions(-) diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseAdapter.java b/database/src/main/java/com/firebase/ui/database/FirebaseAdapter.java index bad980ec7..977958850 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseAdapter.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseAdapter.java @@ -5,7 +5,7 @@ import com.google.firebase.database.DatabaseReference; @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) -interface FirebaseAdapter extends ChangeEventListener, SnapshotParser { +interface FirebaseAdapter extends ChangeEventListener { /** * If you need to do some setup before the adapter starts listening for change events in the * database, do so it here and then call {@code super.startListening()}. diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseIndexListAdapter.java b/database/src/main/java/com/firebase/ui/database/FirebaseIndexListAdapter.java index ed6c64cb0..06799a8ba 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseIndexListAdapter.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseIndexListAdapter.java @@ -2,24 +2,40 @@ import android.app.Activity; import android.support.annotation.LayoutRes; +import android.widget.ListView; +import com.google.firebase.database.DataSnapshot; import com.google.firebase.database.DatabaseReference; import com.google.firebase.database.Query; public abstract class FirebaseIndexListAdapter extends FirebaseListAdapter { /** + * @param parser a custom {@link SnapshotParser} to convert a {@link DataSnapshot} to the + * model class * @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 in {@code - * keyQuery}'s location represents a list item in the {@code ListView}. - * @see FirebaseListAdapter#FirebaseListAdapter(Activity, ObservableSnapshotArray, Class, int) + * @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 ListView}. + * @see FirebaseIndexListAdapter#FirebaseIndexListAdapter(Activity, SnapshotParser, int, Query, + * DatabaseReference) + */ + public FirebaseIndexListAdapter(Activity activity, + SnapshotParser parser, + @LayoutRes int modelLayout, + Query keyQuery, + DatabaseReference dataRef) { + super(activity, new FirebaseIndexArray<>(keyQuery, dataRef, parser), modelLayout); + } + + /** + * @see #FirebaseIndexListAdapter(Activity, SnapshotParser, int, Query, DatabaseReference) */ public FirebaseIndexListAdapter(Activity activity, Class modelClass, @LayoutRes int modelLayout, Query keyQuery, DatabaseReference dataRef) { - init(activity, new FirebaseIndexArray<>(keyQuery, dataRef, this), modelClass, modelLayout); + this(activity, new ClassSnapshotParser<>(modelClass), modelLayout, keyQuery, dataRef); } } diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseIndexRecyclerAdapter.java b/database/src/main/java/com/firebase/ui/database/FirebaseIndexRecyclerAdapter.java index 9bd4daaa3..b2577e44f 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseIndexRecyclerAdapter.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseIndexRecyclerAdapter.java @@ -3,28 +3,42 @@ import android.support.annotation.LayoutRes; import android.support.v7.widget.RecyclerView; +import com.google.firebase.database.DataSnapshot; import com.google.firebase.database.DatabaseReference; import com.google.firebase.database.Query; public abstract class FirebaseIndexRecyclerAdapter extends FirebaseRecyclerAdapter { /** + * @param parser a custom {@link SnapshotParser} to convert a {@link DataSnapshot} to the + * model class * @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 FirebaseRecyclerAdapter#FirebaseRecyclerAdapter(ObservableSnapshotArray, Class, int, - * Class) + * @see FirebaseRecyclerAdapter#FirebaseRecyclerAdapter(ObservableSnapshotArray, int, Class) + */ + public FirebaseIndexRecyclerAdapter(SnapshotParser parser, + @LayoutRes int modelLayout, + Class viewHolderClass, + Query keyQuery, + DatabaseReference dataRef) { + super(new FirebaseIndexArray<>(keyQuery, dataRef, parser), modelLayout, viewHolderClass); + } + + /** + * @see #FirebaseIndexRecyclerAdapter(SnapshotParser, int, Class, Query, DatabaseReference) */ public FirebaseIndexRecyclerAdapter(Class modelClass, @LayoutRes int modelLayout, Class viewHolderClass, Query keyQuery, DatabaseReference dataRef) { - init(new FirebaseIndexArray<>(keyQuery, dataRef, this), - modelClass, + this(new ClassSnapshotParser<>(modelClass), modelLayout, - viewHolderClass); + viewHolderClass, + keyQuery, + dataRef); } } diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseListAdapter.java b/database/src/main/java/com/firebase/ui/database/FirebaseListAdapter.java index ec765e596..acb781a0d 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseListAdapter.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseListAdapter.java @@ -29,22 +29,10 @@ public abstract class FirebaseListAdapter extends BaseAdapter implements Fire protected Activity mActivity; protected ObservableSnapshotArray mSnapshots; - protected Class mModelClass; protected int mLayout; - /** - * Internal constructor that does nothing. Can be used as a workaround to pass `this` into a - * constructor argument. - * - * @see #init(Activity, ObservableSnapshotArray, Class, int) - */ - protected FirebaseListAdapter() { - } - /** * @param activity The {@link Activity} containing the {@link ListView} - * @param modelClass Firebase will marshall the data at a location into an instance of a class - * that you provide * @param modelLayout This is the layout used to represent a single list item. You will be * responsible for populating an instance of the corresponding view with the * data from an instance of modelClass. @@ -52,34 +40,37 @@ protected FirebaseListAdapter() { */ public FirebaseListAdapter(Activity activity, ObservableSnapshotArray snapshots, - Class modelClass, @LayoutRes int modelLayout) { - init(activity, snapshots, modelClass, modelLayout); + mActivity = activity; + mSnapshots = snapshots; + mLayout = modelLayout; + + startListening(); } /** - * @param query The Firebase location to watch for data changes. Can also be a slice of a - * location, using some combination of {@code limit()}, {@code startAt()}, and - * {@code endAt()}. - * @see #FirebaseListAdapter(Activity, ObservableSnapshotArray, Class, int) + * @param parser a custom {@link SnapshotParser} to convert a {@link DataSnapshot} to the model + * class + * @param query The Firebase location to watch for data changes. Can also be a slice of a + * location, using some combination of {@code limit()}, {@code startAt()}, and + * {@code endAt()}. + * @see #FirebaseListAdapter(Activity, ObservableSnapshotArray, int) */ public FirebaseListAdapter(Activity activity, - Class modelClass, + SnapshotParser parser, @LayoutRes int modelLayout, Query query) { - init(activity, new FirebaseArray<>(query, this), modelClass, modelLayout); + this(activity, new FirebaseArray<>(query, parser), modelLayout); } - protected void init(Activity activity, - ObservableSnapshotArray snapshots, - Class modelClass, - @LayoutRes int modelLayout) { - mActivity = activity; - mSnapshots = snapshots; - mModelClass = modelClass; - mLayout = modelLayout; - - startListening(); + /** + * @see #FirebaseListAdapter(Activity, SnapshotParser, int, Query) + */ + public FirebaseListAdapter(Activity activity, + Class modelClass, + @LayoutRes int modelLayout, + Query query) { + this(activity, new ClassSnapshotParser<>(modelClass), modelLayout, query); } @Override @@ -116,11 +107,6 @@ public T getItem(int position) { return mSnapshots.getObject(position); } - @Override - public T parseSnapshot(DataSnapshot snapshot) { - return snapshot.getValue(mModelClass); - } - @Override public DatabaseReference getRef(int position) { return mSnapshots.get(position).getRef(); diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseRecyclerAdapter.java b/database/src/main/java/com/firebase/ui/database/FirebaseRecyclerAdapter.java index ee029ae49..cc05319e7 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseRecyclerAdapter.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseRecyclerAdapter.java @@ -32,23 +32,11 @@ public abstract class FirebaseRecyclerAdapter mSnapshots; - protected Class mModelClass; protected Class mViewHolderClass; protected int mModelLayout; - /** - * Internal constructor that does nothing. Can be used as a workaround to pass `this` into a - * constructor argument. - * - * @see #init(ObservableSnapshotArray, Class, int, Class) - */ - protected FirebaseRecyclerAdapter() { - } - /** * @param snapshots The data used to populate the adapter - * @param modelClass Firebase will marshall the data at a location into an instance of a - * class that you provide * @param modelLayout This is the layout used to represent a single item in the list. You * will be responsible for populating an instance of the corresponding * view with the data from an instance of modelClass. @@ -56,35 +44,38 @@ protected FirebaseRecyclerAdapter() { * modelLayout. */ public FirebaseRecyclerAdapter(ObservableSnapshotArray snapshots, - Class modelClass, @LayoutRes int modelLayout, Class viewHolderClass) { - init(snapshots, modelClass, modelLayout, viewHolderClass); + mSnapshots = snapshots; + mViewHolderClass = viewHolderClass; + mModelLayout = modelLayout; + + startListening(); } /** - * @param query The Firebase location to watch for data changes. Can also be a slice of a - * location, using some combination of {@code limit()}, {@code startAt()}, and - * {@code endAt()}. - * @see #FirebaseRecyclerAdapter(ObservableSnapshotArray, Class, int, Class) + * @param parser a custom {@link SnapshotParser} to convert a {@link DataSnapshot} to the model + * class + * @param query The Firebase location to watch for data changes. Can also be a slice of a + * location, using some combination of {@code limit()}, {@code startAt()}, and + * {@code endAt()}. + * @see #FirebaseRecyclerAdapter(ObservableSnapshotArray, int, Class) */ - public FirebaseRecyclerAdapter(Class modelClass, + public FirebaseRecyclerAdapter(SnapshotParser parser, @LayoutRes int modelLayout, Class viewHolderClass, Query query) { - init(new FirebaseArray<>(query, this), modelClass, modelLayout, viewHolderClass); + this(new FirebaseArray<>(query, parser), modelLayout, viewHolderClass); } - protected void init(ObservableSnapshotArray snapshots, - Class modelClass, - @LayoutRes int modelLayout, - Class viewHolderClass) { - mSnapshots = snapshots; - mModelClass = modelClass; - mViewHolderClass = viewHolderClass; - mModelLayout = modelLayout; - - startListening(); + /** + * @see #FirebaseRecyclerAdapter(SnapshotParser, int, Class, Query) + */ + public FirebaseRecyclerAdapter(Class modelClass, + @LayoutRes int modelLayout, + Class viewHolderClass, + Query query) { + this(new ClassSnapshotParser<>(modelClass), modelLayout, viewHolderClass, query); } @Override @@ -136,11 +127,6 @@ public T getItem(int position) { return mSnapshots.getObject(position); } - @Override - public T parseSnapshot(DataSnapshot snapshot) { - return snapshot.getValue(mModelClass); - } - @Override public DatabaseReference getRef(int position) { return mSnapshots.get(position).getRef(); From 20c15a3c35262b154358bdf051f5571993b586df Mon Sep 17 00:00:00 2001 From: Alex Saveau Date: Sun, 5 Mar 2017 19:36:08 -0800 Subject: [PATCH 91/93] Fix typos Signed-off-by: Alex Saveau --- database/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/database/README.md b/database/README.md index 1a1b16f09..28124e387 100644 --- a/database/README.md +++ b/database/README.md @@ -251,7 +251,7 @@ protected void onCreate(Bundle savedInstanceState) { findViewById(R.id.send_button).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - ref.push().setValue(new Chat("puf", "1234", mMessage.getText().toString())); + ref.push().setValue(new Chat("puf", "1234", message.getText().toString())); message.setText(""); } }); From b2ffadfa15ae05c610fd49178c567e6d5902f424 Mon Sep 17 00:00:00 2001 From: Alex Saveau Date: Sun, 5 Mar 2017 20:16:23 -0800 Subject: [PATCH 92/93] Address review comments Signed-off-by: Alex Saveau --- .../uidemo/database/ChatActivity.java | 22 +------------------ 1 file changed, 1 insertion(+), 21 deletions(-) diff --git a/app/src/main/java/com/firebase/uidemo/database/ChatActivity.java b/app/src/main/java/com/firebase/uidemo/database/ChatActivity.java index c2d894757..8358ce558 100644 --- a/app/src/main/java/com/firebase/uidemo/database/ChatActivity.java +++ b/app/src/main/java/com/firebase/uidemo/database/ChatActivity.java @@ -20,8 +20,6 @@ import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.util.Log; -import android.view.ContextMenu; -import android.view.MenuItem; import android.view.View; import android.widget.Button; import android.widget.EditText; @@ -143,26 +141,8 @@ private void attachRecyclerViewAdapter() { mChatIndicesRef.limitToLast(50), mChatRef) { @Override - public void populateViewHolder(final ChatHolder holder, Chat chat, int position) { + public void populateViewHolder(ChatHolder holder, Chat chat, int position) { holder.bind(chat); - - holder.itemView.setOnCreateContextMenuListener(new View.OnCreateContextMenuListener() { - @Override - public void onCreateContextMenu(ContextMenu menu, - View v, - ContextMenu.ContextMenuInfo menuInfo) { - menu.add("Delete") - .setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { - @Override - public boolean onMenuItemClick(MenuItem item) { - DatabaseReference ref = getRef(holder.getAdapterPosition()); - mChatIndicesRef.child(ref.getKey()).removeValue(); - ref.removeValue(); - return true; - } - }); - } - }); } @Override From 758b69239048050f163956c3c1e56b1e12edd76d Mon Sep 17 00:00:00 2001 From: Alex Saveau Date: Mon, 6 Mar 2017 11:16:40 -0800 Subject: [PATCH 93/93] Add separate activity for index stuff Signed-off-by: Alex Saveau --- app/src/main/AndroidManifest.xml | 3 + .../uidemo/database/ChatActivity.java | 99 ++++++++----------- .../uidemo/database/ChatIndexActivity.java | 67 +++++++++++++ 3 files changed, 113 insertions(+), 56 deletions(-) create mode 100644 app/src/main/java/com/firebase/uidemo/database/ChatIndexActivity.java diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 52b5e1fca..ce2b85437 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -30,6 +30,9 @@ + mAdapter; - private TextView mEmptyListMessage; + protected FirebaseRecyclerAdapter mAdapter; + protected TextView mEmptyListMessage; @Override protected void onCreate(Bundle savedInstanceState) { @@ -64,31 +62,9 @@ protected void onCreate(Bundle savedInstanceState) { mMessageEdit = (EditText) findViewById(R.id.messageEdit); mEmptyListMessage = (TextView) findViewById(R.id.emptyTextView); - DatabaseReference ref = FirebaseDatabase.getInstance().getReference(); - mChatIndicesRef = ref.child("chatIndices"); - mChatRef = ref.child("chats"); + mChatRef = FirebaseDatabase.getInstance().getReference().child("chats"); - mSendButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - String uid = mAuth.getCurrentUser().getUid(); - String name = "User " + uid.substring(0, 6); - - Chat chat = new Chat(name, mMessageEdit.getText().toString(), uid); - DatabaseReference chatRef = mChatRef.push(); - mChatIndicesRef.child(chatRef.getKey()).setValue(true); - chatRef.setValue(chat, new DatabaseReference.CompletionListener() { - @Override - public void onComplete(DatabaseError error, DatabaseReference reference) { - if (error != null) { - Log.e(TAG, "Failed to write message", error.toException()); - } - } - }); - - mMessageEdit.setText(""); - } - }); + mSendButton.setOnClickListener(this); mManager = new LinearLayoutManager(this); mManager.setReverseLayout(false); @@ -128,50 +104,61 @@ public void onDestroy() { } } + @Override + public void onClick(View v) { + String uid = mAuth.getCurrentUser().getUid(); + String name = "User " + uid.substring(0, 6); + + Chat chat = new Chat(name, mMessageEdit.getText().toString(), uid); + mChatRef.push().setValue(chat, new DatabaseReference.CompletionListener() { + @Override + public void onComplete(DatabaseError error, DatabaseReference reference) { + if (error != null) { + Log.e(TAG, "Failed to write message", error.toException()); + } + } + }); + + mMessageEdit.setText(""); + } + @Override public void onAuthStateChanged(@NonNull FirebaseAuth firebaseAuth) { updateUI(); } private void attachRecyclerViewAdapter() { - mAdapter = new FirebaseIndexRecyclerAdapter( + mAdapter = getAdapter(); + + // Scroll to bottom on new messages + mAdapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() { + @Override + public void onItemRangeInserted(int positionStart, int itemCount) { + mManager.smoothScrollToPosition(mMessages, null, mAdapter.getItemCount()); + } + }); + + mMessages.setAdapter(mAdapter); + } + + protected FirebaseRecyclerAdapter getAdapter() { + Query lastFifty = mChatRef.limitToLast(50); + return new FirebaseRecyclerAdapter( Chat.class, R.layout.message, ChatHolder.class, - mChatIndicesRef.limitToLast(50), - mChatRef) { + lastFifty) { @Override public void populateViewHolder(ChatHolder holder, Chat chat, int position) { holder.bind(chat); } - @Override - public void onChildChanged(EventType type, - DataSnapshot snapshot, - int index, - int oldIndex) { - super.onChildChanged(type, snapshot, index, oldIndex); - - // TODO temporary fix for https://github.com/firebase/FirebaseUI-Android/issues/546 - onDataChanged(); - } - @Override public void onDataChanged() { // If there are no chat messages, show a view that invites the user to add a message. mEmptyListMessage.setVisibility(getItemCount() == 0 ? View.VISIBLE : View.GONE); } }; - - // Scroll to bottom on new messages - mAdapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() { - @Override - public void onItemRangeInserted(int positionStart, int itemCount) { - mManager.smoothScrollToPosition(mMessages, null, mAdapter.getItemCount()); - } - }); - - mMessages.setAdapter(mAdapter); } private void signInAnonymously() { diff --git a/app/src/main/java/com/firebase/uidemo/database/ChatIndexActivity.java b/app/src/main/java/com/firebase/uidemo/database/ChatIndexActivity.java new file mode 100644 index 000000000..10186ad61 --- /dev/null +++ b/app/src/main/java/com/firebase/uidemo/database/ChatIndexActivity.java @@ -0,0 +1,67 @@ +package com.firebase.uidemo.database; + +import android.os.Bundle; +import android.view.View; + +import com.firebase.ui.database.FirebaseIndexRecyclerAdapter; +import com.firebase.ui.database.FirebaseRecyclerAdapter; +import com.firebase.uidemo.R; +import com.google.firebase.auth.FirebaseAuth; +import com.google.firebase.database.DataSnapshot; +import com.google.firebase.database.DatabaseReference; +import com.google.firebase.database.FirebaseDatabase; + +public class ChatIndexActivity extends ChatActivity { + private DatabaseReference mChatIndicesRef; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mChatIndicesRef = FirebaseDatabase.getInstance().getReference().child("chatIndices"); + } + + @Override + public void onClick(View v) { + String uid = FirebaseAuth.getInstance().getCurrentUser().getUid(); + String name = "User " + uid.substring(0, 6); + Chat chat = new Chat(name, mMessageEdit.getText().toString(), uid); + + DatabaseReference chatRef = mChatRef.push(); + mChatIndicesRef.child(chatRef.getKey()).setValue(true); + chatRef.setValue(chat); + + mMessageEdit.setText(""); + } + + @Override + protected FirebaseRecyclerAdapter getAdapter() { + return new FirebaseIndexRecyclerAdapter( + Chat.class, + R.layout.message, + ChatHolder.class, + mChatIndicesRef.limitToLast(50), + mChatRef) { + @Override + public void populateViewHolder(ChatHolder holder, Chat chat, int position) { + holder.bind(chat); + } + + @Override + public void onChildChanged(EventType type, + DataSnapshot snapshot, + int index, + int oldIndex) { + super.onChildChanged(type, snapshot, index, oldIndex); + + // TODO temporary fix for https://github.com/firebase/FirebaseUI-Android/issues/546 + onDataChanged(); + } + + @Override + public void onDataChanged() { + // If there are no chat messages, show a view that invites the user to add a message. + mEmptyListMessage.setVisibility(getItemCount() == 0 ? View.VISIBLE : View.GONE); + } + }; + } +}