flutter 状态管理_管理Flutter中的状态

flutter 状态管理

Most non-trivial apps will have some sort of state change going on and over time managing that complexity becomes increasingly difficult. Flutter apps are no different, but luckily for us, the Provider package is a perfect solution for most of our state management needs.

大多数不平凡的应用都会进行某种状态更改,随着时间的流逝,管理这种复杂性变得越来越困难。 Flutter应用程序没有什么不同,但是幸运的是, Provider包是满足我们大多数状态管理需求的完美解决方案。

先决条件 (Prerequisites)

We’re going to be passing data between 3 different screens, to keep things brief I will be assuming that you are already familiar with basic navigation and routes. If you need a quick refresher, you can check out my intro here. We’ll also be setting up a basic form without any validation, so a little knowledge on working with form state is a plus.

我们将在3个不同的屏幕之间传递数据,为使内容简短,我假设您已经熟悉基本的导航和路线。 如果您需要快速复习,可以在这里查看我的介绍 。 我们还将建立未经任何验证的基本表单,因此对使用表单状态有一点了解是加分的。

安装 (Installation)

First, we’re going to need to add the Provider package to our pubspec.yaml file, I really recommend using the Pubspec Assist extension if you’re using VSCode. You can check out the full package here.

首先,我们需要将Provider包添加到我们的pubspec.yaml文件中,如果您使用的是VSCode ,我真的建议您使用Pubspec Assist扩展名。 您可以在此处查看完整的软件包。

pubspec.yaml
pubspec.yaml
dependencies:
  flutter:
    sdk: flutter
  provider: ^3.1.0

问题 (Problem)

Imagine you want to build an app that customizes some of its screens with some of the user’s data, like their name. The normal methods for passing down data between screens would quickly become a tangled mess of callbacks, unused data, and unnecessarily rebuilt widgets. With a front-end library like React this is a common problem called prop drilling.

想象一下,您想构建一个使用某些用户数据(例如用户名)自定义其屏幕的应用程序。 在屏幕之间传递数据的常规方法很快就会变成一团糟的回调,未使用的数据以及不必要的重建小部件。 对于像React这样的前端库,这是一个普遍的问题,称为“道具钻Kong”。

If we wanted to pass data up from any of those widgets then you need to further bloat every intermediate widget with more unused callbacks. For most small features, this may make them almost not worth the effort.

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

Luckily for us, the Provider package allows us to store our data in a higher up widget, like wherever we initialize our MaterialApp, then access and change it directly from sub-widgets, regardless of nesting and without rebuilding everything in between.

对我们来说幸运的是,Provider程序包使我们可以将数据存储在更高级别的小部件中,就像我们初始化MaterialApp ,然后直接从子小部件访问和更改它,而无需嵌套,也无需重建它们之间的所有内容。

建立 (Setup)

We’re just going to need 2 screens, our router, and a navbar. We’re just setting up a page to display our account data and another to update it with the state itself being stored, changed, and passed down from our router.

我们只需要2个屏幕,我们的路由器和一个导航栏。 我们只是在设置一个页面来显示我们的帐户数据,并在另一个页面上进行更新,以将状态本身存储,更改和从路由器传递下来。

* screens 📂
  * account.dart 
  * settings.dart
* main.dart 
* navbar.dart
main.dart
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(),
    });
  }
}

We’re creating our form state, setting a map to store our inputs, and adding a submit button that we’ll use later.

我们正在创建表单状态,设置地图以存储输入,并添加一个稍后将使用的提交按钮。

settings.dart
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>();

  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,
                ),
                FlatButton(
                  onPressed: () => formKey.currentState.save(),
                  child: Text('Submit'),
                  color: Colors.blue,
                  textColor: Colors.white,
                )
              ]),
        ),
      )),
    );
  }
}

Our account page will simply display whatever account information, which doesn’t exist yet.

我们的帐户页面将仅显示尚不存在的任何帐户信息。

account.dart
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: '),
            ]),
      ),
    );
  }
}

提供者 (Provider)

Setting up a Provider in incredibly easy, we just need to wrap our MaterialApp in a Provider with the type of our data, which in our case is a map. Finally, we need to set create to then use our context and data. Just like that, our data map is now available in every other screen and widget, assuming you import main.dart and the provider package.

设置提供程序非常简单,我们只需要将我们的MaterialApp包装在Provider并带有我们的数据类型(在我们的情况下为地图)。 最后,我们需要将create设置为使用上下文和数据。 就像这样,假设您导入main.dart和provider包,我们的data映射现在在所有其他屏幕和小部件中都可用。

main.dart
main.dart
class _MyHomePageState extends State<MyHomePage> {
  Map data = {
    'name': 'Frank Abignale',
    'email': 'someEmail@alligatorio',
    'age': 47
  };

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

Everything we passed to our provider creator is now available on Provider.of<Map>(context). Note that the type you pass in must match the type of data our Provider is expecting.

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

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

I recommend creating a snippet, since you’ll probably be accessing provider a lot. Here’s what the snippet would look like in VSCode:

我建议创建一个代码段,因为您可能会经常访问提供程序。 这是VSCode中的代码片段:

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

变更通知者 (Change Notifier)

Using Provider this way seems very top down, what if we want to pass data up and alter our map? The Provider alone isn’t enough for that. First, we need to break down our data into its own class that extends ChangeNotifier. Provider won’t work with that, so we need to change it to a ChangeNotifierProvider and pass in an instance of our Data class instead.

以这种方式使用Provider似乎非常自上而下,如果我们想向上传递数据并更改地图怎么办? 仅提供者是不够的。 首先,我们需要将数据分解为扩展ChangeNotifier自己的类。 Provider不能使用它,因此我们需要将其更改为ChangeNotifierProvider并传入我们的Data类的实例。

Now we’re passing down a whole class and not just a single variable, this means that we can start creating methods that can manipulate our data, which will also be available to everything that accesses Provider.

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

After we change any of our global data we want to use notifyListeners, which will rebuild every widget that depends on it.

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

main.dart
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': 'Frank Abignale',
    'email': 'someEmail@alligatorio',
    'age': 47
  };

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

Since we changed our Provider type, we need to update our calls to it.

由于我们更改了提供者类型,因此我们需要更新对其的调用。

account.dart
account.dart
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()),
]),

To pass data up, we just need to access the provider just like before and use our method that was passed down in the Data class.

要传递数据,我们只需要像以前一样访问提供程序,并使用在Data类中传递的方法即可。

setting.dart
设置
FlatButton(
  onPressed: () {
    formKey.currentState.save();
    Provider.of<Data>(context).updateAccount(data);
    formKey.currentState.reset();
  },
)

结论 (Conclusion)

Once you get proficient with Provider it will save you an enormous amount of time and frustration. As always, if you had any trouble reproducing this app check out the repo here.

一旦您精通Provider,它将为您节省大量时间和挫败感。 与往常一样,如果您在复制此应用程序时遇到任何麻烦,请在此处查看存储库

翻译自: https://www.digitalocean.com/community/tutorials/flutter-state-management

flutter 状态管理

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值
>