8

I want to populate my recycler view so that, I can see who are the people/places nearby. I am using 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 am using 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 these Chat Holder 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 FirebaseUI library, populates recyclerview so that, only one reference is used and all child events are shown in the view, Now, I want to populate my recyclerView so that when ever a key enters it populates my recyclerview based on my key = to my reference, this how my firebase database looksmy firebase database

Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
virsam
  • 91
  • 5

2 Answers2

1

It'll be simpler to push all data (which is also uid) you have retrieved from geofire to a new node to store geofire 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 FirebaseRecyclerAdapter ref to that 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 populateViewHolder() method in your implementation of FirebaseRecyclerAdapter, you can use the String key to fetch data from main node that contains 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
}

For any changes in geofire result, just push those results to my-node, and it will automatically informs your FirebaseRecyclerAdapter.

P.S. I'm too lazy to match my solution to your data model (sorry), so I made a simplified sample class, so if anyone stumbled upon this, they can understand it easier.

P.S.S. It's been a while since I code in java, I mainly use kotlin (and you soon should too lol), so, if there're some syntax mistakes out there, feel free to edit.

Moses Aprico
  • 1,951
  • 5
  • 30
  • 53
  • could you flesh out how to actually implement the "**//TODO: Use given key to fetch the data from your main node.**" part? That's the part I'm not clear about. – Atticus29 Jul 16 '17 at 04:41
  • 1
    It's just like : `FirebaseDatabase.getInstance().getReference("nodeThatContainsFullData").child(key).addValueEventListener(...)`. `...` is the basic operation on how you initialize a new `ValueEventListener` object. Read the official guide in the firebase website, under realtime database section. Good luck. – Moses Aprico Jul 16 '17 at 05:12
  • Thanks for the rapid response! So, I've already played with that a little bit. Since I have to override onDataChange and type cast my snapshot into my model from within there, I'm not sure how to then bind that object, given that viewHolder is not available from inside onDataChange. Does that make sense? – Atticus29 Jul 16 '17 at 05:16
  • @Atticus29 well it doesn't matter actually. You can call as many as `addValueEventListener` you want inside the `onDataChange`, and attach it to referenced child views in the viewholders. Actually I'm on mobile now, I'll update my post for full solution once I get back. – Moses Aprico Jul 16 '17 at 05:22
  • 1
    @Atticus29 well it doesn't matter actually. You can call as many as "inner" `addValueEventListener` you want inside the `onDataChange`, and attach it to referenced child views in the viewholders. Actually I'm on mobile now, I'll update my post for full solution once I get back. – Moses Aprico Jul 16 '17 at 05:22
  • Much appreciated, @Moses Aprico! – Atticus29 Jul 16 '17 at 05:35
0

This is the Emanuelet's custom FirebaseListAdapter from which I have created by 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 the PostsQueryAdapter with the 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 you populate the data into 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();
        }
Contextioner
  • 75
  • 11
  • It's better just to link GitHub repo. You have fragment variable and you call its methods, so you should put it here as well which makes answer unreadable and hard to apply – Ihor Klimov Feb 01 '17 at 10:21
  • @IgorKlimov what is so hard to understand. The GitHub gist has the almost the same code but it is for listView. I can give you the water but can't make you drink it. – Contextioner Feb 02 '17 at 09:08
  • @Contextioner Did you have any issues with the mKeys not having all items from the Ref? For some reason it only has 2 items while I clearly have 3 in the Firebase Database Node. Cheers – Andy Strife Apr 04 '17 at 04:54
  • @AndyStrife I have added the adapter insatiate code to onCreate do check again. – Contextioner Apr 04 '17 at 06:37