0

I'm writing this after hours searching online. I am trying to fetch specific sub-collection in firestore using dynamic path. I figured that whatever I'm doing wrong is probably about async tasks, but I just couldn't figure out how to solve the problem myself.

I tried using AsyncTask class (which is, if im not mistaken, deprecated now) but it didn't work for me.

I am using firebase-ui's adapter and combining it with geohashing from firestore I want to return specific query to the place the user is closest to.

Main Activity.java


public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    FirebaseFirestore db = FirebaseFirestore.getInstance();
    private FirestoreRecyclerAdapter adapter;
    private RecyclerView mFirestoreList;
    Query dishQuery;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.home_activity);
        mFirestoreList = findViewById(R.id.firestore_list);
        Spinner mEmptyListMessage = findViewById(R.id.empty_list_message);
        new getRightQuery().getQuery(new Callback() {
            @Override
            public void firebaseResponseCallback(Query q) {
                dishQuery = q;

            }
        });
        
        FirestoreRecyclerOptions<Dish> options = new FirestoreRecyclerOptions.Builder<Dish>().setQuery(dishQuery, Dish.class).build();
        adapter = new FirestoreRecyclerAdapter<Dish, DishViewHolder>(options) {
            @NonNull
            @Override
            public DishViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
                View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_single, parent, false);
                return new DishViewHolder(view);
            }
            @Override
            public void onChildChanged(@NonNull ChangeEventType type, @NonNull DocumentSnapshot snapshot, int newIndex, int oldIndex) {
                super.onChildChanged(type, snapshot, newIndex, oldIndex);
            }

            @Override
            protected void onBindViewHolder(@NonNull DishViewHolder holder, int position, @NonNull Dish model) {
                holder.name.setText(model.getName());
                holder.starCount.setText(String.valueOf(model.getStarCount()));
                holder.isVegan.setText(String.valueOf(model.getIsVegan()));
            }

            @Override
            public void onError(@NonNull FirebaseFirestoreException e) {
                super.onError(e);
                Log.e("Error", "got error" + e.getMessage());
            }
        };
        mFirestoreList.setHasFixedSize(true);
        mFirestoreList.setLayoutManager(new LinearLayoutManager(this));
        mFirestoreList.setAdapter(adapter);
    }
  
    @Override
    protected void onStop() {
        super.onStop();
        adapter.stopListening();
    }

    @Override
    protected void onStart() {
        super.onStart();
        adapter.startListening();

    }

    private class DishViewHolder extends RecyclerView.ViewHolder {

        private TextView name, starCount, isVegan;

        public DishViewHolder(@NonNull View itemView) {
            super(itemView);

            name = itemView.findViewById(R.id.name);
            starCount = itemView.findViewById(R.id.star_count);
            isVegan = itemView.findViewById(R.id.is_vegan);
        }
    }
}

getRightQuery.java

public class getRightQuery {
    private String a;
    private final FirebaseFirestore db = FirebaseFirestore.getInstance();
    private Query query;


    public getRightQuery(){

    }

    public void getQuery(Callback callback) {
        List<DocumentSnapshot> matchingDocs = new ArrayList<>();
        final GeoLocation center = new GeoLocation(32.16051, 34.84002);
        final double radiusInM = 50 * 1000;

// Each item in 'bounds' represents a startAt/endAt pair. We have to issue
// a separate query for each pair. There can be up to 9 pairs of bounds
// depending on overlap, but in most cases there are 4.
        List<GeoQueryBounds> bounds = GeoFireUtils.getGeoHashQueryBounds(center, radiusInM);
        final List<Task<QuerySnapshot>> tasks = new ArrayList<>();
        for (GeoQueryBounds b : bounds) {
            Query q = db.collection("Restaurants")
                    .orderBy("geohash")
                    .startAt(b.startHash)
                    .endAt(b.endHash);

            tasks.add(q.get());
        }

// Collect all the query results together into a single list
        Tasks.whenAllComplete(tasks)
                .addOnCompleteListener(t -> {
                    for (Task<QuerySnapshot> task : tasks) {
                        QuerySnapshot snap = task.getResult();
                        for (DocumentSnapshot doc : snap.getDocuments()) {
                            double lat = doc.getDouble("lat");
                            double lng = doc.getDouble("lng");
                            // We have to filter out a few false positives due to GeoHash
                            // accuracy, but most will match
                            GeoLocation docLocation = new GeoLocation(lat, lng);
                            double distanceInM = GeoFireUtils.getDistanceBetween(docLocation, center);
                            if (distanceInM <= radiusInM) {
                                    matchingDocs.add(doc);
                                    Query query = db.collection("Restaurants").document(matchingDocs.get(0).getId()).collection("menu");
                                    Log.d("finish", matchingDocs.get(0).getId());
                                    callback.firebaseResponseCallback(query);
                            }
                        }
                    }
                });
    }
}

EDITED

Callback.java

import com.google.firebase.firestore.Query;

interface Callback{
    void firebaseResponseCallback(Query q);//whatever your return type is.
}

screenshot of how firestore database is designed

I get the following respone:

