Flutter 计数器
在下面的教程中,我们将使用 Bloc 库在 Flutter 中构建一个计数器。
- 使用 BlocObserver 观察状态变更。
- BlocProvider,为子组件提供 bloc 的 Flutter 部件。
- BlocBuilder,用于响应新状态来构建小部件的 Flutter 部件。
- 用 Cubit 替代 Bloc。有什么不同?。
- 使用 context.read 添加事件。
我们从创建一个全新的 Flutter 项目开始
flutter create flutter_counter
用下面的代码替换 pubspec.yaml
的内容:
name: flutter_counterdescription: A new Flutter project.version: 1.0.0+1publish_to: none
environment: sdk: ">=3.6.0 <4.0.0"
dependencies: bloc: ^9.0.0 flutter: sdk: flutter flutter_bloc: ^9.1.0
dev_dependencies: bloc_test: ^10.0.0 flutter_test: sdk: flutter integration_test: sdk: flutter mocktail: ^1.0.0
flutter: uses-material-design: true
然后安装所有的依赖
flutter packages get
├── lib│ ├── app.dart│ ├── counter│ │ ├── counter.dart│ │ ├── cubit│ │ │ └── counter_cubit.dart│ │ └── view│ │ ├── counter_page.dart│ │ ├── counter_view.dart│ │ └── view.dart│ ├── counter_observer.dart│ └── main.dart├── pubspec.lock├── pubspec.yaml
这个应用采用了功能驱动的目录结构。这种项目结构以便于我们按照独立的功能对项目进行扩展。这个示例项目里只有一个功能(计数器),但是在更加复杂的应用里我们可以包含数以百计的功能。
我们要做的第一件事是看看如何创建一个 BlocObserver
来观察应用里所有的状态变更。
我们先创建一个 lib/counter_observer.dart
:
import 'package:bloc/bloc.dart';
/// {@template counter_observer}/// [BlocObserver] for the counter application which/// observes all state changes./// {@endtemplate}class CounterObserver extends BlocObserver { /// {@macro counter_observer} const CounterObserver();
@override void onChange(BlocBase<dynamic> bloc, Change<dynamic> change) { super.onChange(bloc, change); // ignore: avoid_print print('${bloc.runtimeType} $change'); }}
目前,我们仅重写了 onChange
来查看所有发生的状态变更。
下一步,我们替换 lib/main.dart
的代码如下:
import 'package:bloc/bloc.dart';import 'package:flutter/widgets.dart';import 'package:flutter_counter/app.dart';import 'package:flutter_counter/counter_observer.dart';
void main() { Bloc.observer = const CounterObserver(); runApp(const CounterApp());}
我们初始化了我们创建的 CounterObserver
并且在 runApp
里添加了 CounterApp
组件。
创建一个 lib/app.dart
:
CounterApp
是一个 MaterialApp
并且指定了 CounterPage
作为主页。
import 'package:flutter/material.dart';import 'package:flutter_counter/counter/counter.dart';
/// {@template counter_app}/// A [MaterialApp] which sets the `home` to [CounterPage]./// {@endtemplate}class CounterApp extends MaterialApp { /// {@macro counter_app} const CounterApp({super.key}) : super(home: const CounterPage());}
接下来咱们看看 CounterPage
!
创建 lib/counter/view/counter_page.dart
如下:
CounterPage
部件负责创建 CounterCubit
(下面会讲到)并且提供给 CounterView
。
import 'package:flutter/material.dart';import 'package:flutter_bloc/flutter_bloc.dart';import 'package:flutter_counter/counter/counter.dart';
/// {@template counter_page}/// A [StatelessWidget] which is responsible for providing a/// [CounterCubit] instance to the [CounterView]./// {@endtemplate}class CounterPage extends StatelessWidget { /// {@macro counter_page} const CounterPage({super.key});
@override Widget build(BuildContext context) { return BlocProvider( create: (_) => CounterCubit(), child: const CounterView(), ); }}
创建 lib/counter/cubit/counter_cubit.dart
如下:
CounterCubit
会公开以下方法:
increment
: 当前状态 +1decrement
: 当前状态 -1
CounterCubit
管理的是 int
型的状态,初始值为 0
。
import 'package:bloc/bloc.dart';
/// {@template counter_cubit}/// A [Cubit] which manages an [int] as its state./// {@endtemplate}class CounterCubit extends Cubit<int> { /// {@macro counter_cubit} CounterCubit() : super(0);
/// Add 1 to the current state. void increment() => emit(state + 1);
/// Subtract 1 from the current state. void decrement() => emit(state - 1);}
接下来,我们看看负责消费并且与 CounterCubit
进行交互的 CounterView
。
创建 lib/counter/view/counter_view.dart
如下:
CounterView
负责渲染当前的数值,以及两个 FloatingActionButtons 来负责增/减计数。
import 'package:flutter/material.dart';import 'package:flutter_bloc/flutter_bloc.dart';import 'package:flutter_counter/counter/counter.dart';
/// {@template counter_view}/// A [StatelessWidget] which reacts to the provided/// [CounterCubit] state and notifies it in response to user input./// {@endtemplate}class CounterView extends StatelessWidget { /// {@macro counter_view} const CounterView({super.key});
@override Widget build(BuildContext context) { final textTheme = Theme.of(context).textTheme; return Scaffold( body: Center( child: BlocBuilder<CounterCubit, int>( builder: (context, state) { return Text('$state', style: textTheme.displayMedium); }, ), ), floatingActionButton: Column( mainAxisAlignment: MainAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.end, children: <Widget>[ FloatingActionButton( key: const Key('counterView_increment_floatingActionButton'), child: const Icon(Icons.add), onPressed: () => context.read<CounterCubit>().increment(), ), const SizedBox(height: 8), FloatingActionButton( key: const Key('counterView_decrement_floatingActionButton'), child: const Icon(Icons.remove), onPressed: () => context.read<CounterCubit>().decrement(), ), ], ), ); }}
BlocBuilder
用来包装 Text
部件,这样 CounterCubit
任何的状态变化都会更新文本。此外, context.read<CounterCubit>()
用于查找最近的 CounterCubit
实例。
创建 lib/counter/view/view.dart
:
添加 view.dart
来导出所有 counter 视图的公共部分。
export 'counter_page.dart';export 'counter_view.dart';
创建 lib/counter/counter.dart
:
添加 counter.dart
来导出 counter 功能的所有公共部分。
export 'cubit/counter_cubit.dart';export 'view/view.dart';
以上就是全部!我们将业务逻辑层从展现层种分离了出来。 CounterView
不知道用户按下按钮以后会发生什么;它只是通知 CounterCubit
。此外,CounterCubit
也不知道状态(计数器的值)发生了什么;它只是响应调用的方法发出新状态。
我们可以运行 flutter run
来在设备或者模拟器上查看运行的效果。
可以在 这里 找到这个例子的完整代码(包括单元和部件测试)。