FirebaseUI makes it simple to bind data from Cloud Firestore to your app's UI.
Before using this library, you should be familiar with the following topics:
Imagine you have a chat app where each chat message is a document in the chats
collection
of your database. In your app, you may represent a chat message like this:
public class Chat {
private String mName;
private String mMessage;
private String mUid;
private Date mTimestamp;
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; }
@ServerTimestamp
public Date getTimestamp() { return mTimestamp; }
public void setTimestamp(Date timestamp) { mTimestamp = timestamp; }
}
A few things to note about this model class:
- The getters and setters follow the JavaBean naming pattern which allows Firestore to map
the data to field names (ex:
getName()
provides thename
field). - The class has an empty constructor, which is required for Firestore's automatic data mapping.
For a properly constructed model class like the Chat
class above, Firestore can perform automatic
serialization in DocumentReference#set()
and automatic deserialization in
DocumentSnapshot#toObject()
. For more information on data mapping in Firestore, see the
documentation on custom objects.
On the main screen of your app, you may want to show the 50 most recent chat messages. In Firestore, you would use the following query:
Query query = FirebaseFirestore.getInstance()
.collection("chats")
.orderBy("timestamp")
.limit(50);
To retrieve this data without FirebaseUI, you might use addSnapshotListener
to listen for
live query updates:
query.addSnapshotListener(new EventListener<QuerySnapshot>() {
@Override
public void onEvent(@Nullable QuerySnapshot snapshot,
@Nullable FirebaseFirestoreException e) {
if (e != null) {
// Handle error
//...
return;
}
// Convert query snapshot to a list of chats
List<Chat> chats = snapshot.toObjects(Chat.class);
// Update UI
// ...
}
});
If you're displaying a list of data, you likely want to bind the Chat
objects to a
RecyclerView
. This means implementing a custom RecyclerView.Adapter
and coordinating
updates with the EventListener
on the Query
.
Fear not, FirebaseUI does all of this for you automatically!
The FirestoreRecyclerAdapter
binds a Query
to a RecyclerView
. When documents are added,
removed, or change these updates are automatically applied to your UI in real time.
First, configure the adapter by building FirestoreRecyclerOptions
. In this case we will continue
with our chat example:
// Configure recycler adapter options:
// * query is the Query object defined above.
// * Chat.class instructs the adapter to convert each DocumentSnapshot to a Chat object
FirestoreRecyclerOptions<Chat> options = new FirestoreRecyclerOptions.Builder<Chat>()
.setQuery(query, Chat.class)
.build();
Next create the FirestoreRecyclerAdapter
object. You should already have a ViewHolder
subclass
for displaying each item. In this case we will use a custom ChatHolder
class:
FirestoreRecyclerAdapter adapter = new FirestoreRecyclerAdapter<Chat, ChatHolder>(options) {
@Override
public void onBindViewHolder(ChatHolder holder, int position, Chat model) {
// Bind the Chat object to the ChatHolder
// ...
}
@Override
public ChatHolder onCreateViewHolder(ViewGroup group, int i) {
// Create a new instance of the ViewHolder, in this case we are using a custom
// layout called R.layout.message for each item
View view = LayoutInflater.from(group.getContext())
.inflate(R.layout.message, group, false);
return new ChatHolder(view);
}
};
Finally attach the adapter to your RecyclerView
with the RecyclerView#setAdapter()
.
Don't forget to also set a LayoutManager
!
The FirestoreRecyclerAdapter
uses a snapshot listener to monitor changes to the Firestore query.
To begin listening for data, call the startListening()
method. You may want to call this
in your onStart()
method. Make sure you have finished any authentication necessary to read the
data before calling startListening()
or your query will fail.
@Override
protected void onStart() {
super.onStart();
adapter.startListening();
}
Similarly, the stopListening()
call removes the snapshot listener and all data in the adapter.
Call this method when the containing Activity or Fragment stops:
@Override
protected void onStop() {
super.onStop();
adapter.stopListening();
}
If you don't want to manually start/stop listening you can use
Android Architecture Components to automatically manage the lifecycle of the
FirestoreRecyclerAdapter
. Pass a LifecycleOwner
to
FirestoreRecyclerOptions.Builder#setLifecycleOwner(...)
and FirebaseUI will automatically
start and stop listening in onStart()
and onStop()
.
When using the FirestoreRecyclerAdapter
you may want to perform some action every time data
changes or when there is an error. To do this, override the onDataChanged()
and onError()
methods of the adapter:
FirestoreRecyclerAdapter adapter = new FirestoreRecyclerAdapter<Chat, ChatHolder>(options) {
// ...
@Override
public void onDataChanged() {
// Called each time there is a new query snapshot. You may want to use this method
// to hide a loading spinner or check for the "no documents" state and update your UI.
// ...
}
@Override
public void onError(FirebaseFirestoreException e) {
// Called when there is an error getting a query snapshot. You may want to update
// your UI to display an error message to the user.
// ...
}
};