跳转到内容

Flutter 计数器

初级

在下面的教程中,我们将使用 Bloc 库在 Flutter 中构建一个计数器。

demo

关键主题

设置

我们从创建一个全新的 Flutter 项目开始

Terminal window
flutter create flutter_counter

用下面的代码替换 pubspec.yaml 的内容:

pubspec.yaml
name: flutter_counter
description: A new Flutter project.
version: 1.0.0+1
publish_to: none
environment:
sdk: ">=3.0.0 <4.0.0"
dependencies:
bloc: ^8.1.0
flutter:
sdk: flutter
flutter_bloc: ^8.1.1
dev_dependencies:
bloc_test: ^9.1.0
flutter_test:
sdk: flutter
integration_test:
sdk: flutter
mocktail: ^1.0.0
flutter:
uses-material-design: true

然后安装所有的依赖

Terminal window
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

我们要做的第一件事是看看如何创建一个 BlocObserver 来观察应用里所有的状态变更。

我们先创建一个 lib/counter_observer.dart

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 来查看所有发生的状态变更。

main.dart

下一步,我们替换 lib/main.dart 的代码如下:

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 组件。

Counter App

创建一个 lib/app.dart:

CounterApp 是一个 MaterialApp 并且指定了 CounterPage 作为主页。

lib/app.dart
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

Counter Page

创建 lib/counter/view/counter_page.dart 如下:

CounterPage 部件负责创建 CounterCubit (下面会讲到)并且提供给 CounterView

lib/counter/view/counter_page.dart
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(),
);
}
}

Counter Cubit

创建 lib/counter/cubit/counter_cubit.dart 如下:

CounterCubit 会公开以下方法:

  • increment: 当前状态 +1
  • decrement: 当前状态 -1

CounterCubit 管理的是 int 型的状态,初始值为 0

lib/counter/cubit/counter_cubit.dart
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

Counter View

创建 lib/counter/view/counter_view.dart 如下:

CounterView 负责渲染当前的数值,以及两个 FloatingActionButtons 来负责增/减计数。

lib/counter/view/counter_view.dart
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 实例。

Barrel

创建 lib/counter/view/view.dart:

添加 view.dart 来导出所有 counter 视图的公共部分。

lib/counter/view/view.dart
export 'counter_page.dart';
export 'counter_view.dart';

创建 lib/counter/counter.dart:

添加 counter.dart 来导出 counter 功能的所有公共部分。

lib/counter/counter.dart
export 'cubit/counter_cubit.dart';
export 'view/view.dart';

以上就是全部!我们将业务逻辑层从展现层种分离了出来。 CounterView 不知道用户按下按钮以后会发生什么;它只是通知 CounterCubit。此外,CounterCubit 也不知道状态(计数器的值)发生了什么;它只是响应调用的方法发出新状态。

我们可以运行 flutter run 来在设备或者模拟器上查看运行的效果。

可以在 这里 找到这个例子的完整代码(包括单元和部件测试)。