介绍
状态管理涉及跟踪整个应用程序中的状态变化。
该provider
软件包是满足状态管理需求的一种解决方案。
在本文中,您将学习如何应用provider
到示例 Flutter 应用程序来管理用户帐户信息的状态。
先决条件
要完成本教程,您需要:
- 下载并安装Flutter。
- 下载并安装Android Studio 或 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
:
dependencies:
flutter:
sdk: flutter
provider: ^3.1.0
然后,将更改保存到您的文件。
注意:如果您使用 VS Code,您可能需要考虑使用Pubspec Assist扩展来快速添加依赖项。
我们现在可以继续在 iOS 或 Android 模拟器或您选择的设备上运行它。
第 3 步 – 搭建项目的脚手架
我们将需要 2 个屏幕、我们的路由器和一个导航栏。我们正在设置一个页面来显示我们的帐户数据,另一个页面通过存储、更改和从我们的路由器传递的状态本身来更新它。
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
文件并使用代码编辑器打开它:
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
文件。这将用于创建我们的表单状态,设置一个地图来存储我们的输入,并添加一个我们稍后将使用的提交按钮:
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
文件。这将用于显示帐户信息:
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
然后使用我们的context
and data
:
// ...
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
很多:
"Provider": {
"prefix": "provider",
"body": [
"Provider.of<$1>(context).$2"
]
}
重新访问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
仅此而已是不够的。首先,我们需要将我们的数据分解成它自己的扩展类ChangeNotifier
。Provider
不会使用它,所以我们需要将它更改为 aChangeNotifierProvider
并传入我们Data
类的一个实例。
现在我们传递了整个类而不仅仅是一个变量,这意味着我们可以开始创建可以操作我们的数据的方法,这些方法也可用于访问Provider
.
在我们更改我们想要使用的任何全局数据之后notifyListeners
,这将重建依赖于它的每个小部件。
重新访问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
并在您的代码编辑器中打开它:
// ...
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
并在您的代码编辑器中打开它:
TextButton(
onPressed: () {
formKey.currentState.save();
Provider.of<Data>(context, listen: false).updateAccount(data);
formKey.currentState.reset();
},
)
编译你的代码并让它在模拟器中运行:
此时,您的应用程序支持在“设置”屏幕上更新用户信息并在“帐户”屏幕上显示更改。
结论
在本文中,您学习了如何应用provider
到示例 Flutter 应用程序来管理用户帐户信息的状态。
如果您想了解有关 Flutter 的更多信息,请查看我们的 Flutter 主题页面以获取练习和编程项目。