《Flutter全栈开发实战指南:从零到高级》- 12 -状态管理Bloc

Bloc状态管理

为什么我的Flutter应用越来越难维护?

记得刚接触Flutter时,觉得setState简直太方便了。但随着项目规模扩大,问题也逐渐暴漏出来:

问题1:状态分散难以管理

dart 复制代码
// 不推荐
class ProductPage extends StatefulWidget {
  @override
  _ProductPageState createState() => _ProductPageState();
}

class _ProductPageState extends State<ProductPage> {
  Product? _product;
  bool _isLoading = false;
  String? _errorMessage;
  bool _isFavorite = false;
  bool _isInCart = false;
  
  // 各种异步方法混在一起
  Future<void> _loadProduct() async {
    setState(() => _isLoading = true);
    try {
      _product = await repository.getProduct();
      _isFavorite = await repository.checkFavorite();
      _isInCart = await repository.checkCart();
    } catch (e) {
      _errorMessage = e.toString();
    } finally {
      setState(() => _isLoading = false);
    }
  }
}

问题2:跨组件状态共享困难

dart 复制代码
// 用户登录后,需要同步更新多个组件
class Header extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // 如何获取用户状态?
  }
}

class ProfilePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // 如何获取用户状态?
  }
}

class Sidebar extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // 如何获取用户状态?
  }
}

问题3:业务逻辑与UI耦合

dart 复制代码
// 业务逻辑分散在UI层,难以测试和维护
void _onAddToCart() async {
  // 验证登录状态
  // 检查库存
  // 调用API
  // 更新本地状态
  // 显示结果提示
  // 所有这些逻辑都混在一起!
}

面对这些问题,进行了多种状态管理方案尝试,最终发现Bloc提供了最清晰的架构和最佳的可维护性。

一、Bloc核心原理:单向数据流

1.1 Bloc

Bloc的核心思想可以用一句话概括:UI只关心显示什么,不关心为什么这样显示

1.2 Bloc架构图

先通过一个完整的架构图来理解Bloc的各个组成部分:

graph TB subgraph "UI Layer (表示层)" A[Widgets] --> B[发送 Events] C[BlocBuilder] --> D[重建 UI] E[BlocListener] --> F[处理副作用] end subgraph "Bloc Layer (业务逻辑层)" B --> G[Bloc] G --> H[States] H --> C H --> E subgraph "Bloc内部结构" G --> I[Event Handler] I --> J[业务逻辑] J --> K[State Emitter] K --> H end end subgraph "Data Layer (数据层)" J --> L[Repository] L --> M[Local Data] L --> N[Remote Data] M --> O[SQLite/SharedPrefs] N --> P[API/Network] end style G fill:#e1f5fe style J fill:#f3e5f5 style L fill:#e8f5e8

架构分层详解:

层级 职责 对应代码
UI层 显示界面、用户交互 Widget、BlocBuilder、BlocListener
Bloc层 处理业务逻辑、状态管理 Bloc、Cubit、Event、State
数据层 数据获取和持久化 Repository、DataSource、Model

1.3 数据流向原理

Bloc采用严格的单向数据流,这是它可预测性的关键:

sequenceDiagram participant U as UI Widget participant B as Bloc participant R as Repository participant S as State U->>B: 发送 Event Note over B: 处理业务逻辑 B->>R: 调用数据方法 R->>B: 返回数据结果 B->>S: 发射新 State S->>U: 触发重建 Note over U: 根据State显示界面

数据流特点:

  1. 单向性:数据只能沿一个方向流动
  2. 可预测:相同的Event总是产生相同的State变化
  3. 可追踪:可以清晰追踪状态变化的完整路径

二、Bloc核心概念

2.1 Event(事件)

Event代表从UI层发送到Bloc的"指令",它描述了"要做什么",但不关心"怎么做"。

Event设计原则
dart 复制代码
// 好的Event设计
abstract class ProductEvent {}

// 具体的事件 - 使用命名构造函数
class ProductEvent {
  const ProductEvent._();
  
  factory ProductEvent.load(String productId) = ProductLoadEvent;
  factory ProductEvent.addToCart(String productId, int quantity) = ProductAddToCartEvent;
  factory ProductEvent.toggleFavorite(String productId) = ProductToggleFavoriteEvent;
}

