Skip to content

Commit b0877f2

Browse files
authored
refactor(firestore): migrate pagination to Paging 3 (#1915)
1 parent a0e84e4 commit b0877f2

File tree

11 files changed

+313
-719
lines changed

11 files changed

+313
-719
lines changed

app/src/main/java/com/firebase/uidemo/database/firestore/FirestorePagingActivity.java

Lines changed: 37 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212

1313
import com.firebase.ui.firestore.paging.FirestorePagingAdapter;
1414
import com.firebase.ui.firestore.paging.FirestorePagingOptions;
15-
import com.firebase.ui.firestore.paging.LoadingState;
1615
import com.firebase.uidemo.R;
1716
import com.firebase.uidemo.databinding.ActivityFirestorePagingBinding;
1817
import com.google.android.gms.tasks.OnCompleteListener;
@@ -27,10 +26,14 @@
2726
import androidx.annotation.NonNull;
2827
import androidx.annotation.Nullable;
2928
import androidx.appcompat.app.AppCompatActivity;
30-
import androidx.paging.PagedList;
29+
import androidx.paging.CombinedLoadStates;
30+
import androidx.paging.LoadState;
31+
import androidx.paging.PagingConfig;
3132
import androidx.recyclerview.widget.LinearLayoutManager;
3233
import androidx.recyclerview.widget.RecyclerView;
3334
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
35+
import kotlin.Unit;
36+
import kotlin.jvm.functions.Function1;
3437

3538
public class FirestorePagingActivity extends AppCompatActivity {
3639

@@ -56,11 +59,7 @@ protected void onCreate(@Nullable Bundle savedInstanceState) {
5659
private void setUpAdapter() {
5760
Query baseQuery = mItemsCollection.orderBy("value", Query.Direction.ASCENDING);
5861

59-
PagedList.Config config = new PagedList.Config.Builder()
60-
.setEnablePlaceholders(false)
61-
.setPrefetchDistance(10)
62-
.setPageSize(20)
63-
.build();
62+
PagingConfig config = new PagingConfig(20, 10, false);
6463

6564
FirestorePagingOptions<Item> options = new FirestorePagingOptions.Builder<Item>()
6665
.setLifecycleOwner(this)
@@ -84,34 +83,42 @@ protected void onBindViewHolder(@NonNull ItemViewHolder holder,
8483
@NonNull Item model) {
8584
holder.bind(model);
8685
}
86+
};
87+
adapter.addLoadStateListener(new Function1<CombinedLoadStates, Unit>() {
88+
@Override
89+
public Unit invoke(CombinedLoadStates states) {
90+
LoadState refresh = states.getRefresh();
91+
LoadState append = states.getAppend();
8792

88-
@Override
89-
protected void onLoadingStateChanged(@NonNull LoadingState state) {
90-
switch (state) {
91-
case LOADING_INITIAL:
92-
case LOADING_MORE:
93-
mBinding.swipeRefreshLayout.setRefreshing(true);
94-
break;
95-
case LOADED:
96-
mBinding.swipeRefreshLayout.setRefreshing(false);
97-
break;
98-
case FINISHED:
99-
mBinding.swipeRefreshLayout.setRefreshing(false);
100-
showToast("Reached end of data set.");
101-
break;
102-
case ERROR:
103-
showToast("An error occurred.");
104-
retry();
105-
break;
106-
}
93+
if (refresh instanceof LoadState.Error || append instanceof LoadState.Error) {
94+
showToast("An error occurred.");
95+
adapter.retry();
96+
}
97+
98+
if (append instanceof LoadState.Loading) {
99+
mBinding.swipeRefreshLayout.setRefreshing(true);
100+
}
101+
102+
if (append instanceof LoadState.NotLoading) {
103+
LoadState.NotLoading notLoading = (LoadState.NotLoading) append;
104+
if (notLoading.getEndOfPaginationReached()) {
105+
// This indicates that the user has scrolled
106+
// until the end of the data set.
107+
mBinding.swipeRefreshLayout.setRefreshing(false);
108+
showToast("Reached end of data set.");
109+
return null;
107110
}
108111

109-
@Override
110-
protected void onError(@NonNull Exception e) {
112+
if (refresh instanceof LoadState.NotLoading) {
113+
// This indicates the most recent load
114+
// has finished.
111115
mBinding.swipeRefreshLayout.setRefreshing(false);
112-
Log.e(TAG, e.getMessage(), e);
116+
return null;
113117
}
114-
};
118+
}
119+
return null;
120+
}
121+
});
115122

116123
mBinding.pagingRecycler.setLayoutManager(new LinearLayoutManager(this));
117124
mBinding.pagingRecycler.setAdapter(adapter);

buildSrc/src/main/kotlin/Config.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ object Config {
3737
const val lifecycleViewModel = "androidx.lifecycle:lifecycle-viewmodel:2.2.0"
3838
const val legacySupportv4 = "androidx.legacy:legacy-support-v4:1.0.0"
3939
const val multidex = "androidx.multidex:multidex:2.0.1"
40-
const val paging = "androidx.paging:paging-runtime:2.1.2"
40+
const val paging = "androidx.paging:paging-runtime:3.0.0"
41+
const val pagingRxJava = "androidx.paging:paging-rxjava3:3.0.0"
4142
const val recyclerView = "androidx.recyclerview:recyclerview:1.1.0"
4243

4344
const val design = "com.google.android.material:material:1.2.1"

firestore/README.md

Lines changed: 93 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -249,11 +249,11 @@ The `FirestorePagingAdapter` binds a `Query` to a `RecyclerView` by loading docu
249249
This results in a time and memory efficient binding, however it gives up the real-time events
250250
afforded by the `FirestoreRecyclerAdapter`.
251251

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:
252+
The `FirestorePagingAdapter` is built on top of the [Android Paging 3 Library][paging-support].
253+
Before using the adapter in your application, you must add a dependency on that library:
254254

255255
```groovy
256-
implementation 'androidx.paging:paging-runtime:2.x.x'
256+
implementation 'androidx.paging:paging-runtime:3.x.x'
257257
```
258258

259259
First, configure the adapter by building `FirestorePagingOptions`. Since the paging adapter
@@ -262,16 +262,13 @@ an adapter that loads a generic `Item`:
262262

263263
```java
264264
// 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
265+
// to form smaller queries for each page. It should only include where() and orderBy() clauses
266266
Query baseQuery = mItemsCollection.orderBy("value", Query.Direction.ASCENDING);
267267

268-
// This configuration comes from the Paging Support Library
269-
// https://developer.android.com/reference/androidx/paging/PagedList.Config
270-
PagedList.Config config = new PagedList.Config.Builder()
271-
.setEnablePlaceholders(false)
272-
.setPrefetchDistance(10)
273-
.setPageSize(20)
274-
.build();
268+
// This configuration comes from the Paging 3 Library
269+
// https://developer.android.com/reference/kotlin/androidx/paging/PagingConfig
270+
PagingConfig config = new PagingConfig(/* page size */ 20, /* prefetchDistance */ 10,
271+
/* enablePlaceHolders */ false);
275272

276273
// The options for the adapter combine the paging configuration with query information
277274
// and application-specific options for lifecycle, etc.
@@ -362,38 +359,103 @@ start and stop listening in `onStart()` and `onStop()`.
362359
#### Paging events
363360

364361
When using the `FirestorePagingAdapter`, you may want to perform some action every time data
365-
changes or when there is an error. To do this, override the `onLoadingStateChanged()`
366-
method of the adapter:
362+
changes or when there is an error. To do this:
367363

368-
```java
369-
FirestorePagingAdapter<Item, ItemViewHolder> adapter =
370-
new FirestorePagingAdapter<Item, ItemViewHolder>(options) {
364+
##### In Java
371365

372-
// ...
366+
Use the `addLoadStateListener` method from the adapter:
373367

368+
```java
369+
adapter.addLoadStateListener(new Function1<CombinedLoadStates, Unit>() {
374370
@Override
375-
protected void onLoadingStateChanged(@NonNull LoadingState state) {
376-
switch (state) {
377-
case LOADING_INITIAL:
378-
// The initial load has begun
379-
// ...
380-
case LOADING_MORE:
381-
// The adapter has started to load an additional page
371+
public Unit invoke(CombinedLoadStates states) {
372+
LoadState refresh = states.getRefresh();
373+
LoadState append = states.getAppend();
374+
375+
if (refresh instanceof LoadState.Error || append instanceof LoadState.Error) {
376+
// The previous load (either initial or additional) failed. Call
377+
// the retry() method in order to retry the load operation.
378+
// ...
379+
}
380+
381+
if (refresh instanceof LoadState.Loading) {
382+
// The initial Load has begun
383+
// ...
384+
}
385+
386+
if (append instanceof LoadState.Loading) {
387+
// The adapter has started to load an additional page
388+
// ...
389+
}
390+
391+
if (append instanceof LoadState.NotLoading) {
392+
LoadState.NotLoading notLoading = (LoadState.NotLoading) append;
393+
if (notLoading.getEndOfPaginationReached()) {
394+
// The adapter has finished loading all of the data set
382395
// ...
383-
case LOADED:
396+
return null;
397+
}
398+
399+
if (refresh instanceof LoadState.NotLoading) {
384400
// The previous load (either initial or additional) completed
385401
// ...
386-
case ERROR:
387-
// The previous load (either initial or additional) failed. Call
388-
// the retry() method in order to retry the load operation.
389-
// ...
402+
return null;
403+
}
390404
}
405+
return null;
391406
}
392-
};
407+
});
408+
```
409+
410+
#### In Kotlin
411+
412+
Use the `loadStateFlow` exposed by the adapter, in a Coroutine Scope:
413+
414+
```kotlin
415+
// Activities can use lifecycleScope directly, but Fragments should instead use
416+
// viewLifecycleOwner.lifecycleScope.
417+
lifecycleScope.launch {
418+
pagingAdapter.loadStateFlow.collectLatest { loadStates ->
419+
when (loadStates.refresh) {
420+
is LoadState.Error -> {
421+
// The initial load failed. Call the retry() method
422+
// in order to retry the load operation.
423+
// ...
424+
}
425+
is LoadState.Loading -> {
426+
// The initial Load has begun
427+
// ...
428+
}
429+
}
430+
431+
when (loadStates.append) {
432+
is LoadState.Error -> {
433+
// The additional load failed. Call the retry() method
434+
// in order to retry the load operation.
435+
// ...
436+
}
437+
is LoadState.Loading -> {
438+
// The adapter has started to load an additional page
439+
// ...
440+
}
441+
is LoadState.NotLoading -> {
442+
if (loadStates.append.endOfPaginationReached) {
443+
// The adapter has finished loading all of the data set
444+
// ...
445+
}
446+
if (loadStates.refresh is LoadState.NotLoading) {
447+
// The previous load (either initial or additional) completed
448+
// ...
449+
}
450+
}
451+
}
452+
}
453+
}
454+
393455
```
394456

395457
[firestore-docs]: https://firebase.google.com/docs/firestore/
396458
[firestore-custom-objects]: https://firebase.google.com/docs/firestore/manage-data/add-data#custom_objects
397459
[recyclerview]: https://developer.android.com/reference/androidx/recyclerview/widget/RecyclerView
398460
[arch-components]: https://developer.android.com/topic/libraries/architecture/index.html
399-
[paging-support]: https://developer.android.com/topic/libraries/architecture/paging.html
461+
[paging-support]: https://developer.android.com/topic/libraries/architecture/paging/v3-overview

firestore/build.gradle.kts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,11 @@ android {
4242
consumerProguardFiles("proguard-rules.pro")
4343
}
4444
}
45+
46+
compileOptions {
47+
sourceCompatibility = JavaVersion.VERSION_1_8
48+
targetCompatibility = JavaVersion.VERSION_1_8
49+
}
4550
}
4651

4752
dependencies {
@@ -53,6 +58,7 @@ dependencies {
5358
api(Config.Libs.Androidx.recyclerView)
5459

5560
compileOnly(Config.Libs.Androidx.paging)
61+
api(Config.Libs.Androidx.pagingRxJava)
5662
annotationProcessor(Config.Libs.Androidx.lifecycleCompiler)
5763

5864
lintChecks(project(":lint"))

0 commit comments

Comments
 (0)