Using GeoFire to populate Firebase Recycler View in android

I want to fill out my recycler view so that I can see who the people / places are nearby. I use GeoFire to query my database, which looks something like this.

GeoQuery geoQuery = geoFire.queryAtLocation(new GeoLocation(latLngCenter.latitude, latLngCenter.longitude), 0.1); geoQuery.addGeoQueryEventListener(new GeoQueryEventListener() { @Override public void onKeyEntered(String key, GeoLocation location) { System.out.println(String.format("Key %s entered the search area at [%f,%f]", key, location.latitude, location.longitude)); Log.e("TAG", key + location.latitude + location.longitude); } @Override public void onKeyExited(String key) { System.out.println(String.format("Key %s is no longer in the search area", key)); } @Override public void onKeyMoved(String key, GeoLocation location) { System.out.println(String.format("Key %s moved within the search area to [%f,%f]", key, location.latitude, location.longitude)); Log.e("TAG", key + location.latitude + location.longitude); } @Override public void onGeoQueryReady() { System.out.println("All initial data has been loaded and events have been fired!"); } @Override public void onGeoQueryError(DatabaseError error) { System.err.println("There was an error with this query: " + error); } }); 

and I use this Firebase RecyclerView

  RecyclerView recycler = (RecyclerView) findViewById(R.id.RecyclerView); recycler.setHasFixedSize(true); recycler.setLayoutManager(new LinearLayoutManager(this)); FirebaseRecyclerAdapter<Chat, ChatHolder> mAdapter = new FirebaseRecyclerAdapter<Chat, ChatHolder>(Chat.class, R.layout.recyclerview, ChatHolder.class, mUsers) { @Override public void populateViewHolder(final ChatHolder chatMessageViewHolder, final Chat chatMessage, int position) { chatMessageViewHolder.setName(chatMessage.getName()); chatMessageViewHolder.setText(chatMessage.getText()); chatMessageViewHolder.setTimestamp(chatMessage.getTimestamp()); } }; recycler.setAdapter(mAdapter); 

with this chat class class and chat object class

 public static class ChatHolder extends RecyclerView.ViewHolder { View mView; public ChatHolder(View itemView) { super(itemView); mView = itemView; } public void setName(String name) { TextView field = (TextView) mView.findViewById(R.id.textViewName); field.setText(name); } public void setText(String text) { TextView field = (TextView) mView.findViewById(R.id.textViewMessage); field.setText(text); } public void setTimestamp(String text) { TextView field = (TextView) mView.findViewById(R.id.textViewTime); field.setText(text); } } public static class Chat { String name; String text; String uid; String timestamp; public Chat() { } public Chat(String name, String uid, String message, String timestamp) { this.name = name; this.text = message; this.uid = uid; this.timestamp = timestamp; } public String getName() { return name; } public String getUid() { return uid; } public String getText() { return text; } public String getTimestamp() { return timestamp; } } 

Currently, this adapter, which is provided in the FirebaseUI library, fills the recyclerview, so that only one link is used and all child events are displayed in the view. Now I want to fill my recyclerView so that when the key ever comes in, it fills my recyclerview based on my key = to my link, this is how my firebase database looks like my firebase database

+6
source share
2 answers

This is a custom FirebaseListAdapter from Emanuelet from which I created a FirebaseRecyclerAdapter.

  public abstract class FirebaseRecyclerAdapter<T, VH extends RecyclerView.ViewHolder> extends RecyclerView.Adapter<VH > implements Filterable { private static final String LOG_TAG = "FirebaseListAdapter"; private Query mRef; private Class<T> mModelClass; private int mLayout; private LayoutInflater mInflater; protected Class<VH> mViewHolderClass; private List<T> mModels; private List<T> mFilteredModels; private List<String> mKeys = new ArrayList<>(); private Map<String, T> mModelKeys; private Map<String, T> mFilteredKeys; private ChildEventListener mListener; private FirebaseRecyclerAdapter.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 FirebaseRecyclerAdapter(Query mRef, Class<T> mModelClass, int mLayout, Activity activity, Class<VH> viewHolderClass) { this.mRef = mRef; this.mModelClass = mModelClass; this.mLayout = mLayout; this.mViewHolderClass = viewHolderClass; 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(FirebaseRecyclerAdapter.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); mKeys.add(dataSnapshot.getKey()); } else { mModels.add(nextIndex, model); mKeys.add(dataSnapshot.getKey()); } } 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(FirebaseRecyclerAdapter.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); mKeys.remove(dataSnapshot.getKey()); 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(FirebaseRecyclerAdapter.this.mModelClass); int index = mModels.indexOf(oldModel); mModels.remove(index); if (previousChildName == null) { mModels.add(0, newModel); mKeys.add(dataSnapshot.getKey()); } else { T previousModel = mModelKeys.get(previousChildName); int previousIndex = mModels.indexOf(previousModel); int nextIndex = previousIndex + 1; if (nextIndex == mModels.size()) { mModels.add(newModel); mKeys.add(dataSnapshot.getKey()); } else { mModels.add(nextIndex, newModel); mKeys.add(dataSnapshot.getKey()); } } notifyDataSetChanged(); } @Override public void onCancelled(DatabaseError error) { 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(); mKeys.clear(); } @Override public int getItemCount() { return mModels.size(); } public T getItem(int position) { return mModels.get(position); } @Override public void onBindViewHolder(VH holder, int position) { T model = getItem(position); populateViewHolder(holder, model, position, mKeys); } @Override public long getItemId(int i) { return i; } @Override public int getItemViewType(int position) { return mLayout; } public void remove(String key) { T oldModel = mModelKeys.get(key); mModels.remove(oldModel); mKeys.remove(key); mModelKeys.remove(key); notifyDataSetChanged(); } @Override public VH onCreateViewHolder(ViewGroup parent, int viewType) { View view = LayoutInflater.from(parent.getContext()).inflate(viewType, parent, false); try { Constructor<VH> constructor = mViewHolderClass.getConstructor(View.class); return constructor.newInstance(view); } catch (NoSuchMethodException e) { throw new RuntimeException(e); } catch (InvocationTargetException e) { throw new RuntimeException(e); } catch (InstantiationException e) { throw new RuntimeException(e); } catch (IllegalAccessException e) { throw new RuntimeException(e); } } /** * 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 viewHolder The view to populate * @param model The object containing the data used to populate the view */ protected abstract void populateViewHolder(VH viewHolder, T model, int position, List<String> mKeys); public void addSingle(DataSnapshot snapshot) { T model = snapshot.getValue(FirebaseRecyclerAdapter.this.mModelClass); mModelKeys.put(snapshot.getKey(), model); mModels.add(model); mKeys.add(snapshot.getKey()); notifyDataSetChanged(); } public void update(DataSnapshot snapshot, String key) { T oldModel = mModelKeys.get(key); T newModel = snapshot.getValue(FirebaseRecyclerAdapter.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 FirebaseRecyclerAdapter.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 Filter.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); } 

