Skip to content

MAUI Mobile Template - Developer Preparation Guide

Overview

This guide provides essential information for developers implementing the ConnectSoft MAUI mobile template. It covers prerequisites, architecture principles, key patterns, development guidelines, and references to help you get started quickly.

Target Audience: Developers, architects, and engineers working on MAUI mobile template implementation

Prerequisites

Required Knowledge

  • .NET MAUI Fundamentals:
  • MAUI project structure and lifecycle
  • XAML and data binding
  • MVVM patterns
  • Shell navigation
  • Platform-specific implementations

  • .NET Core/ASP.NET Core:

  • Dependency Injection (Microsoft.Extensions.DependencyInjection)
  • Logging (Microsoft.Extensions.Logging)
  • Localization (Microsoft.Extensions.Localization)
  • Options pattern (Microsoft.Extensions.Options)
  • Configuration (Microsoft.Extensions.Configuration)

  • ConnectSoft Ecosystem:

  • Template layering architecture (Base Template + Specialized Templates)
  • Git submodules for template composition
  • Template metadata composition (template.json, extend files)
  • ConnectSoft.Extensions.* libraries usage

Required Tools

  • .NET 9.0 SDK - Latest version
  • Visual Studio 2022 (17.8+) or Visual Studio Code with C# extension
  • .NET MAUI workload installed:
    dotnet workload install maui
    
  • Platform SDKs:
  • iOS: Xcode 15+ (macOS only)
  • Android: Android SDK (API 34+)
  • Git - For submodule management
  • Azure DevOps CLI (optional) - For pipeline management

Development Environment Setup

  1. Install .NET MAUI:

    dotnet workload install maui
    dotnet workload list  # Verify installation
    

  2. Verify MAUI Installation:

    dotnet new maui -n TestApp
    cd TestApp
    dotnet build
    

  3. Platform-Specific Setup:

  4. iOS (macOS only): Install Xcode, configure provisioning profiles
  5. Android: Install Android SDK, configure emulators or physical devices

  6. Clone ConnectSoft Repositories:

    git clone <base-template-repo>
    git clone <uikit-repo>
    git clone <full-template-repo>
    

Architecture Principles

Core Principle: Use Built-in Frameworks First

DO: - Use ILogger<T> from Microsoft.Extensions.Logging directly - Use IStringLocalizer<T> from Microsoft.Extensions.Localization directly - Use MAUI Shell navigation (Shell.Current.GoToAsync()) directly - Use SecureStorage and Preferences from Microsoft.Maui.Storage directly - Use Connectivity from Microsoft.Maui.Networking directly - Use MAUI lifecycle events (OnStart, OnSleep, OnResume) directly

DON'T: - Create custom ILoggingService abstraction - Create custom ILocalizationService abstraction - Create custom INavigationService abstraction (unless minimal wrapper for testability) - Create custom IStorageService abstraction - Create custom IConnectivityService abstraction - Create custom IAppLifecycleService abstraction

Minimal Abstractions Rule

Only create abstractions when absolutely necessary for testability. The only exception is DialogService which wraps Page.DisplayAlert() for testability purposes.

Example - DialogService (Minimal Wrapper):

// Infrastructure/DialogService.cs
public interface IDialogService
{
    Task<bool> DisplayAlert(string title, string message, string accept, string cancel);
    Task<string> DisplayActionSheet(string title, string cancel, string destruction, params string[] buttons);
}

public class DialogService : IDialogService
{
    private readonly Page _page;

    public DialogService(Page page)
    {
        _page = page;
    }

    public async Task<bool> DisplayAlert(string title, string message, string accept, string cancel)
    {
        return await _page.DisplayAlert(title, message, accept, cancel);
    }

    // ... other methods
}

Example - Direct Usage (Preferred):

// ViewModel - Use SecureStorage directly
public class LoginViewModel : BaseViewModel
{
    public async Task SaveToken(string token)
    {
        await SecureStorage.SetAsync("oauth_token", token);
    }

