flutter bloc 使用详细解析

发布于:2025-06-21 ⋅ 阅读:(20) ⋅ 点赞:(0)

源码地址
flutter_bloc 是基于 BLoC(Business Logic Component)模式的 Flutter 状态管理库,它封装了 bloc package,帮助我们更清晰地组织业务逻辑与 UI 的分离。核心思想是 事件驱动状态响应


🧠 原理简介

1. 核心概念

  • Event(事件):用户的输入或其他外部触发,比如按钮点击。
  • State(状态):界面状态的表现,比如加载中、成功、失败。
  • Bloc(逻辑组件):接收事件 -> 处理逻辑 -> 发出新状态。

流程图如下:

UI → Bloc.add(Event) → Bloc → emit(State) → UI rebuild

🛠️ 如何使用

1. 安装依赖

dependencies:
  flutter_bloc: ^8.1.3 # 检查 pub.dev 上的最新版本

2. 创建 Event & State

// counter_event.dart
abstract class CounterEvent {}

class Increment extends CounterEvent {}

class Decrement extends CounterEvent {}
// counter_state.dart
class CounterState {
  final int count;
  CounterState(this.count);
}

3. 创建 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(0)) {
    on<Increment>((event, emit) => emit(CounterState(state.count + 1)));
    on<Decrement>((event, emit) => emit(CounterState(state.count - 1)));
  }
}

4. 在 UI 中使用 Bloc

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

void main() {
  runApp(MyApp());
}

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

class CounterPage extends StatelessWidget {
  
  Widget build(BuildContext context) {
    final bloc = context.read<CounterBloc>();

    return Scaffold(
      appBar: AppBar(title: Text("BLoC Counter")),
      body: Center(
        child: BlocBuilder<CounterBloc, CounterState>(
          builder: (context, state) => Text('Count: ${state.count}', style: TextStyle(fontSize: 30)),
        ),
      ),
      floatingActionButton: Column(
        mainAxisAlignment: MainAxisAlignment.end,
        children: [
          FloatingActionButton(onPressed: () => bloc.add(Increment()), child: Icon(Icons.add)),
          SizedBox(height: 10),
          FloatingActionButton(onPressed: () => bloc.add(Decrement()), child: Icon(Icons.remove)),
        ],
      ),
    );
  }
}

✅ 总结优点

  • UI 和业务逻辑分离,利于测试和维护
  • 明确的事件-状态流转,代码可读性强
  • 强类型 + 自动重建 UI,适用于中大型项目

和 getx 相比有什么优缺点

flutter_blocGetX 是 Flutter 中两种常见的状态管理方案,各有优缺点,适用于不同的场景。下面是二者的详细对比:


🆚 flutter_bloc vs GetX

