Skip to content

Instantly share code, notes, and snippets.

@gppam
Last active March 25, 2020 16:30
Show Gist options
  • Select an option

  • Save gppam/559e6c4fcd1c920b498f383fb8bee55a to your computer and use it in GitHub Desktop.

Select an option

Save gppam/559e6c4fcd1c920b498f383fb8bee55a to your computer and use it in GitHub Desktop.

Revisions

  1. gppam revised this gist Mar 25, 2020. 1 changed file with 1 addition and 0 deletions.
    1 change: 1 addition & 0 deletions FilterableFirestoreRecyclerAdapter.java
    Original file line number Diff line number Diff line change
    @@ -1,5 +1,6 @@
    // 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;
  2. gppam revised this gist Mar 25, 2020. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion FilterableFirestoreRecyclerAdapter.java
    Original file line number Diff line number Diff line change
    @@ -126,7 +126,7 @@ protected void onChildUpdate(T model, ChangeEventType type,
    break;
    case REMOVED:
    removeItem(oldIndex);
    notifyItemRemoved(newIndex);
    notifyItemRemoved(oldIndex);
    break;
    case MOVED:
    moveItem(snapshot.getId(), model, newIndex, oldIndex);
  3. gppam created this gist Feb 17, 2020.
    238 changes: 238 additions & 0 deletions FilterableFirestoreRecyclerAdapter.java
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,238 @@
    // Adapted from https://gist.github.com/Kishanjvaghela/67c42f8f32efaa2fadb682bc980e9280#gistcomment-2860109
    // Thanks to https://github.com/boberproduction, https://github.com/Kishanjvaghela

    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(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();
    }
    }
    }