Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(firestore): migrate pagination to Paging 3 #1915

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
3677ec5
refactor: pass an Activity instead of an Executor to verifyPhone
thatfiredev Nov 4, 2020
15165cd
chore: update Firebase BoM to 26.0.0
thatfiredev Nov 4, 2020
540d94f
refactor: use the new PhoneAuthOptions from auth v20.0.0
thatfiredev Nov 4, 2020
5f7bc1b
refactor(firestore): replace FirestoreDataSource with FirestorePaging…
thatfiredev Feb 11, 2021
96aeabf
deprecated setQuery(PagedList.Config) in favor of setQuery(PagingConfig)
thatfiredev Feb 12, 2021
589375a
fix: prevent the paging source from doing infinite scroll
thatfiredev Feb 12, 2021
b30e486
create FirestorePagingDataAdapter (for paging 3 support)
thatfiredev Feb 12, 2021
b70ccf3
deprecate setData() in favor of setPagingData()
thatfiredev Feb 12, 2021
72f977e
remove loading and error states liveData in favor of new loadState li…
thatfiredev Feb 12, 2021
7888e33
keep loading and error states in pagingSource for paging 2
thatfiredev Feb 12, 2021
1071042
deprecate FirestorePagingAdapter in favor of FirestorePagingDataAdapter
thatfiredev Feb 12, 2021
1515a56
test: update FirestorePagingSourceTest (previously FirestoreDataSourc…
thatfiredev Feb 13, 2021
46f07dc
add @NonNull to FirestorePagingSource constructor arguments
thatfiredev Feb 13, 2021
6d4e326
fix checkstyle errors
thatfiredev Feb 13, 2021
c9479d8
fix NonNull and Nullable annotations
thatfiredev Feb 17, 2021
91f6269
re-add FirestorePagingAdapter.retry() for paging 2
thatfiredev Feb 17, 2021
3c8687a
remove FirestorePagingSource and consequently Java 8
thatfiredev Feb 19, 2021
cb2b4b5
undo unecessary changes
thatfiredev Feb 19, 2021
fa5f998
Merge branch 'master' of https://github.com/firebase/FirebaseUI-Andro…
thatfiredev Apr 14, 2021
3a1b54e
refactor: make this a breaking change
thatfiredev Apr 14, 2021
610ac14
test: delete FirestoreDataSourceTest.java
thatfiredev Apr 14, 2021
824639e
remove unused imports from FirestorePagingAdapter
thatfiredev Apr 14, 2021
b7043aa
chore: update paging library to beta03
thatfiredev Apr 14, 2021
86bab6a
docs: update firestore/README.md
thatfiredev Apr 14, 2021
4e26689
chore: update paging to 3.0.0 (stable)🥳
thatfiredev May 5, 2021
0df2cb2
Merge branch 'version-8.0.0-dev' into rpf-migrate-firestore-paging-3
samtstern Jun 15, 2021
8f1672b
chore(firestore): migrate to java 8 and add Paging RxJava
thatfiredev Jun 25, 2021
5f101cd
refactor: replace FirestoreDataSource with Firestore Paging 3
thatfiredev Jun 25, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@

import com.firebase.ui.firestore.paging.FirestorePagingAdapter;
import com.firebase.ui.firestore.paging.FirestorePagingOptions;
import com.firebase.ui.firestore.paging.LoadingState;
import com.firebase.uidemo.R;
import com.firebase.uidemo.databinding.ActivityFirestorePagingBinding;
import com.google.android.gms.tasks.OnCompleteListener;
Expand All @@ -27,10 +26,14 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.paging.PagedList;
import androidx.paging.CombinedLoadStates;
import androidx.paging.LoadState;
import androidx.paging.PagingConfig;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import kotlin.Unit;
import kotlin.jvm.functions.Function1;

public class FirestorePagingActivity extends AppCompatActivity {

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

PagedList.Config config = new PagedList.Config.Builder()
.setEnablePlaceholders(false)
.setPrefetchDistance(10)
.setPageSize(20)
.build();
PagingConfig config = new PagingConfig(20, 10, false);

FirestorePagingOptions<Item> options = new FirestorePagingOptions.Builder<Item>()
.setLifecycleOwner(this)
Expand All @@ -84,34 +83,42 @@ protected void onBindViewHolder(@NonNull ItemViewHolder holder,
@NonNull Item model) {
holder.bind(model);
}
};
adapter.addLoadStateListener(new Function1<CombinedLoadStates, Unit>() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seeing Function1<> 🤮 reminds me that we can probably convert the sample app to Kotlin once we go to 8.0!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We definitely should!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@samtstern actually, since we migrated to Java 8, this can actually be replaced with a lambda function:

adapter.addLoadStateListener(states -> {
    // ...
}

So we don't really need Function1<> here. 😅

@Override
public Unit invoke(CombinedLoadStates states) {
LoadState refresh = states.getRefresh();
LoadState append = states.getAppend();

@Override
protected void onLoadingStateChanged(@NonNull LoadingState state) {
switch (state) {
case LOADING_INITIAL:
case LOADING_MORE:
mBinding.swipeRefreshLayout.setRefreshing(true);
break;
case LOADED:
mBinding.swipeRefreshLayout.setRefreshing(false);
break;
case FINISHED:
mBinding.swipeRefreshLayout.setRefreshing(false);
showToast("Reached end of data set.");
break;
case ERROR:
showToast("An error occurred.");
retry();
break;
}
if (refresh instanceof LoadState.Error || append instanceof LoadState.Error) {
showToast("An error occurred.");
adapter.retry();
}

if (append instanceof LoadState.Loading) {
mBinding.swipeRefreshLayout.setRefreshing(true);
}

if (append instanceof LoadState.NotLoading) {
LoadState.NotLoading notLoading = (LoadState.NotLoading) append;
if (notLoading.getEndOfPaginationReached()) {
// This indicates that the user has scrolled
// until the end of the data set.
mBinding.swipeRefreshLayout.setRefreshing(false);
showToast("Reached end of data set.");
return null;
}

@Override
protected void onError(@NonNull Exception e) {
if (refresh instanceof LoadState.NotLoading) {
// This indicates the most recent load
// has finished.
mBinding.swipeRefreshLayout.setRefreshing(false);
Log.e(TAG, e.getMessage(), e);
return null;
}
};
}
return null;
}
});

mBinding.pagingRecycler.setLayoutManager(new LinearLayoutManager(this));
mBinding.pagingRecycler.setAdapter(adapter);
Expand Down
3 changes: 2 additions & 1 deletion buildSrc/src/main/kotlin/Config.kt
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ object Config {
const val lifecycleViewModel = "androidx.lifecycle:lifecycle-viewmodel:2.2.0"
const val legacySupportv4 = "androidx.legacy:legacy-support-v4:1.0.0"
const val multidex = "androidx.multidex:multidex:2.0.1"
const val paging = "androidx.paging:paging-runtime:2.1.2"
const val paging = "androidx.paging:paging-runtime:3.0.0"
const val pagingRxJava = "androidx.paging:paging-rxjava3:3.0.0"
const val recyclerView = "androidx.recyclerview:recyclerview:1.1.0"

const val design = "com.google.android.material:material:1.2.1"
Expand Down
124 changes: 93 additions & 31 deletions firestore/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -249,11 +249,11 @@ The `FirestorePagingAdapter` binds a `Query` to a `RecyclerView` by loading docu
This results in a time and memory efficient binding, however it gives up the real-time events
afforded by the `FirestoreRecyclerAdapter`.

The `FirestorePagingAdapter` is built on top of the [Android Paging Support Library][paging-support].
Before using the adapter in your application, you must add a dependency on the support library:
The `FirestorePagingAdapter` is built on top of the [Android Paging 3 Library][paging-support].
Before using the adapter in your application, you must add a dependency on that library:

```groovy
implementation 'androidx.paging:paging-runtime:2.x.x'
implementation 'androidx.paging:paging-runtime:3.x.x'
```

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

```java
// The "base query" is a query with no startAt/endAt/limit clauses that the adapter can use
// to form smaller queries for each page. It should only include where() and orderBy() clauses
// to form smaller queries for each page. It should only include where() and orderBy() clauses
Query baseQuery = mItemsCollection.orderBy("value", Query.Direction.ASCENDING);

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

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

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

```java
FirestorePagingAdapter<Item, ItemViewHolder> adapter =
new FirestorePagingAdapter<Item, ItemViewHolder>(options) {
##### In Java

// ...
Use the `addLoadStateListener` method from the adapter:

```java
adapter.addLoadStateListener(new Function1<CombinedLoadStates, Unit>() {
@Override
protected void onLoadingStateChanged(@NonNull LoadingState state) {
switch (state) {
case LOADING_INITIAL:
// The initial load has begun
// ...
case LOADING_MORE:
// The adapter has started to load an additional page
public Unit invoke(CombinedLoadStates states) {
LoadState refresh = states.getRefresh();
LoadState append = states.getAppend();

if (refresh instanceof LoadState.Error || append instanceof LoadState.Error) {
// The previous load (either initial or additional) failed. Call
// the retry() method in order to retry the load operation.
// ...
}

if (refresh instanceof LoadState.Loading) {
// The initial Load has begun
// ...
}

if (append instanceof LoadState.Loading) {
// The adapter has started to load an additional page
// ...
}

if (append instanceof LoadState.NotLoading) {
LoadState.NotLoading notLoading = (LoadState.NotLoading) append;
if (notLoading.getEndOfPaginationReached()) {
// The adapter has finished loading all of the data set
// ...
case LOADED:
return null;
}

if (refresh instanceof LoadState.NotLoading) {
// The previous load (either initial or additional) completed
// ...
case ERROR:
// The previous load (either initial or additional) failed. Call
// the retry() method in order to retry the load operation.
// ...
return null;
}
}
return null;
}
};
});
```

#### In Kotlin

Use the `loadStateFlow` exposed by the adapter, in a Coroutine Scope:

```kotlin
// Activities can use lifecycleScope directly, but Fragments should instead use
// viewLifecycleOwner.lifecycleScope.
lifecycleScope.launch {
pagingAdapter.loadStateFlow.collectLatest { loadStates ->
when (loadStates.refresh) {
is LoadState.Error -> {
// The initial load failed. Call the retry() method
// in order to retry the load operation.
// ...
}
is LoadState.Loading -> {
// The initial Load has begun
// ...
}
}

when (loadStates.append) {
is LoadState.Error -> {
// The additional load failed. Call the retry() method
// in order to retry the load operation.
// ...
}
is LoadState.Loading -> {
// The adapter has started to load an additional page
// ...
}
is LoadState.NotLoading -> {
if (loadStates.append.endOfPaginationReached) {
// The adapter has finished loading all of the data set
// ...
}
if (loadStates.refresh is LoadState.NotLoading) {
// The previous load (either initial or additional) completed
// ...
}
}
}
}
}

```

[firestore-docs]: https://firebase.google.com/docs/firestore/
[firestore-custom-objects]: https://firebase.google.com/docs/firestore/manage-data/add-data#custom_objects
[recyclerview]: https://developer.android.com/reference/androidx/recyclerview/widget/RecyclerView
[arch-components]: https://developer.android.com/topic/libraries/architecture/index.html
[paging-support]: https://developer.android.com/topic/libraries/architecture/paging.html
[paging-support]: https://developer.android.com/topic/libraries/architecture/paging/v3-overview
6 changes: 6 additions & 0 deletions firestore/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ android {
consumerProguardFiles("proguard-rules.pro")
}
}

compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
}

dependencies {
Expand All @@ -53,6 +58,7 @@ dependencies {
api(Config.Libs.Androidx.recyclerView)

compileOnly(Config.Libs.Androidx.paging)
api(Config.Libs.Androidx.pagingRxJava)
annotationProcessor(Config.Libs.Androidx.lifecycleCompiler)

lintChecks(project(":lint"))
Expand Down
Loading