Skip to content

Instantly share code, notes, and snippets.

@jonahbron
Created November 28, 2016 15:50
Show Gist options
  • Save jonahbron/e9cd4984955b5c4d5d8c5579502cd25a to your computer and use it in GitHub Desktop.
Save jonahbron/e9cd4984955b5c4d5d8c5579502cd25a to your computer and use it in GitHub Desktop.
GeofireListAdapter

This is a ListAdapter for Android Lists that can show results from a Geofire query. It must be extended as shown in usage.java.

import android.app.Activity;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import com.google.firebase.database.ChildEventListener;
import com.google.firebase.database.DataSnapshot;
import com.google.firebase.database.DatabaseError;
import com.google.firebase.database.Query;
import java.util.ArrayList;
import java.util.List;
/**
* @author greg
* @since 6/21/13
*
* This class is a generic way of backing an Android ListView with a Firebase location.
* It handles all of the child events at the given Firebase location. It marshals received data into the given
* class type. Extend this class and provide an implementation of <code>populateView</code>, which will be given an
* instance of your list item mLayout and an instance your class that holds your data. Simply populate the view however
* you like and this class will handle updating the list as the data changes.
*
* @param <T> The class type to use as a model for the data contained in the children of the given Firebase location
*/
public abstract class FirebaseListAdapter<T> extends BaseAdapter {
protected Query mRef;
protected Class<T> mModelClass;
protected int mLayout;
protected LayoutInflater mInflater;
protected List<T> mModels;
protected List<String> mKeys;
protected ChildEventListener mListener;
protected String ownKey = "";
public FirebaseListAdapter() {
}
/**
* @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<T>();
mKeys = new ArrayList<String>();
// 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);
String key = dataSnapshot.getKey();
if (ownKey.equals(key)) {
return;
}
// Insert into the correct location, based on previousChildName
if (previousChildName == null) {
mModels.add(0, model);
mKeys.add(0, key);
} else {
int previousIndex = mKeys.indexOf(previousChildName);
int nextIndex = previousIndex + 1;
if (nextIndex == mModels.size()) {
mModels.add(model);
mKeys.add(key);
} else {
mModels.add(nextIndex, model);
mKeys.add(nextIndex, key);
}
}
notifyDataSetChanged();
}
@Override
public void onChildChanged(DataSnapshot dataSnapshot, String s) {
// One of the mModels changed. Replace it in our list and name mapping
String key = dataSnapshot.getKey();
if (ownKey.equals(key)) {
return;
}
T newModel = dataSnapshot.getValue(FirebaseListAdapter.this.mModelClass);
int index = mKeys.indexOf(key);
mModels.set(index, newModel);
notifyDataSetChanged();
}
@Override
public void onChildRemoved(DataSnapshot dataSnapshot) {
// A model was removed from the list. Remove it from our list and the name mapping
String key = dataSnapshot.getKey();
if (ownKey.equals(key)) {
return;
}
int index = mKeys.indexOf(key);
mKeys.remove(index);
mModels.remove(index);
notifyDataSetChanged();
}
@Override
public void onChildMoved(DataSnapshot dataSnapshot, String previousChildName) {
// A model changed position in the list. Update our list accordingly
String key = dataSnapshot.getKey();
if (ownKey.equals(key)) {
return;
}
T newModel = dataSnapshot.getValue(FirebaseListAdapter.this.mModelClass);
int index = mKeys.indexOf(key);
mModels.remove(index);
mKeys.remove(index);
if (previousChildName == null) {
mModels.add(0, newModel);
mKeys.add(0, key);
} else {
int previousIndex = mKeys.indexOf(previousChildName);
int nextIndex = previousIndex + 1;
if (nextIndex == mModels.size()) {
mModels.add(newModel);
mKeys.add(key);
} else {
mModels.add(nextIndex, newModel);
mKeys.add(nextIndex, key);
}
}
notifyDataSetChanged();
}
@Override
public void onCancelled(DatabaseError firebaseError) {}
});
}
public void cleanup() {
// We're being destroyed, let go of our mListener and forget about all of the mModels
mRef.removeEventListener(mListener);
mModels.clear();
mKeys.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;
}
@Override
public View getView(int i, View view, ViewGroup viewGroup) {
if (view == null) {
view = mInflater.inflate(mLayout, viewGroup, false);
}
T model = mModels.get(i);
// Call out to subclass to marshall this model into the provided view
populateView(view, model);
return view;
}
public void setOwnKey(String key) {
ownKey = key;
}
/**
* 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);
}
import android.app.Activity;
import com.firebase.geofire.GeoLocation;
import com.firebase.geofire.GeoQuery;
import com.firebase.geofire.GeoQueryEventListener;
import com.google.firebase.database.DataSnapshot;
import com.google.firebase.database.DatabaseError;
import com.google.firebase.database.DatabaseReference;
import com.google.firebase.database.FirebaseDatabase;
import com.google.firebase.database.Query;
import com.google.firebase.database.ValueEventListener;
import java.util.ArrayList;
import java.util.HashMap;
/**
* Created by jonah on 10/22/16.
*/
public abstract class GeofireListAdapter<T> extends FirebaseListAdapter<T> {
protected DatabaseReference mRef;
protected GeoQuery geoQuery = null;
protected GeoQueryEventListener geoQueryEventListener = null;
protected HashMap<String, DatabaseReference> pathReferences = new HashMap<String, DatabaseReference>();
protected HashMap<String, ValueEventListener> pathListeners = new HashMap<String, ValueEventListener>();
/**
* @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 GeofireListAdapter(final DatabaseReference mRef, Class<T> mModelClass, int mLayout, Activity activity) {
this.mRef = mRef;
this.mModelClass = mModelClass;
this.mLayout = mLayout;
mInflater = activity.getLayoutInflater();
mModels = new ArrayList<T>();
mKeys = new ArrayList<String>();
}
public void setGeoQuery(GeoQuery geoQuery) {
final DatabaseReference mRef = this.mRef;
if (geoQuery != null) {
geoQuery.removeAllListeners();
}
this.geoQuery = geoQuery;
this.geoQueryEventListener = new GeoQueryEventListener() {
@Override
public void onKeyEntered(String key, GeoLocation location) {
if (ownKey.equals(key)) {
return;
}
DatabaseReference ref = mRef.child("contact/" + key);
ValueEventListener listener = new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot dataSnapshot) {
T model = dataSnapshot.getValue(GeofireListAdapter.this.mModelClass);
if (model == null) {
return;
}
String key = dataSnapshot.getKey();
int index = mKeys.indexOf(key);
if (index == -1) {
mModels.add(model);
mKeys.add(key);
} else {
mModels.set(index, model);
}
notifyDataSetChanged();
}
@Override
public void onCancelled(DatabaseError databaseError) {}
};
pathReferences.put(key, ref);
pathListeners.put(key, listener);
ref.addValueEventListener(listener);
}
@Override
public void onKeyExited(String key) {
int index = mKeys.indexOf(key);
if (index == -1) {
return;
}
mModels.remove(index);
mKeys.remove(index);
pathReferences.get(key).removeEventListener(pathListeners.get(key));
pathReferences.remove(key);
pathListeners.remove(key);
notifyDataSetChanged();
}
@Override
public void onKeyMoved(String key, GeoLocation location) {}
@Override
public void onGeoQueryReady() {}
@Override
public void onGeoQueryError(DatabaseError error) {}
};
geoQuery.addGeoQueryEventListener(geoQueryEventListener);
}
}
import android.app.Activity;
import android.graphics.Color;
import android.view.View;
import android.widget.TextView;
import com.firebase.geofire.GeoQuery;
import com.google.firebase.database.DatabaseReference;
import com.google.firebase.database.Query;
/**
* @author greg
* @since 6/21/13
*
* This class is an example of how to use FirebaseListAdapter. It uses the <code>Chat</code> class to encapsulate the
* data for each individual chat message
*/
public class MyListAdapter extends GeofireListAdapter<Item> {
public MyListAdapter(DatabaseReference ref, Activity activity, int layout) {
super(ref, Item.class, layout, activity);
}
@Override
protected void populateView(View view, Item item) {
// add data to view
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment