Last active
March 25, 2020 16:30
-
-
Save gppam/559e6c4fcd1c920b498f383fb8bee55a to your computer and use it in GitHub Desktop.
Custom FilterableFirestoreRecyclerAdapter (Fixed)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Adapted from https://gist.github.com/Kishanjvaghela/67c42f8f32efaa2fadb682bc980e9280#gistcomment-2860109 | |
// Thanks to https://github.com/boberproduction, https://github.com/Kishanjvaghela | |
// A custom FirestoreRecyclerAdapter with Filterable implementation | |
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 (list.size() > 0) list.clear(); | |
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(type!=ChangeEventType.REMOVED ? newIndex : oldIndex); | |
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(oldIndex); | |
notifyItemRemoved(oldIndex); | |
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(); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment