¿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();
}
}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; }
}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>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));
}
}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;
}
}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/StateContainersContribute to rretamal/StateContainers development by creating an account on GitHub.github.com
Happy Coding!