30天计划第11天-状态管理(Provider 基础)

-
-
2025-11-07 14:21
  • 学习内容

    1. 为什么需要状态管理:跨页面共享数据(如登录状态、用户信息)

    2. Provider 核心:ChangeNotifier(数据模型)、Provider(注入数据)、Consumer(消费数据)

  • 实践任务

    1. 用 Provider 管理 “用户登录状态”:登录后,首页显示 “欢迎 + 用户名”

    2. 退出登录时,清空用户信息并返回登录页

依赖准备:使用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),   
                        ),
                      )
                    ],
                  );
                }
              ),
            ),
          );
        }
      }
  • 运行效果

     

     


目录