Hydrated Bloc: сохраняйте свое состояние приложения
«Hydrated Bloc» упрощает сохранение состояния во Flutter, упрощая сохранение и восстановление состояния вашего приложения. Попрощайтесь с сериализацией и десериализацией состояния вручную — это изящное расширение позаботится обо всем! С Hydrated Bloc вы можете сосредоточиться на разработке функций своего приложения и радовать своих пользователей, не беспокоясь о сложностях управления состоянием.
Что такое Hydrated Bloc и как он работает?
Hydrated Bloc — это мощное расширение, созданное поверх пакета блоков во Flutter. Он автоматизирует процесс сохранения и восстановления состояния, упрощая поддержание состояния вашего приложения в разных сеансах. Когда вы создаете BlocProvider
, Hydrated Bloc автоматически использует метод fromJson
для извлечения сохраненного состояния из локального хранилища.
Всякий раз, когда состояние блока изменяется, в игру вступает метод toJson
. Он преобразует текущее состояние в формат Map<String, dynamic>
, который затем сохраняется в локальном хранилище. Это гарантирует, что состояние вашего приложения всегда актуально и готово к восстановлению в случае необходимости, даже если приложение закрыто или пользователь перемещается между экранами.
Hydrated Bloc оптимизирует процесс сохранения состояния, избавляя от хлопот, связанных с ручной сериализацией и десериализацией. Автоматизируя управление состоянием, оно повышает надежность вашего приложения и устраняет риск потери состояния.
Преимущества Hydrated Bloc по сравнению с Traditional Bloc
Hydrated Bloc | Bloc | |
Постоянство состояния | Автоматизирует сохранение состояния и восстановление. | Ручная сериализация и десериализация состояния. |
Зависимость пакета | Построен как расширение package:bloc. | Напрямую использует package:bloc. |
Сложность | Упрощает управление состоянием с автоматическим сохранением. | Требуется дополнительная обработка для сохранения состояния. |
Инициализация | Инициализируется из сохраненного состояния, если оно доступно. | Запускается с начальным состоянием при каждом запуске. |
Хранилище | Использует локальное хранилище (улей [без SQL]) для сохранения состояний. | Нет встроенного хранилища состояний, оставлено разработчикам. |
Надежность приложения | Повышает надежность приложения, предотвращая потерю состояния. | Склонен к потере состояния при неправильном обращении. |
Начало работы с Hydrated Bloc
Использование Hydrated Bloc очень просто! У вас есть два варианта для начала:
- Путем расширения: вы можете использовать Hydrated Bloc, расширив существующий класс блоков.
sealed class CounterEvent {}
final class CounterIncrementPressed extends CounterEvent {}
class CounterBloc extends HydratedBloc<CounterEvent, int> {
CounterBloc() : super(0) {
on<CounterIncrementPressed>((event, emit) => emit(state + 1));
}
@override
int fromJson(Map<String, dynamic> json) => json['value'] as int;
@override
Map<String, int> toJson(int state) => { 'value': state };
}
- Использование HydratedMixin:
class CounterBloc extends Bloc<CounterEvent, int> with HydratedMixin {
CounterBloc() : super(0) {
on<CounterIncrementPressed>((event, emit) => emit(state + 1));
}
@override
int fromJson(Map<String, dynamic> json) => json['value'] as int;
@override
Map<String, int> toJson(int state) => { 'value': state };
}
Чтобы получить доступ к объекту хранилища в Hydrated Bloc и управлять им, выполните следующие действия:
- Создайте собственный класс хранения, расширив класс
Storage
.
class MyHydratedStorage implements Storage {
@override
dynamic read(String key) {
// TODO: implement read
}
@override
Future<void> write(String key, dynamic value) async {
// TODO: implement write
}
@override
Future<void> delete(String key) async {
// TODO: implement delete
}
@override
Future<void> clear() async {
// TODO: implement clear
}
}
- Установите пользовательскую реализацию хранилища:
HydratedBloc.storage = MyHydratedStorage();
Внутри пользовательского класса хранилища вы можете реализовать методы для чтения, записи, удаления и очистки данных. Эти методы позволяют вам взаимодействовать с локальным хранилищем и управлять данными для Hydrated Bloc.
Давайте создадим простое приложение ToDo
- Создайте проект Flutter и добавьте необходимые зависимости в файл
pubspec.yaml
.
dependencies:
hydrated_bloc: ^9.1.2 //hydrated bloc
path_provider: ^2.0.15 // for getTemporaryDirectory
flutter_easyloading: ^3.0.5 // Helpful for showing loading without context
flutter_bloc: ^8.1.3 //flutter bloc
для получения дополнительной информации hydrated bloc
- Определите модель ToDo для сериализации JSON.
class TodoModel {
final int id;
final String title;
final bool isCompleted;
final String description;
final String createdAt;
final String updatedAt;
TodoModel(
this.id,
this.title,
this.isCompleted,
this.description,
this.createdAt,
this.updatedAt,
);
// JSON serialization: Map<String, dynamic> -> TodoModel
factory TodoModel.fromJson(Map<String, dynamic> json) {
return TodoModel(
json['id'],
json['title'],
json['is_completed'],
json['description'],
json['created_at'],
json['updated_at'],
);
}
// JSON deserialization: TodoModel -> Map<String, dynamic>
Map<String, dynamic> toJson() {
return {
'id': id,
'title': title,
'is_completed': isCompleted,
'description': description,
'created_at': createdAt,
'updated_at': updatedAt,
};
}
//Copy with function
TodoModel copyWith({
int? id,
String? title,
bool? isCompleted,
String? description,
String? createdAt,
String? updatedAt,
}) {
return TodoModel(
id ?? this.id,
title ?? this.title,
isCompleted ?? this.isCompleted,
description ?? this.description,
createdAt ?? this.createdAt,
updatedAt ?? this.updatedAt,
);
}
}
- Создайте репозиторий ToDo для управления задачами.
// ignore_for_file: depend_on_referenced_packages
import 'package:collection/collection.dart';
import 'package:hydated_bloc_example/Data/Model/todo_model.dart';
class TodoRepo {
List<TodoModel> tasks = [];
List<TodoModel> addTodo(TodoModel model) {
tasks.add(model);
return tasks;
}
List<TodoModel> removeTodo(int id) {
tasks.removeWhere((element) => element.id == id);
return tasks;
}
List<TodoModel> updateTask(int id, bool status) {
TodoModel? taskModel =
tasks.firstWhereOrNull((element) => element.id == id);
if (taskModel != null) {
tasks = tasks.map((task) {
if (task.id == id) {
return task.copyWith(isCompleted: status);
}
return task;
}).toList();
}
return tasks;
}
}
- Определите возможные состояния и события для блока Todo.
//`todo_state.dart`:
sealed class TodoState {}
final class TodoInitial extends TodoState {}
final class TodoStateLoaded extends TodoState {
final List<TodoModel> task;
TodoStateLoaded(this.task);
}
import 'package:hydated_bloc_example/Data/Model/todo_model.dart';
sealed class TodoEvent {}
final class TodoFetch extends TodoEvent {}
final class TodoAdd extends TodoEvent {
final TodoModel task;
TodoAdd(this.task);
}
final class TodoRemove extends TodoEvent {
final int id;
TodoRemove(this.id);
}
final class TodoUpdate extends TodoEvent {
final int id;
final bool status;
TodoUpdate(this.id, this.status);
}
- Реализуйте блок ToDo с помощью Hydrated Bloc.
import 'package:hydated_bloc_example/Bloc/todo_event.dart';
import 'package:hydated_bloc_example/Bloc/todo_state.dart';
import 'package:hydated_bloc_example/Data/Model/todo_model.dart';
import 'package:hydated_bloc_example/Data/Repo/todo_repo.dart';
import 'package:hydated_bloc_example/UI/Widgets/fake_loading.dart';
import 'package:hydrated_bloc/hydrated_bloc.dart';
class TodoBloc extends HydratedBloc<TodoEvent, TodoState> {
final TodoRepo todoRepo = TodoRepo();
TodoBloc() : super(TodoInitial()) {
on<TodoFetch>((event, emit) async {
emit(TodoStateLoaded(todoRepo.tasks));
});
on<TodoAdd>((event, emit) async {
await fakeLoading();
emit(TodoStateLoaded(todoRepo.addTodo(event.task)));
});
on<TodoRemove>((event, emit) async {
await fakeLoading();
emit(TodoStateLoaded(todoRepo.removeTodo(event.id)));
});
on<TodoUpdate>((event, emit) async {
await fakeLoading();
emit(TodoStateLoaded(todoRepo.updateTask(event.id, event.status)));
});
}
@override
TodoState? fromJson(Map<String, dynamic> json) {
if (json['data'] != null && (json['data'] as List<dynamic>).isNotEmpty) {
return TodoStateLoaded((json['data'] as List<dynamic>)
.map((e) => TodoModel.fromJson(e))
.toList());
}
return TodoInitial();
}
@override
Map<String, dynamic>? toJson(TodoState state) {
if (state is TodoStateLoaded) {
return {'data': state.task.map((e) => e.toJson()).toList()};
}
return {'data': []};
}
}
- Инициализируйте хранилище Hydrated Bloc в
main.dart
.
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
HydratedBloc.storage = await HydratedStorage.build(
storageDirectory: kIsWeb
? HydratedStorage.webStorageDirectory
: await getTemporaryDirectory(),
);
runApp(const MyApp());
}
- Сейчас мы остаемся с пользовательским интерфейсом и виджетами, которые вы можете создать сами или обратиться к репозиторию github.
Различные функции для управления списком задач
- Добавление нового ToDo:
if (formKey.currentState!.validate()) {
BlocProvider.of<TodoBloc>(context).add(TodoAdd(TodoModel.fromJson({
'id': DateTime.now().millisecondsSinceEpoch+
Random().nextInt(9999999),
'title': titleText.text,
'description': description.text,
'is_completed': false,
'created_at': DateTime.now().toUtc().toIso8601String(),
'updated_at': DateTime.now().toUtc().toIso8601String()
})));
Navigator.pop(context);
}
- Обновить ToDo:
BlocProvider.of<TodoBloc>(context).add(TodoUpdate(task.id, value!));
- Удалить ToDo
BlocProvider.of<TodoBloc>(context).add(TodoRemove(task.id));
Вывод
Hydrated Bloc is a powerful extension built on top of the bloc package in Flutter. It automates the process of state persistence and restoration, making it e...