Flutter BLoC 状态管理

FreeGuideOnline 最新 2026-07-12

yaml dependencies: flutter_bloc: ^8.1.3 equatable: ^2.0.5 # 简化状态比较


执行 `flutter pub get`,即可开始。

## 第一个计数器 BLoC

### 定义事件和状态

使用 `equatable` 可以让状态对象更方便比较,避免不必要的界面重绘。

```dart
// counter_event.dart
import 'package:equatable/equatable.dart';

abstract class CounterEvent extends Equatable {
  @override
  List<Object> get props => [];
}

class Increment extends CounterEvent {}
class Decrement extends CounterEvent {}
// counter_state.dart
import 'package:equatable/equatable.dart';

class CounterState extends Equatable {
  final int count;
  const CounterState({required this.count});

  // 提供初始状态工厂方法
  factory CounterState.initial() => const CounterState(count: 0);

  @override
  List<Object> get props => [count];
}

实现 BLoC

// counter_bloc.dart
import 'package:flutter_bloc/flutter_bloc.dart';
import 'counter_event.dart';
import 'counter_state.dart';

class CounterBloc extends Bloc<CounterEvent, CounterState> {
  CounterBloc() : super(CounterState.initial()) {
    on<Increment>((event, emit) {
      emit(CounterState(count: state.count + 1));
    });

    on<Decrement>((event, emit) {
      emit(CounterState(count: state.count - 1));
    });
  }
}

关键点:

  • 构造函数中通过 on<Event> 注册事件处理器。
  • emit 发出新状态,状态不可变,所以每次创建新实例。
  • state 属性自动持有当前状态。

将 BLoC 注入 Widget 树

使用 BlocProvider 在需要访问 BLoC 的 Widget 子树顶部提供实例。

// main.dart
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'counter_bloc.dart';
import 'counter_page.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: BlocProvider(
        create: (context) => CounterBloc(),
        child: CounterPage(),
      ),
    );
  }
}

界面响应状态变化

BlocBuilder – 重建 UI

BlocBuilder 监听 BLoC 的状态流,当状态变化时重新执行 builder 回调。

// counter_page.dart
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'counter_bloc.dart';
import 'counter_state.dart';

class CounterPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('BLoC Counter')),
      body: Center(
        child: BlocBuilder<CounterBloc, CounterState>(
          builder: (context, state) {
            return Text(
              '${state.count}',
              style: TextStyle(fontSize: 48),
            );
          },
        ),
      ),
      floatingActionButton: Column(
        mainAxisAlignment: MainAxisAlignment.end,
        children: [
          FloatingActionButton(
            heroTag: 'inc',
            onPressed: () => context.read<CounterBloc>().add(Increment()),
            child: Icon(Icons.add),
          ),
          SizedBox(height: 10),
          FloatingActionButton(
            heroTag: 'dec',
            onPressed: () => context.read<CounterBloc>().add(Decrement()),
            child: Icon(Icons.remove),
          ),
        ],
      ),
    );
  }
}

通过 context.read<CounterBloc>() 获取 BLoC 并添加事件,无需监听其状态变化。

BlocListener – 副作用处理

执行一次性的操作,如弹出 SnackBar、导航等,用 BlocListener

BlocListener<CounterBloc, CounterState>(
  listener: (context, state) {
    if (state.count == 5) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('达到5!')),
      );
    }
  },
  child: ... // 子 Widget
)

BlocConsumer – 同时监听与构建

BlocConsumer 合并了 BlocBuilderBlocListener,避免嵌套。

BlocConsumer<CounterBloc, CounterState>(
  listener: (context, state) {
    if (state.count == -1) {
      showDialog(...);
    }
  },
  builder: (context, state) {
    return Text('${state.count}');
  },
)

引入仓库层 解耦数据源

实际项目中,BLoC 不应直接操作 API 或数据库,需要引入仓库 (Repository)。

// counter_repository.dart
class CounterRepository {
  int _value = 0;

  Future<int> fetchCount() async {
    await Future.delayed(Duration(seconds: 1));
    return _value;
  }

  Future<void> saveCount(int value) async {
    await Future.delayed(Duration(milliseconds: 500));
    _value = value;
  }
}

修改 BLoC 使其依赖 Repository:

class CounterBloc extends Bloc<CounterEvent, CounterState> {
  final CounterRepository repository;
  CounterBloc({required this.repository}) : super(CounterState.initial()) {
    on<LoadCounter>(_onLoad);
    on<Increment>(_onIncrement);
  }

  Future<void> _onLoad(LoadCounter event, Emitter<CounterState> emit) async {
    final count = await repository.fetchCount();
    emit(CounterState(count: count));
  }

  Future<void> _onIncrement(Increment event, Emitter<CounterState> emit) async {
    final newCount = state.count + 1;
    await repository.saveCount(newCount);
    emit(CounterState(count: newCount));
  }
}

通过 RepositoryProvider 在 Widget 树顶层提供 Repository,BLoC 通过依赖注入获取。

RepositoryProvider(
  create: (context) => CounterRepository(),
  child: BlocProvider(
    create: (context) => CounterBloc(
      repository: context.read<CounterRepository>(),
    ),
    child: CounterPage(),
  ),
)

多 BLoC 协作

大型应用常有多个 BLoC 需要通信。可通过 BlocListener 监听其他 BLoC,或在 BLoC 内部订阅流。

class CartBloc extends Bloc<CartEvent, CartState> {
  final ProductBloc productBloc; // 注入依赖
  late final StreamSubscription productSubscription;

  CartBloc({required this.productBloc}) : super(CartState.initial()) {
    productSubscription = productBloc.stream.listen((state) {
      // 当 ProductBloc 状态变化时更新购物车
      if (state.status == ProductStatus.added) {
        add(AddItem(state.product));
      }
    });
  }

  @override
  Future<void> close() {
    productSubscription.cancel();
    return super.close();
  }
}

状态追踪与调试

开启 BlocObserver 观察所有 BLoC 的变化:

void main() {
  Bloc.observer = SimpleBlocObserver();
  runApp(MyApp());
}

class SimpleBlocObserver extends BlocObserver {
  @override
  void onChange(BlocBase bloc, Change change) {
    super.onChange(bloc, change);
    print('${bloc.runtimeType} $change');
  }
}

测试 BLoC

BLoC 的测试极其简单,不依赖 Flutter 环境:

import 'package:bloc_test/bloc_test.dart';
import 'package:flutter_test/flutter_test.dart';

void main() {
  group('CounterBloc', () {
    late CounterBloc bloc;
    late MockCounterRepository repository; // 使用 mockito 模拟

    setUp(() {
      repository = MockCounterRepository();
      bloc = CounterBloc(repository: repository);
    });

    blocTest<CounterBloc, CounterState>(
      'emits [CounterState(1)] when Increment is added',
      build: () => bloc,
      act: (bloc) => bloc.add(Increment()),
      expect: () => [CounterState(count: 1)],
    );

    tearDown(() => bloc.close());
  });
}