-
Notifications
You must be signed in to change notification settings - Fork 1.8k
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
Firestore paging adapter #1178
Changes from 25 commits
5109b4c
cbaaca7
8e08336
75240b7
4d68a56
6af779c
2a5f554
51c0d95
09f6a6d
a7153be
11668e2
db39ddf
efc0095
45901b7
4a684f3
754f945
1ea8834
eddcbed
276f677
ea8144c
75e206a
cef8528
154ecde
5391e97
c749cf4
877fb21
202857d
85967cb
e199385
bf0ac33
8bede03
a081a7f
fb64181
00a4818
1e02f0e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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(); | ||
mProgressBar.setIndeterminate(true); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should return |
||
} | ||
|
||
@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++) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Re: pricing yeah they are priced per-write. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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)); | ||
} | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
<?xml version="1.0" encoding="utf-8"?> | ||
<RelativeLayout | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Awwww, relative layout? 😞 I like me some constraints! 😂 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Well technically, |
||
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> |
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> |
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> |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
<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> | ||
|
||
|
@@ -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> |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We decided |
||
|
||
androidTestImplementation 'junit:junit:4.12' | ||
androidTestImplementation 'com.android.support.test:runner:1.0.1' | ||
androidTestImplementation 'com.android.support.test:rules:1.0.1' | ||
|
There was a problem hiding this comment.
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?There was a problem hiding this comment.
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
orFragment
)