Flutter Bloc 状态管理完全指南
引言
Bloc (Business Logic Component) 是一个强大的状态管理库,它提供了一种可预测且可测试的方式来管理应用状态。本文将深入探讨 Bloc 的各种用法和高级技巧。
基础概念回顾
什么是 Bloc
Bloc 是一种状态管理模式,它将业务逻辑与 UI 分离,使代码更加可维护和可测试。
核心概念
- Event: 触发状态变化的事件
- State: 当前状态
- Bloc: 处理事件并产生状态
高级技巧一:创建 Bloc
基本结构
import 'package:bloc/bloc.dart'; enum CounterEvent { increment, decrement } class CounterBloc extends Bloc<CounterEvent, int> { CounterBloc() : super(0) { on<CounterEvent>((event, emit) { switch (event) { case CounterEvent.increment: emit(state + 1); break; case CounterEvent.decrement: emit(state - 1); break; } }); } }使用 Bloc
void main() { final bloc = CounterBloc(); bloc.add(CounterEvent.increment); bloc.add(CounterEvent.increment); bloc.stream.listen((state) { print('Counter: $state'); }); bloc.close(); }高级技巧二:异步操作
处理异步事件
class WeatherBloc extends Bloc<WeatherEvent, WeatherState> { final WeatherRepository repository; WeatherBloc({required this.repository}) : super(WeatherInitial()) { on<FetchWeather>((event, emit) async { emit(WeatherLoading()); try { final weather = await repository.fetchWeather(event.city); emit(WeatherLoaded(weather)); } catch (e) { emit(WeatherError(e.toString())); } }); } } abstract class WeatherEvent {} class FetchWeather extends WeatherEvent { final String city; FetchWeather(this.city); } abstract class WeatherState {} class WeatherInitial extends WeatherState {} class WeatherLoading extends WeatherState {} class WeatherLoaded extends WeatherState { final Weather weather; WeatherLoaded(this.weather); } class WeatherError extends WeatherState { final String message; WeatherError(this.message); }高级技巧三:条件事件处理
使用条件监听
class AuthBloc extends Bloc<AuthEvent, AuthState> { AuthBloc() : super(AuthUnauthenticated()) { on<Login>( (event, emit) async { emit(AuthLoading()); // 登录逻辑 emit(AuthAuthenticated()); }, transformer: (events, mapper) => events.debounceTime(Duration(milliseconds: 300)), ); on<Logout>( (event, emit) => emit(AuthUnauthenticated()), ); } }事件转换器
EventTransformer<E> debounce<E>(Duration duration) { return (events, mapper) => events.debounceTime(duration).flatMap(mapper); }高级技巧四:状态转换
使用 Transition
class CounterBloc extends Bloc<CounterEvent, int> { CounterBloc() : super(0) { on<CounterEvent>((event, emit) { if (event == CounterEvent.increment) { emit(state + 1); } else { emit(state - 1); } }); } @override void onTransition(Transition<CounterEvent, int> transition) { super.onTransition(transition); print('Transition: ${transition.event} -> ${transition.nextState}'); } }实战案例:登录流程
class LoginBloc extends Bloc<LoginEvent, LoginState> { final AuthRepository authRepository; LoginBloc({required this.authRepository}) : super(LoginInitial()) { on<LoginSubmitted>((event, emit) async { emit(LoginLoading()); try { final user = await authRepository.login( event.email, event.password, ); emit(LoginSuccess(user)); } on AuthException catch (e) { emit(LoginFailure(e.message)); } }); } } abstract class LoginEvent {} class LoginSubmitted extends LoginEvent { final String email; final String password; LoginSubmitted(this.email, this.password); } abstract class LoginState {} class LoginInitial extends LoginState {} class LoginLoading extends LoginState {} class LoginSuccess extends LoginState { final User user; LoginSuccess(this.user); } class LoginFailure extends LoginState { final String message; LoginFailure(this.message); }实战案例:使用 BlocBuilder
class LoginPage extends StatelessWidget { @override Widget build(BuildContext context) { return BlocProvider( create: (context) => LoginBloc(authRepository: AuthRepository()), child: Scaffold( appBar: AppBar(title: Text('Login')), body: BlocConsumer<LoginBloc, LoginState>( listener: (context, state) { if (state is LoginSuccess) { Navigator.pushReplacement(context, HomePage.route); } }, builder: (context, state) { if (state is LoginLoading) { return Center(child: CircularProgressIndicator()); } if (state is LoginFailure) { return Column( children: [ LoginForm(), Text(state.message, style: TextStyle(color: Colors.red)), ], ); } return LoginForm(); }, ), ), ); } } class LoginForm extends StatelessWidget { @override Widget build(BuildContext context) { final emailController = TextEditingController(); final passwordController = TextEditingController(); return Column( children: [ TextField( controller: emailController, decoration: InputDecoration(labelText: 'Email'), ), TextField( controller: passwordController, decoration: InputDecoration(labelText: 'Password'), obscureText: true, ), ElevatedButton( onPressed: () { context.read<LoginBloc>().add( LoginSubmitted( emailController.text, passwordController.text, ), ); }, child: Text('Login'), ), ], ); } }实战案例:状态持久化
class CounterBloc extends Bloc<CounterEvent, int> { CounterBloc() : super(_loadCounter()) { on<CounterEvent>((event, emit) { switch (event) { case CounterEvent.increment: final newState = state + 1; _saveCounter(newState); emit(newState); break; case CounterEvent.decrement: final newState = state - 1; _saveCounter(newState); emit(newState); break; } }); } static int _loadCounter() { final prefs = SharedPreferences.getInstance(); return prefs.then((p) => p.getInt('counter') ?? 0); } Future<void> _saveCounter(int value) async { final prefs = await SharedPreferences.getInstance(); await prefs.setInt('counter', value); } }常见问题与解决方案
Q1:如何测试 Bloc?
A:使用 bloc_test:
void main() { group('CounterBloc', () { late CounterBloc bloc; setUp(() { bloc = CounterBloc(); }); tearDown(() { bloc.close(); }); test('initial state is 0', () { expect(bloc.state, 0); }); blocTest( 'emits [1] when increment is added', build: () => bloc, act: (bloc) => bloc.add(CounterEvent.increment), expect: () => [1], ); }); }Q2:如何处理并发事件?
A:使用事件转换器:
on<SearchEvent>( _onSearch, transformer: debounce(Duration(milliseconds: 300)), );Q3:如何共享状态?
A:使用 BlocProvider 和 RepositoryProvider:
MultiBlocProvider( providers: [ BlocProvider(create: (context) => UserBloc()), BlocProvider(create: (context) => ThemeBloc()), ], child: MyApp(), )最佳实践
1. 分离业务逻辑
// 推荐 class UserRepository { Future<User> getUser(int id) => api.getUser(id); } class UserBloc extends Bloc<UserEvent, UserState> { final UserRepository repository; UserBloc(this.repository); }2. 使用 sealed classes
// 推荐 sealed class UserEvent {} final class LoadUser extends UserEvent {} final class UpdateUser extends UserEvent {}3. 合理拆分 Bloc
// 推荐 // user_bloc.dart // theme_bloc.dart // settings_bloc.dart总结
Flutter Bloc 是一个强大的状态管理库。通过本文的学习,你应该能够:
- 创建和使用 Bloc
- 处理异步操作
- 使用事件转换器
- 测试 Bloc
- 实现状态持久化
掌握这些技巧,能够帮助你创建更加可维护和可测试的应用程序。