// 具体的事件类
class ProductLoadEvent extends ProductEvent {
  final String productId;
  const ProductLoadEvent(this.productId);
}

class ProductAddToCartEvent extends ProductEvent {
  final String productId;
  final int quantity;
  const ProductAddToCartEvent(this.productId, this.quantity);
}

class ProductToggleFavoriteEvent extends ProductEvent {
  final String productId;
  const ProductToggleFavoriteEvent(this.productId);
}
Event分类策略

在实际项目中,我会这样组织Event:

matlab 复制代码
events/
├── product_event.dart
├── cart_event.dart
├── auth_event.dart
└── order_event.dart

2.2 State(状态)

State代表应用在某个时刻的完整状况,UI完全由State驱动。

State设计模式
dart 复制代码
// 状态基类
sealed class ProductState {
  const ProductState();
}

// 具体的状态类
class ProductInitialState extends ProductState {
  const ProductInitialState();
}

class ProductLoadingState extends ProductState {
  const ProductLoadingState();
}

class ProductLoadedState extends ProductState {
  final Product product;
  final bool isInCart;
  final bool isFavorite;
  
  const ProductLoadedState({
    required this.product,
    required this.isInCart,
    required this.isFavorite,
  });
  
  // 复制方法 - 用于不可变更新
  ProductLoadedState copyWith({
    Product? product,
    bool? isInCart,
    bool? isFavorite,
  }) {
    return ProductLoadedState(
      product: product ?? this.product,
      isInCart: isInCart ?? this.isInCart,
      isFavorite: isFavorite ?? this.isFavorite,
    );
  }
}

class ProductErrorState extends ProductState {
  final String message;
  final Object? error;
  
  const ProductErrorState(this.message, [this.error]);
}
State状态机模型

理解State之间的关系很重要,它们形成一个状态机:

stateDiagram-v2 [*] --> Initial: 初始化 Initial --> Loading: 开始加载 Loading --> Loaded: 加载成功 Loading --> Error: 加载失败 Loaded --> Loading: 重新加载 Loaded --> Updating: 开始更新 Updating --> Loaded: 更新成功 Updating --> Error: 更新失败 Error --> Loading: 重试 Error --> [*]: 重置

State设计要点:

  • 包含UI需要的所有数据
  • 使用final和const
  • 便于调试和持久化
  • 清晰区分加载、成功、错误等状态

2.3 Bloc

Bloc是连接Event和State的桥梁,包含所有的业务逻辑。

Bloc核心结构
dart 复制代码
class ProductBloc extends Bloc<ProductEvent, ProductState> {
  final ProductRepository repository;
  
  ProductBloc({required this.repository}) : super(const ProductInitialState()) {
    // 注册事件处理器
    on<ProductLoadEvent>(_onLoad);
    on<ProductAddToCartEvent>(_onAddToCart);
    on<ProductToggleFavoriteEvent>(_onToggleFavorite);
  }
  
  // 事件处理方法的详细实现
  Future<void> _onLoad(
    ProductLoadEvent event,
    Emitter<ProductState> emit,
  ) async {
    try {
      emit(const ProductLoadingState());
      
      // 并行获取多个数据
      final results = await Future.wait([
        repository.getProduct(event.productId),
        repository.isInCart(event.productId),
        repository.isFavorite(event.productId),
      ]);
      
      final product = results[0] as Product;
      final isInCart = results[1] as bool;
      final isFavorite = results[2] as bool;
      
      emit(ProductLoadedState(
        product: product,
        isInCart: isInCart,
        isFavorite: isFavorite,
      ));
    } catch (error, stackTrace) {
      // 详细的错误处理
      emit(ProductErrorState(
        '加载商品失败',
        error,
      ));
      addError(error, stackTrace);
    }
  }
  
  Future<void> _onAddToCart(
    ProductAddToCartEvent event,
    Emitter<ProductState> emit,
  ) async {
    final currentState = state;
    
    // 状态保护
    if (currentState is! ProductLoadedState) return;
    
    try {
      emit(currentState.copyWith(isInCart: true));
      
      await repository.addToCart(event.productId, event.quantity);
    } catch (error) {
      emit(currentState.copyWith(isInCart: false));
      rethrow;
    }
  }
  
