如何将Flutter setState实现转换为Bloc模式?求示例核心解析
基于实战示例讲解Flutter Bloc模式核心实现
我正在学习Flutter的Bloc模式,已阅读官方文档但仍未掌握其核心要点。了解到需将代码中的setState部分替换为Bloc且不改动布局,现提供一个基于setState的实战示例,希望基于该示例讲解Bloc模式的关键实现部分。
Bloc官方核心定义
- Bloc是一个类:作为业务逻辑的容器,连接事件与状态。
- 事件是对用户交互的响应:用户的点击、输入等操作都封装为事件。
- 事件处理器负责将传入事件转换为零或多个输出状态:每个事件对应独立处理逻辑,输出新状态。
- 状态间的变更称为Transition:从当前状态到新状态的完整转换过程。
- 当新增事件且对应的事件处理器发出新状态时会发生Transition:事件触发后,处理器生成新状态,触发状态切换。
- Transition包含当前状态、事件和下一个状态:可用于调试和追踪状态变化链路。
- BlocProvider是向子组件提供BLoC的组件:通过它在组件树中共享Bloc实例。
- Bloc绝不能直接发出新状态,所有状态变更必须在事件处理器中响应传入事件输出:状态变更只能通过
emit方法在事件处理逻辑中完成。 - builder函数根据状态返回组件:UI组件根据Bloc的当前状态自动更新。
原setState实现代码
import 'package:flutter/material.dart'; void main() => runApp(const MyApp()); class MyApp extends StatelessWidget { const MyApp({super.key}); static const String title = 'Flutter Sample'; @override Widget build(BuildContext context) { return MaterialApp( title: title, home: Scaffold( appBar: AppBar(title: const Text(title)), body: const MyStatefulWidget())); } } class MyStatefulWidget extends StatefulWidget { const MyStatefulWidget({super.key}); @override State<MyStatefulWidget> createState() => MyStatefulWidgetState(); } class MyStatefulWidgetState extends State<MyStatefulWidget> { bool light = true; List<String> names = []; TextEditingController nc = TextEditingController(); void addItemToList() { setState(() { names.add(nc.text); getList(); }); } List<Widget> getList() { listWidget = []; listWidget = names.map((p) => Text(p)).toList(); return listWidget; } void deleteItemFromList() { setState(() { if (names.isEmpty) return; names.removeLast(); }); } clearInput() { setState(() { nc.text = ""; }); } bool x = true; enableInput() { setState(() { x != x; }); } List<Widget> listWidget = []; @override Widget build(BuildContext context) { Widget test1 = const Text("Hello Flutter"); Widget test2 = Padding( padding: const EdgeInsets.all(20), child: TextField( enabled: x, controller: nc, decoration: const InputDecoration( border: OutlineInputBorder(), labelText: 'Name'))); Widget test3 = SwitchListTile( value: x, onChanged: (bool value) { setState(() { x = value; }); }); Widget buttons = SizedBox( width: 1000, height: 30, child: Row( mainAxisAlignment: MainAxisAlignment.center, mainAxisSize: MainAxisSize.min, children: [ Expanded( child: TextButton( onPressed: clearInput, child: const Text('ClearInput'))), Expanded( child: TextButton( onPressed: addItemToList, child: const Text('Add'))), Expanded( child: TextButton( onPressed: deleteItemFromList, child: const Text('Remove'), ), ) ], ), ); Widget mylist = ListView.builder( scrollDirection: Axis.vertical, shrinkWrap: true, padding: const EdgeInsets.all(8), itemCount: names.length, itemBuilder: (BuildContext context, int index) { return listWidget[index]; }); return MaterialApp( home: Scaffold( resizeToAvoidBottomInset: false, body: SingleChildScrollView( scrollDirection: Axis.vertical, child: Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, mainAxisSize: MainAxisSize.min, children: <Widget>[ Container( padding: const EdgeInsets.all(5), child: SingleChildScrollView( child: Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: <Widget>[test1, test2, test3, buttons, mylist], ), ), ) ], ), ), ), ); } }
Bloc模式改造与核心实现讲解
1. 添加依赖
在pubspec.yaml中添加Bloc相关依赖:
dependencies: flutter: sdk: flutter flutter_bloc: ^8.1.3 bloc: ^8.1.2
2. 定义事件(Event)
将所有用户操作封装为事件类,每个事件对应一种交互场景:
abstract class NameListEvent {} class NameListAddEvent extends NameListEvent { final String name; NameListAddEvent(this.name); } class NameListRemoveEvent extends NameListEvent {} class ClearInputEvent extends NameListEvent {} class ToggleInputEnabledEvent extends NameListEvent { final bool isEnabled; ToggleInputEnabledEvent(this.isEnabled); }
核心要点:事件是用户交互的抽象,携带必要业务数据(如添加的名字),Bloc通过接收事件触发状态变更。
3. 定义状态(State)
状态包含所有需要管理的业务数据,必须是不可变类:
class NameListState { final List<String> names; final bool isInputEnabled; const NameListState({ required this.names, required this.isInputEnabled, }); // 复制方法,用于生成新状态(保证不可变性) NameListState copyWith({ List<String>? names, bool? isInputEnabled, }) { return NameListState( names: names ?? this.names, isInputEnabled: isInputEnabled ?? this.isInputEnabled, ); } }
核心要点:状态是UI的唯一数据源,每次变更都生成新的状态实例,避免状态污染。
4. 实现Bloc类
Bloc作为逻辑核心,接收事件并处理生成新状态:
import 'package:bloc/bloc.dart'; class NameListBloc extends Bloc<NameListEvent, NameListState> { // 初始状态:空列表,输入框启用 NameListBloc() : super(const NameListState(names: [], isInputEnabled: true)) { // 注册添加事件处理器 on<NameListAddEvent>((event, emit) { final newNames = List<String>.from(state.names)..add(event.name); emit(state.copyWith(names: newNames)); }); // 注册删除事件处理器 on<NameListRemoveEvent>((event, emit) { if (state.names.isEmpty) return; final newNames = List<String>.from(state.names)..removeLast(); emit(state.copyWith(names: newNames)); }); // 注册清空输入事件处理器(此处仅触发事件,UI侧直接操作Controller) on<ClearInputEvent>((event, emit) {}); // 注册切换输入启用状态事件处理器 on<ToggleInputEnabledEvent>((event, emit) { emit(state.copyWith(isInputEnabled: event.isEnabled)); }); } }
核心要点:
- Bloc是一个类,继承自
Bloc<Event, State> - 通过
on<Event>注册事件处理器,每个事件对应独立的处理逻辑 - 状态变更必须通过
emit方法发出新状态,不能直接修改原状态 - 每次状态变更都会生成Transition,包含当前状态、事件、新状态
5. UI集成Bloc(不改动原布局)
用BlocProvider提供Bloc实例,用BlocBuilder监听状态更新,替换原setState逻辑:
import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; void main() => runApp(const MyApp()); class MyApp extends StatelessWidget { const MyApp({super.key}); static const String title = 'Flutter Sample'; @override Widget build(BuildContext context) { return MaterialApp( title: title, home: Scaffold( appBar: AppBar(title: const Text(title)), // 用BlocProvider提供NameListBloc实例 body: BlocProvider( create: (context) => NameListBloc(), child: const MyStatelessWidget(), // 原StatefulWidget改为Stateless ), ), ); } } class MyStatelessWidget extends StatelessWidget { const MyStatelessWidget({super.key}); final TextEditingController nc = TextEditingController(); @override Widget build(BuildContext context) { // 获取Bloc实例 final nameListBloc = BlocProvider.of<NameListBloc>(context); Widget test1 = const Text("Hello Flutter"); // 用BlocBuilder监听状态,更新TextField的enabled属性 Widget test2 = BlocBuilder<NameListBloc, NameListState>( builder: (context, state) { return Padding( padding: const EdgeInsets.all(20), child: TextField( enabled: state.isInputEnabled, controller: nc, decoration: const InputDecoration( border: OutlineInputBorder(), labelText: 'Name', ), ), ); }, ); // 切换开关时触发ToggleInputEnabledEvent Widget test3 = BlocBuilder<NameListBloc, NameListState>( builder: (context, state) { return SwitchListTile( value: state.isInputEnabled, onChanged: (bool value) { nameListBloc.add(ToggleInputEnabledEvent(value)); }, ); }, ); Widget buttons = SizedBox( width: 1000, height: 30, child: Row( mainAxisAlignment: MainAxisAlignment.center, mainAxisSize: MainAxisSize.min, children: [ Expanded( child: TextButton( onPressed: () { nc.text = ""; nameListBloc.add(ClearInputEvent()); }, child: const Text('ClearInput'), ), ), Expanded( child: TextButton( onPressed: () { if (nc.text.isNotEmpty) { nameListBloc.add(NameListAddEvent(nc.text)); } }, child: const Text('Add'), ), ), Expanded( child: TextButton( onPressed: () { nameListBloc.add(NameListRemoveEvent()); }, child: const Text('Remove'), ), ) ], ), ); // 用BlocBuilder监听names列表变化,更新ListView Widget mylist = BlocBuilder<NameListBloc, NameListState>( builder: (context, state) { return ListView.builder( scrollDirection: Axis.vertical, shrinkWrap: true, padding: const EdgeInsets.all(8), itemCount: state.names.length, itemBuilder: (BuildContext context, int index) { return Text(state.names[index]); }, ); }, ); return Scaffold( resizeToAvoidBottomInset: false, body: SingleChildScrollView( scrollDirection: Axis.vertical, child: Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, mainAxisSize: MainAxisSize.min, children: <Widget>[ Container( padding: const EdgeInsets.all(5), child: SingleChildScrollView( child: Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: <Widget>[test1, test2, test3, buttons, mylist], ), ), ) ], ), ), ); } }
核心要点:
BlocProvider在组件树中提供Bloc实例,子组件可通过BlocProvider.of获取BlocBuilder根据当前状态自动重建对应的UI部分,替代原setState的作用- 用户操作不再直接修改状态,而是通过
bloc.add(Event)触发事件,由Bloc处理后更新状态
内容的提问来源于stack exchange,提问作者DktPhl2018
相关产品推荐
相关产品推荐

