flutter 编辑一个简单的详情页

1、先说StatelessWidget和StatefulWidget之争

我记得最初自己生啃flutter的时候,只是那些晦涩难懂的“尽量使用StatelessWidget”“StatefulWidget适合有状态的组件,StatelessWidget适合无状态的组件”——到底啥是所谓的“有状态”“无状态”?
当然我现在也理解不完全啊,只是知道类似于Text之类的组件是StatelessWidget,如果需要改变一些东西就用外部状态(例如provider的notifyListeners())刷新;如果你对这个组件有什么刚开始就要干什么事情的需求,那就要用StatefulWidget。
大哥们如果认为这是错的,我错了!轻喷

2、先从main.dart跳转到那个新的页面

上代码!

main.dart
import 'package:day06/detail_page.dart';
import 'package:day06/main_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';


void main() {
  runApp(
    MultiProvider(
      providers: [ChangeNotifierProvider(create: (context) => MainProvider()),
      ],

      child: MyApp(),
    ),
  );
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
      // home: const DetailPage(),
    );
  }
}

class MyHomePage extends StatelessWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  @override
  Widget build(BuildContext context) {
    final provider = Provider.of<MainProvider>(context);

    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme
            .of(context)
            .colorScheme
            .inversePrimary,
        title: Text(title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(provider.text),
            ElevatedButton(
              onPressed: provider.getList,
              child: const Text('请求数据'),
            ),
            ElevatedButton(
              onPressed: () => provider.addContact(),
              child: const Text('添加一个联系人'),
            ),
            Expanded(
                child: RefreshIndicator(
                  onRefresh: () async {
                    // 刷新时重置页码并重新加载数据
                    provider.resetPage();
                    await provider.getListPage(provider.pageNum);
                  },
                  child: NotificationListener<ScrollNotification>(
                    onNotification: (scrollInfo){
                      if (scrollInfo.metrics.pixels == scrollInfo.metrics.maxScrollExtent) {
                        // 滚动到底部,加载下一页
                        if(!provider.isLoading && !provider.finalPage){
                          provider.addPage();
                          provider.getListPage(provider.pageNum);
                        }
                      }
                      return false;
                    },
                    child: ListView.builder(
                      itemCount:provider.dataList.isEmpty? 0 : provider.dataList.length + 1,
                      itemBuilder: (context, index) {
                        if (index < provider.dataList.length) {
                          return ListTile(
                            title: Text('${provider.dataList[index].lastName} ${provider.dataList[index].firstName}'),
                            subtitle: Text('${provider.dataList[index].phoneNumber}'),
                            onTap: () async  {
                              Navigator.of(context).push(
                                MaterialPageRoute(
                                  builder: (context) => DetailPage(id: provider.dataList[index].id),
                                ),
                              );
                            },
                          );
                        } else {
                          // 显示加载提示
                          return Center(child: provider.finalPage
                              ? Text('没有更多数据了')
                              : CircularProgressIndicator()
                          );
                        }
                      },
                    ),
                  ),
                )
            )
          ],
        ),
      ),
    );
  }

  Future<dynamic> showADialog(context,title,content){
    return showDialog(
        context: context,
        builder: (context)=>AlertDialog(
          title: Text(title),
          content: Text(content),
          actions: [
            TextButton(
              onPressed: () => Navigator.of(context).pop(3),
              child: Text('某种中立按钮?'),
            ),
            TextButton(
              onPressed: () => Navigator.of(context).pop(2),
              child: Text('取消'),
            ),
            TextButton(
              onPressed: () => Navigator.of(context).pop(1),
              child: Text('确定'),
            ),
          ],
        )
    );
  }
}

重点在这里:

Navigator.of(context).push(
  MaterialPageRoute(
    builder: (context) => DetailPage(id: provider.dataList[index].id),
  ),
);

这一截意思就是说用Navigator.of(context).push()跳转,MaterialPageRoute()控制一些跳转时的参数(具体控制什么自行问搜索引擎),然后在builder属性里new出新的DetailPage页面,并且要传入一个id参数(也就是联系人的id)。如果不想考虑这么多,直接抄就对了!


那么新的DetailPage现在长啥样呢?

detail_page.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'detail_provider.dart';

class DetailPage extends StatefulWidget {
  final int? id; 
  const DetailPage({super.key, this.id});