  Future<void> _onToggleFavorite(
    ProductToggleFavoriteEvent event,
    Emitter<ProductState> emit,
  ) async {
    final currentState = state;
    if (currentState is! ProductLoadedState) return;
    
    final newFavoriteStatus = !currentState.isFavorite;
    
    try {
      emit(currentState.copyWith(isFavorite: newFavoriteStatus));
      await repository.toggleFavorite(event.productId);
    } catch (error) {
      emit(currentState.copyWith(isFavorite: !newFavoriteStatus));
      rethrow;
    }
  }
}
Bloc内部工作原理

下面我们深入了解Bloc如何处理事件和状态:

graph TB A[Event输入] --> B[Event队列] B --> C[事件循环] subgraph "事件处理流程" C --> D{查找事件处理器} D --> E[找到处理器] E --> F[执行业务逻辑] F --> G[状态发射器] G --> H[状态输出] D --> I[无处理器] I --> J[忽略事件] end H --> K[State流] K --> L[UI更新] style F fill:#f3e5f5 style G fill:#e1f5fe

三、BlocBuilder与BlocListener

3.1 BlocBuilder

BlocBuilder监听状态变化并重建对应的UI部分。

基本使用模式
dart 复制代码
class ProductPage extends StatelessWidget {
  final String productId;
  
  const ProductPage({super.key, required this.productId});
  
  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (context) => ProductBloc(
        repository: context.read<ProductRepository>(),
      )..add(ProductEvent.load(productId)),
      child: Scaffold(
        appBar: AppBar(title: const Text('商品详情')),
        body: const _ProductContent(),
      ),
    );
  }
}

class _ProductContent extends StatelessWidget {
  const _ProductContent();
  
  @override
  Widget build(BuildContext context) {
    return BlocBuilder<ProductBloc, ProductState>(
      builder: (context, state) {
        return switch (state) {
          ProductInitialState() => _buildInitialView(),
          ProductLoadingState() => _buildLoadingView(),
          ProductLoadedState(
            product: final product,
            isInCart: final isInCart,
            isFavorite: final isFavorite,
          ) => _buildProductView(product, isInCart, isFavorite, context),
          ProductErrorState(message: final message) => _buildErrorView(message),
        };
      },
    );
  }
  
  Widget _buildProductView(
    Product product,
    bool isInCart,
    bool isFavorite,
    BuildContext context,
  ) {
    return SingleChildScrollView(
      padding: const EdgeInsets.all(16),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          // 商品图片
          AspectRatio(
            aspectRatio: 1,
            child: Image.network(
              product.imageUrl,
              fit: BoxFit.cover,
            ),
          ),
          
          const SizedBox(height: 16),
          
          // 商品信息
          Text(
            product.name,
            style: Theme.of(context).textTheme.headlineSmall,
          ),
          
          const SizedBox(height: 8),
          
          // 价格
          Text(
            '¥${product.price}',
            style: Theme.of(context).textTheme.headlineMedium?.copyWith(
              color: Colors.red,
            ),
          ),
          
          const SizedBox(height: 16),
          
          // 描述
          Text(
            product.description,
            style: Theme.of(context).textTheme.bodyMedium,
          ),
          
          const SizedBox(height: 24),
          
          // 操作按钮区域
          _buildActionButtons(product, isInCart, isFavorite, context),
        ],
      ),
    );
  }
  
  Widget _buildActionButtons(
    Product product,
    bool isInCart,
    bool isFavorite,
    BuildContext context,
  ) {
    return Row(
      children: [
        // 收藏按钮
        IconButton(
          icon: Icon(
            isFavorite ? Icons.favorite : Icons.favorite_border,
            color: isFavorite ? Colors.red : Colors.grey,
          ),
          onPressed: () {
            context.read<ProductBloc>().add(
              ProductEvent.toggleFavorite(product.id),
            );
          },
        ),
        
        const Spacer(),
        
        // 购物车按钮
        FilledButton.icon(
          icon: const Icon(Icons.shopping_cart),
          label: Text(isInCart ? '已加购' : '加入购物车'),
          onPressed: isInCart ? null : () {
            context.read<ProductBloc>().add(
              ProductEvent.addToCart(product.id, 1),
            );
          },
        ),
      ],
    );
  }
  
  Widget _buildLoadingView() {
    return const Center(
      child: CircularProgressIndicator(),
    );
  }
  
  Widget _buildErrorView(String message) {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          const Icon(Icons.error_outline, size: 64, color: Colors.red),
          const SizedBox(height: 16),
          Text('加载失败: $message'),
        ],
      ),
    );
  }
  
  Widget _buildInitialView() {
    return const Center(
      child: Text('准备加载商品信息...'),
    );
  }
}
BlocBuilder性能优化
dart 复制代码
// 不推荐 - 整个页面重建
BlocBuilder<ProductBloc, ProductState>(
  builder: (context, state) {
    return Scaffold(
      appBar: AppBar(title: Text('商品')), // 每次重建
      body: _buildBody(state), // 每次重建
    );
  },
)

