如何在RecyclerView中通过HashMap从Firebase取值 解决列表项重复显示问题
现有实现的核心问题
出现所有列表项重复展示最后一条记录的现象,是三个逻辑错误共同导致的:
- 完全误解
FirebaseRecyclerAdapter的工作机制:该适配器本身已内置全量数据拉取、数据变更监听、列表位置与数据节点映射的能力,无需在onBindViewHolder中额外手动注册全量数据监听。 - 冗余的全量拉取逻辑是内容重复的直接诱因:每绑定一个列表项的Holder,就会触发一次全量查询,查询返回的所有记录会在循环中把字段值挨个设置给当前Holder,循环执行结束后Holder最终只会保留最后一条记录的内容。列表有多少个项,就会重复执行多少次这个全量覆盖流程,最终所有Holder都会被覆盖成最后一条记录的内容,和实际运行表现完全一致。
- 适配逻辑存在断层:目标是跳过实体类、用HashMap作为数据载体,但当前适配器的泛型仍硬绑定了
RiderModel.class,既未利用SDK自带的HashMap反序列化能力,又额外手动重复实现拉值逻辑,无任何收益还增加了性能损耗。
正确改造方案
核心调整逻辑
直接利用Firebase SDK原生支持的HashMap反序列化能力,完全移除手动拉数据的冗余代码,调整步骤如下:
- 删除
onBindViewHolder中所有query.addListenerForSingleValueEvent相关的全量拉取逻辑、空的HashMap遍历逻辑、成员变量里定义的空arrayList。 - 把适配器和FirebaseRecyclerOptions的泛型从
RiderModel改成HashMap,利用SDK自动把每个Trip节点的内容反序列化成对应位置的HashMap对象,不需要手动拆值。 - 直接从每个位置传入的HashMap对象中取对应字段值绑定到Holder上,避免全量遍历造成的Holder内容覆盖。
改造后核心代码示例
private void FetchHistoryInformation() { String user = FirebaseAuth.getInstance().getCurrentUser().getUid(); DatabaseReference tripRef = FirebaseDatabase.getInstance().getReference(Common.RIDER_INFO_REFERENCE).child(user).child("Trips"); Query query = tripRef.orderByChild("tripCount"); // 直接绑定HashMap作为数据类型,跳过实体类 FirebaseRecyclerOptions<HashMap<String, Object>> options = new FirebaseRecyclerOptions.Builder<HashMap<String, Object>>() .setLifecycleOwner(this) .setQuery(query, (Class<HashMap<String, Object>>)(Class<?>)HashMap.class) .build(); adapter = new FirebaseRecyclerAdapter<HashMap<String, Object>, CompletedTripAdapter>(options) { @Override protected void onBindViewHolder(@NonNull CompletedTripAdapter holder, int position, @NonNull HashMap<String, Object> model) { // 直接从当前位置对应的HashMap取值,无全量遍历 String pickup = (String) model.get("pickupLocation"); String destination = (String) model.get("destination"); Long timeStamp = (Long) model.get("timeStamp"); holder.txt_pickup.setText(pickup); holder.txt_destination.setText(destination); holder.txt_trip_status.setText(R.string.status_complete); holder.txt_trip_date.setText(UserUtils.getDate(timeStamp)); // 点击事件直接取当前位置的数据库Key,避免位置错位 holder.setItemClickListerner((view, pos) -> { String tripKey = getRef(position).getKey(); Toast.makeText(getActivity(), tripKey, Toast.LENGTH_SHORT).show(); Intent intent = new Intent(getActivity(), TripHistorySinglePage.class); intent.putExtra("destination", destination); startActivity(intent); }); } @NonNull @Override public CompletedTripAdapter onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { View view = LayoutInflater.from(parent.getContext()) .inflate(R.layout.item_completed_trips, parent, false); return new CompletedTripAdapter(view); } }; recycler_view.setAdapter(adapter); adapter.startListening(); }
额外注意事项
- 如果不想处理泛型擦除的强转逻辑,也可以定义一个无字段的空实体类(仅保留公共无参构造),SDK同样可以正常完成反序列化,比HashMap的类型强转更稳定,不会因为字段类型不匹配直接崩溃。
- 使用
orderByChild("tripCount")排序时,需要在Firebase Realtime Database的规则中为对应路径配置索引,否则会触发权限校验错误导致数据拉取失败。
内容的提问来源于stack exchange,提问作者The greatest
相关产品推荐
相关产品推荐

