Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature Request] IServiceCollection extension methods for adding Views and VIewModels #56

Open
PreussenKaiser opened this issue Jul 13, 2022 · 0 comments

Comments

@PreussenKaiser
Copy link

PreussenKaiser commented Jul 13, 2022

Summary

Inspired by this discussion.
Extension methods for registering Views and their associated ViewModels in a IServiceCollection. Shell routes can also be specified.

Motivation

Implementing the MVVM pattern for MAUI and Xamarin can be abstracted away with extension methods. This would save time on the developer's end while keeping MauiProgram declarative without the need to create a separate class for bootstrapping. It can also guide developers into using best practices; for example: transient service lifetimes for Views.

Code Example

Repo containing sample can be found here.

Implementation

/// <summary>
/// Adds a View and associated ViewModel to a <see cref="IServiceCollection"/>.
/// </summary>
/// <typeparam name="TView">The View of type <see cref="BindableObject"/>.</typeparam>
/// <typeparam name="TViewModel">The ViewModel which implements <see cref="INotifyPropertyChanged"/>.</typeparam>
/// <param name="services">The <see cref="IServiceCollection"/> to add the View and ViewModel to.</param>
/// <param name="lifetime">The <see cref="ServiceLifetime"/> of the View and ViewModel.</param>
/// <returns>A reference to this instance after the operation has completed.</returns>
public static IServiceCollection AddViewAndViewModel<TView, TViewModel>(this IServiceCollection services, ServiceLifetime lifetime = ServiceLifetime.Transient)
    where TView : BindableObject
    where TViewModel : class, INotifyPropertyChanged
{
    switch (lifetime)
    {
        case ServiceLifetime.Singleton:
            services.AddSingleton<TView>()
                    .AddSingleton<TViewModel>();

            break;

        case ServiceLifetime.Scoped:
            services.AddScoped<TView>()
                    .AddScoped<TViewModel>();

            break;

        case ServiceLifetime.Transient:
            services.AddTransient<TView>()
                    .AddTransient<TViewModel>();

            break;

        default:
            throw !Enum.IsDefined<ServiceLifetime>(lifetime)
                ? new InvalidEnumArgumentException(nameof(lifetime), (int)lifetime, typeof(ServiceLifetime))
                : new NotSupportedException($"{lifetime} not supported");
    }

    return services;
}

/// <summary>
/// Adds a View and ViewModel to <see cref="IServiceCollection"/> and registers it's route.
/// </summary>
/// <typeparam name="TView">The View of type <see cref="BindableObject"/>.</typeparam>
/// <typeparam name="TViewModel">The ViewModel which implements <see cref="INotifyPropertyChanged"/>.</typeparam>
/// <param name="services">The <see cref="IServiceCollection"/> to add the View and ViewModel to.</param>
/// <param name="route">The View's route in <see cref="Shell"/>.</param>
/// <param name="lifetime">The <see cref="ServiceLifetime"/> of the View and ViewModel.</param>
/// <returns>A reference to this instance after the operation has completed.</returns>
public static IServiceCollection AddViewAndViewModelWithRoute<TView, TViewModel>(this IServiceCollection services, string route, ServiceLifetime lifetime = ServiceLifetime.Transient)
    where TView : BindableObject
    where TViewModel : class, INotifyPropertyChanged
{
    Routing.RegisterRoute(route, typeof(TView));

    services.AddViewAndViewModel<TView, TViewModel>(lifetime);

    return services;
}

Usage

public static MauiApp CreateMauiApp()
{
    var assembly = Assembly.GetExecutingAssembly();

    var builder = MauiApp.CreateBuilder();
    builder
	.UseMauiApp<App>()
	.ConfigureFonts(fonts =>
	{
		fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
		fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
	})
	.UseTinyMvvm();

    builder.Services.AddSingleton<ICityService, CityService>();

    builder.Services.AddViewAndViewModel<MainView, MainViewModel>()
		    .AddViewAndViewModel<ListView, ListViewModel>()
		    .AddViewAndViewModelWithRoute<DetailsView, DetailsViewModel>(nameof(DetailsViewModel));

    return builder.Build();
}

Drawbacks

The extensions give developers less control over the service lifetime of individual Views and ViewModels; I can't have a transient View with a singleton ViewModel. It may lead to developers not understanding service lifetimes; for example: why Views typically have a transient lifetime.

The idea itself was discussed for the MAUI Community Tookit, if they were to go through with its implementation there may not be a need to add it here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant