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

Flutter中PaginationNotifier dispose后使用报错的解决问询

解决StateNotifierProvider.autoDispose异步请求完成后更新已销毁状态的错误

问题场景

使用StateNotifierProvider.autoDispose实现图片分页加载,通过滚动控制器监听滚动位置触发fetchNextBatch()加载更多。但当异步请求执行过程中调用navigator.pop()返回上一页时,会抛出以下错误:

[ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: Bad state: Tried to use PaginationNotifier after dispose was called.
Consider checking mounted.

原因是页面出栈后,autoDispose的Provider会立即销毁PaginationNotifier,但未完成的异步请求返回后仍尝试更新已销毁的状态。

解决方案

1. 在异步操作完成后检查mounted状态

在所有会更新state的异步流程中,每次await之后都先检查mounted状态,若已销毁则直接返回,避免执行状态更新操作。

修改后的PaginationNotifier关键代码:

Future<void> _fetchFirstBatch() async {
  try {
    _numberOfItemsInFolder = await (await PhotoManager.getAssetPathList()).firstWhere((element) => element.id == folderId).assetCountAsync;
    // 检查是否已销毁
    if (!mounted) return;
    
    state = const AsyncValue.loading();
    final List<Uint8List?> result = await _fetchNextItems();
    if (!mounted) return;
    
    _updateData(result);
  } catch (e, stk) {
    if (!mounted) return;
    state = AsyncValue.error(e, stk);
  }
}

Future<void> fetchNextBatch() async {
  if (_alreadyFetching || !_hasMoreItems) return;
  _alreadyFetching = true;

  // 先检查是否已销毁
  if (!mounted) {
    _alreadyFetching = false;
    return;
  }
  
  log("data updated");
  state = AsyncValue.data(_items);

  try {
    final result = await _fetchNextItems();
    if (!mounted) return;
    
    _updateData(result);
  } catch (e, stk) {
    if (!mounted) return;
    state = AsyncValue.error(e, stk);
    log("error catched");
  }

  _alreadyFetching = false;
}

void _updateData(List<Uint8List?> result) {
  if (!mounted) return;
  if (result.isEmpty) {
    state = AsyncValue.data(_items);
  } else {
    state = AsyncValue.data(_items..addAll(result));
  }
  _hasMoreItems = _numberOfItemsInFolder > _items.length;
}

2. 确保滚动监听中的mounted检查有效

你的滚动监听代码已经加入了ref.read(paginationImagesProvider.notifier).mounted的判断,这部分可以保留,确保只有在Notifier未销毁时才触发加载请求:

controller.addListener(() {
  double maxScroll = controller.position.maxScrollExtent;
  double position = controller.position.pixels;

  if ((position > maxScroll * 0.2 || position == 0) && ref.read(paginationImagesProvider.notifier).mounted) {
    ref.read(paginationImagesProvider.notifier).fetchNextBatch();
  }
});

3. 可选:取消未完成的异步请求(进阶优化)

如果需要彻底终止未完成的异步操作,可以使用CancelableOperation封装图片加载请求,在StateNotifier的dispose方法中取消操作:

class PaginationNotifier extends StateNotifier<AsyncValue<List<Uint8List?>>> {
  // 新增CancelableOperation用于管理异步请求
  CancelableOperation? _currentFetchOperation;

  // ... 其他成员变量和构造函数

  @override
  void dispose() {
    _currentFetchOperation?.cancel();
    super.dispose();
  }

  Future<List<Uint8List?>> _fetchNextItems() async {
    // 用CancelableOperation封装请求
    _currentFetchOperation = CancelableOperation.fromFuture(
      Future(() async {
        List<AssetEntity> images = (await (await PhotoManager.getAssetPathList())
            .firstWhere((element) => element.id == folderId)
            .getAssetListRange(start: _items.length, end: _items.length + itemsPerBatch));
        List<Uint8List?> newItems = [];
        for (AssetEntity image in images) {
          newItems.add(await image.thumbnailData);
        }
        return newItems;
      })
    );
    
    try {
      return await _currentFetchOperation!.value;
    } on OperationCanceledError {
      // 请求被取消时返回空列表或处理逻辑
      return [];
    } finally {
      _currentFetchOperation = null;
    }
  }
}

这个方案可以避免不必要的资源消耗,同时彻底杜绝异步请求完成后更新状态的可能。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 11:25:30