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

相关推荐
duapple4 小时前
Golang基于反射的ioctl实现
开发语言·后端·golang
my_styles7 小时前
docker-compose部署项目(springboot服务)以及基础环境(mysql、redis等)ruoyi-ry
spring boot·redis·后端·mysql·spring cloud·docker·容器
免檒9 小时前
go语言协程调度器 GPM 模型
开发语言·后端·golang
不知道写什么的作者9 小时前
Flask快速入门和问答项目源码
后端·python·flask
caihuayuan510 小时前
生产模式下react项目报错minified react error #130的问题
java·大数据·spring boot·后端·课程设计
一只码代码的章鱼10 小时前
Spring Boot- 2 (数万字入门教程 ):数据交互篇
spring boot·后端·交互
不再幻想,脚踏实地13 小时前
Spring AOP从0到1
java·后端·spring
编程乐学(Arfan开发工程师)13 小时前
07、基础入门-SpringBoot-自动配置特性
java·spring boot·后端
会敲键盘的猕猴桃很大胆13 小时前
Day11-苍穹外卖(数据统计篇)
java·spring boot·后端·spring·信息可视化
极客智谷13 小时前
Spring Cloud动态配置刷新:@RefreshScope与@Component的协同机制解析
后端·spring·spring cloud