MAUI应用MVVM模式下数据丢失问题及持久化方法咨询
MAUI MVVM应用数据丢失与界面不更新问题
问题描述
搭建采用MVVM架构的MAUI应用,划分Views、Viewmodels、Models文件夹,Shell导航可正常切换视图。DailyPage对应DailyViewModel,ViewModel用ObservableRangeCollection<ExampleModel>维护DailyList并初始化测试数据,通过Community Toolkit弹窗添加新项。弹窗的BindingContext为新建的DailyViewModel实例,点击Add按钮时AddObjectCommand执行,调试显示项已添加,但界面列表未更新,每次操作后集合仅保留初始测试数据,切换视图数据也会丢失。
问题原因分析
- ViewModel实例不共享:弹窗在XAML中手动新建了
DailyViewModel实例,而DailyPage的ViewModel是独立的另一个实例,两者的DailyList集合完全无关。弹窗添加的数据仅存入自身实例的集合,页面绑定的集合没有任何变化,因此界面不更新。 - 页面ViewModel未持久化:Shell导航默认每次导航到DailyPage时,都会创建新的
DailyViewModel实例,之前添加的数据随旧实例被回收,切换视图后重新显示初始测试数据。 - DailyList属性未触发属性变更通知:虽然
ObservableRangeCollection本身会通知集合内元素的变化,但DailyList的setter未调用ViewModelBase的SetProperty方法(若后续需要替换整个集合,会导致界面无法感知)。
解决方案
方案1:共享ViewModel实例给弹窗
核心思路:让弹窗使用和页面相同的DailyViewModel实例,确保操作的是同一个DailyList集合。
- 修改弹窗XAML:移除弹窗中手动创建ViewModel的代码:
<!-- 删除这一行 --> <!-- <toolkit:Popup.BindingContext> <viewmodels:DailyViewModel/> </toolkit:Popup.BindingContext> -->
- 修改DailyPage后台代码:打开弹窗时,将页面的BindingContext(即当前
DailyViewModel实例)赋值给弹窗:
async void onAddEntryClicked(object sender, EventArgs e) { var entryPopup = new AddEntryPopup(); // 共享页面的ViewModel实例 entryPopup.BindingContext = this.BindingContext; await this.ShowPopupAsync(entryPopup); }
方案2:使用单例数据服务维护全局共享数据
核心思路:创建单例类统一管理共享数据,所有ViewModel都操作该单例中的集合,彻底解决实例不共享问题。
- 创建单例DataService:
public class DataService { private static readonly DataService _instance = new DataService(); public static DataService Instance => _instance; public ObservableRangeCollection<ExampleModel> DailyList { get; } private DataService() { // 初始化测试数据 DailyList = new ObservableRangeCollection<ExampleModel> { new ExampleModel{ Name = "Test1" }, new ExampleModel{ Name = "Test2" }, new ExampleModel{ Name = "Test3" }, new ExampleModel{ Name = "Test4" } }; } }
- 修改DailyViewModel:直接使用单例服务的集合:
public class DailyViewModel : ViewModelBase { // 绑定到单例服务的集合 public ObservableRangeCollection<ExampleModel> DailyList => DataService.Instance.DailyList; public AsyncCommand RefreshCommand { get; } public DailyViewModel() { Title = "Daily Page"; AddObjectCommand= new MvvmHelpers.Commands.Command(AddObject); RefreshCommand = new AsyncCommand(Refresh); } string addName = "-"; public string AddName { get => addName; set => SetProperty(ref addName, value); } public ICommand AddObjectCommand { get; } void AddObject() { ExampleModel ex = new ExampleModel() { Name = AddName }; // 操作单例服务的集合 DataService.Instance.DailyList.Add(ex); } }
- 弹窗修改:弹窗的BindingContext可以设置为任意
DailyViewModel实例(或直接绑定单例服务),因为所有实例都操作同一个集合。
方案3:通过依赖注入注册ViewModel为单例
核心思路:利用MAUI的依赖注入系统,将DailyViewModel注册为单例,确保整个应用中只存在一个实例,Shell导航和弹窗都会获取同一个实例。
- 修改MauiProgram.cs:注册
DailyViewModel为单例:
var builder = MauiApp.CreateBuilder(); builder .UseMauiApp<App>() .ConfigureFonts(fonts => { fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular"); }); // 注册DailyViewModel为单例 builder.Services.AddSingleton<DailyViewModel>(); // 注册弹窗(可选,若需要注入ViewModel) builder.Services.AddTransient<AddEntryPopup>(); return builder.Build();
- 修改DailyPage构造函数:通过构造函数注入ViewModel:
public partial class DailyPage : ContentPage { public DailyPage(DailyViewModel viewModel) { InitializeComponent(); BindingContext = viewModel; } async void onAddEntryClicked(object sender, EventArgs e) { var entryPopup = new AddEntryPopup(); // 弹窗使用同一个ViewModel实例 entryPopup.BindingContext = this.BindingContext; await this.ShowPopupAsync(entryPopup); } }
- 可选:弹窗构造函数注入:
public partial class AddEntryPopup : Popup { public AddEntryPopup(DailyViewModel viewModel) { InitializeComponent(); BindingContext = viewModel; } }
额外优化:完善DailyList属性的变更通知
若后续需要替换整个DailyList集合,需确保属性变更能通知界面,修改DailyViewModel中的DailyList属性:
ObservableRangeCollection<ExampleModel> _dailyList; public ObservableRangeCollection<ExampleModel> DailyList { get => _dailyList; set => SetProperty(ref _dailyList, value); }
内容的提问来源于stack exchange,提问作者Nico Sz
相关产品推荐
相关产品推荐

