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,28 +62,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");
+ 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);
- 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("");
- }
- });
+ mSendButton.setOnClickListener(this);
mManager = new LinearLayoutManager(this);
mManager.setReverseLayout(false);
@@ -126,33 +105,30 @@ public void onDestroy() {
}
@Override
- public void onAuthStateChanged(@NonNull FirebaseAuth firebaseAuth) {
- updateUI();
- }
+ public void onClick(View v) {
+ String uid = mAuth.getCurrentUser().getUid();
+ String name = "User " + uid.substring(0, 6);
- private void attachRecyclerViewAdapter() {
- Query lastFifty = mChatRef.limitToLast(50);
- mAdapter = new FirebaseRecyclerAdapter(
- Chat.class, R.layout.message, ChatHolder.class, lastFifty) {
+ Chat chat = new Chat(name, mMessageEdit.getText().toString(), uid);
+ mChatRef.push().setValue(chat, new DatabaseReference.CompletionListener() {
@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 onComplete(DatabaseError error, DatabaseReference reference) {
+ if (error != null) {
+ Log.e(TAG, "Failed to write message", error.toException());
}
}
+ });
- @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);
- }
- };
+ mMessageEdit.setText("");
+ }
+
+ @Override
+ public void onAuthStateChanged(@NonNull FirebaseAuth firebaseAuth) {
+ updateUI();
+ }
+
+ private void attachRecyclerViewAdapter() {
+ mAdapter = getAdapter();
// Scroll to bottom on new messages
mAdapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
@@ -165,6 +141,26 @@ public void onItemRangeInserted(int positionStart, int itemCount) {
mMessages.setAdapter(mAdapter);
}
+ protected FirebaseRecyclerAdapter getAdapter() {
+ Query lastFifty = mChatRef.limitToLast(50);
+ return new FirebaseRecyclerAdapter(
+ Chat.class,
+ R.layout.message,
+ ChatHolder.class,
+ lastFifty) {
+ @Override
+ public void populateViewHolder(ChatHolder holder, Chat chat, int position) {
+ holder.bind(chat);
+ }
+
+ @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);
+ }
+ };
+ }
+
private void signInAnonymously() {
Toast.makeText(this, "Signing in...", Toast.LENGTH_SHORT).show();
mAuth.signInAnonymously()
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);
- }
}
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);
+ }
+ };
+ }
+}
diff --git a/database/README.md b/database/README.md
index 0b572b71b..28124e387 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:
@@ -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
@@ -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 `RecyclerView` 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("");
+ ref.push().setValue(new Chat("puf", "1234", message.getText().toString()));
+ 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;
-
- 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.
+ListView messagesView = (ListView) findViewById(R.id.messages_list);
-### 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:
+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
-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.