Flutter Riverpod 状态管理上手技巧分享

简介: 时代在进步 Riverpod 作为一个优秀的状态管理,猫哥也开始做些技术调研。今天会写两个例子,计数器、拉取数据。

Flutter Riverpod 状态管理上手技巧分享

视频

https://youtu.be/6-8H0A2-e3s

https://www.bilibili.com/video/BV1rf421Z7YT/

前言

原文 https://ducafecat.com/blog/flutter-riverpod-state-management-guide-01

时代在进步 Riverpod 作为一个优秀的状态管理,猫哥也开始做些技术调研。今天会写两个例子,计数器、拉取数据。

先说观点,Riverpod 解决了如下几个方面:

  • 代码比 Provider 简洁,减少嵌套层次
  • 通过注解+代码生成加速开发
  • 有效解决异步与UI交互

参考

https://pub.dev/packages/riverpod

https://riverpod.dev/

https://flutter.ducafecat.com/

初始项目

安装插件

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

yaml 清单

dependencies:
  flutter:
    sdk: flutter

  cupertino_icons: ^1.0.2
  flutter_riverpod: ^2.5.1
  riverpod_annotation: ^2.3.5

dev_dependencies:
  flutter_test:
    sdk: flutter

  flutter_lints: ^2.0.0
  riverpod_generator: ^2.4.0
  build_runner: ^2.4.8
  custom_lint: ^0.6.4

启用 riverpod_lint/custom_lint

pubspec.yaml

analyzer:
  plugins:
    - custom_lint

执行检查

dart run custom_lint

IDE 插件

Flutter Riverpod Snippets

flutter riverpod snippets

Build Runner

build runner

https://marketplace.visualstudio.com/items?itemName=GaetSchwartz.build-runner

例子:计数器

代码

lib/pages/start/index.dart

1 定义代码生成的文件,文件名一直为 index

part 'index.g.dart';

2 注解方式,定义一个 Provider


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

非代码生成方式,不推荐

final helloWorldProvider = Provider((_) => 'Hello world');

3 定义 ConsumerWidget

