This is a ListAdapter for Android Lists that can show results from a Geofire query. It must be extended as shown in usage.java
.
Created
November 28, 2016 15:50
-
-
Save jonahbron/e9cd4984955b5c4d5d8c5579502cd25a to your computer and use it in GitHub Desktop.
GeofireListAdapter
This file contains 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
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); | |
} |
This file contains 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
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); | |
} | |
} |
This file contains 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
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