跳转到正文
YGJoon 技术博客

Flutter Bloc 状态管理详解

发布于:
·1.3k 字 · 约 3 分钟·作者:
事件驱动那一套:on 注册事件处理器,BlocBuilder、BlocListener、BlocConsumer 三种消费方式各用在哪
Flutter Bloc 状态管理详解文章封面
目录

简介

Bloc (Business Logic Component) 是一个基于事件驱动的状态管理模式,它将业务逻辑从UI层分离出来,使用事件(Event)和状态(State)来管理应用数据流。

基本概念

Event

// 事件基类
abstract class CounterEvent {}

// 具体事件
class IncrementEvent extends CounterEvent {}
class DecrementEvent extends CounterEvent {}

State

// 状态基类
abstract class CounterState {
  final int count;
  const CounterState(this.count);
}

// 具体状态
class CounterInitial extends CounterState {
  CounterInitial() : super(0);
}

class CounterUpdated extends CounterState {
  CounterUpdated(int count) : super(count);
}

Bloc

class CounterBloc extends Bloc<CounterEvent, CounterState> {
  CounterBloc() : super(CounterInitial()) {
    on<IncrementEvent>((event, emit) {
      emit(CounterUpdated(state.count + 1));
    });
    
    on<DecrementEvent>((event, emit) {
      emit(CounterUpdated(state.count - 1));
    });
  }
}

基本用法

提供 Bloc

void main() {
  runApp(
    BlocProvider(
      create: (context) => CounterBloc(),
      child: MyApp(),
    ),
  );
}

使用 Bloc

// 监听状态
BlocBuilder<CounterBloc, CounterState>(
  builder: (context, state) {
    return Text('Count: ${state.count}');
  },
);

// 发送事件
context.read<CounterBloc>().add(IncrementEvent());

完整示例

// 用户相关事件
abstract class UserEvent {}

class LoadUserEvent extends UserEvent {}
class UpdateUserEvent extends UserEvent {
  final String name;
  final int age;
  
  UpdateUserEvent({required this.name, required this.age});
}

// 用户相关状态
abstract class UserState {}

class UserInitial extends UserState {}

class UserLoading extends UserState {}

class UserLoaded extends UserState {
  final String name;
  final int age;
  
  UserLoaded({required this.name, required this.age});
}

class UserError extends UserState {
  final String message;
  
  UserError(this.message);
}

// 用户 Bloc
class UserBloc extends Bloc<UserEvent, UserState> {
  final UserRepository repository;
  
  UserBloc({required this.repository}) : super(UserInitial()) {
    // 加载用户
    on<LoadUserEvent>((event, emit) async {
      emit(UserLoading());
      try {
        final user = await repository.getUser();
        emit(UserLoaded(name: user.name, age: user.age));
      } catch (e) {
        emit(UserError('Failed to load user: $e'));
      }
    });
    
    // 更新用户
    on<UpdateUserEvent>((event, emit) async {
      emit(UserLoading());
      try {
        await repository.updateUser(event.name, event.age);
        emit(UserLoaded(name: event.name, age: event.age));
      } catch (e) {
        emit(UserError('Failed to update user: $e'));
      }
    });
  }
}

// 页面组件
class UserPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Bloc Demo')),
      body: BlocBuilder<UserBloc, UserState>(
        builder: (context, state) {
          if (state is UserLoading) {
            return Center(child: CircularProgressIndicator());
          }
          
          if (state is UserError) {
            return Center(child: Text(state.message));
          }
          
          if (state is UserLoaded) {
            return Center(
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  Text(
                    'Name: ${state.name}',
                    style: TextStyle(fontSize: 24),
                  ),
                  Text(
                    'Age: ${state.age}',
                    style: TextStyle(fontSize: 24),
                  ),
                  ElevatedButton(
                    onPressed: () {
                      context.read<UserBloc>().add(
                        UpdateUserEvent(
                          name: 'John Doe',
                          age: 30,
                        ),
                      );
                    },
                    child: Text('Update User'),
                  ),
                ],
              ),
            );
          }
          
          return Center(
            child: ElevatedButton(
              onPressed: () {
                context.read<UserBloc>().add(LoadUserEvent());
              },
              child: Text('Load User'),
            ),
          );
        },
      ),
    );
  }
}

高级用法

BlocListener

BlocListener<UserBloc, UserState>(
  listener: (context, state) {
    if (state is UserError) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text(state.message)),
      );
    }
  },
  child: YourWidget(),
)

BlocConsumer

BlocConsumer<UserBloc, UserState>(
  listener: (context, state) {
    // 处理副作用
  },
  builder: (context, state) {
    // 构建UI
    return YourWidget();
  },
)

状态流转换

Stream<State> transformEvents<Event, State>(
  Stream<Event> events,
  Stream<State> Function(Event event) mapper,
) {
  return events
    .debounceTime(Duration(milliseconds: 300))
    .switchMap(mapper);
}

最佳实践

  1. 用 sealed class 定义状态,靠 switch 穷举防漏分支:Dart 3 的 sealed class LoginState 配 switch,漏写一个分支编译直接报错——这比”记得处理所有状态”靠谱得多
  2. 一个 Bloc 只管一个业务域:登录 Bloc 里别塞注册逻辑;事件名带前缀(LoginSubmitted 而不是裸 Submitted),多个 Bloc 共处时事件不会串味
  3. 跳转/弹 Toast 用 BlocListener,别写在 BlocBuilder 的 builder 里:builder 会被重复调用,导航代码会执行多次,页面会被 push 好几遍
  4. Bloc 之间别直接调用:A 需要 B 的状态时,用 BlocListener 监听 B 再 add 事件给 A,保持单向数据流——在 Bloc 里 context.read<OtherBloc>() 然后调它的方法,是埋雷

注意事项

  1. 网络请求可以放 Bloc 里,但 http client 要从构造器注入:LoginBloc({required this.repo}),测试时才能 mock 掉网络层;在 Bloc 里直接 Dio() new 出来,单测就没法写了
  2. 事件是”发生了什么”(过去式:LoginSubmitted),状态是”现在是什么”(LoginSuccess);“正在加载”是状态不是事件,别为了 loading 多发一个事件
  3. 大对象别塞进 state:图片 bytes、几百条的列表放进 state,每次 emit 都要参与 == 比较(Equatable),直接拖慢;传 id,数据放 repository 里缓存
  4. 别手动 close 被多个页面共享的 Bloc:BlocProvider 会自动调 close,手动 close 之后其他页面再 add 事件直接抛异常

实战案例

Bloc 的核心思想(单向数据流)我认同,但实践中我选了 GetX,原因很俗:快。以 某物流冷链项目 的司机认证页为例,用 GetX 就是 controller 里几个 .obs 变量 + Obx 包 UI,半天写完;用 Bloc 要定义 VerifyEvent、VerifyState、VerifyBloc,还要处理表单校验、图片上传、提交三个状态机,代码量翻倍。对于”表单+列表”为主的业务 App,Bloc 的 ceremony(仪式感代码)是负担。但我也承认:如果做的是 IM 聊天、协同编辑这种状态机复杂的场景,Bloc 的显式状态是值得的。简单场景别上重武器,复杂场景别偷懒——这是我对状态管理的全部看法。

总结

Bloc 提供了一种清晰的方式来管理应用状态,通过分离业务逻辑和UI表现层,可以构建出更易维护、测试的应用程序。理解并掌握 Bloc 的使用对于开发高质量的 Flutter 应用至关重要。


分享这篇文章:


相关文章

上一篇
简单了解Flutter
下一篇
Flutter SingleChildScrollView 组件详解