You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Firestore嵌套查询:双事件监听器同步的正确实现方案

Firestore嵌套查询填充RecyclerView的正确实现方式

问题背景

需要用行程数据填充RecyclerView,需从Firestore两个集合取数:

  • users集合:存储所有用户完整数据
  • trips集合:存储所有行程完整数据

每个行程包含driverId,需通过该ID关联获取司机详情,因此要执行嵌套查询,但现有代码存在两个核心问题:

  1. trip对象和trips列表需声明为类级final属性,代码可读性差
  2. 监听器异步执行,外部监听器在嵌套监听器完成前就已执行完毕,导致设置适配器时列表为空

原有不可用代码

public class Trip {
    private String attribute1, attribute2, etc.
    private User driver;
    // 构造方法、getter、setter
}

public class User {
    private String id, username, email, etc.
    // 构造方法、getter、setter
}

// Fragment类中的方法
private void retrieveTrips(){
        database.collection("trips")
                .get()
                .addOnCompleteListener(task -> {
                    if(task.isSuccessful() && task.getResult() != null)
                    {
                        List<Trip> trips = new ArrayList<>();
                        for(QueryDocumentSnapshot queryDocumentSnapshot : task.getResult()){
                            Trip trip = new Trip();
                            trip.setTripUid(queryDocumentSnapshot.getId());
                            trip.setStartTime(queryDocumentSnapshot.getString("startTime"));
                            trip.setStartLocation(queryDocumentSnapshot.getString("startLocation"));
                            // 其他属性赋值
                            String driverId = queryDocumentSnapshot.getString("driverId");
        
                            // 通过driverId获取司机数据
                            Query driverQuery = database.collection("users").whereEqualTo("userId" , driverId);
                            driverQuery.get().addOnCompleteListener( task2 -> {
                                if (task2.isSuccessful() && !task2.getResult().isEmpty()) {
                                    for (QueryDocumentSnapshot document : task2.getResult()) {
                                        String driverUsername = document.getString("username");
                                        String driverEmail = document.getString("email");
                                        // 其他司机属性
                                        
                                        trip.setDriver(new User(driverId,driverUsername,driverEmail, ...));
                                    }
                                } else if(task2.getResult().isEmpty())
                                {
                                    // 未找到司机
                                }
                                else {
                                    // 查询失败
                                }}).addOnFailureListener(e -> {/* 错误处理 */});
                        }

                        if(!trips.isEmpty()){
                            TripAdapter tripAdapter = new TripAdapter(trips,this);
                            binding.availableTripsRecyclerView.setAdapter(tripAdapter);
                        } else{
                            showErrorMessage();
                        }
                    }
                    else{
                        // 行程查询失败
                    }
                });
}

正确实现方式

方式1:计数回调跟踪嵌套查询完成

核心思路是统计需要执行的嵌套查询总数,每完成一个查询就将计数减一,当计数归零时,再统一更新RecyclerView适配器。

private void retrieveTrips() {
    database.collection("trips")
            .get()
            .addOnCompleteListener(task -> {
                if (task.isSuccessful() && task.getResult() != null) {
                    List<Trip> trips = new ArrayList<>();
                    List<QueryDocumentSnapshot> tripDocs = task.getResult().getDocuments();
                    // 总查询数等于行程数量
                    AtomicInteger completedQueries = new AtomicInteger(tripDocs.size());

                    for (QueryDocumentSnapshot doc : tripDocs) {
                        Trip trip = new Trip();
                        trip.setTripUid(doc.getId());
                        trip.setStartTime(doc.getString("startTime"));
                        trip.setStartLocation(doc.getString("startLocation"));
                        // 其他行程属性赋值
                        String driverId = doc.getString("driverId");

                        // 优化:如果userId就是Firestore文档ID,直接用document()查询更高效,无需whereEqualTo
                        database.collection("users").document(driverId)
                                .get()
                                .addOnCompleteListener(driverTask -> {
                                    if (driverTask.isSuccessful() && driverTask.getResult() != null) {
                                        // 用Firestore对象映射简化属性赋值
                                        User driver = driverTask.getResult().toObject(User.class);
                                        trip.setDriver(driver);
                                    } else {
                                        // 司机查询失败的兜底处理
                                    }
                                    trips.add(trip);
                                    // 计数减一,判断是否全部查询完成
                                    if (completedQueries.decrementAndGet() == 0) {
                                        updateRecyclerView(trips);
                                    }
                                })
                                .addOnFailureListener(e -> {
                                    // 错误处理
                                    trips.add(trip); // 即使司机查询失败,也保留行程数据(可根据需求调整)
                                    if (completedQueries.decrementAndGet() == 0) {
                                        updateRecyclerView(trips);
                                    }
                                });
                    }

                    // 处理没有行程的情况
                    if (tripDocs.isEmpty()) {
                        showErrorMessage();
                    }
                } else {
                    // 行程查询失败处理
                    showErrorMessage();
                }
            });
}

