Riverpod 用法

这篇MD我默认你已经知道了Provider和Riverpod,以及已经安装了Riverpod,现在只是想知道如何使用Riverpod,请往下看。(如果不知道怎么安装,或者不知道什么是状态管理,可以先补习一下状态管理方面的知识)

1. 如何获取Ref

可以使用以下三种方式获取 Ref

1. 使用 ConsumerWidget获取

例如

scala 复制代码
final helloWorldProvider = Provider<String>((_) => 'Hello world');
​
// 1. 继承 ConsumerWidget
class HelloWorldWidget extends ConsumerWidget {
  @override
  // 2. Method中增加了 WidgetRef参数
  Widget build(BuildContext context, WidgetRef ref) {
    // 3. 使用 ref.watch() 获取helloWorldProvider的状态
    final helloWorld = ref.watch(helloWorldProvider);
    return Text(helloWorld);
  }
}

Widget 继承自 ConsumerWidget 而不是 StatelessWidget ,这种方式是最常见的也是最简单的。

2. 使用 Consumer

将需要使用 ref 的 widget 用 Consumer 包裹,从而拿到 ref

scala 复制代码
final helloWorldProvider = Provider<String>((_) => 'Hello world');
​
class HelloWorldWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // 1. 使用 Consumer
    return Consumer(
      // 2. 通过 builder 拿到 ref
      builder: (_, WidgetRef ref, __) {
        // 3. 使用 ref.watch() 获取helloWorldProvider的状态
        final helloWorld = ref.watch(helloWorldProvider);
        return Text(helloWorld);
      },
    );
  }
}

那我们什么时候使用 Consumer,什么时候使用 ConsumerWidget

上面这个例子中, 只有 Text 这个 widget 需要更新,监听状态,其他的Widget都不需要更新,那么我们可以使用 Consumer 来包裹,从而提升性能。(Tip : 在复杂的页面中,这非常有用,无法抽取出去的Widget,可以使用Consumer包括,减少刷新粒度。)

3. 使用 ConsumerStatefulWidget & ConsumerState

相应的, ConsumerWidget 对应 StatelessWidget, 那么 ConsumerStatefulWidget & ConsumerState 对应 StatefulWidget

2. 八种Provider的用法

八种Provider分别是:

  • Provider
  • StateProvider
  • StateNotifierProvider
  • FutureProvider
  • StreamProvider
  • ChangeNotifierProvider
  • NotifierProvider (2.0 新的API)
  • AsyncNotifierProvider (2.0 新的API)

1. Provider

Provider 非常适合访问不改变的依赖项和对象

可以使用它来访问存储库、Logger 或者其他不包含可变状态的类。

例如,你可以返回一个DateFormat

scala 复制代码
// 一个无需更新的Provider
final dateFormatterProvider = Provider<DateFormat>((ref) {
  return DateFormat.MMMEd();
});
​
class SomeWidget extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    // 获取 DateFormat
    final formatter = ref.watch(dateFormatterProvider);
    // 使用 
    return Text(formatter.format(DateTime.now()));
  }
}

2. StateProvider

StateProvider 非常适合存储可以更改的简单状态对象 ,比如 counter value

ini 复制代码
final counterStateProvider = StateProvider<int>((ref) {
  return 0;
});

如果你在 build 方法中watch 这个 provider,那么当状态改变时,这个widget将会进行 rebuild 刷新。

你也可以在 button的点击事件中通过 ref.read() 来更新状态。

scala 复制代码
class CounterWidget extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    // 1. 监听counterStateProvider并且当状态改变时会rebuild
    final counter = ref.watch(counterStateProvider);
    return ElevatedButton(
      // 2. 使用状态
      child: Text('Value: $counter'),
      // 3. 在button的点击事件中,改变状态
      onPressed: () => ref.read(counterStateProvider.notifier).state++,
    );
  }
}

StateProvider 是非常适合存储简单状态变量,比如 enums、strings、booleans,numbers

Notifier 也可以用于相同的目的并且更灵活。对于更复杂或异步状态,请使用 AsyncNotifierProvider、FutureProvider 或 StreamProvider,如下所述。

3. StateNotifierProvider

使用这个 StateNotifierProvider来监听和暴露 StateNotifier

StateNotifierProvider 和 StateNotifier 是管理可能因事件或用户交互而改变的状态的理想选择

如下示例:

scala 复制代码
import 'dart:async';
​
class Clock extends StateNotifier<DateTime> {
  // 1. 初始化
  Clock() : super(DateTime.now()) {
    // 2.创建一个每一秒都更新一次的状态机
    _timer = Timer.periodic(Duration(seconds: 1), (_) {
      // 3. 更新状态
      state = DateTime.now();
    });
  }
​
  late final Timer _timer;
​
  // 4. 销毁
  @override
  void dispose() {
    _timer.cancel();
    super.dispose();
  }
}
​
// 创建一个StateNotifierProvider,有两个 注解
final clockProvider = StateNotifierProvider<Clock, DateTime>((ref) {
  return Clock();
});
​
class ClockWidget extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    // 监听 StateNotifierProvider
    final currentTime = ref.watch(clockProvider);
    // 改变输出
    final timeFormatted = DateFormat.Hms().format(currentTime);
    return Text(timeFormatted);
  }
}