// 推荐 - 局部重建
Scaffold(
  appBar: const AppBar(title: Text('商品')), // 不重建
  body: BlocBuilder<ProductBloc, ProductState>(
    builder: (context, state) {
      return _buildBody(state); // 只有这部分重建
    },
  ),
)

Column(
  children: [
    const Header(), 
    BlocBuilder<ProductBloc, ProductState>(
      builder: (context, state) {
        return ProductImage(state.product.imageUrl);
      },
    ),
    BlocBuilder<ProductBloc, ProductState>(
      builder: (context, state) {
        return ProductInfo(state.product); 
      },
    ),
    BlocBuilder<ProductBloc, ProductState>(
      builder: (context, state) {
        return ActionButtons(state); 
      },
    ),
  ],
)

3.2 BlocListener

BlocListener用于响应状态变化执行一次性操作,如导航、显示对话框等。

处理模式
dart 复制代码
class _ProductContent extends StatelessWidget {
  const _ProductContent();
  
  @override
  Widget build(BuildContext context) {
    return BlocListener<ProductBloc, ProductState>(
      listener: (context, state) {
        // 处理错误状态
        if (state is ProductErrorState) {
          ScaffoldMessenger.of(context).showSnackBar(
            SnackBar(
              content: Text(state.message),
              backgroundColor: Colors.red,
            ),
          );
        }
        
        // 处理成功加入购物车
        if (state is ProductLoadedState && state.isInCart) {
          ScaffoldMessenger.of(context).showSnackBar(
            const SnackBar(
              content: Text('成功加入购物车!'),
              backgroundColor: Colors.green,
            ),
          );
        }
        
        // 处理特定业务逻辑
        _handleSpecialStates(state, context);
      },
      child: BlocBuilder<ProductBloc, ProductState>(
        builder: (context, state) {
          // UI构建逻辑
          return _buildContent(state);
        },
      ),
    );
  }
  
  void _handleSpecialStates(ProductState state, BuildContext context) {
    switch (state) {
      case ProductLoadedState(:final product) when product.stock < 10:
        // 库存不足提示
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(
            content: Text('${product.name} 库存紧张!'),
            backgroundColor: Colors.orange,
          ),
        );
      case ProductLoadedState(:final product) when product.isNew:
        // 新品提示
        _showNewProductDialog(context, product);
      default:
        break;
    }
  }
  
  void _showNewProductDialog(BuildContext context, Product product) {
    showDialog(
      context: context,
      builder: (context) => AlertDialog(
        title: const Text('新品上架!'),
        content: Text('${product.name} 是刚刚上架的新品!'),
        actions: [
          TextButton(
            onPressed: () => Navigator.of(context).pop(),
            child: const Text('知道了'),
          ),
        ],
      ),
    );
  }
}

3.3 BlocConsumer

当需要同时使用Builder和Listener时,BlocConsumer提供了更简洁的写法。

dart 复制代码
BlocConsumer<ProductBloc, ProductState>(
  listener: (context, state) {
    // 处理副作用
    if (state is ProductErrorState) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text(state.message)),
      );
    }
  },
  builder: (context, state) {
    // 构建UI
    return switch (state) {
      ProductLoadedState(:final product) => ProductDetails(product: product),
      _ => const LoadingIndicator(),
    };
  },
)

