MAUI中需远程数据的ViewModel初始化及异常处理方案
MAUI 初始化阶段数据加载与异常处理标准方案
一、ViewModel 数据加载的正确姿势(替代构造函数初始化)
构造函数里执行异步IO或复杂操作会导致UI阻塞、弹窗失效——此时ViewModel/页面未完成UI绑定,Shell也未完全初始化。推荐两种落地方案:
1. 绑定页面生命周期事件触发异步命令
在页面代码后台绑定Appearing事件,触发ViewModel的加载逻辑:
// MainPage.xaml.cs protected override async void OnAppearing() { base.OnAppearing(); if (BindingContext is MainViewModel vm) { await vm.LoadItemsCommand.ExecuteAsync(null); } } // MainViewModel.cs public IAsyncCommand LoadItemsCommand { get; } public MainViewModel(IRepository repo, IDialogService dialogService) { LoadItemsCommand = new AsyncCommand(async () => { try { Items = await repo.GetItems(); } catch (Exception ex) { await dialogService.ShowAlert($"加载失败:{ex.Message}", "错误", "确定"); } }); }
这里的IDialogService是封装的弹窗服务,内部调用Shell.Current.DisplayAlert,确保在UI线程执行。
2. 实现IAsyncInitializable接口(依托MAUI CommunityToolkit)
借助CommunityToolkit的IAsyncInitializable,框架会自动在ViewModel初始化后执行异步逻辑:
using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.DependencyInjection; public partial class MainViewModel : ObservableObject, IAsyncInitializable { private readonly IRepository _repo; private readonly IDialogService _dialogService; [ObservableProperty] private ObservableCollection<Item> _items = new(); public MainViewModel(IRepository repo, IDialogService dialogService) { _repo = repo; _dialogService = dialogService; } public async Task InitializeAsync() { try { Items = new ObservableCollection<Item>(await _repo.GetItems()); } catch (Exception ex) { await _dialogService.ShowAlert($"加载失败:{ex.Message}", "错误", "确定"); } } }
二、工厂模式下的ViewModel创建异常处理
用工厂创建ViewModel时,直接在工厂内捕获异常并弹窗,同时返回一个可用的空状态ViewModel避免黑屏:
public class ViewModelFactory { private readonly IServiceProvider _serviceProvider; private readonly IDialogService _dialogService; public ViewModelFactory(IServiceProvider serviceProvider, IDialogService dialogService) { _serviceProvider = serviceProvider; _dialogService = dialogService; } public async Task<MainViewModel> CreateMainViewModelAsync() { try { var repo = _serviceProvider.GetRequiredService<IRepository>(); var vm = new MainViewModel(repo, _dialogService); await vm.InitializeAsync(); return vm; } catch (Exception ex) { await _dialogService.ShowAlert($"创建ViewModel失败:{ex.Message}", "错误", "确定"); // 返回空状态ViewModel,保证页面正常渲染 return new MainViewModel(new EmptyRepository(), _dialogService); } } }
页面加载时调用工厂:
// MainPage.xaml.cs protected override async void OnAppearing() { base.OnAppearing(); var factory = Ioc.Default.GetRequiredService<ViewModelFactory>(); BindingContext = await factory.CreateMainViewModelAsync(); }
三、启动阶段(AppShell/App/MauiApp)资源加载异常处理
启动阶段Shell未初始化,无法直接用Shell.Current.DisplayAlert,需用平台原生弹窗或错误页面兜底:
1. 平台原生弹窗适配
在MauiApp创建时捕获异常,调用对应平台的原生弹窗:
// MauiProgram.cs public static MauiApp CreateMauiApp() { var builder = MauiApp.CreateBuilder(); builder .UseMauiApp<App>() .ConfigureFonts(fonts => { fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular"); }); // 注册服务 builder.Services.AddSingleton<IRepository, RemoteRepository>(); builder.Services.AddSingleton<IDialogService, DialogService>(); builder.Services.AddSingleton<IResourceLoader, ResourceLoader>(); var app = builder.Build(); // 异步加载全局资源 Task.Run(async () => { var resourceLoader = app.Services.GetRequiredService<IResourceLoader>(); try { await resourceLoader.LoadGlobalResources(); } catch (Exception ex) { await MainThread.InvokeOnMainThreadAsync(async () => { #if ANDROID var activity = Platform.CurrentActivity; new AlertDialog.Builder(activity) .SetTitle("启动错误") .SetMessage($"加载全局资源失败:{ex.Message}") .SetPositiveButton("重试", async (s, e) => await resourceLoader.LoadGlobalResources()) .SetNegativeButton("退出", (s, e) => activity.Finish()) .Show(); #elif IOS var alert = UIAlertController.Create("启动错误", $"加载全局资源失败:{ex.Message}", UIAlertControllerStyle.Alert); alert.AddAction(UIAlertAction.Create("重试", UIAlertActionStyle.Default, async _ => await resourceLoader.LoadGlobalResources())); alert.AddAction(UIAlertAction.Create("退出", UIAlertActionStyle.Destructive, _ => UIApplication.SharedApplication.Terminate())); UIApplication.SharedApplication.KeyWindow.RootViewController.PresentViewController(alert, true, null); #else await App.Current.MainPage.DisplayAlert("启动错误", $"加载全局资源失败:{ex.Message}", "确定"); #endif }); } }); return app; }
2. 显示错误页面作为 fallback
资源加载失败时,直接切换到错误页面,让用户选择重试或退出:
// App.xaml.cs protected override async void OnStart() { base.OnStart(); var resourceLoader = Services.GetRequiredService<IResourceLoader>(); try { await resourceLoader.LoadGlobalResources(); MainPage = new AppShell(); } catch (Exception ex) { // 显示错误页面,提供重试/退出选项 MainPage = new ErrorPage( errorMsg: $"启动失败:{ex.Message}", retryAction: async () => { await resourceLoader.LoadGlobalResources(); MainPage = new AppShell(); }, exitAction: () => Application.Current.Quit() ); } }
关键注意事项
- 所有弹窗操作必须在UI线程执行,用
MainThread.InvokeOnMainThreadAsync保证线程安全。 - 禁止构造函数抛出异常,否则会导致页面绑定失败、黑屏。
- 启动阶段的资源加载必须异步执行,避免阻塞UI线程。
内容的提问来源于stack exchange,提问作者Álvaro García
相关产品推荐
相关产品推荐

