如何使用 Provider 管理 Flutter 中的状态

简介

State management涉及跟踪整个应用程序的状态更改。

provider包是满足状态管理需求的一种解决方案。

在本文中,您将学习如何将Provider应用于一个样例Flight应用程序来管理用户帐户信息的状态。

前提条件

要完成本教程,您需要:

  • 下载并安装Flutter.
  • 下载并安装Android StudioorVisual Studio代码.
  • 建议为您的代码编辑器安装插件: -Android Studio安装的FlutterDart插件。 -为Visual Studio代码安装的Flutter扩展。
  • 熟悉导航和航线将是有益的,但不是必需的。
  • 熟悉表格状态也是有益的,但不是必需的。

本教程使用Ffltter v2.0.6、Android SDK v31.0.2和Android Studio v4.1进行了验证。

了解问题所在

考虑这样一种情况,您想要构建一个应用程序,该应用程序使用用户的一些数据(如他们的姓名)来定制其某些屏幕。在屏幕之间传递数据的正常方法很快就会变成一堆混乱的回调、未使用的数据和不必要的重新构建的小部件。对于像Reaction这样的前端库,这是一个常见的问题,称为道具钻探。

如果我们想要从这些小部件中的任何一个传递数据,那么您需要使用更多未使用的回调来进一步膨胀每个中间小部件。对于大多数小功能,这可能会使它们几乎不值得付出努力。

对我们来说幸运的是,provider包允许我们将数据存储在一个更高的小部件中,就像我们初始化MaterialApp的地方一样,然后直接从子小部件访问和更改它,而不考虑嵌套,也不需要重建其间的所有内容。

第一步-设置项目

将环境设置为颤动后,可以运行以下命令来创建新应用程序:

1flutter create flutter_provider_example

导航到新项目目录:

1cd flutter_provider_example

使用fltter create将生成一个演示应用程序,其中将显示按钮被点击的次数。

第二步-添加Provider插件

接下来,我们需要在pubspec.yaml中添加Provider插件:

1[label pubspec.yaml]
2dependencies:
3  flutter:
4    sdk: flutter
5
6  provider: ^3.1.0

然后,将更改保存到您的文件。

<$>[备注] 注意: 如果您使用的是VS代码,您可能需要考虑使用PubSpec Assist扩展来快速添加依赖项。 <$>

我们现在可以继续在iOS或Android模拟器或您选择的设备上运行此程序。

Step 3 -搭建项目

我们需要两个屏幕,我们的路由器和一个导航栏。我们正在设置一个页面来显示我们的帐户数据,另一个页面则用存储、更改和从路由器向下传递的状态本身来更新它。

在代码编辑器中打开main.dart并修改以下代码行:

 1[label lib/main.dart]
 2import 'package:flutter/material.dart';
 3import 'package:provider/provider.dart';
 4import './screens/account.dart';
 5import './screens/settings.dart';
 6
 7void main() {
 8  runApp(MyApp());
 9}
10
11class MyApp extends StatelessWidget {
12  @override
13  Widget build(BuildContext context) {
14    return MaterialApp(
15      title: 'Flutter Provider Demo',
16      home: MyHomePage(),
17    );
18  }
19}
20
21class MyHomePage extends StatefulWidget {
22  @override
23  _MyHomePageState createState() => _MyHomePageState();
24}
25
26class _MyHomePageState extends State<MyHomePage> {
27  @override
28  Widget build(BuildContext context) {
29    return MaterialApp(home: AccountScreen(), routes: {
30      'account_screen': (context) => AccountScreen(),
31      'settings_screen': (context) => SettingsScreen(),
32    });
33  }
34}

创建一个navbar.dart文件,并使用代码编辑器打开它:

 1[label lib/navbar.dart]
 2import 'package:flutter/material.dart';
 3import './screens/account.dart';
 4import './screens/settings.dart';
 5
 6class Navbar extends StatelessWidget {
 7  @override
 8  Widget build(BuildContext context) {
 9    return Container(
10      color: Colors.blue,
11      child: Row(
12        mainAxisAlignment: MainAxisAlignment.spaceAround,
13        children: <Widget>[
14          TextButton(
15            onPressed: () =>
16              Navigator.pushReplacementNamed(context, AccountScreen.id),
17            child: Icon(Icons.account_circle, color: Colors.white)
18          ),
19          TextButton(
20            onPressed: () =>
21              Navigator.pushReplacementNamed(context, SettingsScreen.id),
22            child: Icon(Icons.settings, color: Colors.white)
23          ),
24        ],
25      ),
26    );
27  }
28}

lib目录下,新建一个creens子目录:

1mkdir lib/screens

在这个子目录中,创建一个settings.dart文件。这将用于创建表单状态、设置映射以存储输入,以及添加稍后将使用的提交按钮:

 1[label lib/screens/settings.dart]
 2import 'package:flutter/material.dart';
 3import 'package:provider/provider.dart';
 4import '../main.dart';
 5import '../navbar.dart';
 6
 7class SettingsScreen extends StatelessWidget {
 8  static const String id = 'settings_screen';
 9
10  final formKey = GlobalKey<FormState>();
11
12  final Map data = {'name': String, 'email': String, 'age': int};
13
14  @override
15  Widget build(BuildContext context) {
16    return Scaffold(
17      bottomNavigationBar: Navbar(),
18      appBar: AppBar(title: Text('Change Account Details')),
19      body: Center(
20        child: Container(
21        padding: EdgeInsets.symmetric(vertical: 20, horizontal: 30),
22        child: Form(
23          key: formKey,
24          child: Column(
25              mainAxisAlignment: MainAxisAlignment.center,
26              crossAxisAlignment: CrossAxisAlignment.center,
27              children: <Widget>[
28                TextFormField(
29                  decoration: InputDecoration(labelText: 'Name'),
30                  onSaved: (input) => data['name'] = input,
31                ),
32                TextFormField(
33                  decoration: InputDecoration(labelText: 'Email'),
34                  onSaved: (input) => data['email'] = input,
35                ),
36                TextFormField(
37                  decoration: InputDecoration(labelText: 'Age'),
38                  onSaved: (input) => data['age'] = input,
39                ),
40                TextButton(
41                  onPressed: () => formKey.currentState.save(),
42                  child: Text('Submit'),
43                  style: TextButton.styleFrom(
44                    primary: Colors.white,
45                    backgroundColor: Colors.blue,
46                  ),
47                )
48              ]
49            ),
50          ),
51        ),
52      ),
53    );
54  }
55}

同样在该子目录中,创建一个addt.dart文件。这将用于显示帐户信息:

 1[label lib/screens/account.dart]
 2import 'package:flutter/material.dart';
 3import 'package:provider/provider.dart';
 4import '../main.dart';
 5import '../navbar.dart';
 6
 7class AccountScreen extends StatelessWidget {
 8  static const String id = 'account_screen';
 9
10  @override
11  Widget build(BuildContext context) {
12    return Scaffold(
13      bottomNavigationBar: Navbar(),
14      appBar: AppBar(
15        title: Text('Account Details'),
16      ),
17      body: Center(
18        child: Column(
19          mainAxisAlignment: MainAxisAlignment.center,
20          children: <Widget>[
21            Text('Name: '),
22            Text('Email: '),
23            Text('Age: '),
24          ],
25        ),
26      ),
27    );
28  }
29}

编译代码并让其在模拟器中运行:

移动设备上运行的应用程序的屏幕截图。它将显示更改帐户详细信息页面,其中包含名称、电子邮件和age.等字段

此时,您拥有了一个带有帐户屏幕和设置屏幕的应用程序。

第四步-使用Provider

设置Provider需要将我们的MaterialApp包装在一个带有我们的数据类型的Provider中。

重新访问main.dart并在代码编辑器中打开它。在本教程中,数据类型为Map。最后,我们需要设置create,然后使用我们的contextdata

 1[label lib/main.dart]
 2// ...
 3
 4class _MyHomePageState extends State<MyHomePage> {
 5  Map data = {
 6    'name': 'Sammy Shark',
 7    'email': '[email protected]',
 8    'age': 42
 9  };
10
11  @override
12  Widget build(BuildContext context) {
13    return Provider<Map>(
14      create: (context) => data,
15      child: MaterialApp(home: AccountScreen(), routes: {
16        'account_screen': (context) => AccountScreen(),
17        'settings_screen': (context) => SettingsScreen(),
18      }),
19    );
20  }
21}

现在,main.dart调用并导入Provider包的所有其他屏幕和小部件中都可以使用data映射。

我们传递给Provider创建者的所有内容现在都可以在Provider.of<Map>(上下文)上使用。请注意,您传入的类型必须与Provider期望的数据类型匹配。

