Flutter状态持久化插件hydrated_bloc的使用

发布于 1周前 作者 h691938207 来自 Flutter

Flutter状态持久化插件hydrated_bloc的使用

介绍

hydrated_blocpackage:bloc 的一个扩展,它能够自动地持久化和恢复 Bloc 和 Cubit 的状态。它基于 hive 实现了一个跨平台、高性能的存储层。下面将详细介绍如何使用这个插件。

安装与配置

首先,在 pubspec.yaml 文件中添加依赖:

dependencies:
  flutter:
    sdk: flutter
  hydrated_bloc: ^8.0.0
  path_provider: ^2.0.11

然后在 main.dart 中设置 HydratedStorage

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:hydrated_bloc/hydrated_bloc.dart';
import 'package:path_provider/path_provider.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  HydratedBloc.storage = await HydratedStorage.build(
    storageDirectory: kIsWeb
        ? HydratedStorageDirectory.web
        : HydratedStorageDirectory((await getTemporaryDirectory()).path),
  );
  runApp(const App());
}

创建持久化的Cubit或Bloc

创建持久化的Cubit

class CounterCubit extends HydratedCubit<int> {
  CounterCubit() : super(0);

  void increment() => emit(state + 1);
  void decrement() => emit(state - 1);

  @override
  int fromJson(Map<String, dynamic> json) => json['value'] as int;

  @override
  Map<String, int> toJson(int state) => {'value': state};
}

创建持久化的Bloc

sealed class CounterEvent {}

final class CounterIncrementPressed extends CounterEvent {}
final class CounterDecrementPressed extends CounterEvent {}

class CounterBloc extends HydratedBloc<CounterEvent, int> {
  CounterBloc() : super(0) {
    on<CounterIncrementPressed>((event, emit) => emit(state + 1));
    on<CounterDecrementPressed>((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创建Cubit

class CounterCubit extends Cubit<int> with HydratedMixin {
  CounterCubit() : super(0) {
    hydrate(); // 必须调用hydrate方法
  }

  void increment() => emit(state + 1);
  void decrement() => emit(state - 1);

  @override
  int fromJson(Map<String, dynamic> json) => json['value'] as int;

  @override
  Map<String, int> toJson(int state) => {'value': state};
}

自定义存储目录

你可以通过自定义 storageDirectory 来指定不同的存储位置:

final storage = await HydratedStorage.build(
  storageDirectory: await getApplicationDocumentsDirectory(),
);

测试

为了测试使用了 hydrated_bloc 的代码,建议使用 mocktail 包来模拟 Storage 接口的行为:

import 'package:flutter_test/flutter_test.dart';
import 'package:hydrated_bloc/hydrated_bloc.dart';
import 'package:mocktail/mocktail.dart';

class MockStorage extends Mock implements Storage {}

void main() {
  late Storage storage;

  setUp(() {
    storage = MockStorage();
    when(() => storage.write(any(), any<dynamic>())).thenAnswer((_) async {});
    HydratedBloc.storage = storage;
  });

  testWidgets('...', (tester) async {
    when<dynamic>(() => storage.read('$MyBloc')).thenReturn(MyState().toJson());

    // ...
  });
}

完整示例

以下是一个完整的例子,展示了如何在一个计数器应用中使用 hydrated_bloc

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:hydrated_bloc/hydrated_bloc.dart';
import 'package:path_provider/path_provider.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  HydratedBloc.storage = await HydratedStorage.build(
    storageDirectory: kIsWeb
        ? HydratedStorageDirectory.web
        : HydratedStorageDirectory((await getTemporaryDirectory()).path),
  );
  runApp(const App());
}

class App extends StatelessWidget {
  const App({super.key});

  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (_) => BrightnessCubit(),
      child: const AppView(),
    );
  }
}

class AppView extends StatelessWidget {
  const AppView({super.key});

  @override
  Widget build(BuildContext context) {
    return BlocBuilder<BrightnessCubit, Brightness>(
      builder: (context, brightness) {
        return MaterialApp(
          theme: ThemeData(brightness: brightness),
          home: const CounterPage(),
        );
      },
    );
  }
}

class CounterPage extends StatelessWidget {
  const CounterPage({super.key});

  @override
  Widget build(BuildContext context) {
    return BlocProvider<CounterBloc>(
      create: (_) => CounterBloc(),
      child: const CounterView(),
    );
  }
}

class CounterView extends StatelessWidget {
  const CounterView({super.key});

  @override
  Widget build(BuildContext context) {
    final textTheme = Theme.of(context).textTheme;
    return Scaffold(
      appBar: AppBar(title: const Text('Counter')),
      body: Center(
        child: BlocBuilder<CounterBloc, int>(
          builder: (context, state) {
            return Text('$state', style: textTheme.displayMedium);
          },
        ),
      ),
      floatingActionButton: Column(
        crossAxisAlignment: CrossAxisAlignment.end,
        mainAxisAlignment: MainAxisAlignment.end,
        children: <Widget>[
          FloatingActionButton(
            child: const Icon(Icons.brightness_6),
            onPressed: () => context.read<BrightnessCubit>().toggleBrightness(),
          ),
          const SizedBox(height: 4),
          FloatingActionButton(
            child: const Icon(Icons.add),
            onPressed: () {
              context.read<CounterBloc>().add(CounterIncrementPressed());
            },
          ),
          const SizedBox(height: 4),
          FloatingActionButton(
            child: const Icon(Icons.remove),
            onPressed: () {
              context.read<CounterBloc>().add(CounterDecrementPressed());
            },
          ),
          const SizedBox(height: 4),
          FloatingActionButton(
            child: const Icon(Icons.delete_forever),
            onPressed: () => HydratedBloc.storage.clear(),
          ),
        ],
      ),
    );
  }
}

sealed class CounterEvent {}

final class CounterIncrementPressed extends CounterEvent {}
final class CounterDecrementPressed extends CounterEvent {}

class CounterBloc extends HydratedBloc<CounterEvent, int> {
  CounterBloc() : super(0) {
    on<CounterIncrementPressed>((event, emit) => emit(state + 1));
    on<CounterDecrementPressed>((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};
}

class BrightnessCubit extends HydratedCubit<Brightness> {
  BrightnessCubit() : super(Brightness.light);

  void toggleBrightness() {
    emit(state == Brightness.light ? Brightness.dark : Brightness.light);
  }

  @override
  Brightness fromJson(Map<String, dynamic> json) {
    return Brightness.values[json['brightness'] as int];
  }

  @override
  Map<String, dynamic> toJson(Brightness state) {
    return <String, int>{'brightness': state.index};
  }
}

这个例子展示了如何使用 hydrated_bloc 来创建一个简单的计数器应用,并且可以持久化计数器的状态。即使应用程序关闭后重新启动,计数器的状态仍然会被保留。


更多关于Flutter状态持久化插件hydrated_bloc的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter状态持久化插件hydrated_bloc的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,下面是一个关于如何在Flutter中使用hydrated_bloc进行状态持久化的代码案例。hydrated_bloc是一个与bloc状态管理库集成的插件,用于简化状态的持久化操作。

步骤 1: 添加依赖

首先,你需要在pubspec.yaml文件中添加blochydrated_bloc的依赖:

dependencies:
  flutter:
    sdk: flutter
  bloc: ^8.0.0  # 确保使用最新版本
  hydrated_bloc: ^8.0.0  # 确保使用最新版本
  # 其他依赖...

步骤 2: 配置HydratedBloc

在你的应用程序入口文件(通常是main.dart)中,配置HydratedBloc

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:hydrated_bloc/hydrated_bloc.dart';
import 'package:your_app/bloc/your_bloc.dart';  // 导入你的Bloc
import 'package:your_app/repository/your_repository.dart';  // 导入你的Repository

void main() async {
  // 初始化HydratedBloc的存储
  final hydratedStorage = await HydratedStorage.build(
    storageDirectory: await getApplicationDocumentsDirectory(),
  );

  runApp(
    HydratedBlocProvider(
      storage: hydratedStorage,
      child: MultiBlocProvider(
        providers: [
          // 提供你的Bloc
          BlocProvider<YourBloc>(
            create: (context) => YourBloc(repository: YourRepository()),
          ),
          // 其他Bloc...
        ],
        child: MyApp(),
      ),
    ),
  );
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: YourHomePage(),
    );
  }
}

步骤 3: 创建Bloc和State

创建一个简单的Bloc和State类。假设我们有一个计数器Bloc:

import 'package:bloc/bloc.dart';
import 'package:meta/meta.dart';

part 'counter_event.dart';
part 'counter_state.dart';

class CounterBloc extends Bloc<CounterEvent, CounterState> {
  CounterBloc() : super(CounterInitial()) {
    on<CounterIncremented>(_ => add(CounterIncremented()));
  }

  @override
  Stream<CounterState> mapEventToState(CounterEvent event) async* {
    if (event is CounterIncremented) {
      yield* _mapToIncrementedState(currentState);
    }
  }

  Stream<CounterState> _mapToIncrementedState(CounterState currentState) async* {
    yield currentState.copy(count: currentState.count + 1);
  }
}

步骤 4: 定义Event和State

定义你的Event和State类:

// counter_event.dart
part of 'counter_bloc.dart';

abstract class CounterEvent {}

class CounterIncremented extends CounterEvent {}

// counter_state.dart
part of 'counter_bloc.dart';

class CounterState {
  final int count;

  CounterState({required this.count});

  CounterState copyWith({int? count}) {
    return CounterState(
      count: count ?? this.count,
    );
  }

  @override
  bool operator ==(Object other) =>
      identical(this, other) ||
      other is CounterState &&
          runtimeType == other.runtimeType &&
          count == other.count;

  @override
  int get hashCode => count.hashCode;
}

class CounterInitial extends CounterState {
  CounterInitial() : super(count: 0);
}

步骤 5: 使用Bloc在UI中

在你的UI组件中使用BlocBuilder或BlocConsumer来监听Bloc的状态变化:

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:your_app/bloc/counter_bloc.dart';

class YourHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Hydrated Bloc Example'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text('You have pushed the button this many times:'),
            BlocBuilder<CounterBloc, CounterState>(
              builder: (context, state) {
                return Text(
                  '${state.count}',
                  style: Theme.of(context).textTheme.headline4,
                );
              },
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => context.read<CounterBloc>().add(CounterIncremented()),
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
}

这样,你就完成了一个简单的Flutter应用,其中使用了hydrated_bloc进行状态持久化。每次应用程序重新启动时,之前的状态都会被恢复。

回到顶部