如何在Main.dart遵循MVVM正确添加HomeView并修复更新失效问题
Flutter MVVM模式下添加元素后UI不更新的解决方案
问题描述
我在main.dart中实现了一个AlertDialog,希望从中获取信息并添加至HomeViewModel。初始加载默认元素时无异常,但后续添加元素时notifyListeners()无法触发UI更新。请问如何在遵循MVVM规范的前提下,正确在main.dart中添加HomeView?
现有代码
HomeViewModel
class HomeViewModel extends ChangeNotifier { List<ViewItemModel> get view => _view; void addView(ViewItemModel view) { _view.add(view); notifyListeners(); } void removeView(int index) { _view.removeAt(index); notifyListeners(); } }
HomeView
HomeViewModel _viewModel = HomeViewModel(); class HomeView extends StatefulWidget { @override _HomeViewState createState() => _HomeViewState(); } class _HomeViewState extends State<HomeView> { @override Widget build(BuildContext context) { return Scaffold( body: Padding( padding: const EdgeInsets.only(left: 10, right: 10), child: Column( children: [ Expanded( child: SingleChildScrollView( child: Wrap( children: [ ..._viewModel.view.map(buildContainer).toList(), ], ), ), ), SizedBox(height: 10,) ], ), ), ); } Widget buildContainer(item) { return Padding( padding: const EdgeInsets.all(3), child: GestureDetector( child: Container( height: 180, width: MediaQuery.of(context).size.width / 2 - 20, decoration: BoxDecoration( color: item.color, borderRadius: BorderRadius.circular(15.0), ), padding: EdgeInsets.all(15.0), child: Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.center, children: [ SvgPicture.asset( item.icon, width: 35, height: 35, ), SizedBox(height: 5,), Opacity( opacity: 0.5, child: Text( item.title, style: TextStyle( fontWeight: FontWeight.normal, fontSize: 16, color: Colors.black), ), ), Text( item.minute.toString(), style: TextStyle( fontWeight: FontWeight.w500, fontSize: 36, color: Colors.black), ), Text( "min", style: TextStyle( fontWeight: FontWeight.w500, fontSize: 11, color: Colors.black), ), item.second > 0 ? Text( "+" + item.second.toString() + " seconds", style: TextStyle( fontWeight: FontWeight.w500, fontSize: 11, color: Colors.black), ) : Text( "", style: TextStyle( fontWeight: FontWeight.w500, fontSize: 11, color: Colors.black), ) ], ), ), onTap: () {}, ), ); } }
Main.dart 添加按钮
Expanded( child: FilledButton( onPressed: () { print("Name : " + _nameController.text); print("Minute : " + selectedMinute.toString()); print("Second : " + selectedSecond.toString()); print("Color : " + selectedColorIndex.toString()); print("Icon : " + selectedIconIndex.toString()); setState(() { _homeViewModel.addView(new ViewItemModel( icons[selectedIconIndex], _nameController.text, colors[selectedColorIndex], selectedMinute.toInt(), selectedSecond.toInt())); }); }, child: Text("Create"))),
问题根源
当前代码的核心问题是HomeView中创建的_viewModel和main.dart中使用的_homeViewModel是两个完全独立的实例。调用main.dart里的addView()时,通知的是另一个ViewModel实例,HomeView监听的ViewModel状态根本没有变化,因此notifyListeners()无法触发UI更新。
解决方案(遵循MVVM规范)
要解决这个问题,需要确保整个应用中使用同一个HomeViewModel实例,并通过状态管理工具(如Provider)实现View和ViewModel的通信。
步骤1:配置Provider依赖
在pubspec.yaml中添加provider依赖:
dependencies: flutter: sdk: flutter provider: ^6.0.5 # 使用最新稳定版
步骤2:全局提供ViewModel实例
修改main.dart的入口,用ChangeNotifierProvider包裹整个应用,提供唯一的HomeViewModel实例:
void main() { runApp( ChangeNotifierProvider( create: (context) => HomeViewModel(), child: MyApp(), ), ); }
步骤3:修改HomeView获取共享ViewModel
去掉HomeView顶部的独立ViewModel实例创建,通过Provider获取全局共享的实例:
class HomeView extends StatefulWidget { @override _HomeViewState createState() => _HomeViewState(); } class _HomeViewState extends State<HomeView> { @override Widget build(BuildContext context) { // 获取全局共享的ViewModel实例 final _viewModel = Provider.of<HomeViewModel>(context); return Scaffold( body: Padding( padding: const EdgeInsets.only(left: 10, right: 10), child: Column( children: [ Expanded( child: SingleChildScrollView( child: Wrap( children: [ ..._viewModel.view.map(buildContainer).toList(), ], ), ), ), SizedBox(height: 10,) ], ), ), ); } // buildContainer方法保持不变 }
步骤4:修改main.dart按钮逻辑
在AlertDialog的按钮回调中,通过Provider获取同一个ViewModel实例,并去掉多余的setState(ViewModel的notifyListeners()已负责触发UI更新):
Expanded( child: FilledButton( onPressed: () { print("Name : " + _nameController.text); print("Minute : " + selectedMinute.toString()); print("Second : " + selectedSecond.toString()); print("Color : " + selectedColorIndex.toString()); print("Icon : " + selectedIconIndex.toString()); // 获取全局共享的ViewModel实例,listen: false表示不监听状态变化 final viewModel = Provider.of<HomeViewModel>(context, listen: false); viewModel.addView(ViewItemModel( icons[selectedIconIndex], _nameController.text, colors[selectedColorIndex], selectedMinute.toInt(), selectedSecond.toInt(), )); // 关闭AlertDialog(可选,根据需求添加) Navigator.pop(context); }, child: Text("Create") ), ),
关键说明
listen: false:在按钮回调中仅调用ViewModel的方法,不需要监听状态变化,添加此参数可避免不必要的UI重建- 实例唯一性:所有使用HomeViewModel的地方都通过Provider获取,确保状态变化能被所有监听的View感知
- MVVM规范遵循:View仅负责UI渲染和用户交互,ViewModel负责业务逻辑和状态管理,两者通过状态管理工具解耦,不直接持有对方实例
内容的提问来源于stack exchange,提问作者Ozgur Saklanmaz
相关产品推荐
相关产品推荐

