【Flutter 工程】001-Flutter 状态管理:Riverpod

172 阅读7分钟

【Flutter 工程】001-Flutter 状态管理:Riverpod

一、概述

1、官方状态管理

状态管理 处理应用程序数据流动和 UI 更新 的关键概念。在 Flutter 应用程序中,状态管理确保应用程序 UI 和数据保持同步,共享和同步数据,并提供良好的代码结构和可维护性。

Flutter 提供了 StatefulWidget 作为最基本的状态管理方法。有状态组件可以存储和更新自身状态,适用于简单的场景和局部状态。

然而,StatefulWidget 存在以下问题:

  1. 状态管理复杂性:当组件树庞大且状态需要在多个组件之间共享时,状态管理变得复杂,代码难以理解和维护。
  2. 性能问题:相比 StatelessWidgetStatefulWidget 在状态变化时会导致更多组件重建,可能影响应用程序性能,尽管Flutter已经进行了性能优化。
  3. 生命周期管理复杂性StatefulWidget 具有复杂的生命周期,需要处理多个生命周期方法(如initStatedidUpdateWidget 和dispose),导致代码复杂和难以管理。
  4. 难以测试:由于 StatefulWidget 具有内部状态,编写单元测试和集成测试变得更加困难,可能影响应用程序的质量和可靠性。
  5. 重用性差StatefulWidget 的状态通常与特定实例紧密耦合,降低了组件的可重用性。

2、状态管理解决方案

在 Flutter 中,还有其他的状态管理方法可供选择:

  1. InheritedWidgetInheritedModel:这些是 Flutter 提供的允许状态在组件树中向下传递的特殊类型的组件。它们可以帮助你在应用程序的不同层级之间共享状态。这种方法对于较小的应用程序或有限的状态共享需求较为合适
  2. Provider:一个依赖注入和状态管理第三方库,它是在 InheritedWidget 基础上做了封装,有上面组件的能力,但是更简单易用。Provider 可以监听状态变化,并在需要时重新构建关联的组件。这种方法适用于各种规模的应用程序,具有良好的可扩展性和灵活性。
  3. Riverpod:一个相对较新的状态管理库,类似于 Provider,但提供了更多的功能和改进。Riverpod 允许你创建不可变的、可组合的和可测试的状态管理解决方案。这种方法适用于需要更高度可控和可测试性的应用程序
  4. BLoC(Business Logic Component):一种基于响应式编程的状态管理方法。BLoC 将业务逻辑与 UI 分离,使你可以轻松地测试和重用代码。BLoC 通常与 RxDart(一种 Dart 的响应式编程库)一起使用,以提供强大的数据流处理能力。这种方法适用于需要处理复杂业务逻辑和大量数据流的应用程序
  5. Redux:一种集中式状态管理库,它将应用程序的状态存储在一个单一的状态树中。Redux 使用纯函数(称为reducers)来处理状态更新,使你可以轻松地跟踪和管理应用程序的状态变化。这种方法适用于需要严格的状态管理和可预测性的应用程序

3、为什么选择 Riverpod

Riverpod 的一些主要特点比较给力,与我们的需求契合:

  1. 不可变性:Riverpod 中的状态是不可变的,这意味着状态在更新时会创建一个新的对象,而不是修改现有对象。这有助于减少错误,并使状态更易于理解和跟踪。
  2. 类型安全:Riverpod 在编译时提供了更强的类型安全性,有助于减少类型错误并提高代码质量。
  3. 无需 BuildContext:与 Provider 不同,Riverpod 不依赖于 BuildContext 来访问状态。这使得在组件之外的位置(如函数或类)访问状态变得更加容易,同时提高了可测试性。
  4. 可组合:Riverpod 允许你组合不同的 Provider 以创建更复杂的状态管理解决方案。这有助于保持代码的模块化和可维护性。
  5. 易于测试:由于 Riverpod 的状态不依赖于 BuildContext,你可以更轻松地编写单元测试。此外,Riverpod 提供了用于模拟状态和测试的实用工具。
  6. 家族功能:Riverpod 具有所谓的"家族"功能,允许你根据参数创建多个相同类型的 Provider 实例。这使得在使用相同逻辑但参数不同的多个组件时,可以更好地管理状态。
  7. 非常灵活:Riverpod 具有很高的灵活性,可以很好地适应不同的应用程序结构和需求。你可以使用 Riverpod 来构建简单的局部状态管理,或者构建复杂的全局状态管理解决方案。

二、官方示例

1、安装

flutter pub add flutter_riverpod dev:custom_lint dev:riverpod_lint riverpod_annotation dev:build_runner dev:riverpod_generator

2、官方示例

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'main.g.dart';