E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.example.dishy, PID: 6122
    java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.dishy/com.example.dishy.MainActivity}: java.lang.NullPointerException: Attempt to invoke virtual method 'com.google.firebase.firestore.ListenerRegistration com.google.firebase.firestore.Query.addSnapshotListener(com.google.firebase.firestore.MetadataChanges, com.google.firebase.firestore.EventListener)' on a null object reference
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2817)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2892)
        at android.app.ActivityThread.-wrap11(Unknown Source:0)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1593)
        at android.os.Handler.dispatchMessage(Handler.java:105)
        at android.os.Looper.loop(Looper.java:164)
        at android.app.ActivityThread.main(ActivityThread.java:6541)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:767)
     Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'com.google.firebase.firestore.ListenerRegistration com.google.firebase.firestore.Query.addSnapshotListener(com.google.firebase.firestore.MetadataChanges, com.google.firebase.firestore.EventListener)' on a null object reference
        at com.firebase.ui.firestore.FirestoreArray.onCreate(FirestoreArray.java:63)
        at com.firebase.ui.common.BaseObservableSnapshotArray.addChangeEventListener(BaseObservableSnapshotArray.java:97)
        at com.firebase.ui.firestore.FirestoreRecyclerAdapter.startListening(FirestoreRecyclerAdapter.java:51)
        at com.example.dishy.MainActivity.onStart(MainActivity.java:116)
        at android.app.Instrumentation.callActivityOnStart(Instrumentation.java:1333)
        at android.app.Activity.performStart(Activity.java:6992)
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2780)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2892) 
        at android.app.ActivityThread.-wrap11(Unknown Source:0) 
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1593) 
        at android.os.Handler.dispatchMessage(Handler.java:105) 
        at android.os.Looper.loop(Looper.java:164) 
        at android.app.ActivityThread.main(ActivityThread.java:6541) 
        at java.lang.reflect.Method.invoke(Native Method) 
        at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240) 
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:767) 

Edited

For anyone having trouble getting data using dynamic queries (created by other tasks in other classes) here is my WORKING code:

MainActivity.java


public class MainActivity extends AppCompatActivity {
    FirebaseFirestore db = FirebaseFirestore.getInstance();
    private FirestoreRecyclerAdapter adapter;
    private RecyclerView mFirestoreList;
    Query dishQuery = db.collection("o");
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.home_activity);
        mFirestoreList = findViewById(R.id.firestore_list);

        FirestoreRecyclerOptions<Dish> options = new FirestoreRecyclerOptions.Builder<Dish>().setQuery(dishQuery, Dish.class).build();
        adapter = new FirestoreRecyclerAdapter<Dish, DishViewHolder>(options) {
            @NonNull
            @Override
            public DishViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
                View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_single, parent, false);
                return new DishViewHolder(view);
            }
            @Override
            public void onChildChanged(@NonNull ChangeEventType type, @NonNull DocumentSnapshot snapshot, int newIndex, int oldIndex) {
                super.onChildChanged(type, snapshot, newIndex, oldIndex);
                adapter.notifyDataSetChanged();
            }


            @Override
            protected void onBindViewHolder(@NonNull DishViewHolder holder, int position, @NonNull Dish model) {
                holder.name.setText(model.getName());
                holder.starCount.setText(String.valueOf(model.getStarCount()));
                holder.isVegan.setText(String.valueOf(model.getIsVegan()));
            }

            @Override
            public void onError(@NonNull FirebaseFirestoreException e) {
                super.onError(e);
                Log.e("Error", "got error" + e.getMessage());
            }
        };
        mFirestoreList.setHasFixedSize(true);
        mFirestoreList.setLayoutManager(new LinearLayoutManager(this));
        mFirestoreList.setAdapter(adapter);

        /////
        new getRightQuery().getQuery(new Callback() {
            @Override
            public void firebaseResponseCallback(Query q) {
                q.addSnapshotListener(new EventListener<QuerySnapshot>() {
                    @Override
                    public void onEvent(@Nullable QuerySnapshot value, @Nullable FirebaseFirestoreException error) {
                        adapter.stopListening();
                        adapter.updateOptions(new FirestoreRecyclerOptions.Builder<Dish>().setQuery(q, Dish.class).build());
                        adapter.startListening();
                        adapter.notifyDataSetChanged();
                    }
                });
            }
        });
        
    }

    @Override
    protected void onStop() {
        super.onStop();
        adapter.stopListening();
    }

    @Override
    protected void onStart() {
        super.onStart();
        adapter.startListening();

    }

    private class DishViewHolder extends RecyclerView.ViewHolder {

        private TextView name, starCount, isVegan;

        public DishViewHolder(@NonNull View itemView) {
            super(itemView);

            name = itemView.findViewById(R.id.name);
            starCount = itemView.findViewById(R.id.star_count);
            isVegan = itemView.findViewById(R.id.is_vegan);
        }
    }
}

I used a "dummy" query just to create an empty recyclerview (as Alex Mamo stated in the comments) and then populated the data and updated the adapter using adapter.updateOptions()

Llyod
  • 1
  • 1
  • At which particular line of code, are you getting that error? – Alex Mamo Apr 12 '21 at 19:37
  • I think it all because of this line: `FirestoreRecyclerOptions options = new FirestoreRecyclerOptions.Builder().setQuery(dishQuery, Dish.class).build();` when I debug I can see that `dishQuery` is null in this line – Llyod Apr 13 '21 at 07:31
  • What does "getRightQuery().getQuery()" return? – Alex Mamo Apr 13 '21 at 08:29
  • I think that's the thing. It returns null because it fires too early, I want it to return the desired query but can't figure out how – Llyod Apr 13 '21 at 08:54
  • You might check [this](https://stackoverflow.com/questions/48499310/how-to-return-a-documentsnapshot-as-a-result-of-a-method/48500679#48500679) out. – Alex Mamo Apr 13 '21 at 08:58
  • First of all, thanks for replying. Second, I implemented the callback "structure" you talked about in the link, but the adapter options firing too soon still, what should I do to make the adapter "wait" until I have the right query in my hands? – Llyod Apr 13 '21 at 09:27
  • Create an adapter with an empty list, get the data and in the end [just notify the adapter about the changes](https://stackoverflow.com/questions/48622480/showing-firebase-data-in-listview). – Alex Mamo Apr 13 '21 at 10:11

0 Answers0