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

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;
    }
}
数据库结构

Marcacoes node
Servicos node

故障原因
  1. 异步执行顺序错误:Firebase查询为异步操作,当前代码在marcacao节点遍历完成后立刻调用notifyDataSetChanged,此时servicos节点的查询还未返回结果,列表刷新时servicosList为空。且servicos查询完成后没有调用列表刷新方法,数据不会二次更新。
  2. servicosList逻辑错误:每次查询单个servicos数据时都会清空整个servicosList,最终servicosList只会保留最后一次查询的servicos数据,和marcacaoList长度不匹配,适配器绑定时对应关系错乱。
  3. marcacao节点遍历层级错误:根据提供的数据库结构不需要三层循环遍历,多余的遍历会导致无法正确解析出Marcacao实体数据,Log无输出说明遍历逻辑没有执行。
  4. 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.04 14:54:00