Skip to content

Commit 0f3e51e

Browse files
authored
Firestore paging adapter (#1178)
2 parents 6f7780d + 1e02f0e commit 0f3e51e

File tree

20 files changed

+1256
-10
lines changed

20 files changed

+1256
-10
lines changed

app/build.gradle

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@ dependencies {
5454
implementation 'com.github.bumptech.glide:glide:4.6.1'
5555
annotationProcessor 'com.github.bumptech.glide:compiler:4.6.1'
5656

57+
// Used for FirestorePagingActivity
58+
implementation "android.arch.paging:runtime:$pagingVersion"
59+
5760
// The following dependencies are not required to use the Firebase UI library.
5861
// They are used to make some aspects of the demo app implementation simpler for
5962
// demonstrative purposes, and you may find them useful in your own apps; YMMV.

app/src/main/AndroidManifest.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,11 @@
4343
android:name=".database.firestore.FirestoreChatActivity"
4444
android:label="@string/title_firestore_activity" />
4545

46+
<!-- Firestore paging demo -->
47+
<activity
48+
android:name=".database.firestore.FirestorePagingActivity"
49+
android:label="@string/title_firestore_paging_activity" />
50+
4651
<!-- Realtime database demo -->
4752
<activity
4853
android:name=".database.realtime.RealtimeDbChatActivity"

app/src/main/java/com/firebase/uidemo/ChooserActivity.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828

2929
import com.firebase.uidemo.auth.AuthUiActivity;
3030
import com.firebase.uidemo.database.firestore.FirestoreChatActivity;
31+
import com.firebase.uidemo.database.firestore.FirestorePagingActivity;
3132
import com.firebase.uidemo.database.realtime.RealtimeDbChatActivity;
3233
import com.firebase.uidemo.storage.ImageActivity;
3334

@@ -53,20 +54,23 @@ private static class ActivityChooserAdapter extends RecyclerView.Adapter<Activit
5354
private static final Class[] CLASSES = new Class[]{
5455
AuthUiActivity.class,
5556
FirestoreChatActivity.class,
57+
FirestorePagingActivity.class,
5658
RealtimeDbChatActivity.class,
5759
ImageActivity.class,
5860
};
5961

6062
private static final int[] DESCRIPTION_NAMES = new int[]{
6163
R.string.title_auth_activity,
6264
R.string.title_firestore_activity,
65+
R.string.title_firestore_paging_activity,
6366
R.string.title_realtime_database_activity,
6467
R.string.title_storage_activity
6568
};
6669

6770
private static final int[] DESCRIPTION_IDS = new int[]{
6871
R.string.desc_auth,
6972
R.string.desc_firestore,
73+
R.string.desc_firestore_paging,
7074
R.string.desc_realtime_database,
7175
R.string.desc_storage
7276
};
Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
package com.firebase.uidemo.database.firestore;
2+
3+
import android.arch.paging.PagedList;
4+
import android.os.Bundle;
5+
import android.support.annotation.NonNull;
6+
import android.support.v7.app.AppCompatActivity;
7+
import android.support.v7.widget.LinearLayoutManager;
8+
import android.support.v7.widget.RecyclerView;
9+
import android.util.Log;
10+
import android.view.LayoutInflater;
11+
import android.view.Menu;
12+
import android.view.MenuItem;
13+
import android.view.View;
14+
import android.view.ViewGroup;
15+
import android.widget.ProgressBar;
16+
import android.widget.TextView;
17+
import android.widget.Toast;
18+
19+
import com.firebase.ui.firestore.paging.FirestorePagingAdapter;
20+
import com.firebase.ui.firestore.paging.FirestorePagingOptions;
21+
import com.firebase.ui.firestore.paging.LoadingState;
22+
import com.firebase.uidemo.R;
23+
import com.google.android.gms.tasks.OnCompleteListener;
24+
import com.google.android.gms.tasks.Task;
25+
import com.google.firebase.firestore.CollectionReference;
26+
import com.google.firebase.firestore.FirebaseFirestore;
27+
import com.google.firebase.firestore.Query;
28+
import com.google.firebase.firestore.WriteBatch;
29+
30+
import java.util.Locale;
31+
32+
import butterknife.BindView;
33+
import butterknife.ButterKnife;
34+
35+
public class FirestorePagingActivity extends AppCompatActivity {
36+
37+
private static final String TAG = "FirestorePagingActivity";
38+
39+
@BindView(R.id.paging_recycler)
40+
RecyclerView mRecycler;
41+
42+
@BindView(R.id.paging_loading)
43+
ProgressBar mProgressBar;
44+
45+
private FirebaseFirestore mFirestore;
46+
private CollectionReference mItemsCollection;
47+
48+
@Override
49+
protected void onCreate(Bundle savedInstanceState) {
50+
super.onCreate(savedInstanceState);
51+
setContentView(R.layout.activity_firestore_paging);
52+
ButterKnife.bind(this);
53+
54+
mFirestore = FirebaseFirestore.getInstance();
55+
mItemsCollection = mFirestore.collection("items");
56+
57+
setUpAdapter();
58+
}
59+
60+
private void setUpAdapter() {
61+
Query baseQuery = mItemsCollection.orderBy("value", Query.Direction.ASCENDING);
62+
63+
PagedList.Config config = new PagedList.Config.Builder()
64+
.setEnablePlaceholders(false)
65+
.setPrefetchDistance(10)
66+
.setPageSize(20)
67+
.build();
68+
69+
FirestorePagingOptions<Item> options = new FirestorePagingOptions.Builder<Item>()
70+
.setLifecycleOwner(this)
71+
.setQuery(baseQuery, config, Item.class)
72+
.build();
73+
74+
FirestorePagingAdapter<Item, ItemViewHolder> adapter =
75+
new FirestorePagingAdapter<Item, ItemViewHolder>(options) {
76+
@NonNull
77+
@Override
78+
public ItemViewHolder onCreateViewHolder(@NonNull ViewGroup parent,
79+
int viewType) {
80+
View view = LayoutInflater.from(parent.getContext())
81+
.inflate(R.layout.item_item, parent, false);
82+
return new ItemViewHolder(view);
83+
}
84+
85+
@Override
86+
protected void onBindViewHolder(@NonNull ItemViewHolder holder,
87+
int position,
88+
Item model) {
89+
holder.bind(model);
90+
}
91+
92+
@Override
93+
protected void onLoadingStateChanged(@NonNull LoadingState state) {
94+
switch (state) {
95+
case LOADING_INITIAL:
96+
case LOADING_MORE:
97+
mProgressBar.setVisibility(View.VISIBLE);
98+
break;
99+
case LOADED:
100+
mProgressBar.setVisibility(View.GONE);
101+
break;
102+
case FINISHED:
103+
mProgressBar.setVisibility(View.GONE);
104+
showToast("Reached end of data set.");
105+
break;
106+
case ERROR:
107+
showToast("An error occurred.");
108+
retry();
109+
break;
110+
}
111+
}
112+
};
113+
114+
mRecycler.setLayoutManager(new LinearLayoutManager(this));
115+
mRecycler.setAdapter(adapter);
116+
}
117+
118+
@Override
119+
public boolean onCreateOptionsMenu(Menu menu) {
120+
getMenuInflater().inflate(R.menu.menu_firestore_paging, menu);
121+
return true;
122+
}
123+
124+
@Override
125+
public boolean onOptionsItemSelected(MenuItem item) {
126+
if (item.getItemId() == R.id.item_add_data) {
127+
showToast("Adding data...");
128+
createItems().addOnCompleteListener(this, new OnCompleteListener<Void>() {
129+
@Override
130+
public void onComplete(@NonNull Task<Void> task) {
131+
if (task.isSuccessful()) {
132+
showToast("Data added.");
133+
} else {
134+
Log.w(TAG, "addData", task.getException());
135+
showToast("Error adding data.");
136+
}
137+
}
138+
});
139+
140+
return true;
141+
}
142+
return super.onOptionsItemSelected(item);
143+
}
144+
145+
private Task<Void> createItems() {
146+
WriteBatch writeBatch = mFirestore.batch();
147+
148+
for (int i = 0; i < 250; i++) {
149+
String title = "Item " + i;
150+
151+
String id = String.format(Locale.getDefault(), "item_%03d", i);
152+
Item item = new Item(title, i);
153+
154+
writeBatch.set(mItemsCollection.document(id), item);
155+
}
156+
157+
return writeBatch.commit();
158+
}
159+
160+
private void showToast(String message) {
161+
Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
162+
}
163+
164+
public static class Item {
165+
166+
public String text;
167+
public int value;
168+
169+
public Item() {}
170+
171+
public Item(String text, int value) {
172+
this.text = text;
173+
this.value = value;
174+
}
175+
176+
}
177+
178+
public static class ItemViewHolder extends RecyclerView.ViewHolder {
179+
180+
@BindView(R.id.item_text)
181+
TextView mTextView;
182+
183+
@BindView(R.id.item_value)
184+
TextView mValueView;
185+
186+
ItemViewHolder(View itemView) {
187+
super(itemView);
188+
ButterKnife.bind(this, itemView);
189+
}
190+
191+
void bind(Item item) {
192+
mTextView.setText(item.text);
193+
mValueView.setText(String.valueOf(item.value));
194+
}
195+
}
196+
197+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<RelativeLayout
3+
xmlns:android="http://schemas.android.com/apk/res/android"
4+
xmlns:tools="http://schemas.android.com/tools"
5+
android:layout_width="match_parent"
6+
android:layout_height="match_parent"
7+
tools:context="com.firebase.uidemo.database.firestore.FirestorePagingActivity">
8+
9+
<ProgressBar
10+
android:id="@+id/paging_loading"
11+
style="?android:attr/progressBarStyleHorizontal"
12+
android:layout_width="match_parent"
13+
android:layout_height="wrap_content"
14+
android:layout_marginTop="-6dp"
15+
android:background="@android:color/transparent"
16+
android:indeterminate="true"
17+
tools:ignore="NegativeMargin" />
18+
19+
<android.support.v7.widget.RecyclerView
20+
android:id="@+id/paging_recycler"
21+
android:layout_width="match_parent"
22+
android:layout_height="wrap_content"
23+
android:layout_below="@+id/paging_loading"
24+
android:paddingLeft="16dp"
25+
android:paddingRight="16dp"
26+
android:paddingTop="8dp"
27+
android:clipToPadding="false"
28+
tools:listitem="@layout/item_item" />
29+
30+
</RelativeLayout>

app/src/main/res/layout/item_item.xml

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<LinearLayout
3+
xmlns:android="http://schemas.android.com/apk/res/android"
4+
xmlns:tools="http://schemas.android.com/tools"
5+
android:layout_width="match_parent"
6+
android:layout_height="wrap_content"
7+
android:layout_marginBottom="8dp"
8+
android:paddingLeft="16dp"
9+
android:paddingRight="16dp"
10+
android:paddingTop="8dp"
11+
android:paddingBottom="8dp"
12+
android:background="@drawable/ic_chat_message_background"
13+
android:orientation="vertical">
14+
15+
<TextView
16+
android:id="@+id/item_text"
17+
android:layout_width="wrap_content"
18+
android:layout_height="wrap_content"
19+
android:textIsSelectable="false"
20+
android:textSize="16sp"
21+
android:textStyle="bold"
22+
tools:text="My Text" />
23+
24+
<TextView
25+
android:id="@+id/item_value"
26+
android:layout_width="wrap_content"
27+
android:layout_height="wrap_content"
28+
android:textIsSelectable="false"
29+
android:textSize="12sp"
30+
tools:text="1234" />
31+
32+
</LinearLayout>
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<menu
3+
xmlns:android="http://schemas.android.com/apk/res/android"
4+
xmlns:app="http://schemas.android.com/apk/res-auto">
5+
6+
<item
7+
android:id="@+id/item_add_data"
8+
android:title="@string/menu_add_data"
9+
app:showAsAction="never" />
10+
11+
</menu>

app/src/main/res/values/strings.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@
44
<!-- Chooser -->
55
<string name="title_auth_activity">Auth UI demo</string>
66
<string name="title_firestore_activity">Cloud Firestore Demo</string>
7+
<string name="title_firestore_paging_activity">Cloud Firestore Paging Demo</string>
78
<string name="title_realtime_database_activity">Real-time database demo</string>
89
<string name="title_storage_activity">Storage Image Demo</string>
910

1011
<string name="desc_auth">Demonstrates the Firebase Auth UI flow, with customization options.</string>
1112
<string name="desc_firestore">Demonstrates using a FirestoreRecyclerAdapter to load data from Cloud Firestore into a RecyclerView for a basic chat app.</string>
13+
<string name="desc_firestore_paging">Demonstrates using a FirestorePagingAdapter to load/infinite scroll paged data from Cloud Firestore.</string>
1214
<string name="desc_realtime_database">Demonstrates using a FirebaseRecyclerAdapter to load data from Firebase Database into a RecyclerView for a basic chat app.</string>
1315
<string name="desc_storage">Demonstrates displaying an image from Cloud Storage using Glide.</string>
1416

@@ -97,5 +99,7 @@
9799
Make sure your device is online and that Anonymous Auth is configured in your Firebase project
98100
(https://console.firebase.google.com/project/_/authentication/providers)
99101
</string>
102+
103+
<string name="menu_add_data">Add Data</string>
100104
<string name="hint_message">Say something…</string>
101105
</resources>

constants.gradle

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ project.ext {
1010

1111
firebaseVersion = '15.0.0'
1212
supportLibraryVersion = '27.1.0'
13-
architectureVersion = '1.1.0'
13+
architectureVersion = '1.1.1'
1414
kotlinVersion = '1.2.30'
15+
pagingVersion = '1.0.0-beta1'
1516
}

0 commit comments

Comments
 (0)