Flutter开发 -- 使用Bloc管理状态

使用Bloc模式开发Flutter Todo应用

在Flutter生态里有很多的状态管理工具。比如,你一开始就会熟悉的setStateprovider这些。在其他的工具里,Bloc这个模式非常流行。所以,我们花点时间来学一学。

本文继续用todo app作为例子,尽量接近真实开发环境。大部分主要的操作需要请求后端服务器。状态管理工具当然就使用Bloc模式来实现。另外,还会实现Bloc的单元测试,以此保证Bloc的类都能按照预想的工作。

Bloc是什么

在正式开始之前,如果你对Dart和Flutter有一定的了解。能理解Flutter的状态管理以及一些Flutter单元测试的知识就更好了。

Bloc的意思是 Business Logic Component ,这个模式用于分离逻辑和用户界面(UI)组件。这样可以让开发者更好的维护代码。

Bloc模式包含一下的部分:

  • Bloc:这个是核心部分。它包含业务逻辑和app的状态管理。
  • 事件:Bloc会对于每个事件做出反应。事件可以是用户的操作(比如,按钮点击)也可以是外部对数据的更新。
  • 状态:状态是用来代表app可以存在的各种状态。Bloc发出更具不同的事件发出状态。UI则根据状态的变化做出反应,绘制出不同的界面。
  • 用户界面:用户界面根据状态的变化呈现不同的界面。当Bloc发出一个新的状态,界面就更具这个新的状态呈现一个新的几面。

开发准备

在开始开发前,我们需要准备好开发环境。对于iOS开发者来说,可以使用appuploader这样的工具来简化开发流程。appuploader是一款专业的iOS开发助手,可以帮助开发者快速完成证书管理、应用打包和上传等工作,让开发者能更专注于代码本身。

首先来添加所需要的依赖:

yaml 复制代码
dependencies:
  flutter:
    sdk: flutter
  flutter_bloc: ^8.1.5
  go_router: ^14.1.0
  json_annotation: ^4.9.0
  http: ^1.2.1
  equatable: ^2.0.5

dev_dependencies:
  flutter_test:
    sdk:flutter
  flutter_lints: ^3.0.0
  json_serializable: ^6.8.0
  build_runner: ^2.4.9
  bloc_test: ^9.1.7
  mocktail: ^1.0.3

执行命令:flutter pub get,这些依赖就安装好了。

Service类

现在来写Service类。这些不用专门写了,直接从其他的todo app里拿过来就可以用。为了方便直接看本文的读者,这里来简单描述一下。有一个http请求的util类:

dart 复制代码
class HttpRequest {
  final String hostUrl = 'http://address_of_your_server:17788';
  var _client = http.Client();

  set client(c) => _client = c;
  get client => _client;

  Future<List<TodoItem>?> fetchTodoList({
    bool all = false,
    String completed = 'completed',
  }) async {
    // ...
  }

  Future<TodoItem?> fetchTodoById(int todoId) async {
    // ...
  }

  Future<TodoItem> addTodoItem(String todoTitle) async {
    // ...
  }

  Future<void> updateTodo(int id,
      {String? todoTitle, String? note, int? status, int? deleted}) async {
    // ...
  }

  Future<void> deleteTodo(int id) async {
    // ...
  }
}

Bloc实现

定义事件和状态

dart 复制代码
sealed class TodoListEvent extends Equatable {}

class TodoListRequested extends TodoListEvent {
  @override
  List<Object?> get props => [];
}

@immutable
sealed class TodoListState extends Equatable {
  const TodoListState({required this.todoList});

  final List<TodoItem> todoList;
  @override
  List<Object?> get props => [todoList];
}

class TodoListInitialState extends TodoListState {
  const TodoListInitialState() : super(todoList: const <TodoItem>[]);
}

class TodoListLoadedState extends TodoListState {
  const TodoListLoadedState({required super.todoList});
}

class TodoListErrorState extends TodoListState {
  const TodoListErrorState() : super(todoList: const <TodoItem>[]);
}

实现Bloc类

dart 复制代码
class TodoListBloc extends Bloc<TodoListEvent, TodoListState> {
  TodoListBloc() : super(const TodoListInitialState()) {
    on<TodoListRequested>(_onFetchTodoList);
  }

  Future<void> _onFetchTodoList(
      TodoListEvent event, Emitter<TodoListState> emit) async {
    emit(const TodoListInitialState());
    try {
      final repository = TodoListRepository();
      final result = await repository.fetchTodoList(all: true);
      emit(TodoListLoadedState(todoList: result ?? []));
    } catch (error) {
      emit(const TodoListErrorState());
    }
  }
}

在UI中使用Bloc

dart 复制代码
class HomePage extends StatelessWidget {
  const HomePage({super.key});

  @override
  build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        // 略
      ),
      body: BlocBuilder<TodoListBloc, TodoListState>(builder: (context, state) {
        switch (state) {
          case TodoListInitialState():
            // Loading...
          case TodoListLoadedState():
            return ListView.separated(
              restorationId: 'sampleItemListView',
              itemCount: state.todoList.length,
              itemBuilder: (BuildContext context, int index) {
                final item = state.todoList[index];
                content: item.content ?? '',
                // 其他略
              },
              separatorBuilder: (context, index) => const Divider(),
            );
          case TodoListErrorState():
            // Error, 略...
        }
      }),
    );
  }
}

测试Bloc

dart 复制代码
class MockTodoListRepository extends Mock implements TodoListRepository {}

blocTest<TodoListBloc, TodoListState>(
  'emits [TodoListInitialState, TodoListErrorState] when http request throws an error',
  setUp: () => when(mockRepository.fetchTodoList).thenThrow(Exception()),
  build: () => TodoListBloc(todoListRepository: mockRepository),
  act: (bloc) => bloc.add(TodoListRequested()),
  expect: () => <TodoListState>[
    const TodoListInitialState(),
    const TodoListErrorState()
  ],
  verify: (_) => verify(() =>
          mockRepository.fetchTodoList(all: true, completed: 'completed'))
      .called(1),
);

总结

通过本文我们学习了如何使用Bloc模式来管理Flutter应用的状态。Bloc模式通过将业务逻辑与UI分离,使代码更易于维护和测试。在实际开发中,可以结合appuploader这样的工具来简化iOS应用的打包和发布流程,提高开发效率。

相关推荐
东方靖岚3 小时前
R语言的数据库交互
开发语言·后端·golang
uhakadotcom5 小时前
Python 量化计算入门:基础库和实用案例
后端·算法·面试
小萌新上大分5 小时前
SpringCloudGateWay
java·开发语言·后端·springcloud·springgateway·cloudalibaba·gateway网关
uhakadotcom5 小时前
使用Python获取Google Trends数据:2025年详细指南
后端·面试·github
uhakadotcom5 小时前
使用 Python 与 Google Cloud Bigtable 进行交互
后端·面试·github
直视太阳6 小时前
springboot+easyexcel实现下载excels模板下拉选择
java·spring boot·后端
追逐时光者6 小时前
C#/.NET/.NET Core技术前沿周刊 | 第 33 期(2025年4.1-4.6)
后端·.net
灼华十一6 小时前
Golang系列 - 内存对齐
开发语言·后端·golang
兰亭序咖啡6 小时前
学透Spring Boot — 009. Spring Boot的四种 Http 客户端
java·spring boot·后端
Asthenia04127 小时前
深入解析Pandas索引机制:离散选择与聚合选择的差异及常见误区
后端