其实详情页,就有点编辑的意味了~
所以编辑页就是详情页改出来的!
改了点啥,其实也有说的。
1、把性别从文本框换成单选
//上方定义性别选项
final List<Map<String, dynamic>> genderOptions = [
{'value': 0, 'label': '女'},
{'value': 1, 'label': '男'},
];
......
//下面Widget里这么玩:
child: DropdownButtonFormField<int>(
value: editProvider.contact?.sex,
items: genderOptions.map((option) {
return DropdownMenuItem<int>(
value: option['value'],
child: Text(option['label']),
);
}).toList(),
onChanged: (int? newValue) {
if (newValue != null) {
editProvider.contact?.sex = newValue;
}
},
decoration: const InputDecoration(
border: UnderlineInputBorder(),
labelText: '性别',
floatingLabelBehavior:
FloatingLabelBehavior.auto, // 确保提示文字不消失
),
)
前端同学貌似比较熟悉类似的玩法。
看到这有人会说是:数据从哪冒出来?
2、provider中对象的改动
//直接有一个暴露出来的对象
ContactBean? contact;
//然后在调用接口的时候也会将数据直接转换为对象
Future<void> getDetail(int id) async {
_error = null;
await DioUtil.getInstance().get(
"http://192.168.5.116:20080/contact/detail",
{'id': id},
() {
_isLoading = true;
notifyListeners();
},
(data) {
_isLoading = false;
BaseBean bean = BaseBean.fromJson(data);
var contact = ContactBean.fromJson(bean.data);
this.contact = contact;
firstName.text = contact.firstName ?? '';
lastName.text = contact.lastName ?? '';
age.text = contact.age.toString();
phoneNumber.text = contact.phoneNumber ?? '';
address.text = contact.address ?? '';
notifyListeners();
},
(reason) {
_isLoading = false;
_error = reason;
notifyListeners();
},
);
}
再加上TextFormField的onChange事件,数据和改动直接赋值给这个对象,操作起来也方便多了。
番外插播:This _InheritedProviderScope<EditProvider?> widget cannot be marked as needing to build because the framework is already in the process of building widgets.的报错处理
人话:我UI还没构建数据就响应回来了!我还没东西呢自动刷新啥玩意啊?
解决办法:WidgetsBinding.instance.addPostFrameCallback
在StatefulWidget里的State私有类的initState方法里照下面方法加上,可确保组件加载完成后再调用刷新数据的办法,这样自动刷新也有的刷啊!宅少不会让UI尬住不爽给你报个错。
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
final editProvider = Provider.of<EditProvider>(context, listen: false);
if (widget.id != null) {
editProvider.getDetail(widget.id!);
}
});
}
3、想都不敢想的提交办法
provider中提交的接口是符合正常逻辑的:
Future<void> submit(int id) async {
await Future.delayed(const Duration(seconds: 2));
_error = null;
DioUtil.getInstance().addHeader("token", "123456789");
await DioUtil.getInstance().post(
"http://192.168.5.116:20080/contact/update",
contact!.toJson(),
() {
},
(data) {
},
(reason) {
},
);
}
但是啊,作为一个Android佬,哪怕是有了ViewModel,我也没那么敢想有flutter这种写法:
Future<void> _submit() async {
final editProvider = Provider.of<EditProvider>(context, listen: false);
final mainProvider = Provider.of<MainProvider>(context, listen: false);
// 显示加载 Dialog
showDialog(
context: context,
barrierDismissible: false,
builder: (context) => Center(child: Container(
width: 100,
child: Dialog(child: Container(height: 100,child: Center(child: CircularProgressIndicator()),)),
)),
);
try {
if (widget.id != null) {
await editProvider.submit(widget.id!);
// 关闭 Dialog
Navigator.of(context, rootNavigator: true).pop();
// 关闭当前页面
Navigator.of(context).pop();
// 通知 main.dart 刷新列表
mainProvider.resetPage();
await mainProvider.getListPage(mainProvider.pageNum);
}
} catch (e) {
// 关闭 Dialog
Navigator.of(context, rootNavigator: true).pop();
showToast('提交失败,请重试', context: context);
}
}
这代码就是按下提交按钮弹出loading dialog,等执行完成了……直接抓上一个provider刷新列表!
我干活的正常思路是:viewModel层提交后拿回提交成功的提示,Activity setResult()告诉上一页数据我提交成功了,再然后退到前一页自动刷新列表。
flutter是啥:我直接抓着前一页的provider干接口,妈呀这简直想都不敢想!
果真啊,活到老学到老…….
最后放出完整代码吧
edit_page.dart
import 'package:day07/edit_provider.dart';
import 'package:flutter/material.dart';
import 'package:flutter_styled_toast/flutter_styled_toast.dart';
import 'package:provider/provider.dart';
import 'detail_provider.dart';
import 'main_provider.dart';
class EditPage extends StatefulWidget {
final int? id; // 示例参数,可按需修改
const EditPage({super.key, this.id});
@override
State<EditPage> createState() => _EditPageState();
}
class _EditPageState extends State<EditPage> {
final List<Map<String, dynamic>> genderOptions = [
{'value': 0, 'label': '女'},
{'value': 1, 'label': '男'},
];
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
final editProvider = Provider.of<EditProvider>(context, listen: false);
if (widget.id != null) {
editProvider.getDetail(widget.id!);
}
});
}
@override
Widget build(BuildContext context) {
final editProvider = Provider.of<EditProvider>(context);
return PopScope(
onPopInvokedWithResult: (didPop, result) {
editProvider.clearData();
},
child: Scaffold(
appBar: AppBar(
title: const Text('详情'),
leading: GestureDetector(
onTap: () {
editProvider.clearData();
Navigator.of(context).pop();
},
child: const Icon(Icons.arrow_back_ios),
),
),
body: Center(
child: editProvider.isLoading
? Column(
mainAxisAlignment: MainAxisAlignment.center,
children: const [
Text('正在加载...'),
SizedBox(height: 16),
CircularProgressIndicator(),
],
)
: editProvider.error != null
? Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(editProvider.error!),
const SizedBox(height: 16),
ElevatedButton(
onPressed: () {
if (widget.id != null) {
editProvider.getDetail(widget.id!);
}
},
child: const Text('点击刷新'),
),
],
)
: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 已有输入框
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 16,
),
child: TextFormField(
controller: editProvider.lastName,
onChanged: (value){
editProvider.contact!.lastName = value;
},
decoration: const InputDecoration(
border: UnderlineInputBorder(),
labelText: '姓',
floatingLabelBehavior:
FloatingLabelBehavior.auto, // 确保提示文字不消失
),
),
),
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 16,
),
child: TextFormField(
controller: editProvider.firstName,
onChanged: (value){
editProvider.contact!.firstName = value;
},
decoration: const InputDecoration(
border: UnderlineInputBorder(),
labelText: '名',
floatingLabelBehavior:
FloatingLabelBehavior.auto, // 确保提示文字不消失
),
),
),
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 16,
),
child: TextFormField(
controller: editProvider.age,
onChanged: (value){
try {
editProvider.contact!.age = int.parse(value);
} catch (e) {
// 处理解析失败的情况
editProvider.age.text=editProvider.contact!.age.toString();
showToast('请输入正确的数字',context: context);
} },
decoration: const InputDecoration(
border: UnderlineInputBorder(),
labelText: '年龄',
floatingLabelBehavior:
FloatingLabelBehavior.auto, // 确保提示文字不消失
),
),
),
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 16,
),
child: DropdownButtonFormField<int>(
value: editProvider.contact?.sex,
items: genderOptions.map((option) {
return DropdownMenuItem<int>(
value: option['value'],
child: Text(option['label']),
);
}).toList(),
onChanged: (int? newValue) {
if (newValue != null) {
editProvider.contact?.sex = newValue;
}
},
decoration: const InputDecoration(
border: UnderlineInputBorder(),
labelText: '性别',
floatingLabelBehavior:
FloatingLabelBehavior.auto, // 确保提示文字不消失
),
),
),
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 16,
),
child: TextFormField(
controller: editProvider.phoneNumber,
onChanged: (value){
editProvider.contact!.phoneNumber = value;
},
decoration: const InputDecoration(
border: UnderlineInputBorder(),
labelText: '联系电话',
floatingLabelBehavior:
FloatingLabelBehavior.auto, // 确保提示文字不消失
),
),
),
// 新增输入框
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 16,
),
child: TextFormField(
controller: editProvider.address,
onChanged: (value){
editProvider.contact!.address = value;
},
decoration: const InputDecoration(
border: UnderlineInputBorder(),
labelText: '地址',
floatingLabelBehavior:
FloatingLabelBehavior.auto, // 确保提示文字不消失
),
),
),
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 16,
),
child: Center(child:
ElevatedButton(
onPressed: () {
_submit();
},
child: Text("提交"),
),)
),
],
),
),
),
);
}
Future<void> _submit() async {
final editProvider = Provider.of<EditProvider>(context, listen: false);
final mainProvider = Provider.of<MainProvider>(context, listen: false);
// 显示加载 Dialog
showDialog(
context: context,
barrierDismissible: false,
builder: (context) => Center(child: Container(
width: 100,
child: Dialog(child: Container(height: 100,child: Center(child: CircularProgressIndicator()),)),
)),
);
try {
if (widget.id != null) {
await editProvider.submit(widget.id!);
// 关闭 Dialog
Navigator.of(context, rootNavigator: true).pop();
// 关闭当前页面
Navigator.of(context).pop();
// 通知 main.dart 刷新列表
mainProvider.resetPage();
await mainProvider.getListPage(mainProvider.pageNum);
}
} catch (e) {
// 关闭 Dialog
Navigator.of(context, rootNavigator: true).pop();
showToast('提交失败,请重试', context: context);
}
}
@override
void dispose() {
super.dispose();
}
}
edit_provider.dart
import 'package:day07/bean/contact_bean.dart';
import 'package:flutter/material.dart';
import 'bean/base_bean.dart';
import 'dio_util.dart';
class EditProvider with ChangeNotifier {
final TextEditingController firstName = TextEditingController();
final TextEditingController lastName = TextEditingController();
final TextEditingController age = TextEditingController();
final TextEditingController phoneNumber = TextEditingController();
final TextEditingController address = TextEditingController();
ContactBean? contact;
bool _isLoading = false;
bool get isLoading => _isLoading;
String? _error;
String? get error => _error;
Future<void> getDetail(int id) async {
_error = null;
await DioUtil.getInstance().get(
"http://192.168.5.116:20080/contact/detail",
{'id': id},
() {
_isLoading = true;
notifyListeners();
},
(data) {
_isLoading = false;
BaseBean bean = BaseBean.fromJson(data);
var contact = ContactBean.fromJson(bean.data);
this.contact = contact;
firstName.text = contact.firstName ?? '';
lastName.text = contact.lastName ?? '';
age.text = contact.age.toString();
phoneNumber.text = contact.phoneNumber ?? '';
address.text = contact.address ?? '';
notifyListeners();
},
(reason) {
_isLoading = false;
_error = reason;
notifyListeners();
},
);
}
Future<void> submit(int id) async {
await Future.delayed(const Duration(seconds: 2));
_error = null;
DioUtil.getInstance().addHeader("token", "123456789");
await DioUtil.getInstance().post(
"http://192.168.5.116:20080/contact/update",
contact!.toJson(),
() {
},
(data) {
},
(reason) {
},
);
}
void clearData() {
lastName.clear();
firstName.clear();
age.clear();
phoneNumber.clear();
address.clear();
notifyListeners(); // 通知监听器状态改变
}
@override
void dispose() {
firstName.dispose();
lastName.dispose();
age.dispose();
phoneNumber.dispose();
address.dispose();
// TODO: implement dispose
super.dispose();
}
}