维度 flutter_bloc GetX
设计思想 响应式 + 明确事件流转(事件 -> 状态) 响应式 + 最小 API(简洁直接)
代码结构 规范、结构清晰(Event / State / Bloc) 极简、灵活(Controller + Observable)
学习曲线 中等偏陡,概念较多 非常简单,上手极快
样板代码(boilerplate) 多,需要定义多个类 极少,一个控制器基本搞定
可维护性(大型项目) 高,适合多人协作和规范化开发 灵活但风险高,依赖命名和使用习惯
社区和文档 强,广泛用于企业项目,如 Google 官方推荐 非官方但很流行,文档充足
依赖注入(DI) 不强制内置,可配合其他包(如 get_it 内置自动依赖注入
导航/路由管理 依赖其他包(如 go_router 自带强大的路由系统
性能 非常高效,基于 Streamemit 极高,Reactive 系统 + 最小重建
测试友好性 强,适合单元测试/集成测试 一般,较多手动控制
异步处理 基于事件流程,清晰且易测试 支持 Future/async,但自由度高可能导致不规范

✅ 何时选 flutter_bloc

适合你如果:

  • 需要严格分离业务逻辑(企业/团队协作项目)
  • 希望状态流转可追踪、可测试
  • 项目规模较大或可预期扩展
  • 你熟悉 Redux / Rx 风格编程

✅ 何时选 GetX

适合你如果:

  • 项目较小或是个人开发
  • 优先快速开发、上手快
  • 喜欢灵活、少写代码
  • 同时希望统一路由/DI/状态/国际化等功能

🏁 举例对比:计数器

Bloc 实现

bloc.add(Increment()); 
// 多个文件:event.dart, state.dart, bloc.dart

GetX 实现

controller.count++;
// 只要一个 Controller 类,UI 使用 Obx 自动监听

📌 总结一句话:

Bloc 注重规范和可维护性,适合大型工程;GetX 注重极简和开发效率,适合快速开发。

BuildContext 上的一些扩展方法

在使用 flutter_bloc(或 provider)时,BuildContext 上的一些扩展方法是关键,它们帮助你从上下文中访问 Bloc、监听状态、或者进行条件性重建。

以下是常见的几种方法的详细解释和对比:


✅ 1. context.read<T>()

功能:

  • 从 widget tree 中 读取 一个 Bloc(或其他 Provider 提供的对象),不会监听其状态变化

用途:

  • 通常用于触发事件,如 .add(Event),因为不需要监听。

示例:

context.read<CounterBloc>().add(Increment());

✅ 2. context.watch<T>()

功能:

  • 读取并订阅监听 Bloc 或状态变化。
  • 状态变化会导致 widget rebuild

用途:

  • 通常用于 UI 需要根据 Bloc 状态实时刷新。

示例:

final state = context.watch<CounterBloc>().state;
return Text('Count: ${state.count}');

✅ 3. context.select<T, R>(R Function(T value))

功能:

  • 读取 Bloc(或 Provider)中某个字段的值,并监听它的变化。
  • 只有当该字段的值发生变化时,才会触发 rebuild

用途:

  • 精细控制重建,避免无谓的 UI 更新

示例:

final count = context.select<CounterBloc, int>((bloc) => bloc.state.count);
return Text('Count: $count');

✅ 4. BlocProvider.of<T>(context)

  • 等价于 context.read<T>()
  • 是旧写法,推荐使用 context.read<T>() 更简洁。

✅ 5. BlocBuilder<T, S>

功能:

  • 监听 Bloc<T> 的状态 S 并根据状态变化 rebuild UI。

示例:

BlocBuilder<CounterBloc, CounterState>(
  builder: (context, state) => Text('Count: ${state.count}'),
);

✅ 6. BlocListener<T, S>

功能:

  • 用于监听状态变化并做一次性副作用操作(如弹窗、跳转)。

示例:

BlocListener<LoginBloc, LoginState>(
  listener: (context, state) {
    if (state is LoginSuccess) {
      Navigator.pushNamed(context, '/home');
    }
  },
  child: ...
);

✅ 7. BlocConsumer<T, S>

  • 相当于 BlocBuilder + BlocListener 的组合。
  • 同时用于 build UI 和执行副作用。

🔁 方法使用场景对比表

方法名 是否 rebuild 是否监听状态变化 用途
read<T>() 获取 Bloc 实例、添加事件
watch<T>() 获取 Bloc 状态,状态变就重建
select<T, R>() ✅ (条件) ✅ (某字段变) 精细控制重建,提高性能
BlocBuilder 渲染 UI
BlocListener 处理一次性副作用
BlocConsumer UI 和副作用一起处理

使用场景举例子

好的,我们用一个异步 API 请求的完整例子来演示 flutter_bloc 中各类常用方法的实际应用,包括:

  • context.read
  • context.watch
  • context.select
  • BlocBuilder
  • BlocListener
  • BlocConsumer

🌐 场景描述:请求用户信息

模拟从网络请求一个用户信息(名字、邮箱),展示加载中、成功、失败三种状态。


📦 第一步:定义状态和事件

// user_event.dart
abstract class UserEvent {}

class FetchUser extends UserEvent {}
// user_state.dart
abstract class UserState {}

class UserInitial extends UserState {}

class UserLoading extends UserState {}

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

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

⚙️ 第二步:创建 UserBloc

// user_bloc.dart
import 'package:flutter_bloc/flutter_bloc.dart';
import 'user_event.dart';
import 'user_state.dart';

class UserBloc extends Bloc<UserEvent, UserState> {
  UserBloc() : super(UserInitial()) {
    on<FetchUser>((event, emit) async {
      emit(UserLoading());
      await Future.delayed(Duration(seconds: 2)); // 模拟网络延迟

      try {
        // 模拟 API 成功返回
        final name = 'Alice';
        final email = 'alice@example.com';
        emit(UserLoaded(name: name, email: email));
      } catch (e) {
        emit(UserError('Failed to fetch user'));
      }
    });
  }
}

🖼️ 第三步:UI 中使用 Bloc

// user_page.dart
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'user_bloc.dart';
import 'user_event.dart';
import 'user_state.dart';

class UserPage extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (_) => UserBloc(),
      child: UserView(),
    );
  }
}

class UserView extends StatelessWidget {
  
  Widget build(BuildContext context) {
    final bloc = context.read<UserBloc>(); // 获取 Bloc 实例

    return Scaffold(
      appBar: AppBar(title: Text('User Info')),
      body: BlocConsumer<UserBloc, UserState>(
        listener: (context, state) {
          if (state is UserError) {
            ScaffoldMessenger.of(context).showSnackBar(
              SnackBar(content: Text(state.message)),
            );
          }
        },
        builder: (context, state) {
          if (state is UserInitial) {
            return Center(
              child: ElevatedButton(
                onPressed: () => bloc.add(FetchUser()),
                child: Text('Load User'),
              ),
            );
          } else if (state is UserLoading) {
            return Center(child: CircularProgressIndicator());
          } else if (state is UserLoaded) {
            return Center(
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  Text('Name: ${state.name}'),
                  Text('Email: ${state.email}'),
                ],
              ),
            );
          } else {
            return Center(child: Text('Unknown state'));
          }
        },
      ),
    );
  }
}

🧠 其他方法用法补充

context.watch 示例(只监听状态)

final userState = context.watch<UserBloc>().state;
if (userState is UserLoaded) {
  print(userState.name);
}

context.select 示例(只监听 name 变化)

final name = context.select<UserBloc, String?>((bloc) {
  final state = bloc.state;
  return state is UserLoaded ? state.name : null;
});

✅ 总结

这个例子完整地展示了:

  • 如何使用 Bloc 组织异步逻辑
  • 如何使用 context.read 触发事件
  • 如何用 BlocConsumer 分离 UI 构建和副作用(如错误提示)
  • 如何用 watch / select 实现更细粒度监听

## 标题RepositoryProvider

RepositoryProviderflutter_bloc 提供的一个工具类,作用是将“非 Bloc 的对象(如 Repository、Service、API 客户端)注入到 widget tree 中”,供 Bloc 或其他组件使用。它本质上是一个语义化的 Provider,目的是让依赖注入更加清晰和语义化。


🧱 1. RepositoryProvider 是什么?

它是 Provider 的语法糖,用于提供数据访问层(Repository),让 Bloc 通过依赖注入获取它。

RepositoryProvider(
  create: (context) => UserRepository(),
  child: BlocProvider(
    create: (context) => UserBloc(context.read<UserRepository>()),
    child: MyApp(),
  ),
)

🧩 2. 为什么需要 RepositoryProvider?

分离关注点 + 提高可测试性

在 BLoC 架构中,Bloc 只处理业务逻辑,而 Repository 专注于数据来源(数据库/API/本地缓存等)。

  • 🧼 清晰的依赖层次结构
  • 🔌 可插拔(方便 mock 测试)
  • ♻️ 依赖共享(避免重复创建对象)