  @override
  State<DetailPage> createState() => _DetailPageState();
}

class _DetailPageState extends State<DetailPage> {
 
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('详情'),
      ),
      body: Column(
        children: [
        
        ],
      ),
    );
  }

  @override
  void dispose() {
    super.dispose();
  }
}

这里用StatefulWidget是因为一会要在进来页面的时候要加载一些东西——也就是需要这个页面的生命周期啦!

3、然后再往里面填充一点文本框?整个返回按钮?

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'detail_provider.dart';

class DetailPage extends StatefulWidget {
  final int? id; 
  const DetailPage({super.key, this.id});

  @override
  State<DetailPage> createState() => _DetailPageState();
}

class _DetailPageState extends State<DetailPage> {
 
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('详情'),
        leading: GestureDetector(
              onTap: () {
                Navigator.of(context).pop();
              },
              child: const Icon(Icons.arrow_back_ios),
            ),
      ),
      body: Column(
        children: [
        Padding(
                padding: const EdgeInsets.symmetric(
                    horizontal: 8, vertical: 16),
                child: TextFormField(
                  decoration: const InputDecoration(
                    border: UnderlineInputBorder(),
                    labelText: '姓',
                    floatingLabelBehavior: FloatingLabelBehavior
                        .auto, // 确保提示文字不消失
                  ),
                ),
              ),

              Padding(
                padding: const EdgeInsets.symmetric(
                    horizontal: 8, vertical: 16),
                child: TextFormField(
                  decoration: const InputDecoration(
                    border: UnderlineInputBorder(),
                    labelText: '名',
                    floatingLabelBehavior: FloatingLabelBehavior
                        .auto, // 确保提示文字不消失
                  ),
                ),
              ),
              Padding(
                padding: const EdgeInsets.symmetric(
                    horizontal: 8, vertical: 16),
                child: TextFormField(
                  decoration: const InputDecoration(
                    border: UnderlineInputBorder(),
                    labelText: '年龄',
                    floatingLabelBehavior: FloatingLabelBehavior
                        .auto, // 确保提示文字不消失
                  ),
                ),
              ),
              Padding(
                padding: const EdgeInsets.symmetric(
                    horizontal: 8, vertical: 16),
                child: TextFormField(
                  decoration: const InputDecoration(
                    border: UnderlineInputBorder(),
                    labelText: '性别',
                    floatingLabelBehavior: FloatingLabelBehavior
                        .auto, // 确保提示文字不消失
                  ),
                ),
              ),

              Padding(
                padding: const EdgeInsets.symmetric(
                    horizontal: 8, vertical: 16),
                child: TextFormField(
                  decoration: const InputDecoration(
                    border: UnderlineInputBorder(),
                    labelText: '联系电话',
                    floatingLabelBehavior: FloatingLabelBehavior
                        .auto, // 确保提示文字不消失
                  ),
                ),
              ),
              Padding(
                padding: const EdgeInsets.symmetric(
                    horizontal: 8, vertical: 16),
                child: TextFormField(
                  decoration: const InputDecoration(
                    border: UnderlineInputBorder(),
                    labelText: '地址',
                    floatingLabelBehavior: FloatingLabelBehavior
                        .auto, // 确保提示文字不消失
                  ),
                ),
              ),
        ],
      ),
    );
  }

  @override
  void dispose() {
    super.dispose();
  }
}

这样页面也有了,返回按钮也有了,点下那个返回按钮就会执行Navigator.of(context).pop()返回上一级页面。

4、现在页面好了,数据呢?不得准备一下?

detail_provider.dart
import 'package:day06/bean/contact_bean.dart';
import 'package:flutter/material.dart';

import 'bean/base_bean.dart';
import 'dio_util.dart';

class DetailProvider with ChangeNotifier {
  final TextEditingController firstName = TextEditingController();
  final TextEditingController lastName = TextEditingController();
  final TextEditingController age = TextEditingController();
  final TextEditingController sex = TextEditingController();
  final TextEditingController phoneNumber = TextEditingController();
  final TextEditingController address = TextEditingController();

