flutter 整个编辑页面?

其实详情页,就有点编辑的意味了~
所以编辑页就是详情页改出来的!
改了点啥,其实也有说的。

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();
  }
}
上一篇
下一篇