🧪 3. 实战示例

用户仓库

class UserRepository {
  Future<String> fetchUserName() async {
    await Future.delayed(Duration(seconds: 1));
    return 'Alice';
  }
}

注入层级

void main() {
  runApp(
    RepositoryProvider(
      create: (context) => UserRepository(),
      child: BlocProvider(
        create: (context) => UserBloc(context.read<UserRepository>()),
        child: MyApp(),
      ),
    ),
  );
}

在 Bloc 中使用

class UserBloc extends Bloc<UserEvent, UserState> {
  final UserRepository userRepository;

  UserBloc(this.userRepository) : super(UserInitial()) {
    on<FetchUser>((event, emit) async {
      emit(UserLoading());
      final name = await userRepository.fetchUserName();
      emit(UserLoaded(name));
    });
  }
}

✅ 4. 优点总结

优点 说明
👓 语义清晰 明确指出这是 Repository,不是 Bloc
♻️ 对象共享 上层创建、下层 Bloc 可复用
🧪 易测试 Bloc 接收参数,可以注入 mock
🧩 解耦结构 Bloc 不负责创建数据层

⚠️ 5. 注意事项 / 潜在缺点

缺点或注意点 说明
⚠️ 滥用嵌套 BlocProvider 和 RepositoryProvider 层级深容易混乱,建议封装为模块
⚠️ 生命周期问题 如果作用域太小,Bloc 中引用的 Repository 可能会被提前销毁
⚠️ 多仓库依赖复杂度提升 Bloc 依赖多个 Repository 时,构造函数会变长,可考虑封装为 Service 层或使用 DI 工具(如 get_it)

📌 何时使用 RepositoryProvider?

✅ 使用场景:

  • 需要复用或共享 Repository 实例(如网络请求、数据库访问)
  • Bloc 不应该直接创建依赖对象
  • 需要单元测试 Bloc 时方便注入 mock

❌ 不建议使用场景:

  • 简单小项目可以直接在 Bloc 中创建对象(仅限临时用)
  • 对象生命周期不需要跨多个组件

一个多仓库 + 多 Bloc 的组合使用场景实例

好的,我们来构建一个中型项目的多仓库 + 多 Bloc + 多 Provider 示例结构,结合 RepositoryProviderBlocProvider,实现高内聚、低耦合的结构设计。


🧱 示例需求:用户信息 + 设置模块

有两个功能模块:

  1. 用户模块(User):从 API 获取用户信息
  2. 设置模块(Settings):管理本地配置(如暗黑模式)

模块依赖:

模块 依赖内容
UserBloc 依赖 UserRepository
SettingsBloc 依赖 SettingsRepository

📦 项目结构建议

lib/
├── main.dart
├── repositories/
│   ├── user_repository.dart
│   └── settings_repository.dart
├── blocs/
│   ├── user/
│   │   ├── user_bloc.dart
│   │   ├── user_event.dart
│   │   └── user_state.dart
│   └── settings/
│       ├── settings_bloc.dart
│       ├── settings_event.dart
│       └── settings_state.dart
├── pages/
│   ├── user_page.dart
│   └── settings_page.dart
└── app.dart

1️⃣ Repository 实现

user_repository.dart

class UserRepository {
  Future<String> fetchUserName() async {
    await Future.delayed(Duration(seconds: 1));
    return 'Alice';
  }
}

settings_repository.dart

class SettingsRepository {
  bool _darkMode = false;

  bool get isDarkMode => _darkMode;
  void toggleDarkMode() => _darkMode = !_darkMode;
}

2️⃣ Bloc 实现(以 User 为例)

user_bloc.dart

class UserBloc extends Bloc<UserEvent, UserState> {
  final UserRepository userRepository;

