I am trying to write a program using .NET MAUI with MVVM community tookit. I am following the official practice from Enterprise Application Patterns using .NET MAUI, the source code of which can be found here.
I have defined the IViewModelBase and implemented it with ViewModelBase just like the example app does:
//IViewModeBase.cs
using Music_Journal.Services.Navigation;
namespace Music_Journal.ViewModels.Base {
public interface IViewModelBase : IQueryAttributable {
public INavigationService NavigationService { get; }
public bool IsBusy { get; }
public bool IsInitialized { get; }
Task InitializeAsync();
}
}
//IViewModeBase.cs
using Music_Journal.Services.Navigation;
namespace Music_Journal.ViewModels.Base {
public abstract partial class ViewModelBase : ObservableObject, IViewModelBase {
private long _isBusy;
public bool IsBusy => Interlocked.Read(ref _isBusy) > 0;
[ObservableProperty]
private bool _isInitialized;
public INavigationService NavigationService { get; }
public IAsyncRelayCommand InitializeAsyncCommand { get; }
public ViewModelBase(INavigationService navigationService) {
NavigationService = navigationService;
InitializeAsyncCommand = new AsyncRelayCommand(
async () => {
await IsBusyFor(InitializeAsync);
IsInitialized = true;
},
AsyncRelayCommandOptions.FlowExceptionsToTaskScheduler
);
}
public virtual void ApplyQueryAttributes(IDictionary<string, object> query) { }
public async Task IsBusyFor(Func<Task> unitOfWork) {
Interlocked.Increment(ref _isBusy);
OnPropertyChanged(nameof(IsBusy));
try {
await unitOfWork();
} finally {
Interlocked.Decrement(ref _isBusy);
OnPropertyChanged(nameof(IsBusy));
}
}
public virtual Task InitializeAsync() {
return Task.CompletedTask;
}
}
}
In one ViewModel called ProjectPageViewModel, I overwrite the InitializeAsync function from the ViewModelBase like this:
using Music_Journal.Models;
using Music_Journal.Services.Navigation;
using Music_Journal.Services.Projects;
using System.Collections.ObjectModel;
namespace Music_Journal.ViewModels {
public partial class ProjectPageViewModel : ViewModelBase {
private IProjectStorage _projectStorage;
public ObservableCollection<Project> Projects { get; } = new();
public ProjectPageViewModel (
IProjectStorage projectStorage,
INavigationService navigationService)
: base (navigationService) {
_projectStorage = projectStorage;
}
// Initialization
private RelayCommand _initializeCommand;
public RelayCommand InitializeCommand => _initializeCommand ??= new RelayCommand(
async () => {
await _projectStorage.InitializeAsync();
});
// Add Project
private RelayCommand _addProjectCommand;
public RelayCommand AddProjectCommand => _addProjectCommand ??= new RelayCommand(
async () => {
var p = new Project();
p.Name = "test project";
await _projectStorage.AddAsync(p);
});
// Read all Projects and put them into this viewmodel
private RelayCommand _listProjectsCommand;
public RelayCommand ListProjectsCommand => _listProjectsCommand ?? new RelayCommand(
async () => {
var list = await _projectStorage.ListAsync();
Projects.Clear();
foreach (var p in list) {
Projects.Add(p);
}
}
);
// NOTE HERE
public override async Task InitializeAsync() {
await IsBusyFor(
async () => {
var list = await _projectStorage.ListAsync();
Projects.Clear();
foreach (var p in list) {
Projects.Add(p);
}
}
);
}
}
}
And here is the ProjectPageView:
using Music_Journal.ViewModels;
namespace Music_Journal.Views;
public partial class ProjectsPage : ContentPage
{
public ProjectsPage(ProjectPageViewModel projectPageViewModel) {
InitializeComponent();
BindingContext = projectPageViewModel;
}
}
I think since I have overwritten the InitializeAsync method, the initialization should be runned once I navigate to the project page. After debugging, however, it seems that when page is initialized, the program will not go through the InitializeAsync method from the ViewModelBase, nor the overwritten InitializeAsync method from the ProjectPageViewModel. Why is that?
I also tried to explicitly call the InitializeAsync method on ProjectPageView.cs. And this time, everything works fine:
//ProjectPageView.cs
using Music_Journal.ViewModels;
namespace Music_Journal.Views;
public partial class ProjectsPage : ContentPage
{
private ProjectPageViewModel viewModel;
public ProjectsPage(ProjectPageViewModel projectPageViewModel) {
InitializeComponent();
viewModel = projectPageViewModel;
BindingContext = projectPageViewModel;
}
protected override async void OnAppearing()
=> await viewModel.InitializeAsync();
}
I am confused. Why the InitializeAsync method won't be called automatically when the page is initialized? How can I fix this without explicitly calling it in the code-behind of the xaml file?