| MAUI-CommunityToolkit-6 :👈 | 👉:MAUI-CommunityToolkit-8 |
Hour 7 β Navigation, Messaging & Dependency Injection in MVVM |
Welcome to Hour 7 of your MAUI MVVM learning series. Today we focus on three essential architecture concepts used in modern MAUI apps:
In MAUI, the recommended way of navigating pages is the Shell Navigation system. It is simple, URLβbased, and works perfectly with MVVM.
Add this inside AppShell.xaml.cs:
Routing.RegisterRoute("details", typeof(DetailsPage));
Inject Shell.Current navigation into ViewModel using Dependency Injection.
[RelayCommand]
private async Task GoToDetails()
{
await Shell.Current.GoToAsync("details");
}
await Shell.Current.GoToAsync("details?itemId=5&title=Hello");
Receiving parameters in destination ViewModel:
[QueryProperty(nameof(ItemId), "itemId")]
[QueryProperty(nameof(Title), "title")]
public partial class DetailsViewModel : ObservableObject
{
[ObservableProperty]
private int itemId;
[ObservableProperty]
private string title;
}
β Parameters are automatically assigned when the page loads.
Messaging is useful when two ViewModels need to communicate without knowing each other. Example: Refreshing a list when another page updates data.
WeakReferenceMessenger.Default.Send(new ItemUpdatedMessage(42));
WeakReferenceMessenger.Default.Register<ItemUpdatedMessage>(this, (r, m) =>
{
Console.WriteLine("Item updated ID: " + m.Value);
});
public class ItemUpdatedMessage : ValueChangedMessage<int>
{
public ItemUpdatedMessage(int value) : base(value)
{
}
}
β Decoupled. β Automatic cleanup. β Ideal for cross-page communication.
.NET MAUI uses builtβin DI. You register your app's services in MauiProgram.cs.
builder.Services.AddSingleton<IDataService, DataService>(); builder.Services.AddTransient<MainViewModel>(); builder.Services.AddTransient<MainPage>();
β Singleton β one instance for entire app β Transient β new instance each time a page loads
public partial class MainViewModel : ObservableObject
{
private readonly IDataService dataService;
public MainViewModel(IDataService service)
{
dataService = service;
}
}
β Constructor injection, no service locators needed.
builder.Services.AddSingleton<ItemService>(); builder.Services.AddTransient<ListViewModel>(); builder.Services.AddTransient<ListPage>(); builder.Services.AddTransient<EditViewModel>(); builder.Services.AddTransient<EditPage>();
public partial class ListViewModel : ObservableObject
{
private readonly ItemService items;
public ListViewModel(ItemService service)
{
items = service;
WeakReferenceMessenger.Default.Register<ItemUpdatedMessage>(this, (r, m) =>
{
LoadItemsCommand.Execute(null);
});
}
[RelayCommand]
private async Task LoadItems()
{
Items = items.GetAll();
}
[ObservableProperty]
private List<string> items;
}
public partial class EditViewModel : ObservableObject
{
[ObservableProperty]
private int itemId;
[ObservableProperty]
private string title;
[RelayCommand]
private async Task Save()
{
WeakReferenceMessenger.Default.Send(new ItemUpdatedMessage(ItemId));
await Shell.Current.GoToAsync("..");
}
}
The Hour 7 focuses on building a complete small app using everything you learned: ObservableProperty, Commands, AsyncRelayCommand, Messenger, DI, Navigation, and Best Practices.
You will:
Option A β Todo App
Option B β Product Inventory App
Option C β Notes App
π ProjectName β£ π Views β£ π ViewModels β£ π Models β£ π Services β£ π Messages β App.xaml / MauiProgram.cs
β Clean separation of responsibility
ITodoService
public interface ITodoService
{
Task<List<TodoItem>> GetTodosAsync();
Task AddAsync(TodoItem item);
}
Implementation:
public class TodoService : ITodoService
{
private readonly List<TodoItem> items = new();
public Task<List<TodoItem>> GetTodosAsync() => Task.FromResult(items);
public Task AddAsync(TodoItem item)
{
items.Add(item);
return Task.CompletedTask;
}
}
public class TodoAddedMessage : ValueChangedMessage<bool>
{
public TodoAddedMessage(bool value) : base(value) { }
}
β When a new task is added, other screens refresh.
public partial class HomeViewModel : ObservableObject
{
private readonly ITodoService service;
[ObservableProperty]
private List<TodoItem> items;
public HomeViewModel(ITodoService todoService)
{
service = todoService;
WeakReferenceMessenger.Default.Register<TodoAddedMessage>(this, (r, m) =>
{
LoadCommand.Execute(null);
});
}
[ICommand]
private async Task LoadAsync()
{
Items = await service.GetTodosAsync();
}
[ICommand]
private async Task AddNewAsync()
{
await Shell.Current.GoToAsync("addtodo");
}
}
β Uses AsyncRelayCommand β Uses Messenger for refresh β Uses DI β Uses Navigation β Uses ObservableProperty β Clean MVVM
public partial class AddTodoViewModel : ObservableObject
{
private readonly ITodoService service;
public AddTodoViewModel(ITodoService todoService)
{
service = todoService;
}
[ObservableProperty]
private string title;
[ICommand]
private async Task SaveAsync()
{
await service.AddAsync(new TodoItem { Title = Title });
WeakReferenceMessenger.Default.Send(new TodoAddedMessage(true));
await Shell.Current.GoToAsync("..");
}
}
β Sends message β Saves item β Navigates back
You now have:
Given:
public partial class HomeViewModel : ObservableObject
{
private readonly ITaskService taskService;
[ObservableProperty]
private List<TaskItem> tasks;
public HomeViewModel(ITaskService service)
{
taskService = service;
WeakReferenceMessenger.Default.Register<TaskAddedMessage>(this, (r, m) =>
{
LoadCommand.Execute(null);
});
}
[ICommand]
private async Task LoadAsync()
{
Tasks = await taskService.GetTasksAsync();
}
}
Given this folder structure:
π Project β£ π Views β£ π ViewModels β£ π Models β£ π Services β π Helpers
Your Hour 7β journey with MVVM Toolkit is completed!
| MAUI-CommunityToolkit-6 :👈 | 👉:MAUI-CommunityToolkit-8 |