  UserBloc(this.userRepository) : super(UserInitial()) {
    on<FetchUser>((event, emit) async {
      emit(UserLoading());
      final name = await userRepository.fetchUserName();
      emit(UserLoaded(name));
    });
  }
}

3️⃣ 组合使用(main.dart)

void main() {
  runApp(
    MultiRepositoryProvider(
      providers: [
        RepositoryProvider(create: (_) => UserRepository()),
        RepositoryProvider(create: (_) => SettingsRepository()),
      ],
      child: MultiBlocProvider(
        providers: [
          BlocProvider(
            create: (context) => UserBloc(context.read<UserRepository>()),
          ),
          BlocProvider(
            create: (context) => SettingsBloc(context.read<SettingsRepository>()),
          ),
        ],
        child: MyApp(),
      ),
    ),
  );
}

4️⃣ 页面中使用

user_page.dart

class UserPage extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return BlocBuilder<UserBloc, UserState>(
      builder: (context, state) {
        if (state is UserInitial) {
          return ElevatedButton(
            onPressed: () => context.read<UserBloc>().add(FetchUser()),
            child: Text("Load User"),
          );
        } else if (state is UserLoaded) {
          return Text('Hello, ${state.name}');
        } else {
          return CircularProgressIndicator();
        }
      },
    );
  }
}

settings_page.dart

class SettingsPage extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return BlocBuilder<SettingsBloc, SettingsState>(
      builder: (context, state) {
        return SwitchListTile(
          title: Text('Dark Mode'),
          value: state.isDarkMode,
          onChanged: (_) => context.read<SettingsBloc>().add(ToggleTheme()),
        );
      },
    );
  }
}

✅ 优点总结

  • 清晰的依赖注入结构:Bloc 和 Repository 解耦,易维护
  • 复用性强:多个 Bloc 可共用同一个 Repository 实例
  • 测试友好:可轻松 mock Repository 进行单元测试
  • 清晰语义:RepositoryProvider 表明它不是 Bloc,而是数据提供者

🚀 Bonus:封装更进一步

✅ 已经成功将 MultiRepositoryProviderMultiBlocProvider 封装到了 AppProviderWrapper 组件中,结构清晰、职责分离得非常好!

下面是总结和优化建议:


✅ 目前结构回顾

// main.dart
runApp(AppProviderWrapper(child: MyApp()));
// provider_wrapper.dart
class AppProviderWrapper extends StatelessWidget {
  final Widget child;
  const AppProviderWrapper({required this.child});
  ...
}

这非常适合中大型项目,可以让 main.dart 逻辑更纯粹,同时也方便后续扩展全局错误监听、日志注入等中间件。


💡 可选优化建议

1. 使用 lazy: false 控制立即初始化 Bloc

有些 Bloc(如 SettingsBloc)可能希望 App 启动时就立即初始化:

BlocProvider(
  lazy: false,
  create: (context) => SettingsBloc(context.read<SettingsRepository>()),
),
2. 使用 const 构造节省 rebuild

如果子 Widget 和 Bloc 无关,记得加 const 以避免重建。

3. 模块化进一步优化(可选)

可以把 Bloc、Repository 的创建封装成方法,增强可维护性:

List<RepositoryProvider> buildRepositories() => [
  RepositoryProvider(create: (_) => UserRepository()),
  RepositoryProvider(create: (_) => SettingsRepository()),
];

List<BlocProvider> buildBlocs(BuildContext context) => [
  BlocProvider(create: (_) => UserBloc(context.read())),
  BlocProvider(create: (_) => SettingsBloc(context.read())),
];

这样 AppProviderWrapper 中就可以这么写:

child: MultiBlocProvider(
  providers: buildBlocs(context),
  child: child,
),

✅ 总结

现在的结构已经非常标准、清晰,完全符合企业级 Flutter 项目推荐架构。
源码地址


网站公告

今日签到

点亮在社区的每一天
去签到