  Future<void> getDetail(int id) async {
    await DioUtil.getInstance().get(
      "http://192.168.5.116:20080/contact/detail",
      {'id': id},
          () {

      },
          (data) {
        BaseBean bean = BaseBean.fromJson(data);
        var contact=ContactBean.fromJson(bean.data);
        firstName.text=contact.firstName??'';
        lastName.text=contact.lastName??'';
        age.text=contact.age.toString();
        sex.text=contact.sex==1?'男':'女';
        phoneNumber.text=contact.phoneNumber??'';
        address.text=contact.address??'';
        notifyListeners();
      },
          (reason) {
        notifyListeners();
      },
    );
  }

  @override
  void dispose() {
    firstName.dispose();
    lastName.dispose();
    age.dispose();
    sex.dispose();
    phoneNumber.dispose();
    address.dispose();
    // TODO: implement dispose
    super.dispose();
  }
}

这样数据不就来了嘛~
首先建了一堆TextEditingController,用于绑定前面页面的TextFormField——这样就能获取到UI那边输入的字和给UI赋值了。然后用getDetail传一个id参数请求接口返回来给TextEditingController赋值从而让前端显示。

然后TextEditingController需要销毁,所以需要重写dispose()方法,在super.dispose()前手动执行一下各个TextEditingController的dispose()方法。

5、让数据回到页面!

接下来就是该绑定了,但是接口在哪调用呢?它讲道理是打开页面后就调用啊~总不可能点进详情还要再按个按钮吧。
还有这是个详情页面,我不想让这些输入框可以编辑。
那就改一下吧

detail_page.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'detail_provider.dart';

class DetailPage extends StatefulWidget {
  final int? id; 
  const DetailPage({super.key, this.id});

  @override
  State<DetailPage> createState() => _DetailPageState();
}

class _DetailPageState extends State<DetailPage> {
  @override
  void initState() {
    super.initState();
    final detailProvider = Provider.of<DetailProvider>(context, listen: false);
    if (widget.id != null) {
      detailProvider.getDetail(widget.id!);
    }
  }

  @override
  Widget build(BuildContext context) {
    final detailProvider = Provider.of<DetailProvider>(context);
    return Scaffold(
      appBar: AppBar(
        title: const Text('详情'),
        leading: GestureDetector(
              onTap: () {
                Navigator.of(context).pop();
              },
              child: const Icon(Icons.arrow_back_ios),
            ),
      ),
      body: Column(
        children: [
              Padding(
                padding: const EdgeInsets.symmetric(
                    horizontal: 8, vertical: 16),
                child: TextFormField(
                  readOnly: true,
                  controller: detailProvider.lastName,
                  decoration: const InputDecoration(
                    border: UnderlineInputBorder(),
                    labelText: '姓',
                    floatingLabelBehavior: FloatingLabelBehavior
                        .auto, // 确保提示文字不消失
                  ),
                ),
              ),

              Padding(
                padding: const EdgeInsets.symmetric(
                    horizontal: 8, vertical: 16),
                child: TextFormField(
                  readOnly: true,
                  controller: detailProvider.firstName,
                  decoration: const InputDecoration(
                    border: UnderlineInputBorder(),
                    labelText: '名',
                    floatingLabelBehavior: FloatingLabelBehavior
                        .auto, // 确保提示文字不消失
                  ),
                ),
              ),
              Padding(
                padding: const EdgeInsets.symmetric(
                    horizontal: 8, vertical: 16),
                child: TextFormField(
                  readOnly: true,
                  controller: detailProvider.age,
                  decoration: const InputDecoration(
                    border: UnderlineInputBorder(),
                    labelText: '年龄',
                    floatingLabelBehavior: FloatingLabelBehavior
                        .auto, // 确保提示文字不消失
                  ),
                ),
              ),
              Padding(
                padding: const EdgeInsets.symmetric(
                    horizontal: 8, vertical: 16),
                child: TextFormField(
                  readOnly: true,
                  controller: detailProvider.sex,
                  decoration: const InputDecoration(
                    border: UnderlineInputBorder(),
                    labelText: '性别',
                    floatingLabelBehavior: FloatingLabelBehavior
                        .auto, // 确保提示文字不消失
                  ),
                ),
              ),

              Padding(
                padding: const EdgeInsets.symmetric(
                    horizontal: 8, vertical: 16),
                child: TextFormField(
                  readOnly: true,
                  controller: detailProvider.phoneNumber,
                  decoration: const InputDecoration(
                    border: UnderlineInputBorder(),
                    labelText: '联系电话',
                    floatingLabelBehavior: FloatingLabelBehavior
                        .auto, // 确保提示文字不消失
                  ),
                ),
              ),
              Padding(
                padding: const EdgeInsets.symmetric(
                    horizontal: 8, vertical: 16),
                child: TextFormField(
                  readOnly: true,
                  controller: detailProvider.address,
                  decoration: const InputDecoration(
                    border: UnderlineInputBorder(),
                    labelText: '地址',
                    floatingLabelBehavior: FloatingLabelBehavior
                        .auto, // 确保提示文字不消失
                  ),
                ),
              ),
        ],
      ),
    );
  }

