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

Firestore paging adapter #1178

Merged
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
5109b4c
Super basic infinite scroll
samtstern Feb 21, 2018
cbaaca7
Make it kinda actually work
samtstern Mar 1, 2018
8e08336
Actual bidirectional unloading
samtstern Mar 1, 2018
75240b7
Some cleanup
samtstern Mar 1, 2018
4d68a56
Separate demo
samtstern Mar 1, 2018
6af779c
Some cleanup
samtstern Mar 1, 2018
2a5f554
Make lint pass
samtstern Mar 2, 2018
51c0d95
Basic JavaDoc
samtstern Mar 2, 2018
09f6a6d
Break out Page
samtstern Mar 2, 2018
a7153be
Space before a semicolon? That's a paddlin
samtstern Mar 2, 2018
11668e2
Experimenting with the support Paging Library, comitting just for safety
samtstern Mar 19, 2018
db39ddf
Move to alpha6, remove home-grown bits
samtstern Mar 19, 2018
efc0095
Real adapter and options classes
samtstern Mar 19, 2018
45901b7
Delete Page
samtstern Mar 19, 2018
4a684f3
Merge branch 'version-3.3.1-dev' into firestore-infinite-scroll
samtstern Mar 29, 2018
754f945
alpha7 update
samtstern Mar 29, 2018
1ea8834
Expose Loading State
samtstern Mar 29, 2018
eddcbed
Reduce complexity of factory
samtstern Apr 2, 2018
276f677
Make build pass
samtstern Apr 2, 2018
ea8144c
Address nits
samtstern Apr 4, 2018
75e206a
Paging library went to beta
samtstern Apr 11, 2018
cef8528
More feedback
samtstern Apr 11, 2018
154ecde
Move diff callback to options
samtstern Apr 11, 2018
5391e97
Add retry() in the data source
samtstern Apr 11, 2018
c749cf4
Finish retry piping
samtstern Apr 12, 2018
877fb21
Address review feedback
samtstern Apr 13, 2018
202857d
Add some tests
samtstern Apr 13, 2018
85967cb
Add documentation
samtstern Apr 13, 2018
e199385
Merge branch 'version-3.3.1-dev' into firestore-infinite-scroll
samtstern Apr 13, 2018
bf0ac33
Build fix
samtstern Apr 13, 2018
8bede03
Doc updates (review feedback)
samtstern Apr 16, 2018
a081a7f
Handle final page
samtstern Apr 16, 2018
fb64181
Fix typo
samtstern Apr 16, 2018
00a4818
Merge branch 'version-3.4.0-dev' into firestore-infinite-scroll
samtstern Apr 16, 2018
1e02f0e
Typo fixes
samtstern Apr 16, 2018
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
5 changes: 5 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@
android:name=".database.firestore.FirestoreChatActivity"
android:label="@string/title_firestore_activity" />

<!-- Firestore paging demo -->
<activity
android:name=".database.firestore.FirestorePagingActivity"
android:label="@string/title_firestore_paging_activity" />

<!-- Realtime database demo -->
<activity
android:name=".database.realtime.RealtimeDbChatActivity"
Expand Down
4 changes: 4 additions & 0 deletions app/src/main/java/com/firebase/uidemo/ChooserActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@

import com.firebase.uidemo.auth.AuthUiActivity;
import com.firebase.uidemo.database.firestore.FirestoreChatActivity;
import com.firebase.uidemo.database.firestore.FirestorePagingActivity;
import com.firebase.uidemo.database.realtime.RealtimeDbChatActivity;
import com.firebase.uidemo.storage.ImageActivity;

Expand All @@ -53,20 +54,23 @@ private static class ActivityChooserAdapter extends RecyclerView.Adapter<Activit
private static final Class[] CLASSES = new Class[]{
AuthUiActivity.class,
FirestoreChatActivity.class,
FirestorePagingActivity.class,
RealtimeDbChatActivity.class,
ImageActivity.class,
};

private static final int[] DESCRIPTION_NAMES = new int[]{
R.string.title_auth_activity,
R.string.title_firestore_activity,
R.string.title_firestore_paging_activity,
R.string.title_realtime_database_activity,
R.string.title_storage_activity
};

