t5050
t5050

Reputation: 61

Retrieving list of users only within 3 miles using geofire

I am trying retrieve only the list of users who are within 3 miles of current users location. I am using a recyclerView to list the users, I am also using geofire to query the keys and I'm using firebase to store my users. When I try to retrieve only the list of users who are within 3 miles,it gives me all of the users including the ones who aren't within 3 miles. Can someone please help me fix this. Below is my code. Thanks in advance

MainClass To retrieve data

@Override
public void onSuccess(Location location) {

    Double latitude=location.getLatitude();
    Double longitude=location.getLongitude();

    Geocoder geocoder=new Geocoder(NewsFeedActivity.this, Locale.getDefault());


     firebaseDatabase=FirebaseDatabase.getInstance().getReference("Users");
    GeoFire geoFire=new GeoFire(firebaseDatabase.child("locals"));


    final GeoQuery geoQuery=geoFire.queryAtLocation(new GeoLocation(latitude,longitude),3);

    geoQuery.addGeoQueryEventListener(new GeoQueryEventListener() {
        @Override
        public void onKeyEntered(final String key, GeoLocation location) {

            firebaseDatabase.addValueEventListener(new ValueEventListener() {


                @Override
                public void onDataChange(@NonNull DataSnapshot dataSnapshot) {
                    list=new ArrayList<Users>();

                    for(DataSnapshot dataSnapshot1:dataSnapshot.getChildren()){
                        Users my=dataSnapshot1.getValue(My.class);
                        list.add(my);

                    }

                adapter=new MyAdapter(MainActivity.this,list);
                            recycler.setAdapter(adapter);


                    adapter=new MyAdapter(MainActivity.this,list);
                    recycler.setAdapter(adapter);

                }

                @Override
                public void onCancelled(@NonNull DatabaseError databaseError) {

                }
            });
        }

        @Override
        public void onKeyExited(String key) {

        }

        @Override
        public void onKeyMoved(String key, GeoLocation location) {

        }

        @Override
        public void onGeoQueryReady() {

        }

        @Override
        public void onGeoQueryError(DatabaseError error) {

            System.err.println("There was an error with this query: " + error);


        }
    });

Adapter class for the recyclerView

public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {

    Context context;
    private ArrayList<UserInformation> users;



    public MyAdapter(Context c,ArrayList<UserInformation> u){

        context=c;
        users=u;

    }

    @NonNull
    @Override
    public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        return new MyViewHolder(LayoutInflater.from(context).inflate(R.layout.cardview,parent,false));
    }

    @Override
    public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
        holder.user_name.setText(users.get(position).getBusinessname());
        holder.address.setText(users.get(position).getAddress());


    }

    @Override
    public int getItemCount() {
        return users.size();
    }


    class MyViewHolder extends RecyclerView.ViewHolder{


        TextView user_name,address;
        public MyViewHolder(@NonNull View itemView) {
            super(itemView);

            user_name=(TextView)itemView.findViewById(R.id.user_name);
            address=(TextView)itemView.findViewById(R.id.user_address);
        }
    }
}


     //Users Class

    public class Users {

    public String usersname;
    public String address;
    public String phonenumber;

    public Users(String usersname, String address, String phonenumber) {
        this.usersname = usersname;
        this.address = address;
        this.phonenumber = phonenumber;
    }

    public Users(){

    }

    public String getBusinessname() {
        return usersname;
    }

    public void setBusinessname(String businessname) {
        this.usersname = usersname;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public String getPhonenumber() {
        return phonenumber;
    }

    public void setPhonenumber(String phonenumber) {
        this.phonenumber = phonenumber;
    }
}

Upvotes: 1

Views: 671

Answers (3)

t5050
t5050

Reputation: 61

I solved it with the Help of answers from Frank Van Puffelen. Here's what I did

public void onKeyEntered(final String key, GeoLocation location) {

                    firebaseDatabase.child(key).addListenerForSingleValueEvent(new ValueEventListener() {
                        @Override
                        public void onDataChange(@NonNull DataSnapshot dataSnapshot) {

                                Users my=dataSnapshot.getValue(Users.class);
                                list.add(my);
                             adapter=new MyAdapter(MainActivity.this,list);
                             recycler.setAdapter(adapter);

                            }

Upvotes: 1

Frank van Puffelen
Frank van Puffelen

Reputation: 598728

Your geoquery correctly searches for keys within 3 kilometers from the given location:

firebaseDatabase=FirebaseDatabase.getInstance().getReference("Users");
GeoFire geoFire=new GeoFire(firebaseDatabase.child("locals"));

final GeoQuery geoQuery=geoFire.queryAtLocation(new GeoLocation(latitude,longitude),3);

But then when GeoFire finds a key within that range and calls onKeyEntered, you add a ValueEventListener to the entire Users node:

geoQuery.addGeoQueryEventListener(new GeoQueryEventListener() {
    @Override
    public void onKeyEntered(final String key, GeoLocation location) {
        firebaseDatabase.addValueEventListener(new ValueEventListener() {

So for each key within the geoquery's range, you load all users. If there are multiple keys within the range, you load all users for each key, and create an adapter each time. At best that is messy and wasteful, but I think in this case it's also why your code doesn't do what you want it to do.

My best guess is that each key in the geoquery identifies a node under Users, and you only want to load that user's node each time your onKeyEntered method is called. That way when there are multiple keys within the geoquery's range, you load each user within that range one by one.

If that is the case, you'll need this listener inside the onKeyEntered:

firebaseDatabase.child(key).addListenerForSingleValueEvent(new ValueEventListener() {
    @Override
    public void onDataChange(@NonNull DataSnapshot dataSnapshot) {
        Users my=dataSnapshot.getValue(My.class);
        list.add(my);
        adapter.notifyDataSetChanged();
    }

    @Override
    public void onCancelled(@NonNull DatabaseError databaseError) {
        throw databaseError.toException(); // never ignore possible errors
    }
});

Some of the changes I made here:

  • Instead of loading all users, this loads a single user for each key that is (or enters) the geoquery's range.
  • Use addListenerForSingleValueEvent, instead of a continuous listener. This way you get the data for the user just once, instead of continuously adding more and more listeners.
  • Call adapter.notifyDataSetChanged(); instead of creating a new adapter for each key that falls into the geoquery's range.

What you'll still need to do:

  • Initialize the adapter only once, outside of the onKeyEntered, passing it the list.

Then when the onKeyEntered fires, and you load the data of that user, you add it to the list and tell the adapter that it needs to repaint with adapter.notifyDataSetChanged().

Upvotes: 3

Robert Harvey
Robert Harvey

Reputation: 180777

You can specify a center and radius several ways. Here is one way:

var geoQuery = geoFire.query({
  center: [10.38, 2.41],
  radius: 10.5
});

Note that radius is specified in kilometers.

Upvotes: 0

Related Questions