ViewPager2调用当前Fragment内Adapter方法触发空指针异常如何解决
问题复现与根因
在ViewPager2+TabLayout架构中,调用当前选中页SettlementProductItemFragment的getTotalPriceInFragment()方法时触发NullPointerException,原因是:
- Fragment的Adapter仅在
onCreateView生命周期才会初始化 - 调用获取总价方法的时机早于Fragment渲染完成,此时
mAdapter还未被赋值 - ViewPager2的Fragment加载由内部Adapter托管,无需手动调用
beginTransaction().commit,但渲染存在异步延迟。
解决方案
以下方案按实现复杂度和可靠性排序,可按需选择:
方案1:数据与Adapter解耦(最优推荐)
当前总价依赖Adapter获取,本质是把业务数据存在了视图层组件里,直接把总价计算逻辑迁移到Fragment的业务层即可从根源避免空指针:
- 在Fragment内维护商品数据源和总价缓存,无需依赖Adapter实例
public class SettlementProductItemFragment extends Fragment { // 新增成员变量存储商品列表和总价 private List<Product> mProductList; private double mTotalPrice; public static SettlementProductItemFragment newInstance() { SettlementProductItemFragment fragment = new SettlementProductItemFragment(); // 初始化商品数据就存到成员变量里,不要只在getProducts()里临时生成 fragment.mProductList = getProducts(); // 提前计算总价 fragment.mTotalPrice = calculateTotalPrice(fragment.mProductList); Bundle args = new Bundle(); fragment.setArguments(args); return fragment; } public double getTotalPriceInFragment() { // 直接返回提前算好的总价,不依赖Adapter return mTotalPrice; } // 提取总价计算方法,Adapter和Fragment共用 private double calculateTotalPrice(List<Product> productList) { double total = 0; for (Product product : productList) { total += product.getPrice() * product.getCount(); } return total; } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_settlement_product_item_list, container, false); if (view instanceof RecyclerView) { Context context = view.getContext(); // 初始化Adapter时直接传入已有的mProductList mAdapter = new SettlementProductItemRecyclerViewAdapter(context, mProductList); mRvProductInFragment = (RecyclerView) view; mRvProductInFragment.setLayoutManager(new LinearLayoutManager(context)); mRvProductInFragment.setAdapter(mAdapter); // 商品数量变化时同步更新mTotalPrice即可 mAdapter.setOnProductCountChangedListener(() -> { mTotalPrice = calculateTotalPrice(mProductList); }); } return view; } }
方案2:监听Fragment就绪状态再调用方法
如果不想调整现有数据结构,可通过回调通知宿主Fragment已渲染完成,再调用获取总价方法:
- 在Fragment内定义就绪回调接口
public class SettlementProductItemFragment extends Fragment { public interface OnFragmentReadyListener { void onReady(SettlementProductItemFragment fragment); } private OnFragmentReadyListener mReadyListener; // 宿主注册监听 public void setOnFragmentReadyListener(OnFragmentReadyListener listener) { mReadyListener = listener; } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // 原有逻辑不变,Adapter初始化完成后触发回调 mRvProductInFragment.setAdapter(mAdapter); if (mReadyListener != null) { mReadyListener.onReady(this); } return view; } }
- 宿主创建Fragment时注册监听,收到回调后再操作
private boolean createFragment(int tabLimited) { if (mVp2Adapter.getItemCount() >= tabLimited) { return false; } String tabText = getTimeOfHMS(); SettlementProductItemFragment fragment = SettlementProductItemFragment.newInstance(); fragment.setOnFragmentReadyListener(readyFragment -> { // 在这里调用获取总价方法,保证Adapter已初始化 double total = readyFragment.getTotalPriceInFragment(); }); mVp2Adapter.addFragment(tabText, fragment); mTabSettlementProduct.selectTab(mTabSettlementProduct.getTabAt( mVp2Adapter.getItemCount() - 1)); return true; }
方案3:延迟调用时机,等待渲染完成
如果只是临时修复,可通过主线程任务队列延迟调用,等Fragment渲染完成后再执行:
mTabSettlementProduct.selectTab(mTabSettlementProduct.getTabAt(mVp2Adapter.getItemCount() - 1)); // 往主线程消息队列尾部插任务,等ViewPager2渲染完Fragment后才会执行 mTabSettlementProduct.post(() -> { SettlementProductItemFragment currentFragment = (SettlementProductItemFragment) mVp2Adapter.getCurrentFragment(); double total = currentFragment.getTotalPriceInFragment(); });
内容的提问来源于stack exchange,提问作者Jared DC
相关产品推荐
相关产品推荐