<$>[备注] 注意: 如果您使用的是VS代码,您可能会经常访问Provider,所以您可能会考虑使用snippets

1[label dart.json]
2"Provider": {
3  "prefix": "provider",
4  "body": [
5    "Provider.of<$1>(context).$2"
6  ]
7}

<$>

重新访问count t.dart并在代码编辑器中打开它。添加以下代码行:

 1[label lib/screens/account.dart]
 2// ...
 3
 4body: Center(
 5  child: Column(
 6    mainAxisAlignment: MainAxisAlignment.center,
 7    children: <Widget>[
 8      Text('Name: ' + Provider.of<Map>(context)['name'].toString()),
 9      Text('Email: ' + Provider.of<Map>(context)['email'].toString()),
10      Text('Age: ' + Provider.of<Map>(context)['age'].toString()),
11    ]),
12  ),
13)
14
15// ...

编译代码并让其在模拟器中运行:

移动设备上运行的应用程序的屏幕截图。它显示示例用户的帐户详细信息-他们的姓名、电子邮件、age.

此时,您就拥有了一个应用程序,其中包含显示在帐户屏幕上的硬编码用户数据。

第五步-使用ChangeNotifier

这样使用Provider似乎是自上而下的,如果我们想向上传递数据并修改我们的地图,该怎么办?光有供应商是不够的。首先,我们需要将我们的数据分解到它自己的类中,该类扩展了ChangeNotifier。Provider不能使用它,所以我们需要将其更改为ChangeNotifierProvider,并传入我们的Data类的实例。

现在我们传递了一个完整的类,而不仅仅是一个变量,这意味着我们可以开始创建可以操作我们的数据的方法,这些方法也将对访问Provider的所有对象可用。

在我们更改了任何全局数据之后,我们想要使用nufyListeners,它将重新构建依赖它的所有小部件。

重新访问main.dart并在代码编辑器中打开它:

 1[label lib/main.dart]
 2// ...
 3
 4class _MyHomePageState extends State<MyHomePage> {
 5  @override
 6  Widget build(BuildContext context) {
 7    return ChangeNotifierProvider<Data>(
 8      create: (context) => Data(),
 9      child: MaterialApp(home: AccountScreen(), routes: {
10        'account_screen': (context) => AccountScreen(),
11        'settings_screen': (context) => SettingsScreen(),
12      }),
13    );
14  }
15}
16
17class Data extends ChangeNotifier {
18  Map data = {
19    'name': 'Sammy Shark',
20    'email': '[email protected]',
21    'age': 42
22  };
23
24  void updateAccount(input) {
25    data = input;
26    notifyListeners();
27  }
28}

由于我们更改了Provider类型,因此需要更新对它的调用。重新访问count t.dart并在代码编辑器中打开它:

 1[label lib/screens/account.dart]
 2// ...
 3
 4body: Center(
 5  child: Column(
 6    mainAxisAlignment: MainAxisAlignment.center,
 7    children: <Widget>[
 8      Text('Name: ' + Provider.of<Data>(context).data['name'].toString()),
 9      Text('Email: ' + Provider.of<Data>(context).data['email'].toString()),
10      Text('Age: ' + Provider.of<Data>(context).data['age'].toString()),
11    ]),
12  ),
13)
14
15// ...

要向上传递数据,我们需要使用在Data类中向下传递的方法访问Provider。重新访问settings.dart并在代码编辑器中打开它:

1[label lib/screens/settings.dart]
2TextButton(
3  onPressed: () {
4    formKey.currentState.save();
5    Provider.of<Data>(context, listen: false).updateAccount(data);
6    formKey.currentState.reset();
7  },
8)

编译代码并让其在模拟器中运行:

移动设备上运行的应用程序的动画gif。它会显示某人在设置屏幕上使用该表单更新用户信息。然后,他们导航到帐户屏幕,新的用户信息为displayed.

至此,您拥有了一个支持在设置屏幕上更新用户信息并在帐户屏幕上显示更改的应用程序。

结论

在本文中,您学习了如何将Provider应用于一个样例Flight应用程序来管理用户帐户信息的状态。

如果您想了解更多有关Ffltter的知识,请查看我们的Ffltter主题页面以获取练习和编程项目。

Published At
Categories with 技术
Tagged with
comments powered by Disqus