Firebase多节点引用数据无法在单个RecyclerView加载显示问题求助
问题描述
在Android开发中遇到无法从Firebase Realtime Database多个引用获取数据、并在单个RecyclerView展示的问题:
- 数据库结构:marcacao节点的idUtilizador字段与usuarios节点取值一致,idServico为Servicos节点的ID,需要在适配器中同时展示Servicos节点的preco、nomeServico字段,以及marcacao节点的data、hora字段。
- 异常现象:RecyclerView无任何数据展示,Logcat和运行窗口也无相关日志输出。
现有实现代码
//This is my fragment where i want to print the data. public class GestaoMarcacaoClienteFragment extends Fragment { private RecyclerView recyclerMarcacaoCliente; private Button botaoAddmarcacao; private GestaoMarcacoesClienteAdapter gestaoMarcacoesClienteAdapter; private List<Marcacao> marcacaoList = new ArrayList<>(); private List<Servicos> servicosList = new ArrayList<>(); private FirebaseAuth auth; private DatabaseReference marcacoesRef, servicosRef; // TODO: Rename parameter arguments, choose names that match // the fragment initialization parameters, e.g. ARG_ITEM_NUMBER private static final String ARG_PARAM1 = "param1"; private static final String ARG_PARAM2 = "param2"; // TODO: Rename and change types of parameters private String mParam1; private String mParam2; public GestaoMarcacaoClienteFragment() { // Required empty public constructor } /** * Use this factory method to create a new instance of * this fragment using the provided parameters. * * @param param1 Parameter 1. * @param param2 Parameter 2. * @return A new instance of fragment GestaoMarcacaoClienteFragment. */ // TODO: Rename and change types and number of parameters public static GestaoMarcacaoClienteFragment newInstance(String param1, String param2) { GestaoMarcacaoClienteFragment fragment = new GestaoMarcacaoClienteFragment(); Bundle args = new Bundle(); args.putString(ARG_PARAM1, param1); args.putString(ARG_PARAM2, param2); fragment.setArguments(args); return fragment; } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (getArguments() != null) { mParam1 = getArguments().getString(ARG_PARAM1); mParam2 = getArguments().getString(ARG_PARAM2); } } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // Inflate the layout for this fragment View view = inflater.inflate(R.layout.fragment_gestao_marcacao_cliente, container, false); auth = ConfiguracaoFirebase.getAuth(); botaoAddmarcacao = view.findViewById(R.id.botaoAddMarc); recyclerMarcacaoCliente = view.findViewById(R.id.recyclerMarcacaoCliente); marcacoesRef = ConfiguracaoFirebase.getReference(); servicosRef = ConfiguracaoFirebase.getReference(); //apanhar dados marcacoes String emailUser = auth.getCurrentUser().getEmail(); String emailEnc = Base64Custom.codificarBase64(emailUser); String data = DateCustom.dataFormatada(DateCustom.dataAtual()); marcacoesRef.child("marcacao").orderByChild("idUtilizador").equalTo(emailEnc).addValueEventListener(new ValueEventListener() { @Override public void onDataChange(@NonNull @NotNull DataSnapshot snapshot) { marcacaoList.clear(); for (DataSnapshot dados : snapshot.getChildren()) { for (DataSnapshot b : dados.getChildren()) { for(DataSnapshot d : b.getChildren()){ Marcacao m = d.getValue(Marcacao.class); String id = m.getIdServico(); Log.i("Dados", " Dados: " + dados); Log.i("Dados", " Dados: " + m.getData()); Log.i("Dados", " Dados: " + m.getHora()); Log.i("Dados", " Id: " + id); marcacaoList.add(m); servicosRef.child("servicos").orderByKey().equalTo(id).addValueEventListener(new ValueEventListener() { @Override public void onDataChange(@NonNull @NotNull DataSnapshot snapshot) { servicosList.clear(); for (DataSnapshot d : snapshot.getChildren()) { Servicos servicos = d.getValue(Servicos.class); servicosList.add(servicos); } } @Override public void onCancelled(@NonNull @NotNull DatabaseError error) { Toast.makeText(getContext(), error.getMessage(), Toast.LENGTH_SHORT).show(); } }); } } } gestaoMarcacoesClienteAdapter.notifyDataSetChanged(); } @Override public void onCancelled(@NonNull @NotNull DatabaseError error) { Toast.makeText(getContext(), error.getMessage(), Toast.LENGTH_SHORT).show(); } }); //Definir layout recycle view RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(getActivity().getApplicationContext()); recyclerMarcacaoCliente.setLayoutManager(layoutManager); recyclerMarcacaoCliente.setHasFixedSize(true); recyclerMarcacaoCliente.addItemDecoration(new SimpleDividerItemDecoration(getContext())); //definir adapter gestaoMarcacoesClienteAdapter = new GestaoMarcacoesClienteAdapter(marcacaoList, servicosList, getContext()); recyclerMarcacaoCliente.setAdapter(gestaoMarcacoesClienteAdapter); botaoAddmarcacao.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { AddMarcacaoFragment firstFragment = new AddMarcacaoFragment(); getActivity().getSupportFragmentManager().beginTransaction() .replace(R.id.frameGestaoMarcacoesCliente, firstFragment).commit(); } }); return view; } }
数据库结构


