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应用的打包和发布流程,提高开发效率。

相关推荐
费益洲8 分钟前
Docker 核心技术:Linux Cgroups
后端
杨DaB10 分钟前
【SpringBoot】Dubbo、Zookeeper
spring boot·后端·zookeeper·dubbo·java-zookeeper
一语长情25 分钟前
Netty流量整形:保障微服务通信稳定性的关键策略
java·后端·架构
冲鸭ONE25 分钟前
java数据类型与语句结构
后端
柯南二号39 分钟前
【后端】SpringBoot中HttpServletRequest参数为啥不需要前端透传
前端·spring boot·后端
MonKingWD1 小时前
MySQL事务篇-事务概念、并发事务问题、隔离级别
数据库·后端·mysql
华仔啊1 小时前
别学23种了!Java项目中最常用的6个设计模式,附案例
java·后端·设计模式
bobz9651 小时前
openstack nova ironic 架构图以及流程
后端
咕白m6251 小时前
C# 实现 PDF 转图片 - 分辨率设置、图片格式选择
后端·c#
Java水解2 小时前
深入理解 SQL 中的 COALESCE、NULLIF 和 IFNULL 函数
后端·sql