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

介绍

状态管理涉及跟踪整个应用程序中的状态变化。

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

在本文中,您将学习如何应用provider到示例 Flutter 应用程序来管理用户帐户信息的状态。

先决条件

要完成本教程,您需要:

  • 下载并安装Flutter
  • 下载并安装Android Studio Visual Studio Code
  • 建议为您的代码编辑器安装插件:

    • FlutterDart为 Android Studio 安装的插件。
    • Flutter 为 Visual Studio Code 安装的扩展。
  • 熟悉导航和路线将是有益的,但不是必需的。
  • 熟悉表单状态也将是有益的,但不是必需的。

本教程通过 Flutter v2.0.6、Android SDK v31.0.2、Android Studio v4.1 验证。

了解问题

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

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

对我们来说幸运的是,该provider包允许我们将数据存储在更高的小部件中,就像我们在任何地方初始化我们的MaterialApp,然后直接从子小部件访问和更改它,无论嵌套如何,也无需重建中间的所有内容。

步骤 1 — 设置项目

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

  • flutter create flutter_provider_example

导航到新的项目目录:

  • cd flutter_provider_example

使用flutter create将生成一个演示应用程序,该应用程序将显示单击按钮的次数。

第 2 步 – 添加provider插件

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

pubspec.yaml
dependencies:
  flutter:
    sdk: flutter

  provider: ^3.1.0

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

注意:如果您使用 VS Code,您可能需要考虑使用Pubspec Assist扩展来快速添加依赖项。

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

第 3 步 – 搭建项目的脚手架

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

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

lib/main.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import './screens/account.dart';
import './screens/settings.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Provider Demo',
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(home: AccountScreen(), routes: {
      'account_screen': (context) => AccountScreen(),
      'settings_screen': (context) => SettingsScreen(),
    });
  }
}

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

lib/navbar.dart
import 'package:flutter/material.dart';
import './screens/account.dart';
import './screens/settings.dart';

class Navbar extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.blue,
      child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceAround,
        children: <Widget>[
          TextButton(
            onPressed: () =>
              Navigator.pushReplacementNamed(context, AccountScreen.id),
            child: Icon(Icons.account_circle, color: Colors.white)
          ),
          TextButton(
            onPressed: () =>
              Navigator.pushReplacementNamed(context, SettingsScreen.id),
            child: Icon(Icons.settings, color: Colors.white)
          ),
        ],
      ),
    );
  }
}

lib目录中,新建一个screens子目录:

  • mkdir lib/screens

在此子目录中,创建一个settings.dart文件。这将用于创建我们的表单状态,设置一个地图来存储我们的输入,并添加一个我们稍后将使用的提交按钮:

lib/screens/settings.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../main.dart';
import '../navbar.dart';

class SettingsScreen extends StatelessWidget {
  static const String id = 'settings_screen';

  final formKey = GlobalKey<FormState>();

  final Map data = {'name': String, 'email': String, 'age': int};

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      bottomNavigationBar: Navbar(),
      appBar: AppBar(title: Text('Change Account Details')),
      body: Center(
        child: Container(
        padding: EdgeInsets.symmetric(vertical: 20, horizontal: 30),
        child: Form(
          key: formKey,
          child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              crossAxisAlignment: CrossAxisAlignment.center,
              children: <Widget>[
                TextFormField(
                  decoration: InputDecoration(labelText: 'Name'),
                  onSaved: (input) => data['name'] = input,
                ),
                TextFormField(
                  decoration: InputDecoration(labelText: 'Email'),
                  onSaved: (input) => data['email'] = input,
                ),
                TextFormField(
                  decoration: InputDecoration(labelText: 'Age'),
                  onSaved: (input) => data['age'] = input,
                ),
                TextButton(
                  onPressed: () => formKey.currentState.save(),
                  child: Text('Submit'),
                  style: TextButton.styleFrom(
                    primary: Colors.white,
                    backgroundColor: Colors.blue,
                  ),
                )
              ]
            ),
          ),
        ),
      ),
    );
  }
}

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

lib/screens/account.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../main.dart';
import '../navbar.dart';