  @override
  void dispose() {
    super.dispose();
  }
}

至于输入框改为不可编辑的问题,其实这里有两种办法:enabledreadOnly,前者为false的话真就只能看,颜色置灰;但后者虽然不能编辑但颜色是正常的,甚至可以复制。所以再这边用的是readOnly。
代码里所有的TextFormField都绑定了来自于Provider的属于各自的controller,这样就可以正常显示各自的文字了。
至于如何在打开页面的时候就调用接口拿数据,那就得重写_DetailPageStateinitState()方法。

  @override
  void initState() {
    super.initState();
    final detailProvider = Provider.of<DetailProvider>(context, listen: false);
    if (widget.id != null) {
      detailProvider.getDetail(widget.id!);
    }
  }

那就该有人问了:为啥又在这里引用了provider?做全局变量不好吗?
看见那个listen: false了没?写了这个就可以不用监听状态变化,我在这初始化数据为啥要监听状态变化呢对吧。省的没事全局触发build()导致额外无用的刷新。

整到现在应该是能看没啥问题了

6、新的问题来了:怎么看详情还能看出残影?

现在点进去第一个,加载正常,返回来,再点进去第二个,怎么会出现第一个的数据???
那就得有个清理的方法插在provider里了:

void clearData() {
    lastName.clear();
    firstName.clear();
    age.clear();
    sex.clear();
    phoneNumber.clear();
    address.clear();
    notifyListeners(); // 通知监听器状态改变
  }

插进去方法就在返回的时候清理掉:
第一个注意的地方就是按下页面左上角返回按钮的时候
第二个就是Android的返回键(谁让我是个安卓开发)
所以UI代码现在就变成这样:

detail_page.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'detail_provider.dart';

class DetailPage extends StatefulWidget {
  final int? id; // 示例参数,可按需修改
  const DetailPage({super.key, this.id});

  @override
  State<DetailPage> createState() => _DetailPageState();
}

class _DetailPageState extends State<DetailPage> {
  @override
  void initState() {
    super.initState();
    final detailProvider = Provider.of<DetailProvider>(context, listen: false);
    if (widget.id != null) {
      detailProvider.getDetail(widget.id!);
    }
  }

