Isaac.

MAUI Cross-Platform Apps

Build cross-platform applications for Android, iOS, Windows, and macOS.

By EMEPublished: February 20, 2025
mauidotnetcross-platformmobilexamarin

A Simple Analogy

MAUI is like building one house that works in different countries. One codebase adjusts to platform requirements automatically (iOS layout vs Android, etc.).


Why MAUI?

  • One codebase: Build for iOS, Android, Windows, macOS
  • C# everywhere: Use same language across platforms
  • Xaml: Declarative UI like WPF
  • Native performance: Direct API access per platform
  • Hot reload: Instant feedback during development

Project Setup

# Create MAUI app
dotnet new maui -n MyApp
cd MyApp

# Run on platform
dotnet build -t run -f net8.0-android
dotnet build -t run -f net8.0-ios
dotnet build -t run -f net8.0-windows

XAML UI Definition

<?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="MyApp.MainPage"
             Title="Order List">

    <VerticalStackLayout Padding="20" Spacing="10">
        <Label Text="My Orders" FontSize="24" FontAttributes="Bold" />
        
        <SearchBar x:Name="SearchBar" Placeholder="Search orders..." />
        
        <CollectionView ItemsSource="{Binding Orders}" SelectionMode="Single">
            <CollectionView.ItemTemplate>
                <DataTemplate>
                    <StackLayout Padding="10" Spacing="5">
                        <Label Text="{Binding Id, StringFormat='Order {0}'}" FontAttributes="Bold" />
                        <Label Text="{Binding Total, StringFormat='${0:F2}'}" FontSize="14" />
                        <Label Text="{Binding CreatedAt, StringFormat='{0:MMM d, yyyy}'}" 
                               FontSize="12" TextColor="Gray" />
                    </StackLayout>
                </DataTemplate>
            </CollectionView.ItemTemplate>
        </CollectionView>
        
        <Button Text="Create Order" Command="{Binding CreateOrderCommand}" />
    </VerticalStackLayout>
</ContentPage>

ViewModel and Binding

public partial class MainPage : ContentPage
{
    public MainPage()
    {
        InitializeComponent();
        BindingContext = new OrderViewModel();
    }
}

public partial class OrderViewModel : BaseViewModel
{
    private readonly IOrderService _orderService;
    
    [ObservableProperty]
    ObservableCollection<OrderDto> orders;

    public OrderViewModel()
    {
        _orderService = ServiceHelper.GetService<IOrderService>();
    }

    [RelayCommand]
    public async Task LoadOrders()
    {
        if (IsBusy) return;
        
        try
        {
            IsBusy = true;
            var orders = await _orderService.GetAllAsync();
            Orders = new ObservableCollection<OrderDto>(orders);
        }
        catch (Exception ex)
        {
            await Shell.Current.DisplayAlert("Error", ex.Message, "OK");
        }
        finally
        {
            IsBusy = false;
        }
    }

    [RelayCommand]
    public async Task CreateOrder()
    {
        await Shell.Current.GoToAsync("create-order");
    }
}

Platform-Specific Code

// Conditional compilation
#if ANDROID
    var platformSpecific = new Android.SpecificFeature();
#elif IOS
    var platformSpecific = new iOS.SpecificFeature();
#elif WINDOWS
    var platformSpecific = new Windows.SpecificFeature();
#endif

// Or using if statements with IsAndroid, IsIOS, etc.
if (DeviceInfo.Platform == DevicePlatform.Android)
{
    // Android-specific code
}
else if (DeviceInfo.Platform == DevicePlatform.iOS)
{
    // iOS-specific code
}

Navigation

// AppShell.xaml
<Shell FlyoutBehavior="Flyout">
    <TabBar>
        <Tab Title="Orders" Icon="list_icon">
            <ShellContent Title="All Orders" ContentTemplate="{DataTemplate local:OrdersPage}" Route="orders" />
        </Tab>
        <Tab Title="Create" Icon="plus_icon">
            <ShellContent ContentTemplate="{DataTemplate local:CreateOrderPage}" Route="create-order" />
        </Tab>
        <Tab Title="Settings" Icon="settings_icon">
            <ShellContent ContentTemplate="{DataTemplate local:SettingsPage}" Route="settings" />
        </Tab>
    </TabBar>
</Shell>

// Navigate programmatically
await Shell.Current.GoToAsync($"order-detail/{orderId}");

Dependency Injection

// MauiProgram.cs
public static class MauiProgram
{
    public static MauiApp CreateMauiApp()
    {
        var builder = MauiApp.CreateBuilder();
        return builder
            .UseMauiApp<App>()
            .ConfigureServices(services =>
            {
                services.AddSingleton<IOrderService, OrderService>();
                services.AddSingleton<OrderViewModel>();
                services.AddSingleton<MainPage>();
            })
            .Build();
    }
}

Best Practices

  1. Use MVVM: Separate logic from UI
  2. Responsive layouts: Handle different screen sizes
  3. Async/await: Keep UI responsive
  4. Platform abstractions: Hide platform differences
  5. Test on devices: Emulator ≠ real device

Related Concepts

  • Xamarin.Forms (predecessor)
  • React Native
  • Flutter
  • Blazor for web

Summary

MAUI enables building native cross-platform applications with C# and XAML. Share business logic across platforms while maintaining native look and feel.