    public async Task<string> GetToken()
    {
        return await SecureStorage.GetAsync("oauth_token");
    }
}

Key Patterns

1. Dependency Injection Setup

MauiProgram.cs Pattern:

public static class MauiProgram
{
    public static MauiApp CreateMauiApp()
    {
        var builder = MauiApp.CreateBuilder();
        builder
            .UseMauiApp<App>()
            .ConfigureFonts(fonts =>
            {
                fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
            });

        // Built-in services
        builder.Services.AddLogging(logging =>
        {
            logging.AddDebug();
            logging.AddConsole();
        });

        builder.Services.AddLocalization();

        // ConnectSoft extensions
        builder.Services.AddConnectSoftObservability(options =>
        {
            options.UseApplicationInsights();
        });

        // Minimal wrappers (only for testability)
        builder.Services.AddSingleton<IDialogService, DialogService>();

        // ViewModels and Services
        builder.Services.AddTransient<MainViewModel>();
        builder.Services.AddTransient<MainPage>();

        return builder.Build();
    }
}

2. Logging Pattern

Use ILogger Directly:

public class MyService : BaseService
{
    private readonly ILogger<MyService> _logger;

    public MyService(ILogger<MyService> logger) : base(logger)
    {
        _logger = logger;
    }

    public async Task DoWork()
    {
        _logger.LogInformation("Starting work");

        try
        {
            // Work here
            _logger.LogInformation("Work completed successfully");
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error occurred during work");
            throw;
        }
    }
}

Structured Logging:

_logger.LogInformation("User {UserId} logged in at {Timestamp}", userId, DateTime.UtcNow);

3. Localization Pattern

Use IStringLocalizer Directly:

public class MainViewModel : BaseViewModel
{
    private readonly IStringLocalizer<MainViewModel> _localizer;

    public MainViewModel(IStringLocalizer<MainViewModel> localizer, ILogger<MainViewModel> logger)
        : base(logger)
    {
        _localizer = localizer;
    }

    public string Title => _localizer["Title"];
    public string WelcomeMessage => _localizer["WelcomeMessage", UserName];
}

Resource Files:

Resources/
  Strings/
    Resources.resx (default)
    Resources.en.resx (English)
    Resources.es.resx (Spanish)

4. Navigation Pattern

Use Shell Navigation Directly:

// Navigate to route
await Shell.Current.GoToAsync("//login");

// Navigate with parameters
await Shell.Current.GoToAsync("//details", new Dictionary<string, object>
{
    { "ItemId", itemId }
});

// Register routes in AppShell.xaml.cs
Routing.RegisterRoute("details", typeof(DetailsPage));

5. Storage Pattern

Use SecureStorage for Sensitive Data:

// Save token
await SecureStorage.SetAsync("oauth_token", token);

// Get token
var token = await SecureStorage.GetAsync("oauth_token");

// Remove token
SecureStorage.Remove("oauth_token");

Use Preferences for Non-Sensitive Data:

// Save preference
Preferences.Set("user_preference", value);

// Get preference
var value = Preferences.Get("user_preference", defaultValue);

// Remove preference
Preferences.Remove("user_preference");

6. Application Lifecycle Pattern

Use Built-in Lifecycle Events:

public partial class App : Application
{
    protected override void OnStart()
    {
        // App started - initialize services, load data
        _logger.LogInformation("Application started");
    }

    protected override void OnSleep()
    {
        // App going to background - save state
        _logger.LogInformation("Application sleeping");
        SaveApplicationState();
    }

    protected override void OnResume()
    {
        // App resuming from background - restore state
        _logger.LogInformation("Application resuming");
        RestoreApplicationState();
    }
}

Template Layering Architecture

Understanding the Three-Layer Model

The MAUI mobile templates follow ConnectSoft's three-layer template architecture:

flowchart TB
    subgraph Layer1["Layer 1: Shared Libraries"]
        LIB1[ConnectSoft.Extensions.Logging]
        LIB2[ConnectSoft.Extensions.Localization]
        LIB3[ConnectSoft.Extensions.Observability]
        LIB4[ConnectSoft.Maui.UIKit]
    end

    subgraph Layer2["Layer 2: Base Template"]
        BASE[ConnectSoft.MauiBaseTemplate<br/>Core Infrastructure<br/>Cross-Cutting Concerns]
    end

    subgraph Layer3["Layer 3: Full Template"]
        FULL[ConnectSoft.MauiTemplate<br/>Base + Full Features<br/>API, Auth, Offline, Platform]
    end

    Layer1 -->|NuGet Packages| Layer2
    Layer1 -->|NuGet Packages| Layer3
    Layer2 -->|Git Submodule| Layer3
Hold "Alt" / "Option" to enable pan & zoom

Base Template Structure

ConnectSoft.MauiBaseTemplate/
├── src/
│   ├── MauiBase.Application/     # Main app (MauiProgram.cs, AppShell)
│   ├── MauiBase.Core/            # BaseViewModel, BaseService
│   ├── MauiBase.Infrastructure/  # Minimal wrappers (DialogService)
│   └── MauiBase.Shared/          # Resources (localization, styles)
├── tests/
│   ├── MauiBase.UnitTests/
│   └── MauiBase.IntegrationTests/
├── docs/
└── .template.config/
    └── template.json

Full Template Structure (with Submodule)

ConnectSoft.MauiTemplate/
├── base-template/                # Git submodule → ConnectSoft.MauiBaseTemplate
│   ├── src/
│   │   ├── MauiBase.Application/
│   │   ├── MauiBase.Core/
│   │   └── ...
│   └── tests/
├── src/
│   ├── MauiApp.Application/      # Full-featured app
│   ├── MauiApp.Domain/          # Domain logic (if needed)
│   └── MauiApp.Infrastructure/  # Full implementations
├── tests/
│   ├── MauiApp.UnitTests/
│   ├── MauiApp.IntegrationTests/
│   └── MauiApp.UITests/
├── docs/
└── .template.config/
    └── maui.template.extend.json  # Extends base template.json

Git Submodule Usage

Initializing Submodule:

# Clone full template repository
git clone <full-template-repo>
cd ConnectSoft.MauiTemplate

# Initialize and update submodule
git submodule update --init --recursive

Updating Submodule:

# Update to latest base template
cd base-template
git pull origin main
cd ..
git add base-template
git commit -m "Update base template submodule"

Development Workflow

1. Starting a New Feature

  1. Review Backlog Plan:
  2. Read the relevant Epic, Feature, and Task descriptions
  3. Understand acceptance criteria
  4. Review dependencies

  5. Set Up Development Environment:

  6. Clone repository (and submodule if needed)
  7. Restore packages: dotnet restore
  8. Build solution: dotnet build
  9. Run tests: dotnet test

  10. Create Feature Branch:

    git checkout -b feature/MAUI-FEAT-XXX-short-description
    

  11. Implement Feature:

  12. Follow architecture principles
  13. Use built-in frameworks
  14. Write tests as you go
  15. Update documentation

  16. Verify Implementation:

  17. All tests pass
  18. Code builds successfully
  19. Manual testing on iOS and Android
  20. Code review ready

2. Testing Guidelines

Unit Tests: - Test ViewModels, Services, and business logic - Use Moq or NSubstitute for mocking - Use FluentAssertions for assertions - Aim for 80%+ code coverage

Integration Tests: - Test API integration, database operations - Use test app factory pattern - Test end-to-end flows

UI Tests: - Test navigation, user interactions - Test on both iOS and Android - Use Appium or MAUI UITest framework

3. Code Quality Standards

Follow ConnectSoft Standards: - Use StyleCop analyzers - Follow .editorconfig rules - Write XML documentation comments - Handle nullable reference types properly - Use async/await correctly