  @override
  Widget build(BuildContext context) {
    final detailProvider = Provider.of<DetailProvider>(context);
    return PopScope(
        onPopInvokedWithResult: (didPop, result) {
          detailProvider.clearData();
        },
        child: Scaffold(
          appBar: AppBar(
            title: const Text('详情'),
            leading: GestureDetector(
              onTap: () {
                detailProvider.clearData();
                Navigator.of(context).pop();
              },
              child: const Icon(Icons.arrow_back_ios),
            ),
          ),
          body: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              // 已有输入框
              Padding(
                padding: const EdgeInsets.symmetric(
                    horizontal: 8, vertical: 16),
                child: TextFormField(
                  readOnly: true,
                  controller: detailProvider.lastName,
                  decoration: const InputDecoration(
                    border: UnderlineInputBorder(),
                    labelText: '姓',
                    floatingLabelBehavior: FloatingLabelBehavior
                        .auto, // 确保提示文字不消失
                  ),
                ),
              ),

              Padding(
                padding: const EdgeInsets.symmetric(
                    horizontal: 8, vertical: 16),
                child: TextFormField(
                  readOnly: true,
                  controller: detailProvider.firstName,
                  decoration: const InputDecoration(
                    border: UnderlineInputBorder(),
                    labelText: '名',
                    floatingLabelBehavior: FloatingLabelBehavior
                        .auto, // 确保提示文字不消失
                  ),
                ),
              ),
              Padding(
                padding: const EdgeInsets.symmetric(
                    horizontal: 8, vertical: 16),
                child: TextFormField(
                  readOnly: true,
                  controller: detailProvider.age,
                  decoration: const InputDecoration(
                    border: UnderlineInputBorder(),
                    labelText: '年龄',
                    floatingLabelBehavior: FloatingLabelBehavior
                        .auto, // 确保提示文字不消失
                  ),
                ),
              ),
              Padding(
                padding: const EdgeInsets.symmetric(
                    horizontal: 8, vertical: 16),
                child: TextFormField(
                  readOnly: true,
                  controller: detailProvider.sex,
                  decoration: const InputDecoration(
                    border: UnderlineInputBorder(),
                    labelText: '性别',
                    floatingLabelBehavior: FloatingLabelBehavior
                        .auto, // 确保提示文字不消失
                  ),
                ),
              ),

              Padding(
                padding: const EdgeInsets.symmetric(
                    horizontal: 8, vertical: 16),
                child: TextFormField(
                  readOnly: true,
                  controller: detailProvider.phoneNumber,
                  decoration: const InputDecoration(
                    border: UnderlineInputBorder(),
                    labelText: '联系电话',
                    floatingLabelBehavior: FloatingLabelBehavior
                        .auto, // 确保提示文字不消失
                  ),
                ),
              ),
              Padding(
                padding: const EdgeInsets.symmetric(
                    horizontal: 8, vertical: 16),
                child: TextFormField(
                  readOnly: true,
                  controller: detailProvider.address,
                  decoration: const InputDecoration(
                    border: UnderlineInputBorder(),
                    labelText: '地址',
                    floatingLabelBehavior: FloatingLabelBehavior
                        .auto, // 确保提示文字不消失
                  ),
                ),
              ),

            ],
          ),
        )
    );
  }

  @override
  void dispose() {
    super.dispose();
  }
}

第一个是在返回按钮(appBarleading属性里)那手动调用一下clearData()方法,第二个监听安卓返回按钮的办法就是用PopScope包裹Scaffold,在PopScope的onPopInvokedWithResult函数里手动清理一下也就OK了。

7、到最后了,送个状态呗~给个加载中状态和加载失败了展示原因和刷新

不多啰嗦上代码!

detail_pge.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'detail_provider.dart';

class DetailPage extends StatefulWidget {
  final int? id; // 示例参数,可按需修改
  const DetailPage({super.key, this.id});

  @override
  State<DetailPage> createState() => _DetailPageState();
}

class _DetailPageState extends State<DetailPage> {
  @override
  void initState() {
    super.initState();
    final detailProvider = Provider.of<DetailProvider>(context, listen: false);
    // 调用网络请求,这里假设 DetailProvider 有 fetchData 方法
    if (widget.id != null) {
      detailProvider.getDetail(widget.id!);
    }
  }

