Flutter轻量级状态管理插件bloc_small的使用

发布于 1周前 作者 caililin 来自 Flutter

Flutter轻量级状态管理插件bloc_small的使用

简介

bloc_small 是一个基于 flutter_bloc 的轻量级且简化的业务逻辑组件(BLoC)库。它简化了状态管理,使其更加直观,同时保持了 BLoC 模式的优点。

Table of Contents
  1. 功能
  2. 安装
  3. 核心概念
  4. 基本用法
  5. 使用 Cubit(替代方法)
  6. Auto Route 集成
  7. 高级用法
  8. 最佳实践
  9. API 参考
  10. 贡献
  11. 许可证
```

功能

  • 使用 flutter_bloc 简化 BLoC 模式实现
  • 基于 Dart Streams 的易用反应式编程
  • 自动资源管理和清理
  • GetIt 集成用于依赖注入
  • 支持加载状态和错误处理
  • 流畅的状态更新和事件处理
  • 内置支持异步操作
  • freezed 集成以支持不可变状态和事件类
  • 增强的 ReactiveSubject 具有强大的流转换方法

安装

pubspec.yaml 文件中添加 bloc_small 依赖:

dependencies:
  bloc_small:
  injectable:
  freezed:

dev_dependencies:
  injectable_generator:
  build_runner:
  freezed_annotation:

然后运行:

flutter pub run build_runner build --delete-conflicting-outputs

注意:每次修改使用 Freezed 或 Injectable 注解的文件时,都需要运行生成命令,以便为您的 BLoCs、事件和状态生成必要的代码。

核心概念

类名 描述 基础类 目的
MainBloc BLoC 模式的基础实现 MainBlocDelegate 处理事件并发出状态
MainCubit 简化状态管理的替代方案 MainCubitDelegate 直接状态突变而不使用事件
MainBlocEvent 所有事件的基类 - 触发 BLoC 中的状态变化
MainBlocState 所有状态的基类 - 表示应用程序状态
CommonBloc 全局功能管理器 - 管理加载状态和通用功能

BLoC 模式示例:

[@injectable](/user/injectable)
class CounterBloc extends MainBloc<CounterEvent, CounterState> {
  CounterBloc() : super(const CounterState.initial()) {
    on<Increment>(_onIncrement);
  }
}

Cubit 模式示例:

[@injectable](/user/injectable)
class CounterCubit extends MainCubit<CounterState> {
  CounterCubit() : super(const CounterState.initial());

  void increment() => emit(state.copyWith(count: state.count + 1));
}

基本用法

1. 设置依赖注入

使用 GetItInjectable 进行依赖注入:

[@InjectableInit](/user/InjectableInit)()
void configureInjectionApp() {
  // 步骤 1: 注册来自 bloc_small 包的核心依赖
  getIt.registerCore();

  // 步骤 2: 注册您的应用依赖
  getIt.init();
}
void main() {
  WidgetsFlutterBinding.ensureInitialized();
  configureInjectionApp(); // 初始化核心和应用依赖
  runApp(MyApp());
}

重要提示:包含 CommonBloc 单例的 RegisterModule 类是必需的。如果不包括此依赖注入设置,您的应用将遇到错误。CommonBlocbloc_small 用于跨应用管理通用功能如加载状态。

确保在运行应用之前调用 configureInjectionApp()

2. 定义您的 BLoC
[@injectable](/user/injectable)
class CountBloc extends MainBloc<CountEvent, CountState> {
  CountBloc() : super(const CountState.initial()) {
    on<Increment>(_onIncrementCounter);
    on<Decrement>(_onDecrementCounter);
  }

  Future<void> _onIncrementCounter(Increment event, Emitter<CountState> emit) async {
    await blocCatch(actions: () async {
      await Future.delayed(Duration(seconds: 2));
      emit(state.copyWith(count: state.count + 1));
    });
  }

  void _onDecrementCounter(Decrement event, Emitter<CountState> emit) {
    if (state.count > 0) emit(state.copyWith(count: state.count - 1));
  }
}
3. 使用 Freezed 定义事件和状态
abstract class CountEvent extends MainBlocEvent {
  const CountEvent._();
}

[@freezed](/user/freezed)
class Increment extends CountEvent with _$Increment {
  const factory Increment() = _Increment;
}

[@freezed](/user/freezed)
class Decrement extends CountEvent with _$Decrement {
  const factory Decrement() = _Decrement;
}
[@freezed](/user/freezed)
class CountState extends MainBlocState with $CountState {
  const factory CountState.initial({[@Default](/user/Default)(0) int count}) = Initial;
}
4. 使用 BaseBlocPageState 创建 StatefulWidget
class MyHomePage extends StatefulWidget {
  MyHomePage({required this.title});

  final String title;

  [@override](/user/override)
  MyHomePageState createState() => _MyHomePageState();
}

class MyHomePageState extends BaseBlocPageState<MyHomePage, CountBloc> {
  [@override](/user/override)
  Widget buildPage(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: BlocBuilder<CountBloc, CountState>(
          builder: (context, state) {
            return Text(
              '${state.count}',
            );
          },
        ),
      ),
      floatingActionButton: Column(
        mainAxisAlignment: MainAxisAlignment.end,
        children: [
          FloatingActionButton(
            onPressed: () => bloc.add(Increment()),
            tooltip: 'Increment',
            child: Icon(Icons.add),
          ),
          FloatingActionButton(
            onPressed: () => bloc.add(Decrement()),
            tooltip: 'decrement',
            child: Icon(Icons.remove),
          ),
        ],
      ),
    );
  }
}

使用 Cubit(替代方法)

如果更喜欢不使用事件的简单方法,可以使用 Cubit 代替 BLoC:

1. 定义您的 Cubit
[@injectable](/user/injectable)
class CounterCubit extends MainCubit<CounterState> {
  CounterCubit() : super(const CounterState());

  Future<void> increment() async {
    await cubitCatch(
      actions: () async {
        await Future.delayed(Duration(seconds: 1));
        emit(state.copyWith(count: state.count + 1));
      },
      keyLoading: 'increment',
    );
  }

  void decrement() {
    if (state.count > 0) {
      emit(state.copyWith(count: state.count - 1));
    }
  }
}
2. 使用 Freezed 定义 Cubit 状态
[@freezed](/user/freezed)
class CountState extends MainBlocState with $CountState {
  const factory CountState.initial({[@Default](/user/Default)(0) int count}) = Initial;
}
3. 使用 BaseCubitPageState 创建 StatefulWidget
class CounterPage extends StatefulWidget {
  const CounterPage({super.key});

  [@override](/user/override)
  State<CounterPage> createState() => _CounterPageState();
}

class _CounterPageState extends BaseCubitPageState<CounterPage, CountCubit> {
  [@override](/user/override)
  Widget buildPage(BuildContext context) {
    return buildLoadingOverlay(
      loadingKey: 'increment',
      child: Scaffold(
        appBar: AppBar(title: const Text('Counter Example')),
        body: Center(
          child: BlocBuilder<CountCubit, CountState>(
            builder: (context, state) {
              return Text(
                '${state.count}',
                style: const TextStyle(fontSize: 48),
              );
            },
          ),
        ),
        floatingActionButton: Column(
          mainAxisAlignment: MainAxisAlignment.end,
          children: [
            FloatingActionButton(
              onPressed: () => bloc.increment(),
              child: const Icon(Icons.add),
            ),
            const SizedBox(height: 16),
            FloatingActionButton(
              onPressed: () => bloc.decrement(),
              child: const Icon(Icons.remove),
            ),
          ],
        ),
      ),
    );
  }
}

使用 StatelessWidget

bloc_small 也支持 StatelessWidget,具有与 StatefulWidget 类似的功能。

1. 使用 BLoC 与 StatelessWidget
class MyHomePage extends BaseBlocPage<CountBloc> {
  const MyHomePage({super.key});

  [@override](/user/override)
  Widget buildPage(BuildContext context, CountBloc bloc) {
    return buildLoadingOverlay(
      context,
      child: Scaffold(
        appBar: AppBar(title: const Text('Counter Example')),
        body: Center(
          child: BlocBuilder<CountBloc, CountState>(
            builder: (context, state) {
              return Text(
                '${state.count}',
                style: const TextStyle(fontSize: 48),
              );
            },
          ),
        ),
        floatingActionButton: FloatingActionButton(
          onPressed: () => bloc.add(const Increment()),
          child: const Icon(Icons.add),
        ),
      ),
    );
  }
}
2. 使用 Cubit 与 StatelessWidget
class CounterPage extends BaseCubitPage<CountCubit> {
  const CounterPage({super.key});

  [@override](/user/override)
  Widget buildPage(BuildContext context, CountCubit cubit) {
    return buildLoadingOverlay(
      context,
      child: Scaffold(
        appBar: AppBar(title: const Text('Counter Example')),
        body: Center(
          child: BlocBuilder<CountCubit, CountState>(
            builder: (context, state) {
              return Text(
                '${state.count}',
                style: const TextStyle(fontSize: 48),
              );
            },
          ),
        ),
        floatingActionButton: FloatingActionButton(
          onPressed: () => cubit.increment(),
          child: const Icon(Icons.add),
        ),
      ),
    );
  }
}

StatelessWidget 实现的关键特性

特性 描述
基础类 BaseBlocPageBaseCubitPage
依赖注入 自动依赖注入
加载管理 内置加载覆盖支持
导航 集成导航能力
状态管理 完整的 BLoC/Cubit 模式支持

StatelessWidget 与 StatefulWidget 的选择

使用场景 小部件类型
简单 UI 而没有本地状态 StatelessWidget
复杂 UI 且具有本地状态 StatefulWidget
性能关键屏幕 StatelessWidget
具有生命周期需求的屏幕 StatefulWidget

如果想使用 Auto Route 集成

  1. 添加 auto_route 到依赖项:
dependencies:
  auto_route:
dev_dependencies:
  auto_route_generator:
  1. 创建路由:
[@AutoRouterConfig](/user/AutoRouterConfig)()
[@LazySingleton](/user/LazySingleton)()
class AppRouter extends BaseAppRouter {
  [@override](/user/override)
  List<AutoRoute> get routes => [
    AutoRoute(page: HomeRoute.page, initial: true),
    AutoRoute(page: SettingsRoute.page),
  ];
}
  1. 在依赖注入中注册路由
void configureInjectionApp() {
  // 注册 AppRouter(推荐)
  getIt.registerAppRouter<AppRouter>(AppRouter(), enableNavigationLogs: true);

  // 注册其他依赖
  getIt.registerCore();
  getIt.init();
}
  1. 配置 MaterialApp 使用 auto_route
class MyApp extends StatelessWidget {
  final _router = getIt<AppRouter>();

  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp.router(
      routerConfig: _router.config(),
      // ... 其他 MaterialApp 属性
    );
  }
}
  1. 导航
class MyWidget extends StatelessWidget {
  final navigator = getIt.getNavigator();

  void _onNavigate() {
    navigator?.push(const HomeRoute());
  }
}

使用 bloccubit 提供的 AppNavigator

class _MyWidgetState extends BaseBlocPageState<MyWidget, MyWidgetBloc> {
  void _onNavigate() {
    navigator?.push(const HomeRoute());
  }
}
class _MyWidgetState extends BaseCubitPageState<MyWidget, MyWidgetCubit> {
  void _onNavigate() {
    navigator?.push(const HomeRoute());
  }
}
// 基本导航
navigator?.push(const HomeRoute());

// 带参数的导航
navigator?.push(UserRoute(userId: 123));

最佳实践

  1. 始终在 DI 设置中注册 AppRouter
  2. 使用 AppNavigator 提供的类型安全方法
  3. 处理可能的初始化错误
  4. 对于复杂应用,考虑创建导航服务类

特点

  • 类型安全路由
  • 自动路由生成
  • 平台自适应过渡
  • 深度链接支持
  • 嵌套导航
  • 与依赖注入集成

优势

  • 编译时路由验证
  • 清洁一致的导航 API
  • 减少样板代码
  • 更好的开发体验
  • bloc_small 包轻松集成

对于更复杂的导航场景和详细文档,请参阅 auto_route 文档。

高级用法

处理加载状态

bloc_small 提供了一个方便的方法来管理加载状态并显示加载指示器,使用 CommonBlocbuildLoadingOverlay 方法。

使用 buildLoadingOverlay

当使用 BaseBlocPageState 时,您可以轻松地向整个页面添加加载覆盖:

class MyHomePageState extends BaseBlocPageState<MyHomePage, CountBloc> {
  [@override](/user/override)
  Widget buildPage(BuildContext context) {
    return buildLoadingOverlay(
      child: Scaffold(
        appBar: AppBar(
          title: Text(widget.title),
        ),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Text('You have pushed the button this many times:'),
              BlocBuilder<CountBloc, CountState>(
                builder: (context, state) {
                  return Text('${state.count}');
                },
              )
            ],
          ),
        ),
        floatingActionButton: Wrap(
          spacing: 5,
          children: [
            FloatingActionButton(
              onPressed: () => bloc.add(Increment()),
              tooltip: 'Increment',
              child: Icon(Icons.add),
            ),
            FloatingActionButton(
              onPressed: () => bloc.add(Decrement()),
              tooltip: 'decrement',
              child: Icon(Icons.remove),
            ),
          ],
        ),
      ),
    );
  }
}

buildLoadingOverlay 方法会包装您的页面内容,并在加载状态活动时自动显示加载指示器。

自定义加载覆盖

您可以自定义加载覆盖,通过提供 loadingWidget 并指定 loadingKey

buildLoadingOverlay(
  child: YourPageContent(),
  loadingWidget: YourCustomLoadingWidget(),
  loadingKey:'customLoadingKey'
)
激活加载状态

要显示或隐藏加载覆盖,请在 BLoC 中使用 showLoadinghideLoading 方法:

class YourBloc extends MainBloc<YourEvent, YourState> {
  Future<void> someAsyncOperation() async {
    showLoading(); // 或 showLoading(key: 'customLoadingKey');
    try {
      // 执行异步操作
    } finally {
      hideLoading(); // 或 hideLoading(key: 'customLoadingKey');
    }
  }
}

这种方法提供了干净一致的方式来处理整个应用程序中的加载状态,具有使用全局或组件特定加载指示器的灵活性。

错误处理

使用 blocCatch 方法在 BLoC 中处理错误:

await blocCatch(
  actions: () async {
    // 异步逻辑
    throw Exception('Something went wrong');
  },
  onError: (error) {
    // 处理错误
    print('Error occurred: $error');
  }
);

ReactiveSubject

使用 ReactiveSubject

ReactiveSubject 是一个功能强大的流控制器,结合了 BehaviorSubject 的功能和额外的反应式操作符。

核心功能
1. 值管理
// 使用初始值创建
final subject = ReactiveSubject<int>(initialValue: 0);

// 创建广播主题
final broadcast = ReactiveSubject<int>.broadcast(initialValue: 0);

// 添加值
subject.add(1);

// 获取当前值
print(subject.value);

// 检查是否关闭
print(subject.isClosed);

// 完成后清理
subject.dispose();
2. 流变换
map - 转换每个值
final celsius = ReactiveSubject<double>();
final fahrenheit = celsius.map((c) => c * 9/5 + 32);
where - 过滤值
final numbers = ReactiveSubject<int>();
final evenNumbers = numbers.where((n) => n % 2 == 0);
switchMap - 切换到新流
final searchQuery = ReactiveSubject<String>();
final results = searchQuery.switchMap((query) => performSearch(query)); // 取消之前的搜索
debounceTime - 延迟发射
final input = ReactiveSubject<String>();
final debouncedInput = input.debounceTime(Duration(milliseconds: 300));
throttleTime - 速率限制发射
final clicks = ReactiveSubject<void>();
final throttledClicks = clicks.throttleTime(Duration(seconds: 1));
distinct - 仅在值改变时发射
final values = ReactiveSubject<String>();
final distinctValues = values.distinct();
高级操作
withLatestFrom - 结合另一个流
final main = ReactiveSubject<int>();
final other = ReactiveSubject<String>();
final combined = main.withLatestFrom(other, 
  (int a, String b) => '$a-$b');
startWith - 开始时带有值
final subject = ReactiveSubject<int>();
final withDefault = subject.startWith(0);
scan - 累加值
final prices = ReactiveSubject<double>();
final total = prices.scan<double>(
  0.0,
  (sum, price, _) => sum + price,
);
doOnData/doOnError - 侧效果
final subject = ReactiveSubject<int>();
final withLogging = subject
  .doOnData((value) => print('Emitted: $value'))
  .doOnError((error, _) => print('Error: $error'));
静态操作符
combineLatest - 组合多个主题
final subject1 = ReactiveSubject<int>();
final subject2 = ReactiveSubject<String>();
final combined = ReactiveSubject.combineLatest([subject1, subject2]);
merge - 合并多个主题
final subject1 = ReactiveSubject<int>();
final subject2 = ReactiveSubject<int>();
final merged = ReactiveSubject.merge([subject1, subject2]);
实际示例在 BLoC 中
class SearchBloc extends MainBloc<SearchEvent, SearchState> {
  final ReactiveSubject<String> _searchQuery = ReactiveSubject<String>();
  late final ReactiveSubject<List<String>> _searchResults;

  SearchBloc() : super(const SearchState.initial()) {
    _searchResults = _searchQuery
        .debounceTime(Duration(milliseconds: 100))
        .doOnData((query) {
      showLoading(key: 'search');
      })
        .switchMap((query) => _performSearch(query))
        .doOnData((query) => hideLoading(key: 'search'));

    _searchResults.stream.listen((results) {
      add(UpdateResults(results));
    });

    on<UpdateQuery>(_onUpdateQuery);
    on<UpdateResults>(_onUpdateResults);
    on<SearchError>(_onSearchError);
  }

  Future<void> _onUpdateQuery(
      UpdateQuery event, Emitter<SearchState> emit) async {
    await blocCatch(
        keyLoading: 'search',
        actions: () async {
          await Future.delayed(Duration(seconds: 2));
          _searchQuery.add(event.query);
        });
  }

  void _onUpdateResults(UpdateResults event, Emitter<SearchState> emit) {
    emit(SearchState.loaded(event.results));
  }

  void _onSearchError(SearchError event, Emitter<SearchState> emit) {
    emit(SearchState.error(event.message));
  }

  Stream<List<String>> _performSearch(String query) {
    final resultSubject = ReactiveSubject<List<String>>();
    Future.delayed(Duration(seconds: 1)).then((_) {
      if (query.isEmpty) {
        resultSubject.add([]);
      } else {
        resultSubject.add(['Result 1 for "$query"', 'Result 2 for "$query"']);
      }
    }).catchError((error) {
      add(SearchError(error.toString()));
    });

    return resultSubject.stream;
  }

  [@override](/user/override)
  Future<void> close() {
    _searchQuery.dispose();
    _searchResults.dispose();
    return super.close();
  }
}
错误处理
// 添加错误
subject.addError('Something went wrong');

// 在流中处理错误
subject.stream.listen(
  (data) => print('Data: $data'),
  onError: (error) => print('Error: $error'),
);

// 使用 fromFutureWithError
final subject = ReactiveSubject.fromFutureWithError(
  Future.delayed(Duration(seconds: 1)),
  onError: (error) => print('Error: $error'),
  onFinally: () => print('Completed'),
  timeout: Duration(seconds: 5),
);

更多关于Flutter轻量级状态管理插件bloc_small的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter轻量级状态管理插件bloc_small的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,下面是一个关于如何在Flutter项目中使用轻量级状态管理插件bloc_small的代码示例。bloc_small 是一个简化的BLoC(Business Logic Component)状态管理库,旨在提供比官方bloc库更轻量级的实现。

首先,确保你已经在pubspec.yaml文件中添加了bloc_small依赖:

dependencies:
  flutter:
    sdk: flutter
  bloc_small: ^最新版本号  # 请替换为最新的版本号

然后运行flutter pub get来安装依赖。

创建一个简单的计数器应用

1. 定义State和Event

首先,我们需要定义状态(State)和事件(Event)。

// counter_event.dart
import 'package:bloc_small/bloc_small.dart';

class IncrementEvent extends Event {}
class DecrementEvent extends Event {}
// counter_state.dart
class CounterState {
  final int count;

  CounterState({required this.count});

  CounterState copyWith({int? count}) {
    return CounterState(count: count ?? this.count);
  }
}

2. 创建Bloc

接下来,我们创建一个Bloc来处理事件并更新状态。

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

class CounterBloc extends Bloc<CounterEvent, CounterState> {
  CounterBloc() : super(CounterState(count: 0));

  @override
  void onEvent(CounterEvent event, Emitter<CounterState> emit) {
    if (event is IncrementEvent) {
      emit(state.copyWith(count: state.count + 1));
    } else if (event is DecrementEvent) {
      emit(state.copyWith(count: state.count - 1));
    }
  }
}

3. 创建UI组件

现在,我们可以创建一个Flutter组件来使用这个Bloc。

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

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('Counter App')),
        body: BlocProvider<CounterBloc, CounterState>(
          create: (_) => CounterBloc(),
          child: CounterPage(),
        ),
      ),
    );
  }
}

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

    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          Text(
            'You have pushed the button this many times:',
          ),
          Text(
            '${counterBloc.state.count}',
            style: Theme.of(context).textTheme.headline4,
          ),
          SizedBox(height: 20),
          ElevatedButton(
            onPressed: () => counterBloc.add(IncrementEvent()),
            child: Text('Increment'),
          ),
          SizedBox(height: 10),
          ElevatedButton(
            onPressed: () => counterBloc.add(DecrementEvent()),
            child: Text('Decrement'),
          ),
        ],
      ),
    );
  }
}

总结

上述代码展示了如何使用bloc_small插件在Flutter应用中实现一个简单的计数器。我们定义了事件和状态,创建了一个Bloc来处理事件并更新状态,并在UI组件中使用了Bloc来显示和更新计数器的值。

这个示例提供了一个基本的框架,你可以根据具体需求扩展和修改。

回到顶部