四、 多Bloc协作模式

dart 复制代码
class AddToCartButton extends StatelessWidget {
  final String productId;
  
  const AddToCartButton({super.key, required this.productId});
  
  @override
  Widget build(BuildContext context) {
    return BlocListener<CartBloc, CartState>(
      listener: (context, state) {
        // 监听购物车状态变化
        if (state is CartErrorState) {
          ScaffoldMessenger.of(context).showSnackBar(
            SnackBar(content: Text(state.message)),
          );
        }
      },
      child: BlocBuilder<ProductBloc, ProductState>(
        builder: (context, productState) {
          final isInCart = switch (productState) {
            ProductLoadedState(:final isInCart) => isInCart,
            _ => false,
          };
          
          return FilledButton(
            onPressed: isInCart ? null : () {
              // 同时更新商品状态和购物车状态
              context.read<ProductBloc>().add(
                ProductEvent.addToCart(productId, 1),
              );
              context.read<CartBloc>().add(
                CartAddItemEvent(productId, 1),
              );
            },
            child: Text(isInCart ? '已加入购物车' : '加入购物车'),
          );
        },
      ),
    );
  }
}

4.1 Bloc间通信模式

方式1:直接事件传递
dart 复制代码
// 在商品Bloc中监听购物车事件
class ProductBloc extends Bloc<ProductEvent, ProductState> {
  final CartBloc cartBloc;
  
  ProductBloc({required this.cartBloc}) : super(const ProductInitialState()) {
    // 监听购物车变化
    cartBloc.stream.listen((cartState) {
      if (cartState is CartLoadedState && state is ProductLoadedState) {
        // 同步购物车状态
        final isInCart = cartState.items.any(
          (item) => item.productId == (state as ProductLoadedState).product.id,
        );
        add(ProductSyncCartEvent(isInCart));
      }
    });
  }
}
方式2:通过Repository共享状态
dart 复制代码
class CartRepository {
  final StreamController<Cart> _cartController = StreamController.broadcast();
  
  Stream<Cart> get cartStream => _cartController.stream;
  
  Future<void> addItem(String productId, int quantity) async {
    // 添加商品逻辑...
    _cartController.add(updatedCart);
  }
}

// 多个Bloc监听同一个Repository
class ProductBloc extends Bloc<ProductEvent, ProductState> {
  final CartRepository cartRepository;
  StreamSubscription? _cartSubscription;
  
  ProductBloc({required this.cartRepository}) : super(const ProductInitialState()) {
    // 监听购物车变化
    _cartSubscription = cartRepository.cartStream.listen((cart) {
      if (state is ProductLoadedState) {
        final isInCart = cart.items.any(
          (item) => item.productId == (state as ProductLoadedState).product.id,
        );
        add(ProductSyncCartEvent(isInCart));
      }
    });
  }
  
  @override
  Future<void> close() {
    _cartSubscription?.cancel();
    return super.close();
  }
}

4.2 高级模式:Bloc转换器和并发控制

dart 复制代码
class ProductBloc extends Bloc<ProductEvent, ProductState> {
  ProductBloc() : super(const ProductInitialState()) {
    on<ProductEvent>(
      _onEvent,
      // 转换器配置
      transformer: (events, mapper) {
        return events
            .debounceTime(const Duration(milliseconds: 300)) // 防抖
            .asyncExpand(mapper); // 并发控制
      },
    );
  }
  
  Future<void> _onEvent(
    ProductEvent event,
    Emitter<ProductState> emit,
  ) async {
    // 事件处理逻辑
  }
}

五、项目结构

5.1 完整的项目结构

