Firestore嵌套查询:双事件监听器同步的正确实现方案
Firestore嵌套查询填充RecyclerView的正确实现方式
问题背景
需要用行程数据填充RecyclerView,需从Firestore两个集合取数:
users集合:存储所有用户完整数据trips集合:存储所有行程完整数据
每个行程包含driverId,需通过该ID关联获取司机详情,因此要执行嵌套查询,但现有代码存在两个核心问题:
- trip对象和trips列表需声明为类级final属性,代码可读性差
- 监听器异步执行,外部监听器在嵌套监听器完成前就已执行完毕,导致设置适配器时列表为空
原有不可用代码
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请求,可优化为批量查询:
- 先获取所有行程,收集所有
driverId并去重 - 一次查询所有司机数据(用
whereIn) - 将司机数据存入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
相关产品推荐
相关产品推荐