@riverpod
String helloWorld(HelloWorldRef ref) {
  return 'Hello world';
}

void main() {
  runApp(
    ProviderScope(
      child: MyApp(),
    ),
  );
}

class MyApp extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final String value = ref.watch(helloWorldProvider);

    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text('Example')),
        body: Center(
          child: Text(value, style: const TextStyle(fontSize: 40),),
      ),
    );
  }
}

3、代码生成

flutter pub run build_runner build --delete-conflicting-outputs

三、基本使用

1、改造 main.dart

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:study/pages/HomePage.dart';

void main() {
  runApp(
    const ProviderScope(
      child: MyApp(),
    ),
  );
}

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

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: HomePage(),
    );
  }
}

2、创建 home_page.dart

import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';

import '../states/hello_state.dart';

class HomePage extends HookConsumerWidget {
  const HomePage({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final hello = ref.watch(helloStateProvider);
    return Scaffold(
      appBar: AppBar(
        title: const Text('Home'),
      ),
      body: Center(
          child: SizedBox(
        height: 400,
        child: Column(
          children: [
            Text(hello.hello, style: const TextStyle(fontSize: 40),),
            ElevatedButton(
              style: ButtonStyle(minimumSize: MaterialStateProperty.all(const Size(200, 50))),
              onPressed: () {
                ref.read(helloStateProvider.notifier).setHello("文本更新了!");
              },
              child: const Text('Update'),
            ),
          ],
        ),
      )),
    );
  }
}

3、创建 hello_state.dart

import 'package:flutter_riverpod/flutter_riverpod.dart';

class HelloState {
  final String hello;

  HelloState({
    this.hello = 'Hello World',
  });
}

class HelloStateProvider extends StateNotifier<HelloState> {
  HelloStateProvider() : super(HelloState());

  void setHello(String hello) {
    state = HelloState(
      hello: hello,
    );
  }
}

final helloStateProvider = StateNotifierProvider<HelloStateProvider, HelloState>(
  (ref) => HelloStateProvider(),
);

四、使用代码生成

1、改造 hello_state.dart

import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'hello_state.g.dart';

@riverpod
class HelloList extends _$HelloList {
  @override
  List<String> build() {
    return ["hello world!"];
  }

  void addHello(String hello) {
    state = [...state, hello];
  }
}

2、代码生成

flutter pub run build_runner build --delete-conflicting-outputs

3、改造 home_page.dart

import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';

import '../states/hello_state.dart';

class HomePage extends HookConsumerWidget {
  const HomePage({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final List<String> hellos = ref.watch(helloListProvider);
    return Scaffold(
      appBar: AppBar(
        title: const Text('Home'),
      ),
      body: Center(
          child: SizedBox(
        height: 400,
        child: Column(
          children: [
            ...hellos.map((e) => Text(e, style: const TextStyle(fontSize: 40),)),
            ElevatedButton(
              style: ButtonStyle(minimumSize: MaterialStateProperty.all(const Size(200, 50))),
              onPressed: () {
                DateTime now = DateTime.now();
                ref.read(helloListProvider.notifier).addHello("hello ${now.second}");
              },
              child: const Text('Update'),
            ),
          ],
        ),
      )),
    );
  }
}

五、为什么在Riverpod中使用代码生成

你可能在想:"如果在Riverpod中代码生成是可选的,为什么要使用?"

让你的代码生活更简单。这包括但不限于:

  • 更好的语法, 更可读且更灵活,而且还能减少学习曲线。
  • 不需要担心FutureProviderProvider 还是其他 provider。仅需写下你的逻辑, Riverpod将为你选择最合适的provider。
  • 向provider传递参数现在不受限制。不再局限于使用 family 和传递单个参数,现在可以传递任何形式的参数。这包括命名参数、可选参数甚至默认值。
  • 在Riverpod中编写的代码支持 有状态热重载
  • 更好地调试,通过生成额外的元数据然后用调试器调试。
  • Riverpod的一些功能将只支持代码生成。

与此同时,许多应用程序中已经使用了代码生成比如 Freezed 或 json_serializable。在这种情况下,你的项目可能已经为代码生成配置好了,使用Riverpod应该很简单。

六、iOS开发辅助工具推荐

在开发Flutter应用时,特别是需要发布到App Store时,可以使用AppUploader这款iOS开发助手工具。它可以帮助开发者:

  1. 简化iOS证书和描述文件的管理
  2. 快速打包和上传应用到App Store Connect
  3. 提供便捷的测试设备管理功能
  4. 支持批量处理多个应用的发布流程

AppUploader与Riverpod这样的状态管理工具配合使用,可以让开发者更专注于应用逻辑的实现,而不用过多操心发布流程中的繁琐操作。