class AccountScreen extends StatelessWidget {
  static const String id = 'account_screen';

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      bottomNavigationBar: Navbar(),
      appBar: AppBar(
        title: Text('Account Details'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text('Name: '),
            Text('Email: '),
            Text('Age: '),
          ],
        ),
      ),
    );
  }
}

编译你的代码并让它在模拟器中运行:

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

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

第 4 步 – 使用 Provider

设置 aprovider需要将我们的数据类型包装MaterialApp在 a 中Provider

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

lib/main.dart
// ...

class _MyHomePageState extends State<MyHomePage> {
  Map data = {
    'name': 'Sammy Shark',
    'email': '[email protected]',
    'age': 42
  };

  @override
  Widget build(BuildContext context) {
    return Provider<Map>(
      create: (context) => data,
      child: MaterialApp(home: AccountScreen(), routes: {
        'account_screen': (context) => AccountScreen(),
        'settings_screen': (context) => SettingsScreen(),
      }),
    );
  }
}

data地图现在可用于main.dart调用和导入provider包的所有其他屏幕和小部件

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

注意:如果您使用 VS Code,您可能需要考虑使用代码段,因为您可能会访问provider很多:

dart.json
"Provider": {
  "prefix": "provider",
  "body": [
    "Provider.of<$1>(context).$2"
  ]
}

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

lib/screens/account.dart
// ...

body: Center(
  child: Column(
    mainAxisAlignment: MainAxisAlignment.center,
    children: <Widget>[
      Text('Name: ' + Provider.of<Map>(context)['name'].toString()),
      Text('Email: ' + Provider.of<Map>(context)['email'].toString()),
      Text('Age: ' + Provider.of<Map>(context)['age'].toString()),
    ]),
  ),
)

// ...

编译你的代码并让它在模拟器中运行:

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

此时,您有一个包含硬编码用户数据的应用程序,该应用程序显示在“帐户”屏幕上。

第 5 步 — 使用 ChangeNotifier

使用Provider这种方式看起来非常自上而下,如果我们想向上传递数据并改变我们的地图怎么办?Provider而已是不够的。首先,我们需要将我们的数据分解成它自己的扩展类ChangeNotifierProvider不会使用它,所以我们需要将它更改为 aChangeNotifierProvider并传入我们Data类的一个实例

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

在我们更改我们想要使用的任何全局数据之后notifyListeners,这将重建依赖于它的每个小部件。

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

lib/main.dart
// ...

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider<Data>(
      create: (context) => Data(),
      child: MaterialApp(home: AccountScreen(), routes: {
        'account_screen': (context) => AccountScreen(),
        'settings_screen': (context) => SettingsScreen(),
      }),
    );
  }
}

class Data extends ChangeNotifier {
  Map data = {
    'name': 'Sammy Shark',
    'email': '[email protected]',
    'age': 42
  };

  void updateAccount(input) {
    data = input;
    notifyListeners();
  }
}

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

lib/screens/account.dart
// ...

body: Center(
  child: Column(
    mainAxisAlignment: MainAxisAlignment.center,
    children: <Widget>[
      Text('Name: ' + Provider.of<Data>(context).data['name'].toString()),
      Text('Email: ' + Provider.of<Data>(context).data['email'].toString()),
      Text('Age: ' + Provider.of<Data>(context).data['age'].toString()),
    ]),
  ),
)

// ...

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

lib/screens/settings.dart
TextButton(
  onPressed: () {
    formKey.currentState.save();
    Provider.of<Data>(context, listen: false).updateAccount(data);
    formKey.currentState.reset();
  },
)

编译你的代码并让它在模拟器中运行:

在移动设备上运行的应用程序的动画 gif。 它显示有人使用“设置”屏幕上的表单来更新用户信息。 然后他们导航到“帐户”屏幕并显示新用户信息。

此时,您的应用程序支持在“设置”屏幕上更新用户信息并在“帐户”屏幕上显示更改。

结论

在本文中,您学习了如何应用provider到示例 Flutter 应用程序来管理用户帐户信息的状态。

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

觉得文章有用?

点个广告表达一下你的爱意吧 !😁