Search This Blog

Showing posts with label MVVM. Show all posts
Showing posts with label MVVM. Show all posts

Tuesday, 30 June 2026

MVVM in .NET MAUI with CommunityToolkit.Mvvm

Writing all your logic in the code‑behind (the .xaml.cs file) quickly becomes messy and hard to test. The MVVM (Model‑View‑ViewModel) pattern fixes this by separating your UI from your logic. In this tutorial you will learn MVVM in .NET MAUI the modern way using CommunityToolkit.Mvvm, which removes almost all the boilerplate with source generators.

Why CommunityToolkit.Mvvm?

Traditionally you had to implement INotifyPropertyChanged, write full properties with backing fields, and create ICommand objects by hand. The toolkit generates all of that for you from simple attributes, so your view model stays tiny and readable.

Step 1 – Install the package

dotnet add package CommunityToolkit.Mvvm

Step 2 – Create the ViewModel

Two things are important: the class must be partial (so the generator can add code to it) and it should inherit from ObservableObject.

using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;

namespace MauiMvvmDemo.ViewModels;

public partial class MainViewModel : ObservableObject
{
    // Generates a public "Name" property with change notification
    [ObservableProperty]
    private string name = string.Empty;

    [ObservableProperty]
    private string greeting = string.Empty;

    // Generates a "GreetCommand" you can bind to a Button
    [RelayCommand]
    private void Greet()
    {
        Greeting = $"Hello, {Name}! Welcome to .NET MAUI MVVM.";
    }

    // Async commands are just as easy
    [RelayCommand]
    private async Task LoadAsync()
    {
        await Task.Delay(500); // simulate loading data
        Greeting = "Data loaded successfully.";
    }
}
How the magic works: the field name generates a property called Name, and the method Greet() generates a command called GreetCommand. Always bind to the generated PascalCase names.

Step 3 – Bind the View (XAML)

Set x:DataType for compiled bindings (faster and safer), then bind controls to the properties and commands.

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:vm="clr-namespace:MauiMvvmDemo.ViewModels"
             x:Class="MauiMvvmDemo.MainPage"
             x:DataType="vm:MainViewModel">

    <VerticalStackLayout Padding="20" Spacing="15">

        <Entry Placeholder="Enter your name"
               Text="{Binding Name}" />

        <Button Text="Greet"
                Command="{Binding GreetCommand}" />

        <Label Text="{Binding Greeting}"
               FontSize="18" />

    </VerticalStackLayout>
</ContentPage>

Step 4 – Connect the ViewModel with Dependency Injection

Register the page and the view model in MauiProgram.cs:

builder.Services.AddTransient<MainViewModel>();
builder.Services.AddTransient<MainPage>();

Then inject the view model into the page and set it as the BindingContext:

public partial class MainPage : ContentPage
{
    public MainPage(MainViewModel viewModel)
    {
        InitializeComponent();
        BindingContext = viewModel;
    }
}

Bonus – Enable/disable a button automatically

You can tell a command when it is allowed to run. The button is disabled automatically when the condition is false.

[RelayCommand(CanExecute = nameof(CanGreet))]
private void Greet() => Greeting = $"Hi {Name}!";

private bool CanGreet() => !string.IsNullOrWhiteSpace(Name);

// Re-check CanGreet whenever Name changes
[ObservableProperty]
[NotifyCanExecuteChangedFor(nameof(GreetCommand))]
private string name = string.Empty;

Conclusion

With CommunityToolkit.Mvvm you get clean, testable view models with almost no boilerplate. This is the recommended way to build .NET MAUI apps today.

Want to load real data into your view model? Check out “Consuming a REST API with HttpClient in .NET MAUI” and Local SQLite Database in .NET MAUI”.

Tuesday, 24 May 2022

Implementing Pull To Refresh With Collection View In .NET MAUI (MVVM)


Youtube: https://youtu.be/XBuSVcBdYo4

Refresh View: Add any view Inside Refresh View to achieve pull to refresh functionality. Let's go through the following examples.


Model:
Employee.cs
namespace MauiApp1.Models
{
    public class Employee
    {
        public string Name { get; set; }
        public string Address { get; set; }
        public string Email { get; set; }
    }
}
 
Views:
EmployeeListView.xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="MauiApp1.Views.EmployeeListView"
             xmlns:viewModels="clr-namespace:MauiApp1.ViewModels"
             xmlns:models="clr-namespace:MauiApp1.Models"
             x:DataType="viewModels:EmployeeListViewModel"
             Title="Employee List">
    <ContentPage.BindingContext>
        <viewModels:EmployeeListViewModel />
    </ContentPage.BindingContext>
    <StackLayout Margin="10">
        <ActivityIndicator IsRunning="True" IsVisible="{Binding IsBusy}" HeightRequest="50" WidthRequest="50" HorizontalOptions="Center" />
        <RefreshView Command="{Binding PullToRefreshCommand}" IsRefreshing="{Binding IsRefreshing}">
            <CollectionView ItemsSource="{Binding Employees}" >
                <CollectionView.ItemTemplate>
                    <DataTemplate x:DataType="models:Employee">
                        <Grid RowDefinitions="5,Auto,5,1">
                            <StackLayout Grid.Row="1">
                                <Label Text="{Binding Name}" FontSize="18" FontAttributes="Bold" />
                                <Label Text="{Binding Email}" FontSize="16"  />
                                <Label Text="{Binding Address}" FontSize="16"  />
                            </StackLayout>
                            <BoxView Grid.Row="3" HeightRequest="1" BackgroundColor="Gray" />
                        </Grid>
                    </DataTemplate>
                </CollectionView.ItemTemplate>
            </CollectionView>
        </RefreshView>
    </StackLayout>
</ContentPage>
 
ViewModels:
BaseViewModel.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
 
namespace MauiApp1.ViewModels
{
    public class BaseViewModel : INotifyPropertyChanged
    {
        private bool _isBusy;
        public bool IsBusy
        {
            get => _isBusy;
            set => SetProperty(ref _isBusy, value);
        }
 
        private bool _isRefreshing;
        public bool IsRefreshing
        {
            get => _isRefreshing;
            set => SetProperty(ref _isRefreshing, value);
        }
 
        protected bool SetProperty<T>(ref T backingStore, T value,
       [CallerMemberName] string propertyName = "",
       Action onChanged = null)
        {
            if (EqualityComparer<T>.Default.Equals(backingStore, value))
                return false;
 
            backingStore = value;
            onChanged?.Invoke();
            OnPropertyChanged(propertyName);
            return true;
        }
 
        #region INotifyPropertyChanged
        public event PropertyChangedEventHandler PropertyChanged;
        protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
        {
            var changed = PropertyChanged;
            if (changed == null)
                return;
 
            changed.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
        #endregion
    }
}
 
EmployeeListViewModel.cs
using MauiApp1.Models;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
 
namespace MauiApp1.ViewModels
{
    public class EmployeeListViewModel : BaseViewModel
    {
        #region Properties
        public ObservableCollection<Employee> Employees { get; set; } = new ObservableCollection<Employee>();
        #endregion
 
 
        #region Constructor
        public EmployeeListViewModel()
        {
            AddDummyEmployeeList();
        }
        #endregion
 
        #region Methods
        private async void AddDummyEmployeeList()
        {
            IsBusy = true;
            await Task.Delay(500); // Write api call here and add that in list
            Employees.Clear();
            Employees.Add(new Employee { Name = "Pragnesh Mistry", Email = "test@gmail.com", Address = "India" });
            Employees.Add(new Employee { Name = "Janani", Email = "abc@gmail.com", Address = "India" });
            Employees.Add(new Employee { Name = "Rahul", Email = "xyz@gmail.com", Address = "India" });
            Employees.Add(new Employee { Name = "Ankit", Email = "test@gmail.com", Address = "India" });
            Employees.Add(new Employee { Name = "Vijay", Email = "test@gmail.com", Address = "India" });
            Employees.Add(new Employee { Name = "Jay", Email = "test@gmail.com", Address = "India" });
            IsBusy = false;
        }
        #endregion
 
 
        #region Commands
        public ICommand PullToRefreshCommand => new Command(() =>
        {
            IsRefreshing = false;
            AddDummyEmployeeList();
        });
        #endregion
 
 
    }
}
 


On App Start Navigate to EmployeeListView Page.
App.xaml.cs
using MauiApp1.Views;
 
namespace MauiApp1;
 
public partial class App: Application
{
    public App()
    {
	InitializeComponent();

        MainPage = new NavigationPage( new EmployeeListView());     } }  


Popular Posts