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
相关推荐
ALLIN7 小时前
Flutter 三种方式实现页面切换后保持原页面状态
flutter
Dabei7 小时前
Flutter 国际化
flutter
Dabei7 小时前
Flutter MQTT 通信文档
flutter
Dabei10 小时前
Flutter 中实现 TCP 通信
flutter
孤鸿玉10 小时前
ios flutter_echarts 不在当前屏幕 白屏修复
flutter
前端 贾公子12 小时前
《Vuejs设计与实现》第 16 章(解析器) 上
vue.js·flutter·ios
tangweiguo0305198721 小时前
Flutter 数据存储的四种核心方式 · 从 SharedPreferences 到 SQLite:Flutter 数据持久化终极整理
flutter
0wioiw021 小时前
Flutter基础(②④事件回调与交互处理)
flutter
肥肥呀呀呀1 天前
flutter配置Android gradle kts 8.0 的打包名称
android·flutter
吴Wu涛涛涛涛涛Tao1 天前
Flutter 实现「可拖拽评论面板 + 回复输入框 + @高亮」的完整方案
android·flutter·ios