《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相关问题,欢迎在评论区留言。


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

相关推荐
行者9618 分钟前
Flutter在鸿蒙平台实现自适应步骤条组件的完整指南
flutter·harmonyos·鸿蒙
云上凯歌37 分钟前
02 Spring Boot企业级配置详解
android·spring boot·后端
hqiangtai1 小时前
Android 高级专家技术能力图谱
android·职场和发展
aqi001 小时前
FFmpeg开发笔记(九十七)国产的开源视频剪辑工具AndroidVideoEditor
android·ffmpeg·音视频·直播·流媒体
stevenzqzq1 小时前
Android Koin 注入入门教程
android·kotlin
炼金术2 小时前
SkyPlayer v1.1.0 - 在线视频播放功能更新
android·ffmpeg
用户276038157812 小时前
鲲鹏+昇腾:开启 AI for Science 新范式——基于PINN的流体仿真加速实践
android
此去正年少2 小时前
编写adb脚本工具对Android设备上的闪退问题进行监控分析
android·adb·logcat·ndk·日志监控
行者962 小时前
Flutter与OpenHarmony深度整合:打造高性能自定义图表组件
flutter·harmonyos·鸿蒙
落羽凉笙2 小时前
Python基础(4)| 玩转循环结构:for、while与嵌套循环全解析(附源码)
android·开发语言·python