private static final int[] DESCRIPTION_IDS = new int[]{
R.string.desc_auth,
R.string.desc_firestore,
R.string.desc_firestore_paging,
R.string.desc_realtime_database,
R.string.desc_storage
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
package com.firebase.uidemo.database.firestore;

import android.arch.paging.PagedList;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;

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.google.android.gms.tasks.OnCompleteListener;
import com.google.android.gms.tasks.Task;
import com.google.firebase.firestore.CollectionReference;
import com.google.firebase.firestore.FirebaseFirestore;
import com.google.firebase.firestore.Query;
import com.google.firebase.firestore.WriteBatch;

import java.util.Locale;

import butterknife.BindView;
import butterknife.ButterKnife;

public class FirestorePagingActivity extends AppCompatActivity {

private static final String TAG = "FirestorePagingActivity";

@BindView(R.id.paging_recycler)
RecyclerView mRecycler;

@BindView(R.id.paging_loading)
ProgressBar mProgressBar;

private FirebaseFirestore mFirestore;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_firestore_paging);
ButterKnife.bind(this);

mFirestore = FirebaseFirestore.getInstance();
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm wondering what the best practices should be... I think we should store the collection as a field, but use the full FirebaseFirestore.getInstance() for other stuff like the batch. And make it final/inline the assignment? Yay, nay?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Gonna do all of that besides the inline assignment (not a fan of that in classes where you don't have a constructor, like Activity or Fragment)

mProgressBar.setIndeterminate(true);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can we move this to the XML?


setUpAdapter();
}

private void setUpAdapter() {
Query baseQuery = mFirestore.collection("items")
.orderBy("value", Query.Direction.ASCENDING);

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

FirestorePagingOptions<Item> options = new FirestorePagingOptions.Builder<Item>()
.setLifecycleOwner(this)
.setQuery(baseQuery, config, Item.class)
.build();

FirestorePagingAdapter<Item, ItemViewHolder> adapter =
new FirestorePagingAdapter<Item, ItemViewHolder>(options) {
@NonNull
@Override
public ItemViewHolder onCreateViewHolder(@NonNull ViewGroup parent,
int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_item, parent, false);
return new ItemViewHolder(view);
}

@Override
protected void onBindViewHolder(@NonNull ItemViewHolder holder,
int position,
Item model) {
holder.bind(model);
}

@Override
protected void onLoadingStateChanged(@NonNull LoadingState state) {
switch (state) {
case LOADING_INITIAL:
case LOADING_MORE:
mProgressBar.setVisibility(View.VISIBLE);
break;
case LOADED:
mProgressBar.setVisibility(View.GONE);
break;
case ERROR:
showToast("An error occurred.");
break;
}
}
};

mRecycler.setLayoutManager(new LinearLayoutManager(this));
mRecycler.setAdapter(adapter);
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_firestore_paging, menu);
return super.onCreateOptionsMenu(menu);
Copy link
Collaborator

Choose a reason for hiding this comment

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

We should return true here to ensure the menu is displayed.

}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == R.id.item_add_data) {
showToast("Adding data...");
createItems().addOnCompleteListener(this, new OnCompleteListener<Void>() {
@Override
public void onComplete(@NonNull Task<Void> task) {
if (task.isSuccessful()) {
showToast("Data added.");
} else {
Log.w(TAG, "addData", task.getException());
showToast("Error adding data.");
}
}
});

return true;
}
return super.onOptionsItemSelected(item);
}

private Task<Void> createItems() {

WriteBatch writeBatch = mFirestore.batch();
CollectionReference collRef = mFirestore.collection("items");

for (int i = 0; i < 500; i++) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Isn't 500 a bit excessive for a page size of 20? Maybe like 250 or something?

Also, while I've snagged your attention, the pricing for write batches really isn't clear in the docs (no mention, actually). You're still paying for 500 additions, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Re: pricing yeah they are priced per-write.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Re: re: pricing thanks! 😁

String title = "Item " + i;

String id = String.format(Locale.getDefault(), "item_%03d", i);
Item item = new Item(title, i);

writeBatch.set(collRef.document(id), item);
}

return writeBatch.commit();
}

private void showToast(String message) {
Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
}

public static class Item {

public String text;
public int value;

public Item() {}

public Item(String text, int value) {
this.text = text;
this.value = value;
}

}

