Saltar al contenido
Ricardo RetamalSoftware y cosas que vale la pena construir
/
Conversemos
← Ver todos los escritos
Software3 min read

Cómo Usar StateContainers en .NET MAUI para Manejar el Estado en Aplicaciones

¿Cuantas veces has querido manejar distintos estados en tu app? Puesto así creo que nunca jaja, así que mejor aterricemos el concepto.

Publicado originalmente en Medium ↗

Ejemplo de implementación de StateContainers en .NET MAUI
Ejemplo de implementación de StateContainers en .NET MAUI

¿Cuantas veces has querido manejar distintos estados en tu app? Puesto así creo que nunca jaja, así que mejor aterricemos el concepto.

Tienes una app y el usuario tiene acceso a ciertos módulos dependiendo del nivel de suscripción o si está logueado o no, con StateContainers puedes ir manejando este tipo de situaciones.

¿Pero dónde está lo cool de esto? Bueno, lo cool está en que son un tipo de estructura que te permite gestionar y almacenar estos estados de forma centralizada y compartir datos entre páginas sin necesidad de pasar información de manera explícita en la navegación.

¿Qué ventajas tienen?

  • Mantenibilidad: al centralizar el estado, el código resultante es más organizado y fácil de mantener.
  • Reactividad: los states containers notifican automáticamente a las vistas cuando el estado cambia.
  • Comunicación simplificada: facilitan la interacción entre componentes que no están directamente relacionados en la jerarquía de navegación.
  • Reutilización: el mismo contenedor puede ser utilizado en múltiples componentes o páginas.

Si revisas la documentación hay muchas más ventajas pero estas en particular me parecieron interesantes.

Pero un artículo sin un demo no funciona así que démosle! hagamos un pequeño demo donde podamos controlar el estado de un usuario, es decir si está autenticado o no.

Así que abramos Visual Studio y creemos una nueva solución para .Net MAUI y coloquémosle un súper nombre impactante que describa totalmente lo que queremos, en mi caso: StateContainersDemo.

Listo, ya con nuestra solución creada tenemos que asegurarnos de tener instalado el Community Toolkit, así que lo puedes instalar desde la vista de Nuget o con este comando:

Install-Package CommunityToolkit.Maui

Seguidamente nuestro archivo MauiProgram.cs debería quedar de la siguiente manera:

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


#if DEBUG
    		builder.Logging.AddDebug();
#endif


            return builder.Build();
        }
    }

GitHub Gist ↗

Seguidamente tendremos que crear dos carpetas una para los modelos (Models) y otra para las vistas (Views).

El modelo con el que manejaremos el estado luce de la siguiente manera:

 public class UserSession
    {
        public bool IsAuthenticated { get; set; }
        public string Username { get; set; }
    }

GitHub Gist ↗

Y nos crearemos un ContentPage para hacer el login llamada: LoginPage.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"
             xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
             x:Class="StateContainersDemo.Views.LoginPage"
             Title="LoginPage">
    <VerticalStackLayout 
        Padding="20"
        VerticalOptions="Center"
        toolkit:StateContainer.CurrentState="{Binding CurrentState}"
        toolkit:StateContainer.CanStateChange="{Binding CanStateChange}">


        <toolkit:StateContainer.StateViews>
            <VerticalStackLayout toolkit:StateView.StateKey="Default">
                <Label Text="Please enter your login details" 
                       HorizontalOptions="Center" />


                <Entry Placeholder="Username" Text="{Binding Username}" />
                <Entry Placeholder="Password" IsPassword="True" Text="{Binding Password}" />
                <Button Text="Login" 
                   Command="{Binding LoginCommand}"
                   IsEnabled="{Binding CanStateChange}" />
            </VerticalStackLayout>


            <VerticalStackLayout toolkit:StateView.StateKey="Loading">
                <ActivityIndicator IsRunning="True" />
                <Label Text="Logging in..." HorizontalOptions="Center" />
            </VerticalStackLayout>


            <Label toolkit:StateView.StateKey="Success" 
                   Text="Login successful!" 
                   HorizontalOptions="Center" />
        </toolkit:StateContainer.StateViews>
    </VerticalStackLayout>
</ContentPage>

GitHub Gist ↗

Aquí, como podrás ver estamos manejando 3 estados, un estado inicial donde el usuario ingresará sus credenciales, seguido de un estado “Loading” donde estamos simulando el proceso de autenticación y finalmente un estado “Success” donde ya el usuario está logeado en el sistema.

Para hacer la simulación del login, nos creamos un archivo LoginViewModel en nuestra carpeta ViewModels que debería lucir de la siguiente manera:

public class LoginViewModel : INotifyPropertyChanged
{
    private string _username = string.Empty;
    public string Username
    {
        get => _username;
        set
        {
            if (_username != value)
            {
                _username = value;
                OnPropertyChanged();
            }
        }
    }


    private string _password = string.Empty;
    public string Password
    {
        get => _password;
        set
        {
            if (_password != value)
            {
                _password = value;
                OnPropertyChanged();
            }
        }
    }


    private string _currentState = string.Empty;
    public string CurrentState
    {
        get => _currentState;
        set
        {
            if (_currentState != value)
            {
                _currentState = value;
                OnPropertyChanged();
            }
        }
    }


    private bool _canStateChange = true;
    public bool CanStateChange
    {
        get => _canStateChange;
        set
        {
            if (_canStateChange != value)
            {
                _canStateChange = value;
                OnPropertyChanged();
                // Notify that the command's CanExecute has changed
                ((Command)LoginCommand).ChangeCanExecute();
            }
        }
    }


    // Implement the LoginCommand using ICommand
    public ICommand LoginCommand { get; }


    public LoginViewModel()
    {
        CurrentState = "Default";


        // Initialize the command with a method that determines CanExecute
        LoginCommand = new Command(async () => await LoginAsync(), () => CanStateChange);
    }


    // Method to perform the login logic
    private async Task LoginAsync()
    {
        if (!CanStateChange)
            return;


        // Simulate loading state
        CurrentState = "Loading";
        CanStateChange = false;


        try
        {
            // Simulate a login process (replace this with real logic)
            await Task.Delay(2000);


            // Update to Success state
            CurrentState = "Success";
            CanStateChange = true;


            // Show success message
            await Application.Current.MainPage.DisplayAlert("Login", "Login successful", "OK");


            // Reset the state
            CurrentState = string.Empty;
        }
        catch (Exception ex)
        {
            // Handle errors and display error message
            await Application.Current.MainPage.DisplayAlert("Error", $"Login failed: {ex.Message}", "OK");
            CurrentState = string.Empty;
            CanStateChange = true;
        }
    }


    // INotifyPropertyChanged implementation
    public event PropertyChangedEventHandler PropertyChanged;


    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

GitHub Gist ↗

Recuerda inyectarlo en el archivo LoginPage.xaml.cs de la siguiente manera:

public partial class LoginPage : ContentPage
{
    public LoginViewModel ViewModel { get; } = new LoginViewModel();


    public LoginPage()
	{
		InitializeComponent();
        BindingContext = ViewModel;
    }
}

GitHub Gist ↗

Como comentaba anteriormente esto da para implementar en muchos lugares donde uno normalmente hacía las cosas a manos por lo que siempre es bueno ver que es lo que ofrecen actualmente las librerías y así enfocarnos en la lógica del negocio más que en el cómo hacemos las cosas.

El código lo puedes conseguir aquí:

GitHub - rretamal/StateContainers
Contribute to rretamal/StateContainers development by creating an account on GitHub.github.com

Happy Coding!