bash 复制代码
lib/
├── src/
│   ├── app/                    # 应用层
│   │   ├── app.dart
│   │   └── routes/
│   ├── features/               # 功能模块
│   │   ├── product/
│   │   │   ├── bloc/          # Bloc相关
│   │   │   │   ├── product_bloc.dart
│   │   │   │   ├── product_event.dart
│   │   │   │   ├── product_state.dart
│   │   │   │   └── product_bloc.freezed.dart
│   │   │   ├── views/         # 页面
│   │   │   ├── widgets/       # 组件
│   │   │   └── models/        # 模型
│   │   ├── cart/
│   │   └── auth/
│   ├── core/                   # 核心层
│   │   ├── bloc/              # Bloc基础设施
│   │   │   ├── app_bloc_observer.dart
│   │   │   └── bloc_providers.dart
│   │   ├── data/              # 数据层
│   │   │   ├── repositories/
│   │   │   ├── datasources/
│   │   │   └── models/
│   │   ├── di/                # 依赖注入
│   │   │   └── service_locator.dart
│   │   └── utils/             # 工具类
│   └── shared/                # 共享资源
│       ├── widgets/
│       ├── themes/
│       └── constants/
└── main.dart

5.2 依赖注入配置

dart 复制代码
// service_locator.dart
final getIt = GetIt.instance;

void setupDependencies() {
  // 数据层
  getIt.registerLazySingleton<ProductRepository>(
    () => ProductRepositoryImpl(
      localDataSource: getIt(),
      remoteDataSource: getIt(),
    ),
  );
  
  getIt.registerLazySingleton<CartRepository>(
    () => CartRepositoryImpl(
      localDataSource: getIt(),
      remoteDataSource: getIt(),
    ),
  );
  
  // Bloc层 - 使用工厂,因为可能有多个实例
  getIt.registerFactoryParam<ProductBloc, String, void>(
    (productId, _) => ProductBloc(
      repository: getIt<ProductRepository>(),
      productId: productId,
    ),
  );
  
  // 购物车Bloc使用单例,因为全局只有一个购物车
  getIt.registerLazySingleton<CartBloc>(
    () => CartBloc(repository: getIt<CartRepository>()),
  );
  
  // 认证Bloc使用单例
  getIt.registerLazySingleton<AuthBloc>(
    () => AuthBloc(repository: getIt<AuthRepository>()),
  );
}

5.3 应用启动配置

dart 复制代码
void main() {
  // 确保Widget绑定初始化
  WidgetsFlutterBinding.ensureInitialized();
  
  // 设置依赖注入
  setupDependencies();
  
  // 设置Bloc观察者
  Bloc.observer = AppBlocObserver();
  
  // 错误处理
  BlocOverrides.runZoned(
    () => runApp(const MyApp()),
    blocObserver: AppBlocObserver(),
  );
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  
  @override
  Widget build(BuildContext context) {
    return MultiBlocProvider(
      providers: [
        // 全局Bloc
        BlocProvider(create: (context) => getIt<AuthBloc>()),
        BlocProvider(create: (context) => getIt<CartBloc>()),
      ],
      child: MaterialApp(
        title: '电商应用',
        theme: AppTheme.light,
        darkTheme: AppTheme.dark,
        home: BlocBuilder<AuthBloc, AuthState>(
          builder: (context, state) {
            return switch (state) {
              AuthAuthenticated() => const HomePage(),
              _ => const LoginPage(),
            };
          },
        ),
        routes: AppRoutes.routes,
      ),
    );
  }
}

六、单元测试

6.1 Bloc单元测试

