-
-
Save Kishanjvaghela/67c42f8f32efaa2fadb682bc980e9280 to your computer and use it in GitHub Desktop.
import android.databinding.DataBindingUtil; | |
import android.support.v7.widget.RecyclerView; | |
import android.view.LayoutInflater; | |
import android.view.View; | |
import android.view.ViewGroup; | |
import com.app.wanna.android.R; | |
import com.app.wanna.android.data.User; | |
import com.app.wanna.android.databinding.LayoutItemPeopleBinding; | |
import com.app.wanna.android.utils.firebaseadapter.FirebaseRecyclerAdapter; | |
import com.firebase.ui.common.ChangeEventType; | |
import com.firebase.ui.database.FirebaseRecyclerOptions; | |
import com.google.firebase.database.DataSnapshot; | |
import com.squareup.picasso.Picasso; | |
public class ExamplePeopleAdapter extends FirebaseRecyclerAdapter<User, PeopleListAdapter.PeopleViewHolder> { | |
private RecycleItemClick recycleItemClick; | |
private static final String TAG = "PeopleListAdapter"; | |
public ExamplePeopleAdapter(FirebaseRecyclerOptions<User> options) { | |
super(options, true); | |
} | |
public interface RecycleItemClick { | |
void onItemClick(String userId, User user, int position); | |
} | |
public void setRecycleItemClick(RecycleItemClick recycleItemClick) { | |
this.recycleItemClick = recycleItemClick; | |
} | |
@Override | |
public PeopleViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { | |
View view = LayoutInflater.from(parent.getContext()) | |
.inflate(R.layout.layout_item_people, parent, false); | |
return new PeopleViewHolder(view); | |
} | |
@Override | |
protected void onBindViewHolder(PeopleViewHolder holder, int position, User model) { | |
holder.bind(model); | |
} | |
@Override | |
protected void onChildUpdate(User model, | |
ChangeEventType type, | |
DataSnapshot snapshot, | |
int newIndex, | |
int oldIndex) { | |
model.setUserId(snapshot.getKey()); | |
super.onChildUpdate(model, type, snapshot, newIndex, oldIndex); | |
} | |
@Override | |
protected boolean filterCondition(User model, String filterPattern) { | |
return model.getFirstName().toLowerCase().contains(filterPattern) || | |
model.getLastName().toLowerCase().contains(filterPattern); | |
} | |
public class PeopleViewHolder extends RecyclerView.ViewHolder { | |
LayoutItemPeopleBinding mBinding; | |
PeopleViewHolder(View view) { | |
super(view); | |
mBinding = DataBindingUtil.bind(view); | |
} | |
public void bind(User user) { | |
Picasso.with(mBinding.peopleImage.getContext()) | |
.load(user.getImage()) | |
.placeholder(R.drawable.place_holder_user) | |
.into(mBinding.peopleImage); | |
mBinding.peopleName.setText(String.format("%s %s", user.getFirstName(), user.getLastName())); | |
itemView.setOnClickListener(new View.OnClickListener() { | |
@Override | |
public void onClick(View v) { | |
int pos = getAdapterPosition(); | |
User user = getItem(pos); | |
recycleItemClick.onItemClick(user.getUserId(), user, pos); | |
} | |
}); | |
} | |
} | |
} |
import android.arch.lifecycle.LifecycleObserver; | |
import android.support.annotation.RestrictTo; | |
import com.firebase.ui.database.ChangeEventListener; | |
import com.firebase.ui.database.FirebaseArray; | |
import com.firebase.ui.database.ObservableSnapshotArray; | |
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) | |
interface FirebaseAdapter<T> extends ChangeEventListener, LifecycleObserver { | |
/** | |
* If you need to do some setup before the adapter starts listening for change events in the | |
* database, do so it here and then call {@code super.startListening()}. | |
*/ | |
void startListening(); | |
/** | |
* Removes listeners and clears all items in the backing {@link FirebaseArray}. | |
*/ | |
void stopListening(); | |
ObservableSnapshotArray<T> getSnapshots(); | |
T getItem(int position); | |
} |
import android.arch.lifecycle.Lifecycle; | |
import android.arch.lifecycle.LifecycleOwner; | |
import android.arch.lifecycle.OnLifecycleEvent; | |
import android.support.v7.widget.RecyclerView; | |
import android.util.Log; | |
import android.widget.Filter; | |
import android.widget.Filterable; | |
import com.firebase.ui.common.ChangeEventType; | |
import com.firebase.ui.database.FirebaseRecyclerOptions; | |
import com.firebase.ui.database.ObservableSnapshotArray; | |
import com.google.firebase.database.DataSnapshot; | |
import com.google.firebase.database.DatabaseError; | |
import java.util.ArrayList; | |
import java.util.Collection; | |
import java.util.List; | |
/** | |
* This class is a generic way of backing a {@link RecyclerView} with a Firebase location. It | |
* handles all of the child events at the given Firebase location and marshals received data into | |
* the given class type. | |
* <p> | |
* See the <a href="https://github.com/firebase/FirebaseUI-Android/blob/master/database/README.md">README</a> | |
* for an in-depth tutorial on how to set up the FirebaseRecyclerAdapter. | |
* | |
* @param <T> The Java class that maps to the type of objects stored in the Firebase location. | |
* @param <VH> The {@link RecyclerView.ViewHolder} class that contains the Views in the layout that | |
* is shown for each object. | |
*/ | |
public abstract class FirebaseRecyclerAdapter<T, VH extends RecyclerView.ViewHolder> | |
extends RecyclerView.Adapter<VH> implements FirebaseAdapter<T>, Filterable { | |
private static final String TAG = "FirebaseRecyclerAdapter"; | |
private final ObservableSnapshotArray<T> mSnapshots; | |
private final List<T> list, backupList; | |
private CustomFilter mCustomFilter; | |
private boolean isFiltarable; | |
/** | |
* Initialize a {@link RecyclerView.Adapter} that listens to a Firebase query. See | |
* {@link FirebaseRecyclerOptions} for configuration options. | |
*/ | |
public FirebaseRecyclerAdapter(FirebaseRecyclerOptions<T> options, boolean isFiltarable) { | |
mSnapshots = options.getSnapshots(); | |
list = new ArrayList<>(); | |
backupList = new ArrayList<>(); | |
if (options.getOwner() != null) { | |
options.getOwner().getLifecycle().addObserver(this); | |
} | |
this.isFiltarable = isFiltarable; | |
} | |
@OnLifecycleEvent(Lifecycle.Event.ON_START) | |
public void startListening() { | |
if (!mSnapshots.isListening(this)) { | |
mSnapshots.addChangeEventListener(this); | |
} | |
} | |
@OnLifecycleEvent(Lifecycle.Event.ON_STOP) | |
public void stopListening() { | |
mSnapshots.removeChangeEventListener(this); | |
notifyDataSetChanged(); | |
} | |
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) | |
void cleanup(LifecycleOwner source) { | |
source.getLifecycle().removeObserver(this); | |
} | |
@Override | |
public void onChildChanged(ChangeEventType type, | |
DataSnapshot snapshot, | |
int newIndex, | |
int oldIndex) { | |
T model = mSnapshots.get(newIndex); | |
onChildUpdate(model, type, snapshot, newIndex, oldIndex); | |
} | |
protected void onChildUpdate(T model, ChangeEventType type, | |
DataSnapshot snapshot, | |
int newIndex, | |
int oldIndex) { | |
switch (type) { | |
case ADDED: | |
addItem(snapshot.getKey(), model); | |
notifyItemInserted(newIndex); | |
break; | |
case CHANGED: | |
addItem(snapshot.getKey(), model, newIndex); | |
notifyItemChanged(newIndex); | |
break; | |
case REMOVED: | |
removeItem(newIndex); | |
notifyItemRemoved(newIndex); | |
break; | |
case MOVED: | |
moveItem(snapshot.getKey(), model, newIndex, oldIndex); | |
notifyItemMoved(oldIndex, newIndex); | |
break; | |
default: | |
throw new IllegalStateException("Incomplete case statement"); | |
} | |
} | |
private void moveItem(String key, T t, int newIndex, int oldIndex) { | |
list.remove(oldIndex); | |
list.add(newIndex, t); | |
if (isFiltarable) { | |
backupList.remove(oldIndex); | |
backupList.add(newIndex, t); | |
} | |
} | |
private void removeItem(int newIndex) { | |
list.remove(newIndex); | |
if (isFiltarable) | |
backupList.remove(newIndex); | |
} | |
private void addItem(String key, T t, int newIndex) { | |
list.remove(newIndex); | |
list.add(newIndex, t); | |
if (isFiltarable) { | |
backupList.remove(newIndex); | |
backupList.add(newIndex, t); | |
} | |
} | |
private void addItem(String id, T t) { | |
list.add(t); | |
if (isFiltarable) | |
backupList.add(t); | |
} | |
@Override | |
public void onDataChanged() { | |
} | |
@Override | |
public void onError(DatabaseError error) { | |
Log.w(TAG, error.toException()); | |
} | |
@Override | |
public ObservableSnapshotArray<T> getSnapshots() { | |
return mSnapshots; | |
} | |
@Override | |
public T getItem(int position) { | |
return list.get(position); | |
} | |
@Override | |
public int getItemCount() { | |
return list.size(); | |
} | |
@Override | |
public void onBindViewHolder(VH holder, int position) { | |
onBindViewHolder(holder, position, getItem(position)); | |
} | |
/** | |
* @param model the model object containing the data that should be used to populate the view. | |
* @see #onBindViewHolder(RecyclerView.ViewHolder, int) | |
*/ | |
protected abstract void onBindViewHolder(VH holder, int position, T model); | |
/** | |
* filter condition for Filter | |
* | |
* @param model model T | |
* @param filterPattern filter pattern with Lower Case | |
*/ | |
protected boolean filterCondition(T model, String filterPattern) { | |
return true; | |
} | |
@Override | |
public Filter getFilter() { | |
if (mCustomFilter == null) { | |
mCustomFilter = new CustomFilter(); | |
} | |
return mCustomFilter; | |
} | |
public class CustomFilter extends Filter { | |
@Override | |
protected FilterResults performFiltering(CharSequence constraint) { | |
final FilterResults results = new FilterResults(); | |
if (constraint.length() == 0) { | |
results.values = backupList; | |
results.count = backupList.size(); | |
} else { | |
List<T> filteredList = new ArrayList<>(); | |
final String filterPattern = constraint.toString().toLowerCase().trim(); | |
for (T t : backupList) { | |
if (filterCondition(t, filterPattern)) { | |
filteredList.add(t); | |
} | |
} | |
results.values = filteredList; | |
results.count = filteredList.size(); | |
} | |
return results; | |
} | |
@Override | |
protected void publishResults(CharSequence constraint, FilterResults results) { | |
list.clear(); | |
list.addAll((Collection<? extends T>) results.values); | |
notifyDataSetChanged(); | |
} | |
} | |
} |
Thank you a lot for this
May Allah give you Happiness, for this Kind work!
Is this applicable to FirestoreRecyclerAdapter as well ? thanks
Really nice job, thank you, you saved me alot of time.
Is this applicable to FirestoreRecyclerAdapter as well ? thanks
Here's the Firestore adapter:
import android.arch.lifecycle.Lifecycle;
import android.arch.lifecycle.LifecycleObserver;
import android.arch.lifecycle.LifecycleOwner;
import android.arch.lifecycle.OnLifecycleEvent;
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.widget.Filter;
import android.widget.Filterable;
import com.firebase.ui.common.ChangeEventType;
import com.firebase.ui.firestore.ChangeEventListener;
import com.firebase.ui.firestore.FirestoreRecyclerOptions;
import com.firebase.ui.firestore.ObservableSnapshotArray;
import com.google.firebase.firestore.DocumentSnapshot;
import com.google.firebase.firestore.FirebaseFirestoreException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* @param <T> model class, for parsing {@link DocumentSnapshot}s.
* @param <VH> {@link RecyclerView.ViewHolder} class.
*/
public abstract class FilterableFirestoreRecyclerAdapter<T, VH extends RecyclerView.ViewHolder>
extends RecyclerView.Adapter<VH>
implements ChangeEventListener, LifecycleObserver, Filterable {
private static final String TAG = "FirestoreRecycler";
private final ObservableSnapshotArray<T> mSnapshots;
private final List<T> list, backupList;
private CustomFilter mCustomFilter;
private boolean isFiltarable;
/**
* Create a new RecyclerView adapter that listens to a Firestore Query. See {@link
* FirestoreRecyclerOptions} for configuration options.
*/
public FilterableFirestoreRecyclerAdapter(@NonNull FirestoreRecyclerOptions<T> options, boolean isFiltarable) {
mSnapshots = options.getSnapshots();
list = new ArrayList<>();
backupList = new ArrayList<>();
if (options.getOwner() != null) {
options.getOwner().getLifecycle().addObserver(this);
}
this.isFiltarable = isFiltarable;
}
/**
* Start listening for database changes and populate the adapter.
*/
@OnLifecycleEvent(Lifecycle.Event.ON_START)
public void startListening() {
if (!mSnapshots.isListening(this)) {
mSnapshots.addChangeEventListener(this);
}
}
/**
* Stop listening for database changes and clear all items in the adapter.
*/
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
public void stopListening() {
mSnapshots.removeChangeEventListener(this);
notifyDataSetChanged();
}
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
void cleanup(LifecycleOwner source) {
source.getLifecycle().removeObserver(this);
}
/**
* Returns the backing {@link ObservableSnapshotArray} used to populate this adapter.
*
* @return the backing snapshot array
*/
@NonNull
public ObservableSnapshotArray<T> getSnapshots() {
return mSnapshots;
}
/**
* Gets the item at the specified position from the backing snapshot array.
*
* @see ObservableSnapshotArray#get(int)
*/
@NonNull
public T getItem(int position) {
return list.get(position);
}
@Override
public int getItemCount() {
return list.size();
}
@Override
public void onChildChanged(@NonNull ChangeEventType type,
@NonNull DocumentSnapshot snapshot,
int newIndex,
int oldIndex) {
T model = mSnapshots.get(newIndex);
onChildUpdate(model, type, snapshot, newIndex, oldIndex);
}
protected void onChildUpdate(T model, ChangeEventType type,
DocumentSnapshot snapshot,
int newIndex,
int oldIndex) {
switch (type) {
case ADDED:
addItem(snapshot.getId(), model);
notifyItemInserted(newIndex);
break;
case CHANGED:
addItem(snapshot.getId(), model, newIndex);
notifyItemChanged(newIndex);
break;
case REMOVED:
removeItem(newIndex);
notifyItemRemoved(newIndex);
break;
case MOVED:
moveItem(snapshot.getId(), model, newIndex, oldIndex);
notifyItemMoved(oldIndex, newIndex);
break;
default:
throw new IllegalStateException("Incomplete case statement");
}
}
private void moveItem(String key, T t, int newIndex, int oldIndex) {
list.remove(oldIndex);
list.add(newIndex, t);
if (isFiltarable) {
backupList.remove(oldIndex);
backupList.add(newIndex, t);
}
}
private void removeItem(int newIndex) {
list.remove(newIndex);
if (isFiltarable)
backupList.remove(newIndex);
}
private void addItem(String key, T t, int newIndex) {
list.remove(newIndex);
list.add(newIndex, t);
if (isFiltarable) {
backupList.remove(newIndex);
backupList.add(newIndex, t);
}
}
private void addItem(String id, T t) {
list.add(t);
if (isFiltarable)
backupList.add(t);
}
@Override
public void onDataChanged() {
}
@Override
public void onError(@NonNull FirebaseFirestoreException e) {
Log.w(TAG, "onError", e);
}
@Override
public void onBindViewHolder(@NonNull VH holder, int position) {
onBindViewHolder(holder, position, getItem(position));
}
/**
* @param model the model object containing the data that should be used to populate the view.
* @see #onBindViewHolder(RecyclerView.ViewHolder, int)
*/
protected abstract void onBindViewHolder(@NonNull VH holder, int position, @NonNull T model);
/**
* filter condition for Filter
*
* @param model model T
* @param filterPattern filter pattern with Lower Case
*/
protected boolean filterCondition(T model, String filterPattern) {
return true;
}
@Override
public Filter getFilter() {
if (mCustomFilter == null) {
mCustomFilter = new CustomFilter();
}
return mCustomFilter;
}
public class CustomFilter extends Filter {
@Override
protected FilterResults performFiltering(CharSequence constraint) {
final FilterResults results = new FilterResults();
if (constraint.length() == 0) {
results.values = backupList;
results.count = backupList.size();
} else {
List<T> filteredList = new ArrayList<>();
final String filterPattern = constraint.toString().toLowerCase().trim();
for (T t : backupList) {
if (filterCondition(t, filterPattern)) {
filteredList.add(t);
}
}
results.values = filteredList;
results.count = filteredList.size();
}
return results;
}
@Override
protected void publishResults(CharSequence constraint, FilterResults results) {
list.clear();
list.addAll((Collection<? extends T>) results.values);
notifyDataSetChanged();
}
}
}
Where do I have to call "peopleListAdapter.getFilter().filter(newText);" method?
@mattherbert1 in your Activity
ADDED
on onChildUpdate
always called while back from another activities, making duplicate whole existing data.
Making duplicate whole existing data.
i did everything but my recycleradapter is not filtrable
no idea why if you can help me cause im working on this of four days without solution
Can you please share your code? or error log if you have
Please read Docs above
Pass
true
asisFiltarable
in adapter's constructer to support filter in adapter
FirebaseRecyclerAdapter(FirebaseRecyclerOptions<T> options, boolean isFiltarable)
I think its done here
public ExamplePeopleAdapter(FirebaseRecyclerOptions options) {
super(options, true);
}
sorry i didnt get it, i still didnt find the solution can you pleaz help me
Let's talk here. https://gitter.im/FirebaseRecyclerAdapter/community
This comments section is not preferable for long chats.
Thank you very match. You saved me. FilterableFirestoreRecyclerAdapter works fine for me
Please sir, when I uses FilterableFirestoreRecyclerAdapter, there is a bug, when a doument is removed from collection.
It says document out of line. the error is pointed on onChilchange method. Thank
FilterableFirestoreRecyclerAdapter
Anyway I also have the same error as @essowe-agnek on the onChildChanged method. I found that the get value in the method modified to this works.
T model = mSnapshots.get(type!=ChangeEventType.REMOVED ? newIndex : oldIndex);
Also I found that the recyclerview is prone to duplicate items. I was able to mitigate it using the band-aid code below.
public void startListening() {
if (list.size() > 0) list.clear(); // Add this
if (!mSnapshots.isListening(this)) {
mSnapshots.addChangeEventListener(this);
}
}
I also have the same error as @essowe-agnek on the onChildChanged method when i remove an item from the recyclerView.
The error log shows me that:
Process: io.beanario.ocho_sidekick, PID: 22924
java.lang.ArrayIndexOutOfBoundsException: length=33; index=-1
at java.util.ArrayList.remove(ArrayList.java:506)
The error is pointed
private void removeItem(int newIndex) {
>>>> list.remove(newIndex); <<<<Error pointed
if (isFiltarable)
backupList.remove(newIndex);
}
Help me, please!
@vito8916 have you tried using my solution above? and also can you change list.remove(newIndex)
to list.remove(oldIndex)
?
Thank you, you made my day sir
Items get doubled when I minimizing the app, so add list.clear()
like this:-
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
public void stopListening() {
itemsList.getSnapshots().removeChangeEventListener(this);
list.clear();
notifyDataSetChanged();
}
@Akash-Shukla123 I am just curious about it. 'list.clear()
' should be on ON_STOP
or ON_DESTROY
Items get doubled when I minimizing the app, so add list.clear()
like this:-
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
public void stopListening() {
itemsList.getSnapshots().removeChangeEventListener(this);
list.clear();
notifyDataSetChanged();
}
@janglapuk, @yagneshshinde This may work for you. Thanks a lot @Akash-Shukla123
I added
public void startListening() {
if (list.size() > 0) list.clear(); // Add this
if (!mSnapshots.isListening(this)) {
mSnapshots.addChangeEventListener(this);
}
}
and also
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
public void stopListening() {
itemsList.getSnapshots().removeChangeEventListener(this);
list.clear(); // add this
notifyDataSetChanged();
}
but it still duplicated everytime the editText was cleared.
and then I added
backupList.clear()
with list.clear()
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
public void stopListening() {
mSnapshots.removeChangeEventListener(this);
list.clear();
backupList.clear(); //add this
notifyDataSetChanged();
}
@OnLifecycleEvent(Lifecycle.Event.ON_START)
public void startListening() {
if (list.size() > 0) list.clear();
if (backupList.size() > 0) backupList.clear(); //add this
if (!mSnapshots.isListening(this)) {
mSnapshots.addChangeEventListener(this);
}
}
and all duplicate problem are gone. this worked for me
@100race THanks :-)
GOD bless you!