Extend PostsQueryAdapter with FirebaseRecyclerAdapter

 public class FirebasePostsQueryAdapter extends FirebaseRecyclerAdapter<Feeds, PostViewHolder> { Activity mActivity; /** * @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 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 * @param viewHolderClass This is the PostsViewHolder Class which will be used to populate data. */ Query query; public FirebasePostsQueryAdapter(Query mRef, int mLayout, Activity activity, Class<PostViewHolder> viewHolderClass) { super(mRef, Feeds.class, mLayout, activity, viewHolderClass); this.query = mRef; this.mActivity = activity; } @Override protected void populateViewHolder(final PostViewHolder viewHolder, final Feeds model, final int position, final List<String> mKeys) { viewHolder.setPhoto(model.getThumb_url()); viewHolder.setTimestamp(DateUtils.getRelativeTimeSpanString( (long) model.getTimestamp()).toString()); viewHolder.setAuthor(model.getUser().getFull_name(), model.getUser().getUid()); viewHolder.setIcon(model.getUser().getProfile_picture(), model.getUser().getUid()); viewHolder.setText(model.getText()); viewHolder.mPhotoView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Intent intent = new Intent(mActivity, SingleVideoView.class); intent.putExtra(Constants.INTENT_VIDEO,model.getVideo_url()); intent.putExtra(Constants.KEY, mKeys.get(position)); mActivity.startActivity(intent); } }); ValueEventListener likeListener = new ValueEventListener() { @Override public void onDataChange(DataSnapshot dataSnapshot) { viewHolder.setNumLikes(dataSnapshot.getChildrenCount()); if (dataSnapshot.hasChild(FirebaseUtil.getCurrentUserId())) { viewHolder.setLikeStatus(PostViewHolder.LikeStatus.LIKED, mActivity); } else { viewHolder.setLikeStatus(PostViewHolder.LikeStatus.NOT_LIKED, mActivity); } } @Override public void onCancelled(DatabaseError databaseError) { } }; FirebaseUtil.getLikesRef().child(mKeys.get(position)).addValueEventListener(likeListener); viewHolder.mLikeListener = likeListener; } @Override protected List<Feeds> filters(List<Feeds> models, CharSequence constraint) { return null; } @Override protected Map<String, Feeds> filterKeys(List<Feeds> mModels) { return null; } } 

