Riverpod的官方文档有多国语言,但是没有汉语,所以个人简单翻译了一版。

官网文档:Riverpod

GitHub:GitHub - rrousselGit/river_pod

Pub:riverpod | Dart Package (flutter-io.cn)

译时版本:riverpod 1.0.3


StateProvider

StateProvider 暴露了改变其状态的方式。 它是 StateNotifierProvider 的简化版, 设计用于很简单的使用场景下避免编写 StateNotifier 类。

StateProvider 的存在主要是允许通过 UI 对 简单 变量的更改。

StateProvider 的状态通常是:

  • 枚举,例如过滤类型
  • 字符串,通常是文件框(TextField之类)的原始内容
  • 逻辑变量,用于复选框
  • 数字,用于分页或表单中的年龄

下面的情况,不应该使用 StateProvider

  • 状态需要验证逻辑
  • 状态是复杂的对象(例如自定义类、列表/Map、等)
  • 更改状态的逻辑比简单的 count++ 高级很多。

更多高级的场景,考虑使用 StateNotifierProvider 来代替,并创建 StateNotifier 类。 对于工程的长期维护性来说,当初始的样板文件会有些大时,自定义 StateNotifier 类很危险 - 因为它在单个地方集中了状态的业务逻辑。

用法示例:使用下拉框改变过滤类型

StateProvider 的一个真实使用场景会是管理如下拉框/文本框/复选框之类的简单窗体组件。 特别是,我们会看到如何使用 StateProvider 实现允许改变商品列表如何排序的下拉框。

为了简化处理,商品列表会在应用里直接构建如下:

class Product {
  Product({required this.name, required this.price});

  final String name;
  final double price;
}

final _products = [
  Product(name: 'iPhone', price: 999),
  Product(name: 'cookie', price: 2),
  Product(name: 'ps5', price: 500),
];

final productsProvider = Provider<List<Product>>((ref) {
  return _products;
}); 

在真实的应用中,该列表通常会使用 FutureProvider 通过网络请求获取。

然后 UI 会如下显示商品列表:

Widget build(BuildContext context, WidgetRef ref) {
  final products = ref.watch(productsProvider);
  return Scaffold(
    body: ListView.builder(
      itemCount: products.length,
      itemBuilder: (context, index) {
        final product = products[index];
        return ListTile(
          title: Text(product.name),
          subtitle: Text('${product.price} $'),
        );
      },
    ),
  );
} 

现在我们完成了基本的处理,然后可以添加下拉框,它可以允许按价格或按名称过滤商品。 为了实现这个,我们会使用 DropDownButton

// 表现过滤类型的枚举
enum ProductSortType {
  name,
  price,
}

Widget build(BuildContext context, WidgetRef ref) {
  final products = ref.watch(productsProvider);
  return Scaffold(
    appBar: AppBar(
      title: const Text('Products'),
      actions: [
        DropdownButton<ProductSortType>(
          value: ProductSortType.price,
          onChanged: (value) {},
          items: const [
            DropdownMenuItem(
              value: ProductSortType.name,
              child: Icon(Icons.sort_by_alpha),
            ),
            DropdownMenuItem(
              value: ProductSortType.price,
              child: Icon(Icons.sort),
            ),
          ],
        ),
      ],
    ),
    body: ListView.builder(
      // ... 
    ),
  );
} 

现在有了下拉框,让我们创建一个 StateProvider 并用 provider 同步下拉框的状态。

首先,创建 StateProvider

final productSortTypeProvider = StateProvider<ProductSortType>(
  // 我们返回排序类型的默认值,这里是名称。
  (ref) => ProductSortType.name,
); 

然后,我们可以如下将 provider 和 下拉框连接:

DropdownButton<ProductSortType>(
  // 当排序类型改变时,这会重新构建下拉框来改变显示的图标。
  value: ref.watch(productSortTypeProvider),
  // 当用户和下拉框交互时,我们更新 provider 的状态。
  onChanged: (value) =>
      ref.read(productSortTypeProvider.notifier).state = value!,
  items: [
    // ...
  ],
), 

这样,现在我们应该能改变排序类型了。尽管它还不会影响商品列表! 现在是最后一部分了:更新 productsProvider 排序商品列表。

实现该点的关键组件是使用 ref.watch,每当排序类型改变时,使 productsProvider 获取排序类型并重新计算商品列表。

该实现会是:

final productsProvider = Provider<List<Product>>((ref) {
  final sortType = ref.watch(productSortTypeProvider);
  switch (sortType) {
    case ProductSortType.name:
      return _products.sorted((a, b) => a.name.compareTo(b.name));
    case ProductSortType.price:
      return _products.sorted((a, b) => a.price.compareTo(b.price));
  }
}); 

这就是所有了!当排序类型改变时,对于 UI 自动重新渲染商品列表来说足够了。

Dartpad 上的完整示例:

// 该代码基于 MIT 许可证分发。
// Copyright (c) 2022 Remi Rousselet.
// 原始代码可在 https://github.com/rrousselGit/river_pod 找到。

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

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

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

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

class Product {
  Product({required this.name, required this.price});

  final String name;
  final double price;
}

final _products = [
  Product(name: 'iPhone', price: 999),
  Product(name: 'cookie', price: 2),
  Product(name: 'ps5', price: 500),
];

enum ProductSortType {
  name,
  price,
}

final productSortTypeProvider = StateProvider<ProductSortType>(
  // 我们返回排序类型的默认值,这里是名称。
  (ref) => ProductSortType.name,
);

final productsProvider = Provider<List<Product>>((ref) {
  final sortType = ref.watch(productSortTypeProvider);
  switch (sortType) {
    case ProductSortType.name:
      return _products.sorted((a, b) => a.name.compareTo(b.name));
    case ProductSortType.price:
      return _products.sorted((a, b) => a.price.compareTo(b.price));
  }
});

class MyHomePage extends ConsumerWidget {
  const MyHomePage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final products = ref.watch(productsProvider);
    return Scaffold(
      appBar: AppBar(
        title: const Text('Products'),
        actions: [
          DropdownButton<ProductSortType>(
            // 当排序类型改变时,这会重新构建下拉框来改变显示的图标。
            value: ref.watch(productSortTypeProvider),
            // 当用户和下拉框交互时,我们更新 provider 的状态。
            onChanged: (value) =>
                ref.read(productSortTypeProvider.notifier).state = value!,
            items: const [
              DropdownMenuItem(
                value: ProductSortType.name,
                child: Icon(Icons.sort_by_alpha),
              ),
              DropdownMenuItem(
                value: ProductSortType.price,
                child: Icon(Icons.sort),
              ),
            ],
          ),
        ],
      ),
      body: ListView.builder(
        itemCount: products.length,
        itemBuilder: (context, index) {
          final product = products[index];
          return ListTile(
            title: Text(product.name),
            subtitle: Text('${product.price} \$'),
          );
        },
      ),
    );
  }
} 

如何基于前一次的值更新状态时避免读取 provider 两次

有时候,想基于前一次的值更新 StateProvider 的状态。很自然地,你可以会如下去写:

final counterProvider = StateProvider<int>((ref) => 0);

class HomeView extends ConsumerWidget {
  const HomeView({Key? key}): super(key: key);

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return Scaffold(
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          // 我们从前一次的值更新状态,这样结束时会读取 provider 两次!
          ref.read(counterProvider.notifier).state = ref.read(counterProvider.notifier).state + 1;
        },
      ),
    );
  }
} 

该代码版本也没有什么特别错误,只是语法上有点不方便。

要使语法看上去更好些,可以使用 update 函数。 该函数会接收一个回调函数,该回调函数会接收当前状态然后期望结果是返回新的状态。

我们可以用它来重构前面的代码:

final counterProvider = StateProvider<int>((ref) => 0);

class HomeView extends ConsumerWidget {
  const HomeView({Key? key}): super(key: key);

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return Scaffold(
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          ref.read(counterProvider.notifier).update((state) => state + 1);
        },
      ),
    );
  }
} 

该改动会达到同样的效果,也能使语法更好些。


总结

写到这里也结束了,在文章最后放上一个小小的福利,以下为小编自己在学习过程中整理出的一个关于Flutter的学习思路及方向,从事互联网开发,最主要的是要学好技术,而学习技术是一条慢长而艰苦的道路,不能靠一时激情,也不是熬几天几夜就能学好的,必须养成平时努力学习的习惯,更加需要准确的学习方向达到有效的学习效果。
由于内容较多就只放上一个大概的大纲,需要更及详细的学习思维导图的可点击下方CSDN官方认证卡片免费获取。**]。

还有免费的高级UI、性能优化、架构师课程、NDK、混合式开发(ReactNative+Weex)微信小程序、Flutter全方面的Android进阶实践技术资料,并且还有技术大牛一起讨论交流解决问题。**

跨平台开发:Flutter.png

Logo

瓜分20万奖金 获得内推名额 丰厚实物奖励 易参与易上手

更多推荐