纯Flutter MVC加载更多示例问题:模型中记录未更新
问题分析与解决方案
核心问题
- UI未随模型数据更新:首次加载和加载更多后,模型数据变化但未触发视图重绘,界面无更新。
- ScrollController职责混乱:模型中持有ScrollController,违反MVC职责分离原则,且未与视图关联,导致加载更多的条件判断失效。
- MVC分层不清晰:控制器仅转发调用,未承担数据变化通知职责;模型缺少数据更新的回调机制。
优化后的代码实现
1. 模型层(LoadMoreModelMVC)
移除ScrollController,添加数据更新回调,聚焦数据操作逻辑:
import 'package:http/http.dart' as http; import 'dart:convert'; import '../config/config.dart'; class LoadMoreModelMVC { int _page = 0; final int _limit = 20; bool _hasNextPage = true; bool _isFirstLoadRunning = false; bool _isLoadMoreRunning = false; List _records = []; final _baseUrl = ApiEndpoint.loadMoreUrl; // 数据更新回调,通知外部数据变化 Function()? onDataUpdated; // Getters List get records => _records; bool get hasNextPage => _hasNextPage; bool get isFirstLoadRunning => _isFirstLoadRunning; bool get isLoadMoreRunning => _isLoadMoreRunning; Future<void> firstLoad() async { _isFirstLoadRunning = true; _notifyUpdate(); try { final res = await http.get(Uri.parse("$_baseUrl?_page=$_page&_limit=$_limit")); _records = json.decode(res.body); } catch (err) { print('首次加载失败: $err'); } _isFirstLoadRunning = false; _notifyUpdate(); } Future<void> loadMore() async { if (_hasNextPage && !_isFirstLoadRunning && !_isLoadMoreRunning) { _isLoadMoreRunning = true; _notifyUpdate(); _page += 1; try { final res = await http.get(Uri.parse("$_baseUrl?_page=$_page&_limit=$_limit")); final List fetchedRecords = json.decode(res.body); if (fetchedRecords.isNotEmpty) { _records.addAll(fetchedRecords); } else { _hasNextPage = false; } } catch (err) { print('加载更多失败: $err'); _page -=1; // 加载失败回退页码 } _isLoadMoreRunning = false; _notifyUpdate(); } } Future<void> refresh() async { _page = 0; _hasNextPage = true; _records = []; await firstLoad(); } // 触发数据更新通知 void _notifyUpdate() { onDataUpdated?.call(); } }
2. 控制器层(LoadMoreControllerMVC)
承担模型与视图的中介职责,统一管理数据更新通知:
import '../models/LoadMoreModelMVC.dart'; class LoadMoreControllerMVC { final LoadMoreModelMVC model = LoadMoreModelMVC(); // 注册数据更新监听 void addDataListener(Function() listener) { model.onDataUpdated = listener; } // 移除监听 void removeDataListener() { model.onDataUpdated = null; } Future<void> fetchFirstRecords() async { await model.firstLoad(); } Future<void> fetchMoreRecords() async { await model.loadMore(); } Future<void> refreshData() async { await model.refresh(); } }
3. 视图层(LoadMoreViewMVC)
负责UI渲染与交互,监听数据变化并触发重绘:
import 'package:flutter/material.dart'; import '../../controllers/controllers.dart'; import '../../widgets/widgets.dart'; class LoadMoreViewMVC extends StatefulWidget { const LoadMoreViewMVC({Key? key}) : super(key: key); @override State<LoadMoreViewMVC> createState() => _LoadMoreViewMVCState(); } class _LoadMoreViewMVCState extends State<LoadMoreViewMVC> { final LoadMoreControllerMVC controller = LoadMoreControllerMVC(); final ScrollController _scrollController = ScrollController(); @override void initState() { super.initState(); // 监听数据变化,触发UI刷新 controller.addDataListener(() { if(mounted) setState(() {}); }); // 首次加载数据 controller.fetchFirstRecords(); // 添加滚动监听 _scrollController.addListener(_loadMore); } @override void dispose() { controller.removeDataListener(); _scrollController.removeListener(_loadMore); _scrollController.dispose(); super.dispose(); } void _loadMore() { // 滚动到底部附近时触发加载更多 if (_scrollController.position.extentAfter < 300 && controller.model.hasNextPage && !controller.model.isLoadMoreRunning) { controller.fetchMoreRecords(); } } Widget _buildList() { // 首次加载中显示加载框 if (controller.model.isFirstLoadRunning) { return const Center(child: CircularProgressIndicator()); } // 数据为空且未加载时显示空状态 if (controller.model.records.isEmpty && !controller.model.isFirstLoadRunning) { return const Center(child: Text("暂无数据")); } return ListView.builder( controller: _scrollController, itemCount: controller.model.records.length + (controller.model.hasNextPage && controller.model.isLoadMoreRunning ? 1 : 0), itemBuilder: (_, index) { if (index == controller.model.records.length) { return const Padding( padding: EdgeInsets.symmetric(vertical: 16), child: Center(child: CircularProgressIndicator()), ); } else { final record = controller.model.records[index]; return Card( margin: const EdgeInsets.symmetric(vertical: 8, horizontal: 10), child: ListTile( title: Text(record['title']), subtitle: Text(record['body']), ), ); } }, ); } @override Widget build(BuildContext context) { return Scaffold( drawer: const MoonLightDrawer("Load More MVC"), appBar: const Navbar(title: "Load More MVC"), body: RefreshIndicator( onRefresh: controller.refreshData, child: _buildList(), ), ); } }
关键优化说明
- 数据更新通知:模型通过回调通知控制器,视图监听后调用
setState()触发重绘,解决数据更新不显示的问题。 - 职责分离:移除模型中的ScrollController,由视图管理,符合MVC中视图负责UI组件的原则。
- 分层清晰:控制器统一管理状态通知,视图专注UI渲染,模型专注数据逻辑。
- 错误处理:加载更多失败时回退页码,避免无效请求;补充空状态、加载状态的合理展示。
内容的提问来源于stack exchange,提问作者Bhargav Raviya
相关产品推荐
相关产品推荐