Where do you fill in the data in FirebasePostsQueryAdapter

  @Override public void onCreate() { super.onCreate(); mFirebaseRef = FirebaseDatabase.getInstance().getReference(POSTS_STRING); mFirebaseRef.keepSynced(true); this.geoFire = new GeoFire(FirebaseDatabase.getInstance().getReference(GEO_POINTS)); mItemListAdapter = new FirebasePostQueryAdapter(mFirebaseRef.equalTo(GEOFIRE_CHILD), getActivity(), R.layout.list_item_items); } @Override public void onConnected(Bundle bundle) { startLocationUpdates(); center = new GeoLocation(MainActivity.mLastLocation.getLatitude(), MainActivity.mLastLocation.getLongitude()); if (!mActiveGeoQuery) { center = new GeoLocation(mCurrentLocation.getLatitude(), mCurrentLocation.getLongitude()); startGeoQuery(); mAdapter.notifyDataSetChanged(); } center = new GeoLocation(MainActivity.mLastLocation.getLatitude(), MainActivity.mLastLocation.getLongitude()); if (center.latitude != 0 && center.longitude != 0 && !mActiveGeoQuery) { startGeoQuery(); } else if (mActiveGeoQuery) { Log.d(TAG, "geoquery already active"); } else { Log.d(TAG, "center not setted"); //I first try to set the center at the Last Known Location if retrieved if (Double.isNaN(mCurrentLocation.getLatitude()) && mCurrentLocation.getLatitude() != 0) { center = new GeoLocation(mCurrentLocation.getLatitude(), mCurrentLocation.getLongitude()); startGeoQuery(); } } fragment.checkForItems(); } private void startGeoQuery() { query = geoFire.queryAtLocation(center, 2); Log.d(TAG, "center: " + center.toString() + ", radius: " + 2); query.addGeoQueryEventListener(new GeoQueryEventListener() { @Override public void onKeyEntered(String key, GeoLocation location) { Log.d(TAG, "Key " + key + " entered the search area at [" + location.latitude + "," + location.longitude + "]"); DatabaseReference tempRef = mFirebaseRef.child(key); tempRef.addValueEventListener(new ValueEventListener() { @Override public void onDataChange(DataSnapshot snapshot) { // I add the deal only if it doesn't exist already in the adapter String key = snapshot.getKey(); if (!mAdapter.exists(key)) { Log.d(TAG, "item added " + key); mAdapter.addSingle(snapshot); mAdapter.notifyDataSetChanged(); } else { //...otherwise I will update the record Log.d(TAG, "item updated: " + key); mAdapter.update(snapshot, key); mAdapter.notifyDataSetChanged(); } } @Override public void onCancelled(DatabaseError databaseError) { } }); } @Override public void onKeyExited(String key) { Log.d(TAG, "deal " + key + " is no longer in the search area"); mAdapter.remove(key); fragment.isListEmpty(); } @Override public void onKeyMoved(String key, GeoLocation location) { Log.d(TAG, String.format("Key " + key + " moved within the search area to [%f,%f]", location.latitude, location.longitude)); DatabaseReference tempRef = mFirebaseRef.child(key); tempRef.addValueEventListener(new ValueEventListener() { @Override public void onDataChange(DataSnapshot snapshot) { // I add the deal only if it doesn't exist already in the adapter String key = snapshot.getKey(); if (!mAdapter.exists(key)) { Log.d(TAG, "item added " + key); mAdapter.addSingle(snapshot); mAdapter.notifyDataSetChanged(); } else { //...otherwise I will update the record Log.d(TAG, "item updated: " + key); mAdapter.update(snapshot, key); mAdapter.notifyDataSetChanged(); } } @Override public void onCancelled(DatabaseError databaseError) { } }); } @Override public void onGeoQueryReady() { Log.d(TAG, "All initial data has been loaded and events have been fired!"); fragment.isListEmpty(); mActiveGeoQuery = true; } @Override public void onGeoQueryError(DatabaseError error) { Log.e(TAG, "There was an error with this query: " + error); mActiveGeoQuery = false; } }); fragment.bindRecyclerView(); } 
0
source

It will be easier to pull all the data (also uid ) that you extracted from geofire to a new node to store the geophony results in firebase, something like

 my-node //your new firebase node to store all uids retrieved from geofire - {chatUid}: true //true is just a dummy data, you can use anything else except null and empty string. - {chatUid}: true - ... 

and set the FirebaseRecyclerAdapter ref to node.

 DatabaseReference ref = FirebaseDatabase.getInstance().getReference("my-node"); FirebaseRecyclerAdapter fra = new FirebaseRecyclerAdapter<Boolean, MyVH>(Boolean.class, R.layout.my_layout, MyVH.class, ref) { ... } 

and then in the populateViewHolder() method in your FirebaseRecyclerAdapter implementation, you can use the String key to retrieve data from the main node containing the data.

 public void populateViewHolder(MyVH viewHolder, Boolean model, int position){ // Get references of child views final TextView nameTextView = viewHolder.getItemView().findViewById(R.id.my_name_text_view_in_vh_layout); final TextView addressTextView = viewHolder.getItemView().findViewById(R.id.my_address_text_view_in_vh_layout); // Key in this position. String key = getRef(position).key; // Query the full data of the current key located in the `main-data-node` FirebaseDatabase.getInstance().getReference("main-data-node").child(key).addValueEventListener(new ValueEventListener(){ ... //Truncated onCancelled @Override public void onDataChange(snap: DataSnapshot){ MyDataModel model = snap.getValue(MyDataModel.class); nameTextView = model.getName(); addressTextView = model.getAddress(); } } } // The data model class public class MyDataModel { private String name; private String address; ... // Truncated getter, setter, constructor } 

With any changes to the result of the geofix, just click these results on my-node and it will automatically inform your FirebaseRecyclerAdapter .

PS I'm too lazy to fit my decision of your data model (sorry), so I made a simplified class of samples, so if anyone came across this, they can understand it more easily.

PSS Some time has passed since I am code in java, I mainly use kotlin (and you should too lol soon), so if there are some syntax errors, feel free to edit.

0
source

All Articles