Flutter状态管理之Riverpod 2.0

Posted 唯鹿

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Flutter状态管理之Riverpod 2.0相关的知识,希望对你有一定的参考价值。

两年前分享过一篇Flutter状态管理之Riverpod,当时riverpod的版本还是0.8.0(后来文章更新到0.14版本)。当时提到过有一些不足之处:

  • 毕竟诞生不久,它还不能保证是完全稳定的。
  • 可能后期会有API的破坏性改动。
  • 目前生产环境中使用需要谨慎。

随着作者两年来的不断维护完善,这些问题基本上已经不存在了。毕竟最痛苦的API破坏性改动已经在1.0版本时处理了一遍。

目前版本来到了2.1.1,所以本篇是对最新API用法的一个更新,还是沿用之前那篇的用例作说明。

1.配置

将它添加到pubspec.yaml中:

flutter_riverpod: ^2.1.1

然后执行flutter pub get

3.基础使用

Provider

这里使用RiverpodProvider需要三步就可以。

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

// 1.创建一个全局的provider,里面储存“Hello World!”
final Provider<String> helloWorldProvider = Provider((_) => 'Hello World!');

void main() 
  runApp(
    // 2.添加“ProviderScope”。所有使用Riverpod的Flutter程序都必须
    // 在widget tree的根部添加它,用来储存各个provider。
    ProviderScope(
      child: MyApp(),
    ),
  );


class MyApp extends StatelessWidget 
  
  Widget build(BuildContext context) 
    return MaterialApp(
      title: 'Riverpod Example',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: ProviderExample(),
    );
  


/// 3.使用“ConsumerWidget”,在“build”中获取对应的provider
class ProviderExample extends ConsumerWidget 

  
  Widget build(BuildContext context, WidgetRef ref) 
    final String value = ref.watch(helloWorldProvider);

    return Scaffold(
      appBar: AppBar(title: Text('Provider')),
      body: Center(
        child: Text(value),
      ),
    );
  

这里储存“Hello World!” 使用的是Provider,它提供一个永远不变的对象。不过大部分场景下状态都是可变的,下面用计数器来举例。

StateProvider

在“Hello World”的基础上,做两点修改即可。

定义一个全局常量StateProvider

final StateProvider<int> counterProvider = StateProvider((_) => 0);
class StateProviderExample extends ConsumerWidget 

  
  Widget build(BuildContext context, WidgetRef ref) 
    return Scaffold(
      appBar: AppBar(
        title: Text('StateProvider'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            Consumer(
              builder: (context, ref, _) 
                /// 使用Consumer(ConsumerWidget的封装),控制刷新的范围。
                int count = ref.watch(stateProvider);
                return Text(
                  '$count',
                  style: Theme.of(context).textTheme.headline4,
                );
              ,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        /// 使用read获取counterProvider,操作state。
        onPressed: () => ref.read(stateProvider.notifier).state++,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  


ChangeNotifierProvider

这部分没啥说的,注意ChangeNotifier需要自己调用notifyListeners通知变更。

final ChangeNotifierProvider<Counter> _counterProvider = ChangeNotifierProvider((_) => Counter());

class Counter extends ChangeNotifier 
  int _count = 0;

  int get count => _count;

  void increment() 
    _count++;
    notifyListeners();
  
  void decrement()
    _count--;
    notifyListeners();
  


class ChangeProviderNotifierExample extends ConsumerWidget 

  
  Widget build(BuildContext context, WidgetRef ref) 
    return Scaffold(
      appBar: AppBar(
        title: Text('ChangeNotifierProvider'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            Consumer(
              builder: (context, ref, _) 
                int count = ref.watch(changeNotifierProvider).count;
                return Text(
                  '$count',
                  style: Theme.of(context).textTheme.headline4,
                );
              ,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        /// 使用read获取counterProvider。
        onPressed: () => ref.read(changeNotifierProvider).increment(),
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  


FutureProvider

final FutureProvider<String> futureProvider = FutureProvider((_) async 
  /// 延时3s
  await Future.delayed(const Duration(seconds: 3));
  return 'Riverpod';
);

class FutureProviderExample extends StatelessWidget 

  
  Widget build(BuildContext context) 
    return Scaffold(
      appBar: AppBar(
        title: Text('FutureProvider'),
      ),
      body: Center(
        child: Consumer(
          builder: (context, ref, _) 
            AsyncValue<String> futureProviderValue = ref.watch(futureProvider);
            return futureProviderValue.when(
              loading: () => CircularProgressIndicator(),
              error: (error, stack) => Text('Oops, something unexpected happened'),
              data: (value) => Text(
                'Hello $value',
                style: Theme.of(context).textTheme.headline4,
              ),
            );
          ,
        ),
      ),
    );
  

作者也提供了StreamProvider。用法大同小异,有兴趣的可以查看我的示例代码。

ProviderListener

如果你希望在Widget Tree上监听provider的状态变化,可以使用listen。用上面的计数器例子,当计数器为5时,触发监听。

final StateProvider<int> stateProvider = StateProvider((_) => 0);

class ProviderListenerExample extends ConsumerWidget 

  @override
  Widget build(BuildContext context, WidgetRef ref) 
    ref.listen(stateProvider, (previous, next) 
      if (next == 5) 
        print('当前计数器为5,触发监听。');
      
    );
    ...
  

ProviderScope

一般我们在实现一个列表的Item时,需要传入相应的index大致如下:

ListView.builder(
  itemCount: 50,
  itemBuilder: (context, index) 
    return ProductItem(index: index);
  ,
)

如果使用 ProviderScope,就可以覆盖index的值,内部通过ref.watch获取,就不必从构造方法接收它。使用起来很简单,直接上代码:

final Provider<int> currentProductIndex = Provider<int>((_) => 0);

class ScopeProviderExample extends StatelessWidget 
  
  Widget build(BuildContext context) 
    return Scaffold(
      appBar: AppBar(
        title: Text('ScopedProvider'),
      ),
      body: ListView.builder(
        itemCount: 50,
        itemBuilder: (context, index) 
          return ProviderScope(
            overrides: [
              /// 修改value
              currentProductIndex.overrideWithValue(index),
            ],
            /// 使用'const'关键字实例化了“ProductItem”,但仍然可以在内部动态获取内容。
            child: const ProductItem(),
          );
        ,
      ),
    );
  


class ProductItem extends ConsumerWidget 

  const ProductItem(Key? key): super(key: key);

  
  Widget build(BuildContext context, WidgetRef ref) 
    final index = ref.watch(currentProductIndex);
    return ListTile(title: Text('item $index'));
  

4.修饰符

family

family的作用是可以在获取provider时可以添加一个参数。直接上例子,一看便知:

/// 使用family,可以在获取provider时传入city
final _weatherProvider = Provider.family<String, String>((ref, city) 
  return '$city (Sunny)';
);

class FamilyExample extends StatelessWidget 
  
  Widget build(BuildContext context) 
    return Scaffold(
      appBar: AppBar(title: Text('Family')),
      body: Center(
        child: Consumer(
          builder: (context, ref, _) 
            /// 这里可以传参“London”
            final String weather = ref.watch(_weatherProvider('London'));
            return Text('$weather',);
          ,
        ),
      ),
    );
  

注意: 使用family时传入的参数是有限制的。比如bool intdoubleString 、常量或是重写了==hashCode的不可变对象。

family的目的是根据外部参数获取对应的provider。例如接口传参:

final messagesFamily = FutureProvider.family<Message, String>((ref, id) async 
  return dio.get('http://my_api.dev/messages/$id');
);

autoDispose

前面我们的例子中,创建的provider因为保存在Widget Tree的根部。所以即使页面关闭,再次进入页面时会获取之前的状态。

这显然是有局限性的,那么这里就可以使用autoDispose,它可以在我们不再使用provider时,自动将其销毁。那么合理的使用它可以避免内存泄漏。

比如之前的计数器例子,只需加一个autoDispose就可以避免此类问题。

final stateProvider = StateProvider.autoDispose((_) => 0);

如果你需要自定义dispose事件,可以使用onDispose。比如你的provider中有网络请求(使用Dio):

final myProvider = FutureProvider.autoDispose((ref) async 
  
  final cancelToken = CancelToken();
  // 当provider被销毁时,取消http请求
  ref.onDispose(() => cancelToken.cancel());

  // http请求
  final response = await dio.get('path', cancelToken: cancelToken);
  // 如果请求成功完成,则保持该状态。
  ref.keepAlive();
  return response;
);

上面代码中出现了keepAlive方法。如果用户离开页面并且请求失败,下次则将再次执行该请求。但是,如果请求成功完成调用了keepAlivee,则将保留状态,下次重新进入页面时不会触发新的请求。

使用autoDispose可以达到限制provider是全局还是局部作用。这样一来,可以更方便的解决跨页面使用provider的问题。

5.进阶使用

Combining providers

1.如果创建的provider需要另一个provider的状态,这时就需要使用Refread方法。

下面的示例是,给予城市和国家的provider,当创建locationProvider时,获取城市和国家的状态。

final Provider<String> cityProvider = Provider<String>((ref) => 'London');
final Provider<String> countryProvider = Provider<String>((ref) => 'England');
final Provider<Location> locationProvider = Provider<Location>((ref) => Location(ref));

class Location 
  Location(this._ref);

  final Ref _ref;

  String get label 
    final city = _ref.read(cityProvider);
    final country = _ref.read(countryProvider);
    return '$city ($country)';
  


使用Riverpod就可以提供多个相同类型的Provider,这也是相比Provider框架的一个优点。

2.如果获取的状态值会发生变化,我们需要监听它。可以使用Refwatch方法。

下面的示例是,当cityProvider变化时,weatherProvider也相应变化。

final StateProvider<String> cityProvider = StateProvider((ref) => 'London');
final StateProvider<String> weatherProvider = StateProvider((ref) 
  /// watch监听
  final String city = ref.watch(cityProvider).state;
  return '$city (Sunny)';
);

class CombiningProviderExample2 extends ConsumerWidget 

  
  Widget build(BuildContext context, WidgetRef ref) 
    return Scaffold(
      appBar: AppBar(title: Text('CombiningProvider')),
      body: Center(
        child: Consumer(
          builder: (context, ref, _) 
            final String weather = ref.watch(weatherProvider);
            return Text('$weather',);
          ,
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () 
          /// 修改状态
          String city = ref.read(cityProvider.notifier).state;
          if (city == 'London') 
            ref.read(cityProvider.notifier).state = "Xi'an";
           else 
            ref.read以上是关于Flutter状态管理之Riverpod 2.0的主要内容,如果未能解决你的问题,请参考以下文章

Flutter状态管理之Riverpod 2.0

Flutter状态管理之Riverpod

使用 RiverPod 状态管理 Flutter 更改 Text 值

重走Flutter状态管理之路—Riverpod入门篇

重走Flutter状态管理之路—Riverpod入门篇

重走Flutter状态管理之路—Riverpod最终篇