以上示例中可以看到,使用 ref.watch(clockProvider) ,widget 将会在每次状态更新时进行重构,更新 Text 显示。

笔记: ref.watch(clockProvider) 返回的是 Provider的状态。如果需要获取 通知对象(Notifier),可以使用 ref.watch(clockProvider.notifier),这个示例返回的是 Clock

想要获取如何以及何时使用 StateNotifierProvider更加完整的示例,可以查看这篇文章

如果您只需要读取一些异步数据,那么使用 StateNotifierProvider 就太过分了。这就是 FutureProvider 的用途

4. FutureProvider

想要从返回 Future 的 API 调用中获取结果?

那么可以像下面这样创建一个 FutureProvider

rust 复制代码
final weatherFutureProvider = FutureProvider.autoDispose<Weather>((ref) {
  // 获取下面 weatherRepositoryProvider 的结果
  final weatherRepository = ref.watch(weatherRepositoryProvider);
  // 调用函数获取 Future<Weather>
  return weatherRepository.getWeather(city: 'London');
});
​
//  weather repository provider
final weatherRepositoryProvider = Provider<WeatherRepository>((ref) {
  return WeatherRepository(); 
});

FutureProvider 通常和 autoDispose 修饰符一起使用

然后你可以在 build 方法中观察它并使用模式匹配将结果 AsyncValue (数据,加载,错误)映射到你的 UI

javascript 复制代码
Widget build(BuildContext context, WidgetRef ref) {
  // 监听 FutureProvider 得到一个 AsyncValue<Weather>
  final weatherAsync = ref.watch(weatherFutureProvider);
  // 更新 UI
  return weatherAsync.when(
    loading: () => const CircularProgressIndicator(),
    error: (err, stack) => Text('Error: $err'),
    data: (weather) => Text(weather.toString()),
  );
}

注意:当监听 FutureProviderStreamProvider 时,返回类型是 AsyncValueAsyncValue 是 Riverpod 中处理异步数据的实用类。有关详细信息,请阅读:Flutter Riverpod 提示:使用 AsyncValue 而不是 FutureBuilder 或 StreamBuilder

FutureProvider 非常强大,你可以使用它做以下操作:

  • 执行和缓存异步操作(例如网络请求)

  • 处理异步操作的错误和 loading 状态

  • 将多个异步值组合成另一个值

  • 重新获取和刷新数据(搭配 pull-to-refresh 进行使用)

5. StreamProvider

使用 StreamProvider 监听来自实时 API 的结果流并响应式地重建 UI。

以下示例

csharp 复制代码
final authStateChangesProvider = StreamProvider.autoDispose<User?>((ref) {
  // 获取 FirebaseAuth
  final firebaseAuth = ref.watch(firebaseAuthProvider);
  // 调用方法,返回一个 Stream<User?>
  return firebaseAuth.authStateChanges();
});
​
// 提供一个 FirebaseAuth 实例
final firebaseAuthProvider = Provider<FirebaseAuth>((ref) {
  return FirebaseAuth.instance;
});
​
/// 使用
Widget build(BuildContext context, WidgetRef ref) {
  //监听 StreamProvider 得到 AsyncValue<User?>
  final authStateAsync = ref.watch(authStateChangesProvider);
  // 更新 UI
  return authStateAsync.when(
    data: (user) => user != null ? HomePage() : SignInPage(),
    loading: () => const CircularProgressIndicator(),
    error: (err, stack) => Text('Error: $err'),
  );
}

StreamProvider 搭配 StreamBuilder widget 将有许多的便捷之处。

6. ChangeNotifierProvider

通过使用 ChangeNotifier,我们可以存储一些状态并且能够监听他们的改变。

如下示例中,我们可以使用 ChangeNotifierProvider 来包裹 ChangeNotifier

csharp 复制代码
class AuthController extends ChangeNotifier {
  User? user;
  bool get isSignedIn => user != null;
​
  Future<void> signOut() {
    user = null;
    notifyListeners();
  }
}
​
final authControllerProvider = ChangeNotifierProvider<AuthController>((ref) {
  return AuthController();
});
​
Widget build(BuildContext context, WidgetRef ref) {
  return ElevatedButton(
    onPressed: () => ref.read(authControllerProvider).signOut(),
    child: const Text('Logout'),
  );
}

使用 ChangeNotifier API 可以轻松打破两个重要规则:不可变状态和单向数据流。

因此,不鼓励使用 ChangeNotifier,我们应该改用 StateNotifier。