Code Review Checklist: - [ ] Uses built-in frameworks (no unnecessary abstractions) - [ ] Follows MVVM patterns - [ ] Has unit tests - [ ] Documentation updated - [ ] Builds successfully - [ ] Tests pass - [ ] Works on both iOS and Android

Common Pitfalls and Solutions

Pitfall 1: Creating Unnecessary Abstractions

Problem: Creating custom abstractions for built-in frameworks

// DON'T DO THIS
public interface INavigationService
{
    Task NavigateToAsync(string route);
}

Solution: Use built-in frameworks directly

// DO THIS
await Shell.Current.GoToAsync("//login");

Pitfall 2: Not Using Dependency Injection

Problem: Creating services with new keyword

// DON'T DO THIS
var service = new MyService();

Solution: Use dependency injection

// DO THIS
public class MyViewModel : BaseViewModel
{
    private readonly IMyService _service;

    public MyViewModel(IMyService service, ILogger<MyViewModel> logger)
        : base(logger)
    {
        _service = service;
    }
}

Pitfall 3: Hard-coding Strings

Problem: Hard-coding user-facing strings

// DON'T DO THIS
Title = "Welcome";

Solution: Use localization

// DO THIS
private readonly IStringLocalizer<MyViewModel> _localizer;
Title = _localizer["Welcome"];

Pitfall 4: Not Handling Platform Differences

Problem: Assuming same behavior on iOS and Android

// DON'T DO THIS (without platform checks)
await SecureStorage.SetAsync("key", "value"); // May behave differently

Solution: Test on both platforms and handle differences

// DO THIS (with platform-specific handling if needed)
#if IOS
    // iOS-specific code
#elif ANDROID
    // Android-specific code
#endif

Reference Documentation

ConnectSoft Documentation

External Documentation

ConnectSoft Code References

  • ConnectSoft.BaseTemplate - Backend template patterns (DI, logging, options)
  • ConnectSoft.Blazor.UIKit - UI component library patterns
  • ConnectSoft.Blazor.ShellTemplate - Shell template patterns
  • ConnectSoft.Extensions.* - Extension library usage examples

Getting Help

Internal Resources

  1. ConnectSoft Documentation:
  2. Review template architecture docs
  3. Check existing template implementations
  4. Review extension library documentation

  5. Code Examples:

  6. ConnectSoft.BaseTemplate for backend patterns
  7. ConnectSoft.Blazor templates for frontend patterns
  8. ConnectSoft.Extensions.* for extension patterns

  9. Team Communication:

  10. Ask questions in team channels
  11. Request code reviews early
  12. Share findings and patterns

External Resources

  1. .NET MAUI Community:
  2. .NET MAUI GitHub
  3. .NET MAUI Discussions

  4. Stack Overflow:

  5. Tag: dotnet-maui
  6. Tag: xamarin.forms (legacy, but relevant)

Next Steps

  1. Read the Backlog Plan:
  2. Review MAUI Mobile Template Backlog Plan
  3. Understand the Epic, Feature, and Task you're working on

  4. Set Up Your Environment:

  5. Install required tools
  6. Clone repositories
  7. Verify setup

  8. Review Architecture:

  9. Understand the three-layer model
  10. Review base template structure
  11. Understand template composition

  12. Start Development:

  13. Pick a task from the backlog
  14. Follow development workflow
  15. Write tests and documentation

Summary

Key takeaways for developers:

  • Use built-in frameworks first - ILogger, IStringLocalizer, Shell navigation, SecureStorage, Preferences
  • Minimal abstractions - Only create wrappers for testability when absolutely necessary
  • Follow ConnectSoft patterns - Template layering, Git submodules, template composition
  • Test on both platforms - iOS and Android have differences
  • Write comprehensive tests - Unit, integration, and UI tests
  • Document as you go - Update documentation with code changes

Happy coding!