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

BlocListener未更新排查:修改activate()后监听失效问题

BLoC监听失效问题:从缓存取数据后BlocListener不触发

问题描述

修改SelectMultipleCubit的activate()方法后,UI中的BlocListener不再响应SelectMultipleActivated状态。原版本从数据库异步获取数据,监听正常;修改后改为从folderViewCubit的缓存cachedCurrentlyDisplayedFiles同步取数据,即便activate()已emit目标状态,监听代码仍不执行。

修改后的activate()方法

void activate(ListItem selectedItem) async {
  emit(SelectMultipleLoading());

  try {
    // 获取选中项的父ID和类型
    String parentId;
    dynamic selectedObject;

    if (selectedItem.item is Folder) {
      parentId = (selectedItem.item as Folder).parentId;
      selectedObject = selectedItem.item as Folder;
    } else if (selectedItem.item is Receipt) {
      parentId = (selectedItem.item as Receipt).parentId;
      selectedObject = selectedItem.item as Receipt;
    } else {
      emit(SelectMultipleError());
      return;
    }

    List<Object> items = List.from(folderViewCubit.cachedCurrentlyDisplayedFiles);

    // 移除选中项
    items.removeWhere((element) {
      if (element is Folder && selectedObject is Folder) {
        return element.id == selectedObject.id;
      } else if (element is Receipt && selectedObject is Receipt) {
        return element.id == selectedObject.id;
      }
      return false;
    });

    print('items: ${items.length}');

    // 转换为ListItem列表
    List<ListItem> listItems =
        items.map((item) => ListItem(item: item)).toList();

    print('listItems: ${listItems.length}');

    emit(SelectMultipleActivated(
        items: listItems, initiallySelectedItem: selectedItem));
  } on Exception catch (e) {
    print(e.toString());
    emit(SelectMultipleError());
  }
}

原版本activate()方法(从数据库获取数据)

void activate(ListItem selectedItem) async {
  emit(SelectMultipleLoading());

  try {
    // 获取选中项的父ID和类型
    String parentId;
    dynamic selectedObject;

    if (selectedItem.item is Folder) {
      parentId = (selectedItem.item as Folder).parentId;
      selectedObject = selectedItem.item as Folder;
    } else if (selectedItem.item is Receipt) {
      parentId = (selectedItem.item as Receipt).parentId;
      selectedObject = selectedItem.item as Receipt;
    } else {
      emit(SelectMultipleError());
      return;
    }

    // 获取排序配置
    final String lastColumn = _prefs.getLastColumn();
    final String lastOrder = _prefs.getLastOrder();

    List<Object> items;

    switch (lastColumn) {
      case 'price':
        final List<FolderWithPrice> foldersWithPrice = await DatabaseRepository.instance.getFoldersByPrice(parentId, lastOrder);
        final List<ReceiptWithPrice> receiptsWithPrices = await DatabaseRepository.instance.getReceiptsByPrice(parentId, lastOrder);
        items = [...foldersWithPrice, ...receiptsWithPrices];
        break;
      case 'storageSize':
        final List<FolderWithSize> foldersWithSize = await DatabaseRepository.instance.getFoldersByTotalReceiptSize(parentId, lastOrder);
        final List<ReceiptWithSize> receiptsWithSize =  await DatabaseRepository.instance.getReceiptsBySize(parentId, lastOrder);
        items = [...foldersWithSize, ...receiptsWithSize];
        break;
      default:
        final List<Folder> folders = await DatabaseRepository.instance.getFoldersInFolderSortedBy(parentId, lastColumn, lastOrder);
        final List<Receipt> receipts = await DatabaseRepository.instance.getReceiptsInFolderSortedBy(parentId, lastColumn, lastOrder);
        items = [...folders, ...receipts];
        break;
    }

    // 移除选中项
    items.removeWhere((element) {
      if (element is Folder && selectedObject is Folder) {
        return element.id == selectedObject.id;
      } else if (element is Receipt && selectedObject is Receipt) {
        return element.id == selectedObject.id;
      }
      return false;
    });

    // 转换为ListItem列表
    List<ListItem> listItems =
        items.map((item) => ListItem(item: item)).toList();

    emit(SelectMultipleActivated(
        items: listItems, initiallySelectedItem: selectedItem));
  } on Exception catch (e) {
    // 异常处理逻辑
    emit(SelectMultipleError());
  }
}

UI中的BlocListener代码

@override
Widget build(BuildContext context) {
  return BlocListener<SelectMultipleCubit, SelectMultipleState>(
    listener: (context, state) {
      if (state is SelectMultipleActivated) {
        // 仅在SelectMultipleView初始化时执行一次
        final initiallySelectedListItem = state.initiallySelectedItem;
        final restOfListItems = state.items;
        // 添加选中项到当前选中列表
        currentlySelectedListItemsNotifier.value.add(initiallySelectedListItem);
        // 添加选中项到全量列表
        allItems.add(initiallySelectedListItem);
        // 添加剩余内容到全量列表
        allItems.addAll(restOfListItems);
      }
      if (state is SelectMultipleActionSuccess) {
        Navigator.of(context).pop();
      }
    },
    // 其他Widget代码...
  );
}

问题原因及解决方案

核心原因

原版本中的异步数据库操作会让事件循环有时间处理状态更新,确保BlocListener能接收到每一次状态变化。而修改后的版本全为同步逻辑,emit(SelectMultipleLoading())后立刻同步执行完所有代码并emitSelectMultipleActivated,两个状态更新处于同一个事件循环周期,BlocListener可能只处理最后一个状态,或因状态更新过快跳过监听触发。

另外需排查:

  • 状态相等性:若SelectMultipleActivated未正确重写==和hashCode,BLoC会认为新状态与旧状态相同,不触发监听。
  • 缓存数据有效性:确认cachedCurrentlyDisplayedFiles的元素类型、内容与原数据库返回一致,避免removeWhere逻辑失效导致状态异常。

解决方案

  1. 拆分事件循环周期:在同步逻辑中加入微任务,让最终状态的emit进入下一个事件循环,确保前一个状态被处理:

    // 在emit SelectMultipleActivated前添加
    await Future.microtask(() {});
    emit(SelectMultipleActivated(
        items: listItems, initiallySelectedItem: selectedItem));
    
  2. 正确实现状态的相等性判断:

    import 'package:flutter/foundation.dart';
    
    class SelectMultipleActivated extends SelectMultipleState {
      final List<ListItem> items;
      final ListItem initiallySelectedItem;
    
      SelectMultipleActivated({required this.items, required this.initiallySelectedItem});
    
      @override
      bool operator ==(Object other) =>
          identical(this, other) ||
          other is SelectMultipleActivated &&
              runtimeType == other.runtimeType &&
              listEquals(items, other.items) &&
              initiallySelectedItem == other.initiallySelectedItem;
    
      @override
      int get hashCode => items.hashCode ^ initiallySelectedItem.hashCode;
    }
    
  3. 验证缓存数据:打印cachedCurrentlyDisplayedFiles内容,确认removeWhere逻辑正确执行,生成的listItems符合预期。

内容的提问来源于stack exchange,提问作者N.K.

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 21:10:04