故障原因
- 异步执行顺序错误:Firebase查询为异步操作,当前代码在marcacao节点遍历完成后立刻调用
notifyDataSetChanged,此时servicos节点的查询还未返回结果,列表刷新时servicosList为空。且servicos查询完成后没有调用列表刷新方法,数据不会二次更新。 - servicosList逻辑错误:每次查询单个servicos数据时都会清空整个servicosList,最终servicosList只会保留最后一次查询的servicos数据,和marcacaoList长度不匹配,适配器绑定时对应关系错乱。
- marcacao节点遍历层级错误:根据提供的数据库结构不需要三层循环遍历,多余的遍历会导致无法正确解析出Marcacao实体数据,Log无输出说明遍历逻辑没有执行。
- servicos查询方式低效:使用
orderByKey().equalTo(id)的查询可以直接用child(id)查询指定节点,性能更高,也避免多余的遍历逻辑。
解决方案
步骤1:创建合并数据实体类
新增MarcacaoComServico.java,包含需要展示的全部字段:
public class MarcacaoComServico { private String data; private String hora; private String preco; private String nomeServico; // 按需添加其他需要的字段 // 生成对应的getter和setter方法 }
步骤2:修改Fragment查询逻辑
替换原有的查询代码,调整遍历和查询逻辑,所有关联查询完成后再刷新列表:
// 替换原有marcacoesRef的查询代码 List<MarcacaoComServico> marcacaoComServicoList = new ArrayList<>(); // 适配器改为使用marcacaoComServicoList作为数据源 marcacoesRef.child("marcacao").orderByChild("idUtilizador").equalTo(emailEnc).addValueEventListener(new ValueEventListener() { @Override public void onDataChange(@NonNull DataSnapshot snapshot) { marcacaoComServicoList.clear(); // 统计需要查询的servico总数,全部查询完成后再刷新列表 int totalMarcacao = (int) snapshot.getChildrenCount(); int[] completedCount = {0}; for (DataSnapshot dados : snapshot.getChildren()) { // 调整为和数据库结构匹配的遍历层级,示例为单层遍历,可根据实际结构调整 Marcacao m = dados.getValue(Marcacao.class); if(m == null) continue; String servicoId = m.getIdServico(); // 直接查询对应servico节点 servicosRef.child("servicos").child(servicoId).addListenerForSingleValueEvent(new ValueEventListener() { @Override public void onDataChange(@NonNull DataSnapshot snapshot) { Servicos servico = snapshot.getValue(Servicos.class); if(servico != null){ MarcacaoComServico item = new MarcacaoComServico(); item.setData(m.getData()); item.setHora(m.getHora()); item.setPreco(servico.getPreco()); item.setNomeServico(servico.getNomeServico()); marcacaoComServicoList.add(item); } completedCount[0]++; // 所有关联查询完成后刷新列表 if(completedCount[0] == totalMarcacao){ gestaoMarcacoesClienteAdapter.notifyDataSetChanged(); } } @Override public void onCancelled(@NonNull DatabaseError error) { completedCount[0]++; if(completedCount[0] == totalMarcacao){ gestaoMarcacoesClienteAdapter.notifyDataSetChanged(); } Toast.makeText(getContext(), error.getMessage(), Toast.LENGTH_SHORT).show(); } }); } } @Override public void onCancelled(@NonNull DatabaseError error) { Toast.makeText(getContext(), error.getMessage(), Toast.LENGTH_SHORT).show(); } });
步骤3:修改适配器逻辑
适配器改为使用MarcacaoComServico列表作为数据源,直接从单列表绑定数据,不需要再维护两个独立列表。
内容的提问来源于stack exchange,提问作者Guilherme Golaio
相关产品推荐
相关产品推荐

