学习内容:
为什么需要状态管理:跨页面共享数据(如登录状态、用户信息)
Provider 核心:
ChangeNotifier(数据模型)、Provider(注入数据)、Consumer(消费数据)实践任务:
用 Provider 管理 “用户登录状态”:登录后,首页显示 “欢迎 + 用户名”
退出登录时,清空用户信息并返回登录页
依赖准备:使用Provider需要添加依赖,在pubspec.yaml中添加:
dependencies:
flutter:
sdk: flutter
provider: ^6.0.5 # 添加 Provider 依赖一、为什么需要状态管理※
1. 什么是“状态”※
在Flutter中,“状态(State)”指的是影响UI显示的数据,例如:
按钮是否被点记(isPressed: bool)
输入框中的文本(username: String)
用户登录状态(isLogin: bool)
购物车中的商品列表(cartItems: List<Product>)
状态分为两种:
局部状态:仅在单个组件内部使用的状态(如:开关按钮的开关状态),用setState管理即可。
全局状态:需要跨多个组件/页面共享的状态(如:登录状态、用户信息),需用状态管理方案。
2. 为什么需要专门的状态管理※
当应用规模扩大,多个页面需要共享数据时,仅用setState会出现以下问题:
问题1:跨页面数据传递繁琐※
例如,登录页登录成功后需要将用户名传递到首页、个人中心页、设置页等多个页面,用路由参数传递会导致代码冗余:
// 路由参数传递的弊端:多层传递时代码臃肿
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => HomePage(username: "张三"), // 首页传参
),
).then((_) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ProfilePage(username: "张三"), // 个人中心再传参
),
);
});问题2:状态变更后UI无法自动更新※
如果用户在个人中心修改了用户名,首页需要同步更新,但用参数传递的方式无法实现自动同步,必须手动刷新,容易导致数据不一致。
问题3:代码耦合度高,维护困难※
多个页面依赖一份数据时,修改数据逻辑需要改动所有相关页面,不符合“单一职责”原则。
3. 状态管理的核心目标※
- 数据共享:让多个组件/页面能访问同一份数据。
- 状态同步:当数据发生变化时,所有依赖该数据的UI自动更新。
- 解耦代码:数据管理与UI展示分离,便于维护。
Provider是Flutter官方推荐的轻量级状态管理方案,适合中小型应用,学习成本低且性能优秀。
二、Provider核心概念与用法※
1. Provider工作原理※
Provider基于“观察者模式”,核心是数据模型(被观察者)和UI组件(观察者):
- 当数据模型发生变化时,自动通知所有依赖它的UI组件更新。
- 数据通过“依赖注入”的方式在Widget树中传递,无需手动层层传递参数。
2. Provider三大核心组件※
| 组件 | 作用 | 类比 |
| ChangeNotifier | 存储数据和业务逻辑(如:登录、退出),提供通知更新的能力 | 数据仓库+通知器 |
| ChangeNotifierProvider | 在Widget树中注入ChangeNotifier实例,让子组件可以访问。 | 数据分发中心 |
| Consumer | 监听ChangeNotifier的变化,当数据更新时重建自身。 | 数据消费者(UI展示) |
3. ChangeNotifier:数据模型※
ChangeNotifier是一个抽象类,提供了两个核心方法:
- notifyListeners():当数据发生变化时调用,通知所有监听者(Consumer)更新UI。
- 通常需要自定义一个继承自ChangeNotifier的类,存储数据和业务逻辑。
示例代码:用户状态模型
// models/user_model.dart
import 'package:flutter/foundation.dart';
// 用户状态模型(继承ChangeNotifier)
class UserModel extends ChangeNotifier{
// 用户名(默认未登录)
String? _username;
// getter: 获取用户名(外部只能读取,不能直接修改)
String? get username => _username;
// getter:判断是否登录(用户名不为空即为登录状态)
bool get isLogin => _username != null;
// 登录方法:保存用户名并通知UI更新
void login(String username) {
_username = username;
notifyListeners(); // 通知所有监听者(UI会重建)
}
// 注销方法:清空用户名并通知UI更新
void logout() {
_username = null;
notifyListeners();
}
}4. ChangeNotifierProvider:注入数据※
ChangeNotifierProvider用于在Widget树的顶层注入ChangeNotifier实例,让整个应用的子组件都能访问该数据。
代码示例:在根组件注入用户模型
// main.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'models/user_model.dart';
import 'pages/login_page.dart';
void main() => runApp(const App());
class App extends StatelessWidget{
const App({super.key});
@override
Widget build(BuildContext context) {
// 用ChangeNotifierProvider注入UserModel
return ChangeNotifierProvider(
// 创建UserModel实例(全局唯一)
create: (context) => UserModel(),
child: MaterialApp(
title: 'Provider状态管理示例',
home: const LoginPage(), // 初始页面为登录页
),
);
}
}5. Consumer:消费数据※
Consumer用于在UI组件中获取ChangeNotifier实例,并监听数据变化。当notifyListeners()被调用时,Consumer会自动重建,更新UI。
代码示例:用Consumer获取用户名
// pages/home_page.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../models/user_model.dart';
import 'login_page.dart';
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('首页'),),
body: Center(
// 用Consumer消费UserModel
child: Consumer<UserModel>(
// builder函数:接收context、userModel实例、child(可选)
builder: (context, userModel, child) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// 显示欢迎信息(使用userModel中的用户名)
Text(
'欢迎,${userModel.username}!',
style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
),
const SizedBox(height: 30,),
// 退出登录按钮
ElevatedButton(
onPressed: () {
// 调用userModel的logout方法
userModel.logout();
// 退出后返回登录页
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => const LoginPage())
);
},
child: const Text('退出登录'),
)
],
);
}
),
),
);
}
}Consumer工作原理
- Consumer<T> 中的泛型T必须是ChangeNotifer的子类(如:UserModel)。
- builder函数会在一下两种情况被调用:
- 首次构建时,获取T实例并构建UI。
- 当T实例调用notifyListeners()时,重新构建UI。
- 优点:仅重建Consumer包裹的部分,而非整个页面,优化性能。
6. 其他获取数据的方式※
除了Consumer,还可以通过以下方式获取ChangeNotifier实例:
Provider.of<T>(context)
直接在build方法中获取实例,适合不需要自动监听的场景(需要手动控制监听):
// 获取实例但不监听变化(不会自动重建) final userModel = Provider.of<UserModel>(context, listen: false); // 获取实例并监听变化(等同于 Consumer,会自动重建) final userModel = Provider.of<UserModel>(context);Selector(更精细的监听)
Selector可以指定监听数据的某个字段,只有该字段变化时才重建,进一步优化性能:
Selector<UserModel, String?>( // 只监听 username 字段 selector: (context, userModel) => userModel.username, builder: (context, username, child) { return Text("欢迎,$username!"); }, )
三、综合实战※
- 任务需求
- 实现两个页面:
- 登录页(LoginPage):包含输入框、登录按钮,然后通过Provider保存用户名。
- 首页(HomePage):显示“欢迎+用户名”,包含退出登录按钮,点击后清空用户信息并返回登录页。
- 状态管理逻辑
- 登录成功:调用UserModel的login方法,保存用户名,跳转到首页。
- 退出登录:调用UserModel的logout方法,清空用户名,返回登录页。
- 未登录时访问首页:自动跳转到登录页(路由守卫)。
- 实现两个页面:
- 代码实现
项目结构
lib/ ├── main.dart # 入口文件,注入 Provider ├── models/ │ └── user_model.dart # UserModel 状态模型 └── pages/ ├── login_page.dart # 登录页 └── home_page.dart # 首页用户状态模型(models/user_model.dart)
// models/user_model.dart import 'package:flutter/foundation.dart'; class UserModel extends ChangeNotifier{ String? _username; String? get username => _username; bool get isLogin => _username != null; // 登录:保存用户名并通知UI更新 void login(String username) { _username = username; notifyListeners(); } // 注销:清空用户名并通知UI更新 void logout() { _username = null; notifyListeners(); } }入口文件(main.dart)
// main.dart import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'models/user_model.dart'; import 'pages/login_page.dart'; void main() => runApp(const App()); class App extends StatelessWidget{ const App({super.key}); @override Widget build(BuildContext context) { // 注入UserModel,让整个应用可访问 return ChangeNotifierProvider( create: (context) => UserModel(), child: MaterialApp( title: 'Provider状态管理示例', theme: ThemeData(primarySwatch: Colors.yellow), home: const LoginPage(), // 初始页面为登录页 ), ); } }登录页(login_page.dart)
import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../models/user_model.dart'; import 'home_page.dart'; class LoginPage extends StatefulWidget { const LoginPage({super.key}); @override State<LoginPage> createState() => _LoginPageState(); } class _LoginPageState extends State<LoginPage> { final _formKey = GlobalKey<FormState>(); final _usernameController = TextEditingController(); // 登录逻辑 void _submitForm() { if (_formKey.currentState!.validate()) { // 1. 获取用户名 String username = _usernameController.text; // 2. 通过Provider获取UserModel实例,调用login方法 Provider.of<UserModel>(context, listen: false).login(username); // 3. 跳转到首页(替换当前路由,避免返回登录页) Navigator.pushReplacement(context, MaterialPageRoute(builder: (context) => const HomePage())); } } @override void dispose() { _usernameController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.grey[50], body: Center( child: SingleChildScrollView( padding: const EdgeInsets.symmetric(horizontal: 20), child: Form( key: _formKey, child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ // 应用图标 const Icon( Icons.lock, size: 80, color: Colors.blue, ), const SizedBox(height: 40,), // 账号输入框 TextFormField( controller: _usernameController, keyboardType: TextInputType.text, // 自动获取焦点(进入页面后自动弹出键盘) autofocus: true, decoration: InputDecoration( labelText: '账号', hintText: '请输入账号', prefixIcon: const Icon(Icons.person), border: const OutlineInputBorder( borderRadius: BorderRadius.all(Radius.circular(10)), ), // 聚焦时边框颜色 focusedBorder: OutlineInputBorder( borderSide: const BorderSide(color: Colors.blue, width: 2), borderRadius: BorderRadius.circular(10), ) ), validator: (value) { if (value == null || value.isEmpty) { return '请输入账号'; // 验证失败,返回错误提示 } return null; }, ), const SizedBox(height: 20,), // 登录按钮 ElevatedButton( onPressed: _submitForm, style: ElevatedButton.styleFrom( minimumSize: const Size(double.infinity, 50), backgroundColor: Colors.blue, foregroundColor: Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), // 圆角 ) ), child: const Text('登录', style: TextStyle(fontSize: 18, fontWeight: FontWeight.w500),), ) ], ), ), ), ), ); } }首页(home_page.dart)
// pages/home_page.dart import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../models/user_model.dart'; import 'login_page.dart'; class HomePage extends StatelessWidget { const HomePage({super.key}); @override Widget build(BuildContext context) { // 路由守卫:如果未登录,自动跳转到登录页 final userModel = Provider.of<UserModel>(context); if (!userModel.isLogin) { // 延迟执行,避免在build中直接跳转导致错误 WidgetsBinding.instance.addPostFrameCallback((_){ Navigator.pushReplacement(context, MaterialPageRoute(builder: (context) => const LoginPage())); }); return const Scaffold(body: Center(child: CircularProgressIndicator(),),); } return Scaffold( appBar: AppBar(title: const Text('首页'),), body: Center( // 用Consumer监听用户信息变化 child: Consumer<UserModel>( builder: (context, userModel, child) { return Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( '欢迎,${userModel.username}!', style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold), ), const SizedBox(height: 40,), // 退出登录按钮 ElevatedButton( onPressed: () { // 退出登录 userModel.logout(); // 退出后返回登录页 Navigator.pushReplacement( context, MaterialPageRoute(builder: (context) => const LoginPage()) ); }, style: ElevatedButton.styleFrom( backgroundColor: Colors.red, padding: const EdgeInsets.symmetric( horizontal: 40, vertical: 15, ), ), child: const Text( '退出登录', style: TextStyle(fontSize: 18, color: Colors.white), ), ) ], ); } ), ), ); } }
运行效果