private void updateRecyclerView(List<Trip> trips) {
    if (!trips.isEmpty()) {
        TripAdapter adapter = new TripAdapter(trips, this);
        binding.availableTripsRecyclerView.setAdapter(adapter);
    } else {
        showErrorMessage();
    }
}

方式2:Kotlin协程实现(项目支持Kotlin时推荐)

用协程可以将异步代码写得像同步代码,大幅提升可读性和维护性:

private suspend fun retrieveTrips() {
    try {
        val tripDocs = database.collection("trips").get().await()
        val trips = mutableListOf<Trip>()

        for (doc in tripDocs) {
            val trip = doc.toObject(Trip::class.java).apply {
                tripUid = doc.id
            }
            val driverId = doc.getString("driverId")
            driverId?.let {
                val driverDoc = database.collection("users").document(it).get().await()
                trip.driver = driverDoc.toObject(User::class.java)
            }
            trips.add(trip)
        }

        updateRecyclerView(trips)
    } catch (e: Exception) {
        showErrorMessage()
    }
}

private fun updateRecyclerView(trips: List<Trip>) {
    if (trips.isNotEmpty()) {
        val adapter = TripAdapter(trips, this)
        binding.availableTripsRecyclerView.adapter = adapter
    } else {
        showErrorMessage()
    }
}

// 在Fragment生命周期方法中调用(如onViewCreated)
lifecycleScope.launch {
    retrieveTrips()
}

优化建议:批量查询减少请求次数

如果行程数量较多,逐个查询司机会产生大量Firestore请求,可优化为批量查询:

  1. 先获取所有行程,收集所有driverId并去重
  2. 一次查询所有司机数据(用whereIn)
  3. 将司机数据存入Map,再逐个关联到对应行程
private void retrieveTrips() {
    database.collection("trips")
            .get()
            .addOnCompleteListener(tripTask -> {
                if (tripTask.isSuccessful() && tripTask.getResult() != null) {
                    List<Trip> trips = new ArrayList<>();
                    List<String> driverIds = new ArrayList<>();
                    Map<String, Trip> tripMap = new HashMap<>();

                    // 第一步:收集所有行程和对应的driverId
                    for (QueryDocumentSnapshot doc : tripTask.getResult()) {
                        Trip trip = new Trip();
                        trip.setTripUid(doc.getId());
                        trip.setStartTime(doc.getString("startTime"));
                        trip.setStartLocation(doc.getString("startLocation"));
                        // 其他属性赋值
                        String driverId = doc.getString("driverId");
                        if (driverId != null) {
                            driverIds.add(driverId);
                            tripMap.put(driverId, trip);
                        }
                        trips.add(trip);
                    }

                    if (driverIds.isEmpty()) {
                        // 没有司机需要查询,直接更新列表
                        updateRecyclerView(trips);
                        return;
                    }

                    // 第二步:批量查询所有司机
                    database.collection("users")
                            .whereIn("userId", driverIds)
                            .get()
                            .addOnCompleteListener(driverTask -> {
                                if (driverTask.isSuccessful() && driverTask.getResult() != null) {
                                    // 第三步:关联司机到对应的行程
                                    for (QueryDocumentSnapshot doc : driverTask.getResult()) {
                                        String driverId = doc.getString("userId");
                                        User driver = doc.toObject(User.class);
                                        Trip trip = tripMap.get(driverId);
                                        if (trip != null) {
                                            trip.setDriver(driver);
                                        }
                                    }
                                }
                                // 全部处理完成,更新RecyclerView
                                updateRecyclerView(trips);
                            })
                            .addOnFailureListener(e -> {
                                // 司机批量查询失败,仍更新行程列表(可根据需求调整)
                                updateRecyclerView(trips);
                            });
                } else {
                    showErrorMessage();
                }
            });
}

内容的提问来源于stack exchange,提问作者ngc-224

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 19:18:16