  @override
  Widget build(BuildContext context) {
    final detailProvider = Provider.of<DetailProvider>(context);

    return PopScope(
      onPopInvokedWithResult: (didPop, result) {
        detailProvider.clearData();
      },
      child: Scaffold(
        appBar: AppBar(
          title: const Text('详情'),
          leading: GestureDetector(
            onTap: () {
              detailProvider.clearData();
              Navigator.of(context).pop();
            },
            child: const Icon(Icons.arrow_back_ios),
          ),
        ),
        body: Center(
          child: detailProvider.isLoading
              ? Column(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: const [
                    Text('正在加载...'),
                    SizedBox(height: 16),
                    CircularProgressIndicator(),
                  ],
                )
              : detailProvider.error != null
              ? Column(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: [
                    Text(detailProvider.error!),
                    const SizedBox(height: 16),
                    ElevatedButton(
                      onPressed: () {
                        if (widget.id != null) {
                          detailProvider.getDetail(widget.id!);
                        }
                      },
                      child: const Text('点击刷新'),
                    ),
                  ],
                )
              : Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    // 已有输入框
                    Padding(
                      padding: const EdgeInsets.symmetric(
                        horizontal: 8,
                        vertical: 16,
                      ),
                      child: TextFormField(
                        readOnly: true,
                        controller: detailProvider.lastName,
                        decoration: const InputDecoration(
                          border: UnderlineInputBorder(),
                          labelText: '姓',
                          floatingLabelBehavior:
                              FloatingLabelBehavior.auto, // 确保提示文字不消失
                        ),
                      ),
                    ),

                    Padding(
                      padding: const EdgeInsets.symmetric(
                        horizontal: 8,
                        vertical: 16,
                      ),
                      child: TextFormField(
                        readOnly: true,
                        controller: detailProvider.firstName,
                        decoration: const InputDecoration(
                          border: UnderlineInputBorder(),
                          labelText: '名',
                          floatingLabelBehavior:
                              FloatingLabelBehavior.auto, // 确保提示文字不消失
                        ),
                      ),
                    ),
                    Padding(
                      padding: const EdgeInsets.symmetric(
                        horizontal: 8,
                        vertical: 16,
                      ),
                      child: TextFormField(
                        readOnly: true,
                        controller: detailProvider.age,
                        decoration: const InputDecoration(
                          border: UnderlineInputBorder(),
                          labelText: '年龄',
                          floatingLabelBehavior:
                              FloatingLabelBehavior.auto, // 确保提示文字不消失
                        ),
                      ),
                    ),
                    Padding(
                      padding: const EdgeInsets.symmetric(
                        horizontal: 8,
                        vertical: 16,
                      ),
                      child: TextFormField(
                        readOnly: true,
                        controller: detailProvider.sex,
                        decoration: const InputDecoration(
                          border: UnderlineInputBorder(),
                          labelText: '性别',
                          floatingLabelBehavior:
                              FloatingLabelBehavior.auto, // 确保提示文字不消失
                        ),
                      ),
                    ),

                    Padding(
                      padding: const EdgeInsets.symmetric(
                        horizontal: 8,
                        vertical: 16,
                      ),
                      child: TextFormField(
                        readOnly: true,
                        controller: detailProvider.phoneNumber,
                        decoration: const InputDecoration(
                          border: UnderlineInputBorder(),
                          labelText: '联系电话',
                          floatingLabelBehavior:
                              FloatingLabelBehavior.auto, // 确保提示文字不消失
                        ),
                      ),
                    ),

                    // 新增输入框
                    Padding(
                      padding: const EdgeInsets.symmetric(
                        horizontal: 8,
                        vertical: 16,
                      ),
                      child: TextFormField(
                        readOnly: true,
                        controller: detailProvider.address,
                        decoration: const InputDecoration(
                          border: UnderlineInputBorder(),
                          labelText: '地址',
                          floatingLabelBehavior:
                              FloatingLabelBehavior.auto, // 确保提示文字不消失
                        ),
                      ),
                    ),
                  ],
                ),
        ),
      ),
    );
  }

  @override
  void dispose() {
    super.dispose();
  }
}
detail_provider.dart
import 'package:day06/bean/contact_bean.dart';
import 'package:flutter/material.dart';

import 'bean/base_bean.dart';
import 'dio_util.dart';

class DetailProvider with ChangeNotifier {
  final TextEditingController firstName = TextEditingController();
  final TextEditingController lastName = TextEditingController();
  final TextEditingController age = TextEditingController();
  final TextEditingController sex = TextEditingController();
  final TextEditingController phoneNumber = TextEditingController();
  final TextEditingController address = TextEditingController();

  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);
        firstName.text = contact.firstName ?? '';
        lastName.text = contact.lastName ?? '';
        age.text = contact.age.toString();
        sex.text = contact.sex == 1 ? '男' : '女';
        phoneNumber.text = contact.phoneNumber ?? '';
        address.text = contact.address ?? '';
        notifyListeners();
      },
      (reason) {
        _isLoading = false;
        _error = reason;
        notifyListeners();
      },
    );
  }

  void clearData() {
    lastName.clear();
    firstName.clear();
    age.clear();
    sex.clear();
    phoneNumber.clear();
    address.clear();
    notifyListeners(); // 通知监听器状态改变
  }

  @override
  void dispose() {
    firstName.dispose();
    lastName.dispose();
    age.dispose();
    sex.dispose();
    phoneNumber.dispose();
    address.dispose();
    // TODO: implement dispose
    super.dispose();
  }
}

没啥可说的,就是加了个isLoading和error,当前端isLoading的时候转圈,转完了有数据展示数据,报错就展示错误原因呗~

上一篇
下一篇