Skip to content

Instantly share code, notes, and snippets.

@emanuelet
Last active September 16, 2017 12:20
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save emanuelet/38f490bdbb4b7b91f183 to your computer and use it in GitHub Desktop.
Save emanuelet/38f490bdbb4b7b91f183 to your computer and use it in GitHub Desktop.
Generic List Adapter for Firebase
/**
* This class is an example of how to use FirebaseListAdapter. It uses the ExampleObject class to encapsulate the
* data for each individual chat message
*/
public class ExampleAdapter extends FirebaseListAdapter<ExampleObject> {
@InjectView(R.id.example_list_item)
TextView exampleView;
public DealListAdapter(Query ref, Activity activity, int layout) {
super(ref, ExampleObject.class, layout, activity);
this.context = activity.getApplicationContext();
}
/**
* Bind an instance of the ExampleObject class to our view. This method is called by <code>FirebaseListAdapter</code>
* when there is a data change, and we are given an instance of a View that corresponds to the layout that we passed
* to the constructor, as well as a single ExampleObject instance that represents the current data to bind.
*
* @param view A view instance corresponding to the layout we passed to the constructor.
* @param example An instance representing the current state of a message
*/
@Override
protected void populateView(View view, ExampleObject example) {
ButterKnife.inject(this, view);
// populate the list element
exampleView.setText(example.getText());
}
@Override
protected List<ExampleObject> filters(List<ExampleObject> models, CharSequence filter) {
List<ExampleObject> filterList = new ArrayList<>();
for (int i = 0; i < models.size(); i++) {
/* implement your own filtering logic
* and then call filterList.add(models.get(i));
*/
}
return filterList;
}
public abstract class FirebaseListAdapter<T> extends BaseAdapter implements Filterable {
private static final String LOG_TAG = "FirebaseListAdapter";
private Query mRef;
private Class<T> mModelClass;
private int mLayout;
private LayoutInflater mInflater;
private List<T> mModels;
private List<T> mFilteredModels;
private Map<String, T> mModelKeys;
private Map<String, T> mFilteredKeys;
private ChildEventListener mListener;
private Context mContext;
private ValueFilter valueFilter;
/**
* @param mRef The Firebase location to watch for data changes. Can also be a slice of a location, using some
* combination of <code>limit()</code>, <code>startAt()</code>, and <code>endAt()</code>,
* @param mModelClass Firebase will marshall the data at a location into an instance of a class that you provide
* @param mLayout This is the mLayout used to represent a single list item. You will be responsible for populating an
* instance of the corresponding view with the data from an instance of mModelClass.
* @param activity The activity containing the ListView
*/
public FirebaseListAdapter(Query mRef, Class<T> mModelClass, int mLayout, Activity activity) {
this.mRef = mRef;
this.mModelClass = mModelClass;
this.mLayout = mLayout;
mInflater = activity.getLayoutInflater();
mModels = new ArrayList<>();
mModelKeys = new HashMap<>();
// Look for all child events. We will then map them to our own internal ArrayList, which backs ListView
mListener = this.mRef.addChildEventListener(new ChildEventListener() {
@Override
public void onChildAdded(DataSnapshot dataSnapshot, String previousChildName) {
T model = dataSnapshot.getValue(FirebaseListAdapter.this.mModelClass);
mModelKeys.put(dataSnapshot.getKey(), model);
// Insert into the correct location, based on previousChildName
if (previousChildName == null) {
mModels.add(0, model);
} else {
T previousModel = mModelKeys.get(previousChildName);
int previousIndex = mModels.indexOf(previousModel);
int nextIndex = previousIndex + 1;
if (nextIndex == mModels.size()) {
mModels.add(model);
} else {
mModels.add(nextIndex, model);
}
}
notifyDataSetChanged();
}
@Override
public void onChildChanged(DataSnapshot dataSnapshot, String s) {
Log.d(LOG_TAG, "onChildChanged");
// One of the mModels changed. Replace it in our list and name mapping
String modelName = dataSnapshot.getKey();
T oldModel = mModelKeys.get(modelName);
T newModel = dataSnapshot.getValue(FirebaseListAdapter.this.mModelClass);
int index = mModels.indexOf(oldModel);
mModels.set(index, newModel);
mModelKeys.put(modelName, newModel);
notifyDataSetChanged();
}
@Override
public void onChildRemoved(DataSnapshot dataSnapshot) {
Log.d(LOG_TAG, "onChildRemoved");
// A model was removed from the list. Remove it from our list and the name mapping
String modelName = dataSnapshot.getKey();
T oldModel = mModelKeys.get(modelName);
mModels.remove(oldModel);
mModelKeys.remove(modelName);
notifyDataSetChanged();
}
@Override
public void onChildMoved(DataSnapshot dataSnapshot, String previousChildName) {
Log.d(LOG_TAG, "onChildMoved");
// A model changed position in the list. Update our list accordingly
String modelName = dataSnapshot.getKey();
T oldModel = mModelKeys.get(modelName);
T newModel = dataSnapshot.getValue(FirebaseListAdapter.this.mModelClass);
int index = mModels.indexOf(oldModel);
mModels.remove(index);
if (previousChildName == null) {
mModels.add(0, newModel);
} else {
T previousModel = mModelKeys.get(previousChildName);
int previousIndex = mModels.indexOf(previousModel);
int nextIndex = previousIndex + 1;
if (nextIndex == mModels.size()) {
mModels.add(newModel);
} else {
mModels.add(nextIndex, newModel);
}
}
notifyDataSetChanged();
}
@Override
public void onCancelled(FirebaseError firebaseError) {
Log.e("FirebaseListAdapter", "Listen was cancelled, no more updates will occur");
}
});
}
public void cleanup() {
// We're being destroyed, let go of our mListener and forget about all of the mModels
mRef.removeEventListener(mListener);
mModels.clear();
mModelKeys.clear();
}
@Override
public int getCount() {
return mModels.size();
}
@Override
public Object getItem(int i) {
return mModels.get(i);
}
@Override
public long getItemId(int i) {
return i;
}
public void remove(String key) {
T oldModel = mModelKeys.get(key);
mModels.remove(oldModel);
mModelKeys.remove(key);
notifyDataSetChanged();
}
@Override
public View getView(int i, View view, ViewGroup viewGroup) {
if (view == null) {
view = mInflater.inflate(mLayout, viewGroup, false);
}
T model = mModels.get(i);
if (model != null) {
// Call out to subclass to marshall this model into the provided view
populateView(view, model);
}
return view;
}
/**
* Each time the data at the given Firebase location changes, this method will be called for each item that needs
* to be displayed. The arguments correspond to the mLayout and mModelClass given to the constructor of this class.
* <p/>
* Your implementation should populate the view using the data contained in the model.
*
* @param v The view to populate
* @param model The object containing the data used to populate the view
*/
protected abstract void populateView(View v, T model);
public void addSingle(DataSnapshot snapshot) {
T model = snapshot.getValue(FirebaseListAdapter.this.mModelClass);
mModelKeys.put(snapshot.getKey(), model);
mModels.add(model);
notifyDataSetChanged();
}
public void update(DataSnapshot snapshot, String key) {
T oldModel = mModelKeys.get(key);
T newModel = snapshot.getValue(FirebaseListAdapter.this.mModelClass);
int index = mModels.indexOf(oldModel);
if (index >= 0) {
mModels.set(index, newModel);
mModelKeys.put(key, newModel);
notifyDataSetChanged();
}
}
public boolean exists(String key) {
return mModelKeys.containsKey(key);
}
@Override
public Filter getFilter() {
if (valueFilter == null) {
valueFilter = new ValueFilter();
}
return valueFilter;
}
protected abstract List<T> filters(List<T> models, CharSequence constraint);
private class ValueFilter extends Filter {
//Invoked in a worker thread to filter the data according to the constraint.
@Override
protected FilterResults performFiltering(CharSequence constraint) {
FilterResults results = new FilterResults();
if (mFilteredModels == null) {
mFilteredModels = new ArrayList<>(mModels); // saves the original data in mOriginalValues
mFilteredKeys = new HashMap<>(mModelKeys); // saves the original data in mOriginalValues
}
if (constraint != null && constraint.length() > 0) {
List<T> filtered = filters(mFilteredModels, constraint);
results.count = filtered.size();
results.values = filtered;
mModelKeys = filterKeys(mModels);
} else {
results.count = mFilteredModels.size();
results.values = mFilteredModels;
mModelKeys = mFilteredKeys;
}
return results;
}
//Invoked in the UI thread to publish the filtering results in the user interface.
@SuppressWarnings("unchecked")
@Override
protected void publishResults(CharSequence constraint,
FilterResults results) {
Log.d(LOG_TAG, "filter for " + constraint + ", results nr: " + results.count);
mModels = (List<T>) results.values;
notifyDataSetChanged();
}
}
protected abstract Map<String, T> filterKeys(List<T> mModels);
}
@emanuelet
Copy link
Author

This is my implementation of the FirebaseListAdapter that you can find in the Android chat example of Firebase.
Other than the normal features there is: filtering, single element manual addition/update/deletion (very useful if you need to populate the list with geofire).

@emanuelet
Copy link
Author

the power of this implementation is that if you have multiple list inside your app that has to be populated from Firebase you have a single source adapter for all of them and then a single specific implementation for every one of them

@R00t6
Copy link

R00t6 commented Jul 17, 2016

I am having issue with the Example Adapter cannot override method from its upper class.

 @Override
    protected List<Post> filters(List<Post> models, CharSequence filter) {
        List<Post> filterList = new ArrayList<>();
        for (int i = 0; i < models.size(); i++) {
            /* implement your own filtering logic
             * and then call  filterList.add(models.get(i));
             */
        }
        return filterList;
    }

@Dwite
Copy link

Dwite commented Jul 26, 2016

Any chances for such good thing for recyclerview?

@Charan1010
Copy link

Can u tell me where the getView() method is getting called

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment