📜  Flutter – 使用 Riverpod 进行状态管理简介

📅  最后修改于: 2022-05-13 01:55:00.749000             🧑  作者: Mango

Flutter – 使用 Riverpod 进行状态管理简介

Riverpod 是一个响应式状态管理和依赖注入框架,它使用不同的提供程序让我们访问和监听应用程序中的状态变化,它由 Remi Rousselet 构建。如果您不知道什么是状态,那么我建议您先阅读这篇文章,因为在不了解“状态”本身的情况下,您很难理解 Riverpod。

Riverpod实际上是做什么的?

Riverpod 是一个状态管理助手。它基本上使我们的状态(明确地说,我们的变量的值)在应用程序的所有部分都可以访问,它将我们的状态放在小部件树的顶部,让我们监听这些状态变化并相应地更新我们的用户界面。

Riverpod 提供多种类型的提供商,我们将一一介绍。

什么是提供者?

提供者是 Riverpod 应用程序中最重要的部分, “提供者是一个对象,它封装了一段状态并允许监听该状态。”

供应商类型:

  • 状态提供者
  • 未来提供者
  • 流提供者
  • 提供者

让我们开始建造吧!



1. 将 Riverpod 添加到我们的应用程序中:

Dart
dependencies:
  flutter_riverpod: ^0.14.0+3


Dart
import 'package:flutter_riverpod/flutter_riverpod.dart';
void main() {
  runApp(ProviderScope(child: MyApp()));
}


Dart
import 'package:flutter_riverpod/flutter_riverpod.dart';
  
// Instead of String you can use other data types as well,
// we can also use custom data types.
final userNameProvider=StateProvider((ref) {
  
   // we can also return an empty String here, for the sake of simplicity, 
  //  let's return a sample name 
  return "SomeName";
});


Dart
import 'Providers/providers.dart' as providers.dart; //for easy access
import 'package:flutter_riverpod/flutter_riverpod.dart';
  
class Home extends ConsumerWidget {
  @override
  Widget build(BuildContext context, ScopedReader watch) {
      
    // Listens to the value exposed by userNameProvider
    // ".state" method lets us get the state easily & directly
    String name = watch(providers.userNameProvider).state;
         
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Center(child: const Text('GFG 🙂 '))),
        body: Center(
            
          // displaying the value
          child: Text('$name'), 
        ),
      ),
    );
  }
}


Dart
// for easy access
import 'Providers/providers.dart' as providers.dart;
  
class Home extends ConsumerWidget {
  int n = 0;
  @override
  Widget build(BuildContext context, ScopedReader watch) {
      
    // Listens to the value exposed by userNameProvider
    StateController nameController = watch(providers.userNameProvider);
  
    // ".state" method lets us get the state easily & directly
    return MaterialApp(
      home: Scaffold(
        floatingActionButton: FloatingActionButton(
          onPressed: () {
            n++;
            nameController.state =
                
                // now we can set the state using the state setter.
                "New Name $n"; 
          },
        ),
        appBar: AppBar(title: Center(child: const Text('GFG 🙂 '))),
        body: Center(
          child: Text(
              
            // displaying the value
            '${nameController.state}',
          ), 
        ),
      ),
    );
  }
}


Dart
class FutureClass {
    
  // in production, the below method could be any network call
  Future getData(String para) async {
    await Future.delayed(Duration(seconds: 5));
    return 25;
  }
}


Dart
final response = FutureProvider((ref) async {
  final client = ref.read(futureClass);
  return client.getData('some text as a parameter');
});


Dart
class HomeScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
    appBar: AppBar(title: Text("FutureProvider Demo"),),
      body: Center(
        child: Column(
         children: [
            Consumer(
              builder: (context, watch, child) {
                final futureData = watch(response);               
              },
            ),
          ],
        ),
      ),
    );
  }
}


Dart
Consumer(
  builder: (context, watch, child) {
    final futureData = watch(response);
  
    return  futureData.map(
        
      ,// have data
      data: (data) => Text('${data.value}',)
        
      ,// in progress
      loading: (_) => CircularProgressIndicator()
        
      // has an error
      error: (message) => Text(message.error),
    );
  },
),


Dart
final response=
    FutureProvider.autoDispose.family((ref, i_am_a_param) async {
  final client = ref.read(futureClass);
  return client.getData(i_am_a_param);
});
  
// the family modifier is used to pass a String value,
// which is used to call the get() method.
// The modifier "autoDispose" destroys the state of a provider 
// when it is no longer used, even when the widget state is not yet dispose


Dart
class HomeScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text("FutureProvider Demo"),),
        body: SafeArea(
          child: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Consumer(
                  builder: (context, watch, child) {
                    final futureData = watch(providers.response);
  
                    return futureData.map(
                      data: (data) => Text(
                        '${data.value}',
                      ), // have data
                      loading: (_) => Column(
                        children: [
                          CircularProgressIndicator(),
                          Text(
                            'Fetching data',
                          ),
                        ],
                      ), // in progress
                      error: (message) =>
                          Text(message.error.toString()),
                      // has an error
                    );
                  },
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}


Dart
class HomeScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text("StreamProvider Demo"),
        ),
        body: SafeArea(
          child: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Consumer(
                  builder: (context, watch, child) {
                    final streamValue = watch(providers.streamProvider);
  
                    return streamValue.when(
                      data: (data) => Text(
                        '${data}',
                      ), // have data
                      loading: () => Column(
                        children: [
                          CircularProgressIndicator(),
                          Text(
                            'Fetching data',
                          ),
                        ],
                      ), // in progress
                      error: (message, e) =>
                          Text(message.toString()),
                      // has an error
                    );
                  },
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}


Dart
final stream = Stream.fromIterable([105, 50]);
  
StreamBuilder(
  stream: stream,
  builder: (context, snapshot) {
    if (snapshot.connectionState == ConnectionState.active) {
      if (snapshot.hasData) {
          
         // data
        return SomeWidget(snapshot.data);
      } else if (snapshot.hasError) {
          
        // error state
        return SomeErrorWidget(snapshot.error); 
      } else {
          
        // no data
        return Text('No data'); 
      }
    } else {
        
       // loading state
      return CircularProgressIndicator();
    }
  }
)


2. 包装我们的应用程序:

为了使提供者工作,我们必须在Flutter应用程序的根目录中添加 ProviderScope:

Dart

import 'package:flutter_riverpod/flutter_riverpod.dart';
void main() {
  runApp(ProviderScope(child: MyApp()));
}

3. 创建提供者。dart文件:

我们将在此文件中定义所有全局提供程序,以便更轻松地维护项目。让我们定义一个 StateProvider。

Dart

import 'package:flutter_riverpod/flutter_riverpod.dart';
  
// Instead of String you can use other data types as well,
// we can also use custom data types.
final userNameProvider=StateProvider((ref) {
  
   // we can also return an empty String here, for the sake of simplicity, 
  //  let's return a sample name 
  return "SomeName";
});

4. 阅读提供者:

现在我们已经声明了一个 StateProvider,让我们学习如何读取提供者。要阅读任何提供程序,我们有多个小部件,在本文中,我们将介绍 ConsumerWidget() 和 Consumer()。

使用 ConsumerWidget():

消费者小部件是我们可以用来代替有状态/无状态小部件的小部件。它使我们能够阅读/更改提供者的状态并听取他们的意见。

Dart



import 'Providers/providers.dart' as providers.dart; //for easy access
import 'package:flutter_riverpod/flutter_riverpod.dart';
  
class Home extends ConsumerWidget {
  @override
  Widget build(BuildContext context, ScopedReader watch) {
      
    // Listens to the value exposed by userNameProvider
    // ".state" method lets us get the state easily & directly
    String name = watch(providers.userNameProvider).state;
         
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Center(child: const Text('GFG 🙂 '))),
        body: Center(
            
          // displaying the value
          child: Text('$name'), 
        ),
      ),
    );
  }
}

输出:

很好。所以现在,只要userNameProvider的值发生变化,Text() 就会相应地更新。但是我们如何更新 userNameProvider 的值呢?

5. 更新/更改 StateProvider 的值:

要更改 StateProvider 的值,我们需要一个 StateController 。所以让我们创建它。

首先,让我们删除变量“name”。 ̶

S̶t̶r̶i̶n̶g̶ ̶n̶a̶m̶e̶ ̶=̶ ̶w̶a̶t̶c̶h̶(̶p̶r̶o̶v̶i̶d̶e̶r̶s̶.̶u̶s̶e̶r̶N̶a̶m̶e̶P̶r̶o̶v̶i̶d̶e̶r̶)̶.̶s̶t̶a̶t̶e̶;̶

//removed as with it we can only listen/get the state not change/mutate the state.

//let's use this StateController instead.

StateController nameController = watch(providers.userNameProvider);

// now we can get / set the state using name.state.

为简单起见,让我们使用 FloatingActionButton,并尝试使用它来改变(改变)userNameProvider 的状态。

Dart

// for easy access
import 'Providers/providers.dart' as providers.dart;
  
class Home extends ConsumerWidget {
  int n = 0;
  @override
  Widget build(BuildContext context, ScopedReader watch) {
      
    // Listens to the value exposed by userNameProvider
    StateController nameController = watch(providers.userNameProvider);
  
    // ".state" method lets us get the state easily & directly
    return MaterialApp(
      home: Scaffold(
        floatingActionButton: FloatingActionButton(
          onPressed: () {
            n++;
            nameController.state =
                
                // now we can set the state using the state setter.
                "New Name $n"; 
          },
        ),
        appBar: AppBar(title: Center(child: const Text('GFG 🙂 '))),
        body: Center(
          child: Text(
              
            // displaying the value
            '${nameController.state}',
          ), 
        ),
      ),
    );
  }
}

输出:

现在,当我们按下 FloatingActionButton() 时,我们会看到名称每次都会更改为不同的数字。就是这样了。使用这些信息,我们可以轻松地使用 StateProviders,改变它们的值并听取它。



现在,让我们回顾一下异步提供程序。

1. FutureProvider:

当我们处理异步代码时,我们经常使用一些基于 Future 的 API,让我们看看如何使用 Riverpod 处理 Future 事件。为了演示,我们将创建一个返回 Future 的方法。

Dart

class FutureClass {
    
  // in production, the below method could be any network call
  Future getData(String para) async {
    await Future.delayed(Duration(seconds: 5));
    return 25;
  }
}

为 FutureClass 创建一个提供者:

final futureClass = Provider((ref) => FutureClass());

现在,让我们创建我们的 FutureProvider,

Dart

final response = FutureProvider((ref) async {
  final client = ref.read(futureClass);
  return client.getData('some text as a parameter');
});

现在,我们可以使用 ConsumerWidget 来监听状态变化并相应地更新我们的 UI。

使用 FutureProvider:

除了 ConsumerWidget(),我们还有一个 Consumer() 小部件。不同之处在于,当状态改变时 ConsumerWidget 重建整个屏幕,而 Consumer() 只重建它的子屏幕,确保我们不会遇到性能问题。

让我们使用 Consumer() 小部件来观察(监听)状态变化。

Dart



class HomeScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
    appBar: AppBar(title: Text("FutureProvider Demo"),),
      body: Center(
        child: Column(
         children: [
            Consumer(
              builder: (context, watch, child) {
                final futureData = watch(response);               
              },
            ),
          ],
        ),
      ),
    );
  }
}

由于 Future 可以有 3 个状态,即完成、进行中和错误,我们有能力使用 .map函数分别处理这些状态。

Dart

Consumer(
  builder: (context, watch, child) {
    final futureData = watch(response);
  
    return  futureData.map(
        
      ,// have data
      data: (data) => Text('${data.value}',)
        
      ,// in progress
      loading: (_) => CircularProgressIndicator()
        
      // has an error
      error: (message) => Text(message.error),
    );
  },
),

如果我们想将一个变量/对象作为参数传递给提供者,那么我们可以这样做——

Dart

final response=
    FutureProvider.autoDispose.family((ref, i_am_a_param) async {
  final client = ref.read(futureClass);
  return client.getData(i_am_a_param);
});
  
// the family modifier is used to pass a String value,
// which is used to call the get() method.
// The modifier "autoDispose" destroys the state of a provider 
// when it is no longer used, even when the widget state is not yet dispose

完整代码:

Dart

class HomeScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text("FutureProvider Demo"),),
        body: SafeArea(
          child: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Consumer(
                  builder: (context, watch, child) {
                    final futureData = watch(providers.response);
  
                    return futureData.map(
                      data: (data) => Text(
                        '${data.value}',
                      ), // have data
                      loading: (_) => Column(
                        children: [
                          CircularProgressIndicator(),
                          Text(
                            'Fetching data',
                          ),
                        ],
                      ), // in progress
                      error: (message) =>
                          Text(message.error.toString()),
                      // has an error
                    );
                  },
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

输出:

2. 流提供者:

我们经常在flutter应用程序中使用流,无论是从 Firestore 获取数据还是从文件中读取数据。让我们学习如何使用 Riverpod 处理这些流。

让我们先声明一个流,



final streamProvider = StreamProvider((ref) {
  return Stream.fromIterable([105, 50]);
  //in production this could be a stream of documents from Firestore
});

使用 StreamProvider:

这次我们用ConsumerWidget()来消费StreamProvider的数据,和FutureProvider的差不多。

Dart

class HomeScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text("StreamProvider Demo"),
        ),
        body: SafeArea(
          child: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Consumer(
                  builder: (context, watch, child) {
                    final streamValue = watch(providers.streamProvider);
  
                    return streamValue.when(
                      data: (data) => Text(
                        '${data}',
                      ), // have data
                      loading: () => Column(
                        children: [
                          CircularProgressIndicator(),
                          Text(
                            'Fetching data',
                          ),
                        ],
                      ), // in progress
                      error: (message, e) =>
                          Text(message.toString()),
                      // has an error
                    );
                  },
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

现在,每次流有新数据时,我们的 UI 都会相应地更新。

请注意,我们不必自己管理不同的状态,如果没有 Riverpod,上面的代码将如下所示:

Dart

final stream = Stream.fromIterable([105, 50]);
  
StreamBuilder(
  stream: stream,
  builder: (context, snapshot) {
    if (snapshot.connectionState == ConnectionState.active) {
      if (snapshot.hasData) {
          
         // data
        return SomeWidget(snapshot.data);
      } else if (snapshot.hasError) {
          
        // error state
        return SomeErrorWidget(snapshot.error); 
      } else {
          
        // no data
        return Text('No data'); 
      }
    } else {
        
       // loading state
      return CircularProgressIndicator();
    }
  }
)

就是这样。这应该涵盖了我们开始使用 Riverpod 所需的几乎所有内容。

可以在此处找到上述文章的完整代码。

想要一个更快节奏和更具竞争力的环境来学习 Android 的基础知识吗?
单击此处前往由我们的专家精心策划的指南,旨在让您立即做好行业准备!