7. NotifierProvider 和 AsyncNotifierProvider

这两个Provider出现在 Riverpod 2.0之后, NotifierAsyncNotifier 旨在取代 StateNotifier 并带来一些新的好处:

  • 更容易执行复杂的异步初始化
  • 更人性化的 API:不再需要传递 ref
  • 如果我们使用 Riverpod Generator,那就不再需要手动声明提供者

通过以下示例来了解这些好处。

计数器示例:

在介绍StateProvider使用中,举了一个计数器示例。如你看到的,确实比较简单方便,但是如果状态稍微复杂一些,那么StateProvider可能不再适用,可能需要 StateNotifierProvider,那么现在我们可以直接使用 Notifier。

  1. 创建 Notifier 类

    scala 复制代码
    import 'package:flutter_riverpod/flutter_riverpod.dart';
    ​
    class Counter extends Notifier<int> {
      @override
      int build() {
        return 0;
      }
    ​
      void increment() {
        state++;
      }
    }

    这里做了两件事, build 方法中初始化, 暴露一个可更改状态的方法。

  2. 创建一个provider来包装这个类

    arduino 复制代码
    final counterProvider = NotifierProvider<Counter, int>(() {
      return Counter();
    });
    //或者
    final counterProvider = NotifierProvider<Counter, int>(Counter.new);
  3. 现在我们可以使用这个Provider

    scala 复制代码
    import 'counter.dart';
    ​
    class CounterWidget extends ConsumerWidget {
      @override
      Widget build(BuildContext context, WidgetRef ref) {
        // 1. 监听provider
        final counter = ref.watch(counterProvider);
        return ElevatedButton(
          // 2. 使用
          child: Text('Value: $counter'),
          // 3. 更新状态
          onPressed: () => ref.read(counterProvider.notifier).state++,
        );
      }
    }

    我们可以使用 ref.read(counterProvider.notifier).increment()

  4. 现在来比较 StateProviderNotifierProvider

    到目前为止,我们已经了解到当我们需要修改简单变量时,StateProvider 可以很好的实现。 但是如果我们的状态(以及更新它的逻辑)更复杂,Notifier 和 NotifierProvider 是一个很好的选择,并且也很容易实现。

    Notifier 可以使用 riverpod_generator 自动生成 Provider

    scala 复制代码
    import 'package:riverpod_annotation/riverpod_annotation.dart';
    ​
    part 'counter.g.dart';
    ​
    @riverpod
    class Counter extends _$Counter {
      @override
      int build() {
        return 0;
      }
    ​
      void increment() {
        state++;
      }
    }

    其中 _$Counter 最终被解析成为了

    scala 复制代码
    /// See also [Counter].
    final counterProvider = AutoDisposeNotifierProvider<Counter, int>(
      Counter.new,
      name: r'counterProvider',
      debugGetCreateSourceHash:
          const bool.fromEnvironment('dart.vm.product') ? null : $CounterHash,
    );
    typedef CounterRef = AutoDisposeNotifierProviderRef<int>;
    ​
    abstract class _$Counter extends AutoDisposeNotifier<int> {
      @override
      int build();
    }

    可以看到 _$Counter 继承自 AutoDisposeNotifier, 且 重载了 build,而 AutoDisposeNotifier 接受的状态,是根据 build 返回的,所以 Counter 需要 override build 方法。

    csharp 复制代码
    /// {@template riverpod.notifier}
    abstract class AutoDisposeNotifier<State>
        extends BuildlessAutoDisposeNotifier<State> {
      /// {@macro riverpod.asyncnotifier.build}
      @visibleForOverriding
      State build();
    }
这就是为什么 Provider 可以检测到 State
相关推荐
yuanlaile15 小时前
纯Dart Flutter库适配HarmonyOS
flutter·华为·harmonyos·flutter开发鸿蒙·harmonyos教程
yuanlaile15 小时前
Flutter开发HarmonyOS 鸿蒙App的好处、能力以及把Flutter项目打包成鸿蒙应用
flutter·华为·harmonyos·flutter开发鸿蒙
zacksleo17 小时前
鸿蒙原生开发手记:04-一个完整元服务案例
flutter
jcLee952 天前
Flutter/Dart:使用日志模块Logger Easier
flutter·log4j·dart·logger
tmacfrank2 天前
Flutter 异步编程简述
flutter
tmacfrank2 天前
Flutter 基础知识总结
flutter
叫我菜菜就好2 天前
【Flutter_Web】Flutter编译Web第三篇(网络请求篇):dio如何改造方法,变成web之后数据如何处理
前端·网络·flutter
AiFlutter2 天前
Flutter-底部分享弹窗(showModalBottomSheet)
java·前端·flutter
m0_748247803 天前
Flutter Intl包使用指南:实现国际化和本地化
前端·javascript·flutter
迷雾漫步者3 天前
Flutter组件————PageView
flutter·跨平台·dart