public static class ItemViewHolder extends RecyclerView.ViewHolder {

@BindView(R.id.item_text)
TextView mTextView;

@BindView(R.id.item_value)
TextView mValueView;

ItemViewHolder(View itemView) {
super(itemView);
ButterKnife.bind(this, itemView);
}

void bind(Item item) {
mTextView.setText(item.text);
mValueView.setText(String.valueOf(item.value));
}
}

}
29 changes: 29 additions & 0 deletions app/src/main/res/layout/activity_firestore_paging.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
Copy link
Collaborator

Choose a reason for hiding this comment

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

Awwww, relative layout? 😞 I like me some constraints! 😂

Copy link
Contributor Author

Choose a reason for hiding this comment

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

app module doesn't currently have any ConstraintLayouts, didn't feel like introducing it for this simple layout.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Well technically, auth already includes it so it wouldn't hurt... I guess if you don't want to that's fine—I love CL though so I gotta put in my plug whenever I can. 😆

xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.firebase.uidemo.database.firestore.FirestorePagingActivity">

<ProgressBar
android:id="@+id/paging_loading"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="-6dp"
android:background="@android:color/transparent"
tools:ignore="NegativeMargin" />

<android.support.v7.widget.RecyclerView
android:id="@+id/paging_recycler"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/paging_loading"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:paddingTop="8dp"
android:clipToPadding="false"
tools:listitem="@layout/item_item" />

</RelativeLayout>
32 changes: 32 additions & 0 deletions app/src/main/res/layout/item_item.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:paddingTop="8dp"
android:paddingBottom="8dp"
android:background="@drawable/ic_chat_message_background"
android:orientation="vertical">

<TextView
android:id="@+id/item_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textIsSelectable="false"
android:textSize="16sp"
android:textStyle="bold"
tools:text="My Text" />

<TextView
android:id="@+id/item_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textIsSelectable="false"
android:textSize="12sp"
tools:text="1234" />

</LinearLayout>
11 changes: 11 additions & 0 deletions app/src/main/res/menu/menu_firestore_paging.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<menu
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">

<item
android:id="@+id/item_add_data"
android:title="@string/menu_add_data"
app:showAsAction="never" />

</menu>
4 changes: 4 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@
<!-- Chooser -->
<string name="title_auth_activity">Auth UI demo</string>
<string name="title_firestore_activity">Cloud Firestore Demo</string>
<string name="title_firestore_paging_activity">Cloud Firestore Paging Demo</string>
<string name="title_realtime_database_activity">Real-time database demo</string>
<string name="title_storage_activity">Storage Image Demo</string>

<string name="desc_auth">Demonstrates the Firebase Auth UI flow, with customization options.</string>
<string name="desc_firestore">Demonstrates using a FirestoreRecyclerAdapter to load data from Cloud Firestore into a RecyclerView for a basic chat app.</string>
<string name="desc_firestore_paging">Demonstrates using a FirestorePagingAdapter to load/infinite scroll paging data from Cloud Firestore.</string>
Copy link
Collaborator

Choose a reason for hiding this comment

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

... paging ... -> ... paged ...?

<string name="desc_realtime_database">Demonstrates using a FirebaseRecyclerAdapter to load data from Firebase Database into a RecyclerView for a basic chat app.</string>
<string name="desc_storage">Demonstrates displaying an image from Cloud Storage using Glide.</string>

Expand Down Expand Up @@ -97,5 +99,7 @@
Make sure your device is online and that Anonymous Auth is configured in your Firebase project
(https://console.firebase.google.com/project/_/authentication/providers)
</string>

<string name="menu_add_data">Add Data</string>
<string name="hint_message">Say something…</string>
</resources>
2 changes: 1 addition & 1 deletion constants.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@ project.ext {

firebaseVersion = '12.0.1'
supportLibraryVersion = '27.1.0'
architectureVersion = '1.1.0'
architectureVersion = '1.1.1'
kotlinVersion = '1.2.30'
}
2 changes: 2 additions & 0 deletions firestore/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ dependencies {
api "com.android.support:recyclerview-v7:$supportLibraryVersion"
annotationProcessor "android.arch.lifecycle:compiler:$architectureVersion"

api "android.arch.paging:runtime:1.0.0-beta1"
Copy link
Collaborator

Choose a reason for hiding this comment

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

We decided compileOnly, right?


androidTestImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.1'
androidTestImplementation 'com.android.support.test:rules:1.0.1'
Expand Down
Loading