logo
Published on

简单学习riverpod

Authors
  • avatar
    Name
    realth000
    Twitter

“在短暂的flutter学习中我意识到一件事,越是追求简单越是有报错。”
“说人话。”
“我不用getx做状态管理了,jojo!”

为何不用getx

getx在pub上有一万多个like,足以见得其强大,状态管理是getx的主要功能之一。使用getx以后可以几乎不写StatefulWidget,简单的.obsObx()GetControllerGetBuilder就能控制好状态管理,对于不想写state的新手来说真是新手福音。同时还有很强的性能:只在数值更新且变化时更新少量组件。

那么为什么不用getx呢?

  • State算flutter很主要的一块内容,脱离State玩自然简单,但是还是和官方的主要方向不和,未来有风险,何况现在getx更新很少了(有可能是没bug,但是也没feature啊)。
  • 使用getx时绑定的数值必须挨个传递,否则就会……error [Get] the improper use of a GetX has been detected。实际使用中,这个错误并不少,容易触发。我好菜啊
  • Get.find()强大,也吓人,任何位置只要调用Get.find()就能获取甚至操作绑定的数据,容易出错,而且写多了很乱。我个人觉得这个地方需要加以限制,不能太随意,本来菜鸡写得就随意,这下更乱了

介绍

Riverpod是一款提供数据缓存和数据绑定功能的库,由Provider的作者开发,作为Provider的升级版,能解决一些Provider难以解决的问题,毕竟Provider已经用得太广泛了。

官方小彩蛋:riverpod这个词是由provider单词重新排列顺序后形成的。

Riverpod具有以下特点:

  • 报错出现在编译期而不是运行期。
  • 不需要嵌套监听和绑定的对象。
  • 保证代码可测试。

根据官网的介绍,riverpod一共由三个版本:

  • riverpod,适合仅包含dart,不使用flutter的场景。
  • flutter_riverpod,适合使用flutter的场景。
  • hook_riverpod,同时使用flutter_hooksriverpod

嗯……反正用第二个就对了。

极速开始

完整代码:

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

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  // This widget is the root of your application.
  
  Widget build(BuildContext context) => ProviderScope(
        child: MaterialApp(
          title: 'Flutter Demo',
          home: MyHomePage(title: 'Flutter Demo Home Page'),
        ),
      );
}

class Wapper {
  final countProvider = StateProvider((ref) => 0);
}

final globalProvider = StateProvider((ref) => "asd");

class MyHomePage extends ConsumerWidget {
  MyHomePage({super.key, required this.title});

  final String title;

  final countProvider = StateProvider((ref) => 0);
  final w = Wapper();

  
  Widget build(BuildContext context, WidgetRef ref) {
    print('AAAA rebuild!');

    return Scaffold(
      appBar: AppBar(
        // the App.build method, and use it to set our appbar title.
        title: Text(title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              'You have pushed the button this many times:',
            ),
            Consumer(
              builder: (context, ref, _) => Text(
                '${ref.watch(countProvider)}, ${ref.watch(w.countProvider)}, ${ref.watch(globalProvider)}',
                style: Theme.of(context).textTheme.headlineMedium,
              ),
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          ref.read(countProvider.notifier).state++;
          ref.read(w.countProvider.notifier).state--;
          ref.read(globalProvider.notifier).state += "a";
        },
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}

老朋友,flutter默认的计数器demo,相比于使用原生的setState()riverpod的主要改动点如下:

  • MyAppbuild()中,MaterialApp外面包了一层ProviderScope
  • MyHomePage继承ConsumerWidget而不是StatefulWidget,同时去掉了State,build方法加了一个WidgetRef参数。
  • MyHomePage中的count改成了final countProvider = StateProvider((ref) => 0);,把初始值0包在StateProvider里面。
  • 原本显示countText现在显示ref.watch(countProvider),而且外面包了一层Consumer
  • 按下按钮后原本的setStatecount +=1改成了ref.read(countProvider.notifier).state++

看来riverpod主要中ConsumerWidget是关键,它让一个Widget变成可以使用riverpod相关功能的StatefulWidget

需要绑定的数值包在StateProvider里,需要读数值时ref.watch(providerObject),需要写入数值时对ref.read(provider.notifier).state做操作。

那么ref可以理解为一个在观察的搬运工,你我通过ref.read告诉ref“该更新数值了”,通过ref.watch()告诉ref“这个位置看好了,数值变了的话记得更新”。

同时也能看出来,StateProvider可以隔着Wrapper,也可以是全局的Object,使用起来没有getx必须相邻传递的限制,同时也可以方便地遵守各种层级暴露关系,思路不容易乱。

性能也不错,上文代码中更改的部分只有Consumer包裹的那一部分,已经和getx的性能外表上看上去一样了,同样是手动setState最极限小的更新范围。

所以嘛,用上去不比getx麻烦多少。

当然,路由管理什么的还是可以用getx,如果没有其他好用的路由管理包的话