简体   繁体   中英

Using GeoFire to Populate Firebase Recycler View in android

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 looks my firebase database

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 .

PS 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.

PSS 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.

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();
        }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM