Skip to content

Commit 85967cb

Browse files
committed
Add documentation
Change-Id: I1d5cf1f6d49d5964b7cd5d702ccd7a24db3f79a8
1 parent 202857d commit 85967cb

File tree

3 files changed

+201
-20
lines changed

3 files changed

+201
-20
lines changed

firestore/README.md

Lines changed: 171 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,13 @@ Before using this library, you should be familiar with the following topics:
1212
1. [Data model](#data-model)
1313
1. [Querying](#querying)
1414
1. [Populating a RecyclerView](#using-firebaseui-to-populate-a-recyclerview)
15-
1. [Using the adapter](#using-the-firestorerecycleradapter)
16-
1. [Adapter lifecyle](#firestorerecycleradapter-lifecycle)
17-
1. [Events](#data-and-error-events)
15+
1. [Choosing an adapter](#choosing-an-adapter)
16+
1. [Using the FirestoreRecyclerAdapter](#using-the-firestorerecycleradapter)
17+
1. [Adapter lifecyle](#firestorerecycleradapter-lifecycle)
18+
1. [Events](#data-and-error-events)
19+
1. [Using the FirestoreRecyclerAdapter](#using-the-firestorepagingadapter)
20+
1. [Adapter lifecyle](#firestorepagingadapter-lifecycle)
21+
1. [Events](#paging-events)
1822

1923
## Data model
2024

@@ -109,6 +113,18 @@ updates with the `EventListener` on the `Query`.
109113

110114
Fear not, FirebaseUI does all of this for you automatically!
111115

116+
117+
### Choosing an adapter
118+
119+
FirebaseUI offers two types of RecyclerView adapters for Cloud Firestore:
120+
121+
* `FirestoreRecyclerAdapter` - bind a `Query` to a `RecyclerView` and respond to all real-time
122+
events included items being added, removed, moved, or changed. Best used with small result sets
123+
since all results are loaded at once.
124+
* `FirestorePagingAdapter` - bind a `Query` to a `RecyclerView` by loading data in pages. Best
125+
used with large, static data sets. Real-time events are not respected by this adapter, so it
126+
will not detect new/removed items or changes to items already loaded.
127+
112128
### Using the `FirestoreRecyclerAdapter`
113129

114130
The `FirestoreRecyclerAdapter` binds a `Query` to a `RecyclerView`. When documents are added,
@@ -164,9 +180,9 @@ FirestoreRecyclerAdapter adapter = new FirestoreRecyclerAdapter<Chat, ChatHolder
164180
Finally attach the adapter to your `RecyclerView` with the `RecyclerView#setAdapter()`.
165181
Don't forget to also set a `LayoutManager`!
166182

167-
### `FirestoreRecyclerAdapter` lifecycle
183+
#### `FirestoreRecyclerAdapter` lifecycle
168184

169-
#### Start/stop listening
185+
##### Start/stop listening
170186

171187
The `FirestoreRecyclerAdapter` uses a snapshot listener to monitor changes to the Firestore query.
172188
To begin listening for data, call the `startListening()` method. You may want to call this
@@ -192,15 +208,15 @@ protected void onStop() {
192208
}
193209
```
194210

195-
#### Automatic listening
211+
##### Automatic listening
196212

197213
If you don't want to manually start/stop listening you can use
198214
[Android Architecture Components][arch-components] to automatically manage the lifecycle of the
199215
`FirestoreRecyclerAdapter`. Pass a `LifecycleOwner` to
200216
`FirestoreRecyclerOptions.Builder#setLifecycleOwner(...)` and FirebaseUI will automatically
201217
start and stop listening in `onStart()` and `onStop()`.
202218

203-
### Data and error events
219+
#### Data and error events
204220

205221
When using the `FirestoreRecyclerAdapter` you may want to perform some action every time data
206222
changes or when there is an error. To do this, override the `onDataChanged()` and `onError()`
@@ -227,7 +243,155 @@ FirestoreRecyclerAdapter adapter = new FirestoreRecyclerAdapter<Chat, ChatHolder
227243
```
228244

229245

246+
### Using the `FirestorePagingAdapter`
247+
248+
The `FirestorePagingAdapter` binds a `Query` to a `RecyclerView` by loading documents in pages.
249+
This results in a time and memory efficient binding, however it gives up the real-time events
250+
afforted by the `FirestoreRecyclerAdaoter`.
251+
252+
The `FirestorePagingAdapter` is built on top of the [Android Paging Support Library][paging-support].
253+
Before using the adapter in your application, you must add a dependency on the support library:
254+
255+
```groovy
256+
implementation 'android.arch.paging:runtime:1.0.0-beta1'
257+
```
258+
259+
First, configure the adapter by building `FirestorePagingOptions`. Since the paging adapter
260+
is not appropriate for a chat application (it would not detect new messages), we will consider
261+
an adapter that loads a generic `Item`:
262+
263+
```java
264+
// The "base query" is a query with no startAt/endAt/limit clauses that the adapter can use
265+
// to form smaller queries for each page. It should only include where() and orderBy() clauses
266+
Query baseQuery = mItemsCollection.orderBy("value", Query.Direction.ASCENDING);
267+
268+
// This configuration comes from the Paging Support Library
269+
// https://developer.android.com/reference/android/arch/paging/PagedList.Config.html
270+
PagedList.Config config = new PagedList.Config.Builder()
271+
.setEnablePlaceholders(false)
272+
.setPrefetchDistance(10)
273+
.setPageSize(20)
274+
.build();
275+
276+
// The options for the adapter combine the paging configuration with query information
277+
// and application-specific options for lifecycle, etc.
278+
FirestorePagingOptions<Item> options = new FirestorePagingOptions.Builder<Item>()
279+
.setLifecycleOwner(this)
280+
.setQuery(baseQuery, config, Item.class)
281+
.build();
282+
```
283+
284+
If you need to customize how your model class is parsed, you can use a custom `SnapshotParser`:
285+
286+
```java
287+
...setQuery(..., new SnapshotParser<Item>() {
288+
@NonNull
289+
@Override
290+
public Item parseSnapshot(@NonNull DocumentSnapshot snapshot) {
291+
return ...;
292+
}
293+
});
294+
```
295+
296+
Next create the `FirestorePagingAdapter` object. You should already have a `ViewHolder` subclass
297+
for displaying each item. In this case we will use a custom `ItemViewHolder` class:
298+
299+
```java
300+
FirestorePagingAdapter<Item, ItemViewHolder> adapter =
301+
new FirestorePagingAdapter<Item, ItemViewHolder>(options) {
302+
@NonNull
303+
@Override
304+
public ItemViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
305+
// Create the ItemViewHolder
306+
// ,,,
307+
}
308+
309+
@Override
310+
protected void onBindViewHolder(@NonNull ItemViewHolder holder,
311+
int position,
312+
@NonNull Item model) {
313+
// Bind the item to the view holder
314+
// ...
315+
}
316+
};
317+
```
318+
319+
Finally attach the adapter to your `RecyclerView` with the `RecyclerView#setAdapter()`.
320+
Don't forget to also set a `LayoutManager`!
321+
322+
#### `FirestorePagingAdapter` lifecycle
323+
324+
##### Start/stop listening
325+
326+
The `FirestorePagingAdapter` listens for scrolling events and loads additional pages from the
327+
database only when needed.
328+
329+
To begin populating data, call the `startListening()` method. You may want to call this
330+
in your `onStart()` method. Make sure you have finished any authentication necessary to read the
331+
data before calling `startListening()` or your query will fail.
332+
333+
```java
334+
@Override
335+
protected void onStart() {
336+
super.onStart();
337+
adapter.startListening();
338+
}
339+
```
340+
341+
Similarly, the `stopListening()` call removes the snapshot listener and all data in the adapter.
342+
Call this method when the containing Activity or Fragment stops:
343+
344+
```java
345+
@Override
346+
protected void onStop() {
347+
super.onStop();
348+
adapter.stopListening();
349+
}
350+
```
351+
352+
##### Automatic listening
353+
354+
If you don't want to manually start/stop listening you can use
355+
[Android Architecture Components][arch-components] to automatically manage the lifecycle of the
356+
`FirestorePagingAdapter`. Pass a `LifecycleOwner` to
357+
`FirestorePagingOptions.Builder#setLifecycleOwner(...)` and FirebaseUI will automatically
358+
start and stop listening in `onStart()` and `onStop()`.
359+
360+
#### Paging events
361+
362+
When using the `FirestorePagingAdapter` you may want to perform some action every time data
363+
changes or when there is an error. To do this, override the `onLoadingStateChanged()`
364+
method of the adapter:
365+
366+
```java
367+
FirestorePagingAdapter<Item, ItemViewHolder> adapter =
368+
new FirestorePagingAdapter<Item, ItemViewHolder>(options) {
369+
370+
// ...
371+
372+
@Override
373+
protected void onLoadingStateChanged(@NonNull LoadingState state) {
374+
switch (state) {
375+
case LOADING_INITIAL:
376+
// The initial load has begun
377+
// ...
378+
case LOADING_MORE:
379+
// The adapter has started to load an additional page
380+
// ...
381+
case LOADED:
382+
// The previous load (either initial or additional) completed
383+
// ...
384+
case ERROR:
385+
// The previous load (either initial or additional) failed. Call
386+
// the retry() method in order to retry the load operation.
387+
// ...
388+
}
389+
}
390+
};
391+
```
392+
230393
[firestore-docs]: https://firebase.google.com/docs/firestore/
231394
[firestore-custom-objects]: https://firebase.google.com/docs/firestore/manage-data/add-data#custom_objects
232395
[recyclerview]: https://developer.android.com/reference/android/support/v7/widget/RecyclerView.html
233396
[arch-components]: https://developer.android.com/topic/libraries/architecture/index.html
397+
[paging-support]: https://developer.android.com/topic/libraries/architecture/paging.html

firestore/src/androidTest/java/com/firebase/ui/firestore/FirestoreDataSourceTest.java

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
import com.firebase.ui.firestore.paging.PageKey;
1111
import com.google.android.gms.tasks.Tasks;
1212
import com.google.firebase.firestore.DocumentSnapshot;
13-
import com.google.firebase.firestore.FirebaseFirestore;
1413
import com.google.firebase.firestore.Query;
1514
import com.google.firebase.firestore.QuerySnapshot;
1615

@@ -21,6 +20,7 @@
2120
import org.mockito.MockitoAnnotations;
2221

2322
import java.util.ArrayList;
23+
import java.util.Arrays;
2424
import java.util.List;
2525
import java.util.concurrent.CountDownLatch;
2626

@@ -33,9 +33,6 @@
3333
@RunWith(AndroidJUnit4.class)
3434
public class FirestoreDataSourceTest {
3535

36-
private static final String TAG = "DataSourceTest";
37-
38-
private FirebaseFirestore mFirestore;
3936
private FirestoreDataSource mDataSource;
4037

4138
@Mock Query mMockQuery;
@@ -65,7 +62,7 @@ public void testLoadInitial_success() throws Exception {
6562

6663
// Should go from LOADING_INITIAL --> LOADED
6764
observer.await();
68-
observer.assertResults(LoadingState.LOADING_INITIAL, LoadingState.LOADED);
65+
observer.assertResults(Arrays.asList(LoadingState.LOADING_INITIAL, LoadingState.LOADED));
6966
}
7067

7168
@Test
@@ -82,7 +79,7 @@ public void testLoadInitial_failure() throws Exception {
8279

8380
// Should go from LOADING_INITIAL --> ERROR
8481
observer.await();
85-
observer.assertResults(LoadingState.LOADING_INITIAL, LoadingState.ERROR);
82+
observer.assertResults(Arrays.asList(LoadingState.LOADING_INITIAL, LoadingState.ERROR));
8683
}
8784

8885
@Test
@@ -100,7 +97,7 @@ public void testLoadAfter_success() throws Exception {
10097

10198
// Should go from LOADING_MORE --> LOADED
10299
observer.await();
103-
observer.assertResults(LoadingState.LOADING_MORE, LoadingState.LOADED);
100+
observer.assertResults(Arrays.asList(LoadingState.LOADING_MORE, LoadingState.LOADED));
104101
}
105102

106103
@Test
@@ -118,7 +115,7 @@ public void testLoadAfter_failure() throws Exception {
118115

119116
// Should go from LOADING_MORE --> ERROR
120117
observer.await();
121-
observer.assertResults(LoadingState.LOADING_MORE, LoadingState.ERROR);
118+
observer.assertResults(Arrays.asList(LoadingState.LOADING_MORE, LoadingState.ERROR));
122119
}
123120

124121
@Test
@@ -136,7 +133,7 @@ public void testLoadAfter_retry() throws Exception {
136133

137134
// Should go from LOADING_MORE --> ERROR
138135
observer1.await();
139-
observer1.assertResults(LoadingState.LOADING_MORE, LoadingState.ERROR);
136+
observer1.assertResults(Arrays.asList(LoadingState.LOADING_MORE, LoadingState.ERROR));
140137

141138
// Create a new observer
142139
TestObserver<LoadingState> observer2 = new TestObserver<>(3);
@@ -148,7 +145,8 @@ public void testLoadAfter_retry() throws Exception {
148145

149146
// Should go from ERROR --> LOADING_MORE --> SUCCESS
150147
observer2.await();
151-
observer2.assertResults(LoadingState.ERROR, LoadingState.LOADING_MORE, LoadingState.LOADED);
148+
observer2.assertResults(
149+
Arrays.asList(LoadingState.ERROR, LoadingState.LOADING_MORE, LoadingState.LOADED));
152150
}
153151

154152
private void initMockQuery() {
@@ -194,11 +192,11 @@ public void await() throws InterruptedException {
194192
mLatch.await();
195193
}
196194

197-
public void assertResults(T... results) {
198-
assertEquals(results.length, mResults.size());
195+
public void assertResults(List<T> expected) {
196+
assertEquals(expected.size(), mResults.size());
199197

200198
for (int i = 0; i < mResults.size(); i++) {
201-
assertEquals(mResults.get(i), results[i]);
199+
assertEquals(mResults.get(i), expected.get(i));
202200
}
203201
}
204202

firestore/src/main/java/com/firebase/ui/firestore/paging/FirestorePagingAdapter.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,9 @@ public void onChanged(@Nullable PagedList<DocumentSnapshot> snapshots) {
5858
}
5959
};
6060

61+
/**
62+
* Construct a new FirestorePagingAdapter from the given {@link FirestorePagingOptions}.
63+
*/
6164
public FirestorePagingAdapter(@NonNull FirestorePagingOptions<T> options) {
6265
super(options.getDiffCallback());
6366

@@ -101,12 +104,18 @@ public void retry() {
101104
source.retry();
102105
}
103106

107+
/**
108+
* Start listening to paging / scrolling events and populating adapter data.
109+
*/
104110
@OnLifecycleEvent(Lifecycle.Event.ON_START)
105111
public void startListening() {
106112
mSnapshots.observeForever(mDataObserver);
107113
mLoadingState.observeForever(mStateObserver);
108114
}
109115

116+
/**
117+
* Unsubscribe from paging / scrolling events, no more data will be populated.
118+
*/
110119
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
111120
public void stopListening() {
112121
mSnapshots.removeObserver(mDataObserver);
@@ -119,8 +128,18 @@ public void onBindViewHolder(@NonNull VH holder, int position) {
119128
onBindViewHolder(holder, position, mParser.parseSnapshot(snapshot));
120129
}
121130

131+
/**
132+
* @param model the model object containing the data that should be used to populate the view.
133+
* @see #onBindViewHolder(RecyclerView.ViewHolder, int)
134+
*/
122135
protected abstract void onBindViewHolder(@NonNull VH holder, int position, @NonNull T model);
123136

137+
/**
138+
* Called whenever the loading state of the adapter changes.
139+
*
140+
* When the state is {@link LoadingState#ERROR} the adapter will stop loading any data unless
141+
* {@link #retry()} is called.
142+
*/
124143
protected void onLoadingStateChanged(@NonNull LoadingState state) {
125144
// For overriding
126145
}

0 commit comments

Comments
 (0)