dart 复制代码
void main() {
  group('ProductBloc测试', () {
    late ProductBloc productBloc;
    late MockProductRepository mockRepository;
    
    setUp(() {
      mockRepository = MockProductRepository();
      productBloc = ProductBloc(repository: mockRepository);
    });
    
    tearDown(() {
      productBloc.close();
    });
    
    test('初始状态正确', () {
      expect(productBloc.state, equals(const ProductInitialState()));
    });
    
    test('加载商品成功流程', () async {
      // 准备
      const product = Product(
        id: '1',
        name: '测试商品',
        price: 100,
        imageUrl: 'test.jpg',
        description: '测试描述',
      );
      
      when(mockRepository.getProduct('1'))
          .thenAnswer((_) async => product);
      when(mockRepository.isInCart('1'))
          .thenAnswer((_) async => false);
      when(mockRepository.isFavorite('1'))
          .thenAnswer((_) async => true);
      
      // 期望的状态序列
      final expectedStates = [
        const ProductInitialState(),
        const ProductLoadingState(),
        const ProductLoadedState(
          product: product,
          isInCart: false,
          isFavorite: true,
        ),
      ];
      
      // 执行并验证
      expectLater(
        productBloc.stream,
        emitsInOrder(expectedStates),
      );
      
      productBloc.add(const ProductEvent.load('1'));
    });
    
    test('添加到购物车成功', () async {
      // 先加载商品
      const product = Product(id: '1', name: '测试商品', price: 100);
      when(mockRepository.getProduct('1')).thenAnswer((_) async => product);
      when(mockRepository.isInCart('1')).thenAnswer((_) async => false);
      when(mockRepository.isFavorite('1')).thenAnswer((_) async => false);
      
      productBloc.add(const ProductEvent.load('1'));
      await pumpEventQueue();
      
      // 准备添加到购物车
      when(mockRepository.addToCart('1', 1))
          .thenAnswer((_) async {});
      
      // 执行添加到购物车
      productBloc.add(const ProductEvent.addToCart('1', 1));
      
      // 验证状态变化
      await expectLater(
        productBloc.stream,
        emitsThrough(
          const ProductLoadedState(
            product: product,
            isInCart: true,  // 应该变为true
            isFavorite: false,
          ),
        ),
      );
    });
  });
}

6.2 Widget测试

dart 复制代码
void main() {
  group('ProductPage Widget测试', () {
    testWidgets('显示加载状态', (WidgetTester tester) async {
      // 创建测试Bloc
      final productBloc = MockProductBloc();
      when(productBloc.state).thenReturn(const ProductLoadingState());
      
      await tester.pumpWidget(
        MaterialApp(
          home: BlocProvider.value(
            value: productBloc,
            child: const ProductPage(productId: '1'),
          ),
        ),
      );
      
      // 验证显示加载指示器
      expect(find.byType(CircularProgressIndicator), findsOneWidget);
    });
    
    testWidgets('显示商品信息', (WidgetTester tester) async {
      final productBloc = MockProductBloc();
      const product = Product(
        id: '1',
        name: '测试商品',
        price: 100,
        imageUrl: 'test.jpg',
        description: '测试描述',
      );
      
      when(productBloc.state).thenReturn(
        const ProductLoadedState(
          product: product,
          isInCart: false,
          isFavorite: false,
        ),
      );
      
      await tester.pumpWidget(
        MaterialApp(
          home: BlocProvider.value(
            value: productBloc,
            child: const ProductPage(productId: '1'),
          ),
        ),
      );
      
      // 验证商品信息显示
      expect(find.text('测试商品'), findsOneWidget);
      expect(find.text('¥100'), findsOneWidget);
      expect(find.text('测试描述'), findsOneWidget);
    });
  });
}

结语

通过以上学习,我们系统掌握了Bloc状态管理的完整体系:架构思想三大核心概念核心组件高级特性,如果觉得这篇文章对你有帮助,别忘了一键三连(点赞、关注、收藏)~~~** 在实际开发中遇到任何Bloc相关问题,欢迎在评论区留言。


版权声明:本文内容基于多个商业项目实战经验总结,欢迎分享交流,但请注明出处。

相关推荐
zhaoyufei13310 小时前
Android13删除Taskbar
android
消失的旧时光-194312 小时前
Flutter 组件:Row / Column
flutter
6***B4812 小时前
存储过程(SQL)
android·数据库·sql
学困昇13 小时前
C++中的异常
android·java·c++
Jerry14 小时前
问题记录 - Android IdleHandler 没有执行
android
没有了遇见14 小时前
Android ButterKnife Android 35情况下 适配 Gradle 8.+
android
方白羽14 小时前
Android多层嵌套RecyclerView滚动
android·java·kotlin
程序员老刘15 小时前
Flutter版本选择指南:3.35稳定,3.38发布 | 2025年11月
flutter·客户端
菜就多学15 小时前
SurfaceControlViewHost 实现跨进程UI渲染
android·设计
2501_9151063216 小时前
iOS App 测试工具全景分析,构建从开发调试到线上监控的多阶段工具链体系
android·测试工具·ios·小程序·uni-app·iphone·webview