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 = "none";
  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

相关文章
|
1天前
|
存储 JavaScript 前端开发
盘点主流 Flutter 状态管理库2024
状态管理是每个应用不可缺少的,本文将会盘点下主流的状态管理包。
100 2
盘点主流 Flutter 状态管理库2024
|
8月前
|
存储 Dart 数据库
重识Flutter状态管理 — 探索Flutter中的状态
我遇到过很多没有了解过响应式编程框架的,或者从事后端开发,自己想用Flutter写个app玩玩的朋友,一上来,不管在哪里都用`setState`,我问为啥不用状态管理,大部分都回了一句:啥是状态管理?
|
8月前
|
存储 数据库 索引
Flutter笔记:滚动之-无限滚动与动态加载的实现(GetX简单状态管理版)
本文介绍Flutter中如何实无线滚动(基于GetX简单状态管理而非有状态组件)
77 0
|
1天前
|
存储 JavaScript 前端开发
【Flutter 前端技术开发专栏】Flutter 中的状态管理框架(如 Provider、Redux 等)
【4月更文挑战第30天】本文探讨了 Flutter 开发中的状态管理,重点介绍了 Provider 和 Redux 两种框架。Provider 以其简单易用性适合初学者和小项目,而 Redux 则适用于大型复杂应用,保证状态一致性。此外,还提到了 Riverpod 和 BLoC 等其他框架。选择框架时要考虑项目规模、团队技术水平和个人偏好。文章通过购物车应用示例展示了不同框架的使用,并展望了状态管理框架的未来发展。
【Flutter 前端技术开发专栏】Flutter 中的状态管理框架(如 Provider、Redux 等)
|
1天前
|
JavaScript 前端开发 开发者
【Flutter前端技术开发专栏】Flutter中的Widget与状态管理
【4月更文挑战第30天】本文探讨了Flutter的Widget和状态管理。Widget是Flutter构建UI的基础,分为有状态和无状态两种。状态管理确保UI随应用状态变化更新,影响应用性能和可维护性。文章介绍了`setState`、`Provider`、`Riverpod`、`Bloc`和`Redux`等状态管理方法,并通过计数器应用展示了其实现。选择合适的状态管理策略对高效开发至关重要。
【Flutter前端技术开发专栏】Flutter中的Widget与状态管理
|
1天前
|
存储 UED 开发者
Flutter的状态管理:setState、Provider、Bloc的使用详解
【4月更文挑战第26天】Flutter状态管理详解:涵盖setState基础,Provider的跨组件共享及Bloc的复杂场景处理。了解这三种方法的优缺点,助力优化应用数据一致性与用户体验。当状态管理需求升级,从简单的setState到Provider的便利,再到Bloc的强大功能,开发者可根据项目规模和复杂度选择合适策略。
|
1天前
|
Dart
Flutter状态管理:RxDart,详细介绍
Flutter状态管理:RxDart,详细介绍 RxDart是一个基于Dart语言的响应式编程库,它提供了一套用于处理异步事件序列的工具。在Flutter应用中,RxDart可以很好地用于管理应用状态。
117 0
|
1天前
|
存储 前端开发
Flutter Provider状态管理---MVVM架构实战
Flutter Provider状态管理—MVVM架构实战 在Flutter中,状态管理是一个非常重要的概念。Flutter Provider是一种状态管理的解决方案,它提供了一种简单,灵活和高效的方法来管理Flutter应用程序中的状态。本文将详细介绍Flutter Provider的使用,以及如何在MVVM架构中使用它。
182 0
|
1天前
|
API
Flutter状态管理终极方案GetX第一篇——路由
Flutter状态管理终极方案GetX第一篇——路由 GetX是Flutter中一个非常流行的状态管理库,它不仅提供了简单易用的状态管理功能,还可以帮助我们方便地管理路由。在这篇文章中,我们将介绍如何使用GetX来实现路由管理。
158 0
|
1天前
|
开发者
Flutter状态管理终极方案GetX第二篇——状态管理
Flutter状态管理终极方案GetX第二篇——状态管理 在Flutter应用程序中,状态管理是必不可少的。GetX提供了简单易用的状态管理方案,使得开发者可以更加轻松地管理应用程序状态。下面介绍GetX中的状态管理方案。
131 0

热门文章

最新文章

http://www.vxiaotou.com