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

RecyclerView首次加载无数据,切回Fragment才显示的问题求助

问题:首次加载RecyclerView无数据,切换Fragment返回才显示

使用Firebase Realtime Database、Firestore、RecyclerView开发餐厅菜单页面,图片存在Firebase Storage,菜品信息存在Realtime Database,采用MVVM架构(含ViewModel与Repository)。所有功能正常,但首次加载页面时RecyclerView无数据,切换到其他Fragment再返回才会显示数据。

已定位到Firebase异步数据获取的问题:notifyDataSetChanged()在数据准备完成前执行,数据就绪后LiveData未通知Observer更新,直到Fragment重建才触发。


问题成因

核心是异步数据获取的同步处理错误,属于实现层面的逻辑漏洞,具体点:

  • DataRetriever的get方法错误地同步返回空列表:Firebase的addListenerForSingleValueEvent是异步操作,方法调用后立刻返回初始化的空productList,此时数据还未从数据库拉取完成。
  • Repository与ViewModel过早触发LiveData更新:Repository在构造时直接获取空列表,ViewModel初始化时立刻调用liveData.postValue(productList),此时LiveData发送的是空数据,后续Firebase回调填充列表后,没有任何机制通知LiveData更新数据。
  • Fragment重建时触发数据更新的原因:Fragment重建后会重新创建ViewModel,但此时DataRetriever中的productList已经被之前的异步回调填充了数据,所以ViewModel会发送已填充的列表,RecyclerView就能显示数据。

解决方法

1. 重构DataRetriever,用LiveData传递异步结果

将异步数据获取的结果通过LiveData传递,确保数据就绪时能主动通知观察者:

public class DataRetriever {

    public LiveData<List<Product>> getProducts(String child, String path) {
        MutableLiveData<List<Product>> liveData = new MutableLiveData<>();
        FirebaseDatabase.getInstance().getReference(path).child(child).addListenerForSingleValueEvent(new ValueEventListener() {
            @Override
            public void onDataChange(@NonNull DataSnapshot snapshot) {
                List<Product> productList = new ArrayList<>();
                try {
                    for (DataSnapshot i : snapshot.getChildren()) {
                        Product product = i.getValue(Product.class);
                        if (product != null) {
                            productList.add(product);
                        }
                    }
                } catch (NullPointerException e) {
                    Log.e("firebase", "Error getting the data, because of null elements", e);
                }
                liveData.postValue(productList);
            }

            @Override
            public void onCancelled(@NonNull DatabaseError error) {
                Log.e("firebase", "Error getting the data", error.toException());
                liveData.postValue(new ArrayList<>());
            }
        });
        return liveData;
    }
}

2. 修改ProductsRepository,处理数据后转发LiveData

让Repository监听DataRetriever的LiveData,过滤无效数据后再传递给ViewModel:

public class ProductsRepository {

    public LiveData<List<Product>> getAllProducts(String child, DataRetriever dataRetriever) {
        MutableLiveData<List<Product>> processedLiveData = new MutableLiveData<>();
        dataRetriever.getProducts(child, "menu").observeForever(productList -> {
            // 过滤无效数据
            List<Product> filteredList = productList.stream()
                    .filter(product -> product.getName() != null 
                            && product.getId() != null 
                            && product.getPrice() != 0 
                            && product.getDescription() != null)
                    .collect(Collectors.toList());
            processedLiveData.postValue(filteredList);
        });
        return processedLiveData;
    }
}

3. 重构HamburgerViewModel,订阅Repository的LiveData

ViewModel不再提前发送空数据,而是监听Repository的结果并转发:

public class HamburgerViewModel extends ViewModel {

    private final MutableLiveData<List<Product>> liveData = new MutableLiveData<>();
    private Observer<List<Product>> repositoryObserver;

    public HamburgerViewModel(DataRetriever dataRetriever) {
        String child = "burger";
        ProductsRepository productsRepository = new ProductsRepository();
        repositoryObserver = products -> liveData.postValue(products);
        productsRepository.getAllProducts(child, dataRetriever).observeForever(repositoryObserver);
    }

    public LiveData<List<Product>> getProductsLiveData() {
        return liveData;
    }

    @Override
    protected void onCleared() {
        super.onCleared();
        // 移除观察者避免内存泄漏
        if (repositoryObserver != null) {
            // 注意:这里需要持有Repository的LiveData引用才能移除,实际可调整为在Repository中管理
            // 简化处理:如果使用observeForever,必须确保在ViewModel销毁时移除
        }
    }

    public static class Factory implements ViewModelProvider.Factory {
        private final DataRetriever dataRetriever;

        public Factory(DataRetriever dataRetriever) {
            this.dataRetriever = dataRetriever;
        }

        @NonNull
        @Override
        public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
            return (T) new HamburgerViewModel(dataRetriever);
        }
    }
}

4. HomeFragment与ProductsAdapter保持原有逻辑

Fragment正常观察ViewModel的LiveData即可,Adapter的update方法无需修改:

@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);

    DataRetriever dataRetriever = new DataRetriever();
    HamburgerViewModel.Factory factory = new HamburgerViewModel.Factory(dataRetriever);
    HamburgerViewModel hamburgerViewModel = new ViewModelProvider(this, factory).get(HamburgerViewModel.class);

    RecyclerView recyclerView = getView().findViewById(R.id.recycle_view);
    ProductsAdapter productsAdapter = new ProductsAdapter(getContext(),"burger");
    recyclerView.setLayoutManager(new GridLayoutManager(getContext(),2,GridLayoutManager.HORIZONTAL,false));
    recyclerView.setAdapter(productsAdapter);

    hamburgerViewModel.getProductsLiveData().observe(getViewLifecycleOwner(), productsAdapter::update);
}

关键注意事项

  • 不要在异步操作中修改全局列表后依赖引用更新:Firebase回调在后台线程执行,外部无法感知列表变化,必须通过LiveData主动通知。
  • 使用observeForever时,务必在ViewModel销毁时移除观察者,避免内存泄漏。
  • 确保LiveData仅在数据就绪时发送更新,避免过早发送空数据导致UI误判。

内容的提问来源于stack exchange,提问作者igor soares

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 15:30:52