class StartPage extends ConsumerWidget {
   
   
  const StartPage({
   
   super.key});

4 通过 ref 方式获取 Provider 的值

  
  Widget build(BuildContext context, WidgetRef ref) {
   
   
    final String value = ref.watch(helloWorldProvider);
    return Scaffold(
      appBar: AppBar(title: const Text('hello word')),
      body: Center(
        child: Text(value),
      ),
    );

执行 Build Runner

命令方式

dart run build_runner watch

插件方式

runner bar watch

启动APP

菜单界面

lib/index.dart

  Widget _buildBtn(BuildContext context, String title, Widget page) {
   
   
    return ElevatedButton(
      onPressed: () {
   
   
        Navigator.push(
          context,
          MaterialPageRoute(builder: (context) => page),
        );
      },
      child: Text(title),
    );
  }
  Widget _buildView(BuildContext context) {
   
   
    return Center(
      child: Column(
        children: <Widget>[
          _buildBtn(context, '01 HelloWord', const StartPage()),
        ],
      ),
    );
  }
  
  Widget build(BuildContext context) {
   
   
    return Scaffold(
      appBar: AppBar(title: const Text('Riverpod 示例')),
      body: _buildView(context),
    );
  }

ProviderScope 包裹

lib/main.dart

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

例子:拉取数据

本节我们会用到 freezed 一个生成数据实体的工具包。

freezed 使用详解见 https://ducafecat.com/blog/flutter_application_freezed

安装包

命令

# 拉取数据
flutter pub add dio

# freezed 生成
flutter pub add freezed_annotation
flutter pub add --dev build_runner
flutter pub add --dev freezed

#(可选)如果你要使用 freezed 来生成 fromJson/toJson,则执行:
flutter pub add json_annotation
flutter pub add --dev json_serializable

yaml 清单

dependencies:
  flutter:
    sdk: flutter

  cupertino_icons: ^1.0.2
  flutter_riverpod: ^2.5.1
  riverpod_annotation: ^2.3.5
  dio: ^5.4.2
  freezed_annotation: ^2.4.1
  json_annotation: ^4.8.1

dev_dependencies:
  flutter_test:
    sdk: flutter

  flutter_lints: ^2.0.0
  riverpod_generator: ^2.4.0
  build_runner: ^2.4.8
  custom_lint: ^0.6.4
  freezed: ^2.4.7
  json_serializable: ^6.7.1

数据实例 Entity

数据来源,猫哥 woo 课程商品列表 API https://wpapi.ducafecat.tech/products

在线转换 https://app.quicktype.io/

编写实体类

lib/entity/product_entity.dart

// To parse this JSON data, do
//
//     final productEntity = productEntityFromJson(jsonString);

import 'package:freezed_annotation/freezed_annotation.dart';
import 'dart:convert';

part 'product_entity.freezed.dart';
part 'product_entity.g.dart';

ProductEntity productEntityFromJson(String str) =>
    ProductEntity.fromJson(json.decode(str));

String productEntityToJson(ProductEntity data) => json.encode(data.toJson());


class ProductEntity with _$ProductEntity {
   
   
  const factory ProductEntity({
   
   
    int? id,
    String? name,
    String? slug,
    String? permalink,
    DateTime? dateCreated,
    DateTime? dateCreatedGmt,
    DateTime? dateModified,
    DateTime? dateModifiedGmt,
    String? type,
    String? status,
    bool? featured,
    String? catalogVisibility,
    String? description,
    String? shortDescription,
    String? sku,
    String? price,
    String? regularPrice,
    String? salePrice,
    dynamic dateOnSaleFrom,
    dynamic dateOnSaleFromGmt,
    dynamic dateOnSaleTo,
    dynamic dateOnSaleToGmt,
    bool? onSale,
    bool? purchasable,
    int? totalSales,
    bool? virtual,
    bool? downloadable,
    List<dynamic>? downloads,
    int? downloadLimit,
    int? downloadExpiry,
    String? externalUrl,
    String? buttonText,
    String? taxStatus,
    String? taxClass,
    bool? manageStock,
    dynamic stockQuantity,
    String? backorders,
    bool? backordersAllowed,
    bool? backordered,
    dynamic lowStockAmount,
    bool? soldIndividually,
    String? weight,
    Dimensions? dimensions,
    bool? shippingRequired,
    bool? shippingTaxable,
    String? shippingClass,
    int? shippingClassId,
    bool? reviewsAllowed,
    String? averageRating,
    int? ratingCount,
    List<dynamic>? upsellIds,
    List<dynamic>? crossSellIds,
    int? parentId,
    String? purchaseNote,
    List<Category>? categories,
    List<Category>? tags,
    List<Image>? images,
    List<Attribute>? attributes,
    List<dynamic>? defaultAttributes,
    List<dynamic>? variations,
    List<dynamic>? groupedProducts,
    int? menuOrder,
    String? priceHtml,
    List<int>? relatedIds,
    List<MetaDatum>? metaData,
    String? stockStatus,
    bool? hasOptions,
    Links? links,
  }) = _ProductEntity;

  factory ProductEntity.fromJson(Map<String, dynamic> json) =>
      _$ProductEntityFromJson(json);
}


class Attribute with _$Attribute {
   
   
  const factory Attribute({
   
   
    int? id,
    String? name,
    int? position,
    bool? visible,
    bool? variation,
    List<String>? options,
  }) = _Attribute;

  factory Attribute.fromJson(Map<String, dynamic> json) =>
      _$AttributeFromJson(json);
}


class Category with _$Category {
   
   
  const factory Category({
   
   
    int? id,
    String? name,
    String? slug,
  }) = _Category;

  factory Category.fromJson(Map<String, dynamic> json) =>
      _$CategoryFromJson(json);
}


class Dimensions with _$Dimensions {
   
   
  const factory Dimensions({
   
   
    String? length,
    String? width,
    String? height,
  }) = _Dimensions;

  factory Dimensions.fromJson(Map<String, dynamic> json) =>
      _$DimensionsFromJson(json);
}


class Image with _$Image {
   
   
  const factory Image({
   
   
    int? id,
    DateTime? dateCreated,
    DateTime? dateCreatedGmt,
    DateTime? dateModified,
    DateTime? dateModifiedGmt,
    String? src,
    String? name,
    String? alt,
  }) = _Image;

  factory Image.fromJson(Map<String, dynamic> json) => _$ImageFromJson(json);
}


class Links with _$Links {
   
   
  const factory Links({
   
   
    List<Collection>? self,
    List<Collection>? collection,
  }) = _Links;

  factory Links.fromJson(Map<String, dynamic> json) => _$LinksFromJson(json);
}


class Collection with _$Collection {
   
   
  const factory Collection({
   
   
    String? href,
  }) = _Collection;

  factory Collection.fromJson(Map<String, dynamic> json) =>
      _$CollectionFromJson(json);
}


class MetaDatum with _$MetaDatum {
   
   
  const factory MetaDatum({
   
   
    int? id,
    String? key,
    String? value,
  }) = _MetaDatum;

  factory MetaDatum.fromJson(Map<String, dynamic> json) =>
      _$MetaDatumFromJson(json);
}

执行编译,工具或命令,我使用插件方式

dart run build_runner watch

定义 provider

lib/provider/products.dart

定义生成代码的文件

part 'products.g.dart';

注解方式 异步请求数据并返回


Future<List<ProductEntity>> products(ProductsRef ref) async {
   
   
  String url = "https://wpapi.ducafecat.tech/products";
  Response response = await Dio().get(url);

  List<ProductEntity> list = [];
  for (var item in response.data) {
   
   
    list.add(ProductEntity.fromJson(item));
  }

  return list;
}

业务界面

lib/pages/network/index.dart

ConsumerWidget 方式

// 1 ConsumerWidget 方式
class NetworkPage extends ConsumerWidget {
   
   
  const NetworkPage({
   
   super.key});

通过 ref.watch 获取数据

  
  Widget build(BuildContext context, WidgetRef ref) {
   
   
    // 2 通过 ref.watch 获取数据
    final AsyncValue<List<ProductEntity>> products =
        ref.watch(productsProvider);

    return Scaffold(
      appBar: AppBar(
        title: const Text('拉取数据'),
      ),
      body: _buildView(products),
    );
  }

构建视图

  // 3 构建视图
  Widget _buildView(AsyncValue<List<ProductEntity>> products) {
   
   
    return Center(
      child: switch (products) {
   
   
        // 4 根据状态显示不同的视图
        AsyncData<List<ProductEntity>>(:final value) => ListView.builder(
            itemCount: value.length,
            itemBuilder: (context, index) {
   
   
              return ListTile(
                title: Text(value[index].name ?? ""),
                subtitle: Text(value[index].description ?? ""),
              );
            },
          ),
        // 5 错误处理
        AsyncError() => const Text('Oops, something unexpected happened'),
        // 6 加载中
        _ => const CircularProgressIndicator(),
      },
    );
  }

启动菜单

lib/pages/index.dart

  Widget _buildView(BuildContext context) {
   
   
    return Center(
      child: Column(
        children: <Widget>[
          _buildBtn(context, '01 HelloWord', const StartPage()),
          _buildBtn(context, '02 网络请求', const NetworkPage()),
        ],
      ),
    );
  }

代码

https://github.com/ducafecat/flutter_develop_tips/tree/main/flutter_application_riverpod

小结

Riverpod通过声明式和反应式编程风格,为开发者处理应用程序的逻辑提供了全新方式。最后说几点猫哥个人建议:

  • 如果可能不要用 flutter_hooks 不是必须项
  • 推荐用注解+代码生成方式(时代在进步,阅读、扩展、维护)
  • 使用 ConsumerWidget 简化代码
  • 使用插件生成代码

感谢阅读本文

如果有什么建议,请在评论中让我知道。我很乐意改进。


flutter 学习路径


© 猫哥
ducafecat.com

end

相关文章
|
3月前
|
存储 JavaScript 前端开发
盘点主流 Flutter 状态管理库2024
状态管理是每个应用不可缺少的,本文将会盘点下主流的状态管理包。
296 2
盘点主流 Flutter 状态管理库2024
|
18天前
Flutter 状态管理新境界:多Provider并行驱动UI
Flutter 状态管理新境界:多Provider并行驱动UI
19 0
|
18天前
|
Dart API
状态管理的艺术:探索Flutter的Provider库
状态管理的艺术:探索Flutter的Provider库
15 0
|
1月前
|
容器
flutter 布局管理【详解】
flutter 布局管理【详解】
21 3
|
1月前
flutter的状态管理学习
flutter的状态管理学习
|
3月前
|
前端开发 开发者 UED
【Flutter前端技术开发专栏】Flutter中的图标、字体与样式管理
【4月更文挑战第30天】本文介绍了在Flutter中管理图标、字体和样式的做法。Flutter提供`Icons`类用于内置矢量图标,支持第三方图标库如FontAwesome。自定义字体可通过添加字体文件至`assets`目录并配置`pubspec.yaml`,然后使用`TextStyle`设置。借助`ThemeData`,开发者能统一管理应用主题样式,局部样式可覆盖全局。通过集中管理样式,提升代码复用性和应用一致性。
109 0
【Flutter前端技术开发专栏】Flutter中的图标、字体与样式管理
|
3月前
|
存储 JavaScript 前端开发
【Flutter 前端技术开发专栏】Flutter 中的状态管理框架(如 Provider、Redux 等)
【4月更文挑战第30天】本文探讨了 Flutter 开发中的状态管理,重点介绍了 Provider 和 Redux 两种框架。Provider 以其简单易用性适合初学者和小项目,而 Redux 则适用于大型复杂应用,保证状态一致性。此外,还提到了 Riverpod 和 BLoC 等其他框架。选择框架时要考虑项目规模、团队技术水平和个人偏好。文章通过购物车应用示例展示了不同框架的使用,并展望了状态管理框架的未来发展。
100 0
【Flutter 前端技术开发专栏】Flutter 中的状态管理框架(如 Provider、Redux 等)
|
3月前
|
JavaScript 前端开发 开发者
【Flutter前端技术开发专栏】Flutter中的Widget与状态管理
【4月更文挑战第30天】本文探讨了Flutter的Widget和状态管理。Widget是Flutter构建UI的基础,分为有状态和无状态两种。状态管理确保UI随应用状态变化更新,影响应用性能和可维护性。文章介绍了`setState`、`Provider`、`Riverpod`、`Bloc`和`Redux`等状态管理方法,并通过计数器应用展示了其实现。选择合适的状态管理策略对高效开发至关重要。
33 0
【Flutter前端技术开发专栏】Flutter中的Widget与状态管理
|
3月前
|
前端开发 开发者 iOS开发
【Flutter前端技术开发专栏】Flutter中的路由管理与页面跳转
【4月更文挑战第30天】本文介绍了Flutter的路由管理与页面跳转,包括基本和命名路由管理。基本路由使用`Navigator`的`push`和`pop`方法,如`MaterialPageRoute`和`CupertinoPageRoute`。命名路由则通过路由表注册名称进行跳转,如`Navigator.pushNamed`。此外,还展示了如何通过构造函数、`arguments`和`PageRouteBuilder`进行路由传值。掌握这些知识能提升Flutter开发效率。
51 0
【Flutter前端技术开发专栏】Flutter中的路由管理与页面跳转
|
3月前
|
存储 UED 开发者
Flutter的状态管理:setState、Provider、Bloc的使用详解
【4月更文挑战第26天】Flutter状态管理详解:涵盖setState基础,Provider的跨组件共享及Bloc的复杂场景处理。了解这三种方法的优缺点,助力优化应用数据一致性与用户体验。当状态管理需求升级,从简单的setState到Provider的便利,再到Bloc的强大功能,开发者可根据项目规模和复杂度选择合适策略。