一、数据存储
1、本地持久化存储SharedPreferences
SharedPreferences为轻量级存储,存储少量简单数据,键值对形式,不适合大量、复杂数据的存储。
dart
import 'package:shared_preferences/shared_preferences.dart';
// 存储数据
Future<void> saveData() async {
// 获取 SharedPreferences 实例
final prefs = await SharedPreferences.getInstance();
// 存储不同类型的数据 await prefs.setString('user_token', 'abc123456'); // 字符串
await prefs.setInt('user_age', 25); // 整数 a
wait prefs.setBool('is_login', true); // 布尔值
await prefs.setDouble('height', 1.75); // 浮点数
await prefs.setStringList('hobbies', ['读书', '运动']); // 字符串列表 }
// 读取数据 Future<void> readData() async {
final prefs = await SharedPreferences.getInstance();
// 读取数据(第二个参数是默认值,避免 null)
String? token = prefs.getString('user_token') ?? '';
int age = prefs.getInt('user_age') ?? 0;
bool isLogin = prefs.getBool('is_login') ?? false;
print('token: $token, age: $age, isLogin: $isLogin');
}
// 删除数据
Future<void> removeData() async {
final prefs = await SharedPreferences.getInstance(); await prefs.remove('user_token');
// 删除单个键
// await prefs.clear();
// 清空所有数据 }
2、Provider存储
Provider 是运行时的内存状态管理工具 非本地持久化存储,Provider 存储的数据只在 App 运行时有效,重启后丢失。它的核心价值是让数据在多个 Widget 之间共享、响应式更新,是 "内存级" 的数据存储与共享方案。适用于需要跨组件共享、实时响应更新 的运行时数据。 Provider的原理是基于 Flutter 原生的 InheritedWidget 实现的,而 InheritedWidget 的核心特性就是通过 Context 向上查找共享数据。
-
从 Provider 中读取 / 监听数据 :必须依赖
context(因为要确定查找的上下文范围); -
从 Provider 中修改数据 :通常也需要
context,但有替代方案(无需 Context); -
初始化 / 注入 Provider:不需要
context(在根节点创建时)。
2.1 定义数据模型
dart
import 'package:flutter/foundation.dart';
class ContractDataModel extends ChangeNotifier {
// ========== 核心数据字段 (仅保留3个示例) ==========
// 1. 房源类型名称 (来自第一步)
String _goodsTypeName = '';
// 2. 租客姓名 (来自第二步)
String _customerName = '';
// 3. 租金单价 (来自第三步)
String _unitPrice = '';
// ========== Getters ==========
String get goodsTypeName => _goodsTypeName;
String get customerName => _customerName;
String get unitPrice => _unitPrice;
// ========== 统一更新方法 ==========
/// 更新核心数据
/// 只要传入的值不为 null 就更新,允许空字符串覆盖原有数据
void updateCoreInfo({
String? goodsTypeName,
String? customerName,
String? unitPrice,
}) {
if (goodsTypeName != null) _goodsTypeName = goodsTypeName;
if (customerName != null) _customerName = customerName;
if (unitPrice != null) _unitPrice = unitPrice;
// 通知监听者重建 UI
notifyListeners();
}
// ========== 验证数据是否完整 (示例) ==========
bool validateCoreInfo() {
if (_goodsTypeName.isEmpty) return false;
if (_customerName.isEmpty) return false;
if (_unitPrice.isEmpty) return false;
return true;
}
// ========== 获取所有数据的 Map ==========
Map<String, dynamic> toMap() {
return {
'goodsTypeName': _goodsTypeName,
'customerName': _customerName,
'unitPrice': _unitPrice,
};
}
// ========== 清空所有数据 ==========
void clear() {
_goodsTypeName = '';
_customerName = '';
_unitPrice = '';
notifyListeners();
}
}
2.2 更新provider中的数据
ini
final contractModel = Provider.of<ContractDataModel>(context, listen: false);
contractModel.updatePersonInfo(
customerCertificateId: _customerCertificateId,
customerName: _customerName,
);
2.3 获取provider,提取内部的数据
ini
late _contractDataModel = Provider.of<ContractDataModel>(context, listen: false);
if(_contractDataModel.signClientType == '1'){//企业
_customerCertificateType = 'Z';
}else{
_customerCertificateType = '';
}
tips: 使用late的作用是什么? late 是用来修饰延迟初始化变量的关键字。
- late用来声明变量时无法立即赋值的问题,Provider依赖context,而
context通常在 Widget 的build方法、initState的位置才能获取,声明无法获取从而报错。 - 允许变量非空但延迟复制。(Dart空安全核心)Dart 开启空安全后,未用
?标记的变量必须声明时赋值 或用late修饰。

二、Provider 流程页面剖析

应该这样理解:
✅ Provider 是在 CreateReserveStepPage 中创建的(第52行)
✅ 三个 Step Widget 各自有自己独立的 context
✅ 但它们都是 ChangeNotifierProvider 的子孙节点
✅ 所以它们都能通过各自的 context 向上查找,找到同一个 Provider 实例
或者说:
✅ 三个 Widget 的 context 都能访问到在 CreateReserveStepPage 中创建的 Provider,因为它们都在 Provider 的子树中。
如下:
php
class CreateReserveStepPage extends StatefulWidget {
final Map params;
const CreateReserveStepPage({Key key,this.params}) : super(key: key);
@override
_CreateReserveStepPage createState() => _CreateReserveStepPage();
}
class _CreateReserveStepPage extends State<CreateReserveStepPage> {
final BrnMetaHorizontalStepsManager _stepsManager = BrnMetaHorizontalStepsManager();
int _currentIndex = 0;
bool _isCompleted = false;
Timer _timer;
int _elapsed = 0;
Map<String, dynamic> _contractParams = {};
final List<String> _stepTitles = ['合同信息', '租客/入住人信息', '账单/补充信息'];
// 验证回调函数
Function _validateContractWidget;
Function _validatePersonWidget;
Function _validateBillWidget;
// 保存数据回调函数
Function _savePersonWidget;
Function _saveBillWidget;
// 添加ScrollController
final ScrollController _scrollController = ScrollController();
void initState() {
super.initState();
}
@override
void dispose() {
_scrollController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (_) => ContractDataModel(),
builder: (providerContext, child) {
return Scaffold(
appBar: BrnAppBar(
title: '创建签约',
leading: IconButton(
icon: Icon(Icons.arrow_back_ios_new, color: Colors.black),
onPressed: () => BoostUtil.finish(),
),
),
body: Column(
children: [
_stepsManager.buildSteps(
steps: _stepTitles.map((title) => BrunoStep(stepContentText: title)).toList(),
currentIndex: _currentIndex,
isCompleted: _isCompleted,
),
const SizedBox(height: 24),
Expanded(
child: SingleChildScrollView(
controller: _scrollController,
child: _buildStepContent(_currentIndex),
),
),
Container(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16),
decoration: BoxDecoration(
color: Colors.white,
border: Border(top: BorderSide(color: Color(0xFFF0F0F0), width: 1)),
),
child: _buildBottomButtons(providerContext),
),
],
),
);
},
);
}
Widget _buildStepContent(int index) {
switch (index) {
case 0:
return ReservationStepContractWidget(
params: this.widget.params,
onDataChanged: _handleContractDataChanged,
onValidateCallback: (validateFunc) {
_validateContractWidget = validateFunc;
},
);
case 1:
return ReservationStepPersonWidget(
onDataChanged: _handlePersonDataChanged,
onValidateCallback: (validateFunc) {
_validatePersonWidget = validateFunc;
},
onSaveCallback: (saveFunc) {
_savePersonWidget = saveFunc;
},
);
case 2:
return ReservationStepBillWidget(
onDataChanged: _handleBillDataChanged,
onValidateCallback: (validateFunc) {
_validateBillWidget = validateFunc;
},
onSaveCallback: (saveFunc) {
_saveBillWidget = saveFunc;
},
);
default:
return SizedBox();
}
}