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

相关文章
|
13天前
|
存储 Shell 开发工具
Flutter&鸿蒙next 中使用 MobX 进行状态管理
本文介绍了如何在 Flutter 中使用 MobX 进行状态管理。MobX 是一个基于观察者模式的响应式编程库,通过 `@observable` 和 `@action` 注解管理状态,并使用 `Observer` 小部件自动更新 UI。文章详细讲解了 MobX 的核心概念、如何集成到 Flutter 项目中以及具体的代码示例。适合希望在 Flutter 应用中实现高效状态管理的开发者阅读。
85 9
|
13天前
|
存储 开发者
Flutter&鸿蒙next 使用 BLoC 模式进行状态管理详解
本文详细介绍了如何在 Flutter 中使用 BLoC 模式进行状态管理。BLoC 模式通过将业务逻辑与 UI 层分离,利用 Streams 和 Sinks 实现状态管理和 UI 更新,提高代码的可维护性和可测试性。文章涵盖了 BLoC 的基本概念、实现步骤及代码示例,包括定义 Event 和 State 类、创建 Bloc 类、提供 Bloc 实例以及通过 BlocBuilder 更新 UI。通过一个简单的计数器应用示例,展示了 BLoC 模式的具体应用和代码实现。
67 1
|
15天前
|
开发工具 开发者
Flutter&鸿蒙next 状态管理高级使用:深入探讨 Provider
本文深入探讨了 Flutter 中 Provider 的高级用法,涵盖多 Provider 组合、Selector 优化性能、ChangeNotifierProxyProvider 管理依赖关系以及自定义 Provider。通过这些技巧,开发者可以构建高效、可维护的响应式应用。
62 2
|
27天前
|
开发工具 开发者
Flutter&鸿蒙next 状态管理高级使用:深入探讨 Provider
Flutter&鸿蒙next 状态管理高级使用:深入探讨 Provider
|
13天前
|
缓存 JavaScript API
Flutter&鸿蒙next 状态管理框架对比分析
在 Flutter 开发中,状态管理至关重要,直接影响应用的性能和可维护性。本文对比分析了常见的状态管理框架,包括 setState()、InheritedWidget、Provider、Riverpod、Bloc 和 GetX,详细介绍了它们的优缺点及适用场景,并提供了 Provider 的示例代码。选择合适的状态管理框架需考虑应用复杂度、团队熟悉程度和性能要求。
81 0
|
27天前
【Flutter】状态管理:Provider状态管理
【Flutter】状态管理:Provider状态管理
14 0
|
1月前
|
UED
flutter:动画&状态管理 (十三)
本文档介绍了Flutter中`animatedList`的使用方法和状态管理的示例。`animatedList`用于创建带有动画效果的列表,示例代码展示了如何添加、删除列表项,并执行相应的动画效果。状态管理部分通过一个简单的点击切换颜色的示例,演示了如何在Flutter中管理组件的状态。
|
3月前
Flutter 状态管理新境界:多Provider并行驱动UI
Flutter 状态管理新境界:多Provider并行驱动UI
62 0
|
3月前
|
Dart API
状态管理的艺术:探索Flutter的Provider库
状态管理的艺术:探索Flutter的Provider库
47 0
|
4月前
|
容器
flutter 布局管理【详解】
flutter 布局管理【详解】
38 3