Android 使用动态路径的Firestore查询

Android 使用动态路径的Firestore查询,android,google-cloud-firestore,android-recyclerview,android-asynctask,firebaseui,Android,Google Cloud Firestore,Android Recyclerview,Android Asynctask,Firebaseui,我在网上搜索几个小时后写这篇文章。 我正在尝试使用动态路径获取firestore中的特定子集合。 我认为无论我做错了什么,都可能与异步任务有关,但我自己就是不知道如何解决这个问题 我尝试过使用AsyncTask类(如果我没有弄错的话,现在已经不推荐了),但它对我不起作用 我正在使用firebase ui的适配器,并将其与firestore中的GeoHash相结合,我希望将特定查询返回到用户最近的位置 Main Activity.java public class MainActivity ex

我在网上搜索几个小时后写这篇文章。 我正在尝试使用动态路径获取firestore中的特定子集合。 我认为无论我做错了什么,都可能与异步任务有关,但我自己就是不知道如何解决这个问题

我尝试过使用AsyncTask类(如果我没有弄错的话,现在已经不推荐了),但它对我不起作用

我正在使用firebase ui的适配器,并将其与firestore中的GeoHash相结合,我希望将特定查询返回到用户最近的位置

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

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);
                            }
                        }
                    }
                });
    }
}
import com.google.firebase.firestore.Query;

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

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

我得到以下回应:

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) 
已编辑

对于使用动态查询(由其他类中的其他任务创建)获取数据有困难的人,以下是我的工作代码:

MainActivity.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);
        }
    }
}

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);
                            }
                        }
                    }
                });
    }
}
import com.google.firebase.firestore.Query;

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

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


公共类MainActivity扩展了AppCompatActivity{
FirebaseFirestore db=FirebaseFirestore.getInstance();
私人FirestoreRecyclerAdapter适配器;
私人RecyclerView mFirestoreList;
Query dishQuery=db.collection(“o”);
@凌驾
创建时受保护的void(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.home_活动);
mFirestoreList=findviewbyd(R.id.firestore\u列表);
FirestoreRecyclerOptions选项=新建FirestoreRecyclerOptions.Builder().setQuery(dishQuery,Dish.class.build();
适配器=新FirestoreRecyclerAdapter(选项){
@非空
@凌驾
public DishViewHolder onCreateViewHolder(@NonNull ViewGroup父级,int-viewType){
View View=LayoutInflater.from(parent.getContext()).flate(R.layout.list\u item\u single,parent,false);
返回新的洗碗机支架(视图);
}
@凌驾
public void onChildChanged(@NonNull changeentype类型,@NonNull DocumentSnapshot快照,int newIndex,int oldinex){
super.onChildChanged(类型、快照、新索引、旧索引);
adapter.notifyDataSetChanged();
}
@凌驾
BindViewHolder上的受保护无效(@NonNull DishViewHolder holder,int位置,@NonNull Dish model){
holder.name.setText(model.getName());
holder.starCount.setText(String.valueOf(model.getStarCount());
holder.isVegan.setText(String.valueOf(model.getIsVegan());
}
@凌驾
public void onError(@NonNull FirebaseFirestoreException e){
super.onError(e);
Log.e(“Error”,“got Error”+e.getMessage());
}
};
mFirestoreList.setHasFixedSize(true);
setLayoutManager(新的LinearLayoutManager(this));
mFirestoreList.setAdapter(适配器);
/////
新建getRightQuery().getQuery(新建回调()){
@凌驾
public void firebaseResponseCallback(查询q){
q、 addSnapshotListener(新的EventListener(){
@凌驾
public void onEvent(@Nullable QuerySnapshot值,@Nullable FirebaseFirestoreException错误){
adapter.stopListening();
adapter.updateOptions(新的FirestoreRecyclerOptions.Builder().setQuery(q,Dish.class.build());
adapter.startListening();
adapter.notifyDataSetChanged();
}
});
}
});
}
@凌驾
受保护的void onStop(){
super.onStop();
adapter.stopListening();
}
@凌驾
受保护的void onStart(){
super.onStart();
adapter.startListening();
}
私有类DishViewHolder扩展了RecyclerView.ViewHolder{
私有文本视图名称,starCount,isVegan;
公共碟架(@NonNull View itemView){
超级(项目视图);
name=itemView.findviewbyd(R.id.name);
starCount=itemView.findviewbyd(R.id.star\u count);
isVegan=itemView.findviewbyd(R.id.is_vegan);
}
}
}
我使用了一个“虚拟”查询来创建一个空的recyclerview(正如Alex Mamo在评论中所说的),然后填充数据并使用
adapter.updateOptions()

在哪一行代码中,您收到了该错误?我认为这一切都是因为这一行:
FirestoreRecyclerOptions选项=new FirestoreRecyclerOptions.Builder().setQuery(dishQuery,Dish.class.build()当我调试时,我可以看到
dishQuery
在这行中为null,“getRightQuery().getQuery()”返回什么?我想这就是问题所在。它返回null,因为它触发得太早,我希望它返回所需的查询,但无法确定如何签出。