Post

A simple but surprising tour of the capabilities of .NET MAUI with Meow.

En este artículo, exploraremos el potencial que .NET MAUI ofrece a la aplicación Meow. Descubrirás cómo estas características pueden enriquecer tu experiencia, ¡te encantará!

INFO: The authenticity of my publications is my most sincere commitment. I always start by putting my ideas into my own words and occasionally use GPT-3.5 to improve the content. In this way, I seek to ensure maximum clarity in my writings. If I use GPT-3.5 or another similar tool again in the future, I will share it openly to maintain transparency at all times.


Fuente de inspiracion para crear Meow

Meow es una modesta pero funcional aplicación desarrollada en .NET MAUI. Su origen se remonta a una publicación que encontré en la página de Freecodecamp, donde se mencionaban APIs públicas gratuitas disponibles para desarrolladores. Entre todas ellas, una en particular captó mi atención: TheCatAPI. Esta API ofrece un amplio catálogo de fotos de adorables gatitos, acompañado de una documentación excepcionalmente clara y organizada. Inspirado por esta fuente de información, me lancé a la creación de Meow.

En cuanto al diseño, requería referencias visuales para las interfaces, y las encontré en dos fuentes clave: Dribbble y el ejemplo de una aplicación web proporcionado por TheCatAPI. Estas influencias visuales me sirvieron de guía para dar forma a las interfaces de Meow, contribuyendo así a su creación.


Estructura y finalidad de VotePage

Cuando se inicia la aplicación Meow lo primero que se ve es la interfaz de VotePage, esta página se usa para votar a los gatitos, se que suena terrible que gatito nos gusta o no, pero queria que tuviera ese dinanismo.

10-1-component-spacing-in-votepage Component spacing in VotePage

Cuando hagan clic en los botones de “Me gusta” o “No me gusta”, primero se mostrará una imagen de fondo centrada del logotipo de la aplicación, que luego desaparecerá mediante una animación de opacidad. Posteriormente, la imagen cambiará de forma aleatoria a una foto diferente de un gatito. Este sería el flujo de la acción.

Si lo vemos en código este flujo de información recae en el objeto de lista llamada Cats, y cuando se inicia el contructor de VoteViewModel inicia el método InitializeDataAsync() y genera un gatito al azar, ahora cuando hace click para votar por si o por no, el método GetKittyAsync() es el responsable de ello y genera el mismo flujo de InitializeDataAsync(), generando un gatito al azar.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public VoteViewModel(ICatService catService)
{
    _catService = catService;
    InitializeDataAsync();
}

public async Task InitializeDataAsync()
{
    Cats = await _catService.GetRandomKitty();
}

[RelayCommand]
public async Task GetKittyAsync()
{
    await InitializeDataAsync();
}

Cuando quieras añadir un gatito a tus favoritos, simplemente toca la imagen. Al hacerlo, se activa la función ManageFavoriteKittenAsync. Esto provoca que el corazón se vuelva rojo, luego se inicie una animación Lottie y se muestra un encantador efecto de corazones sobre la imagen. Además, el gatito se guarda en la página de “Favoritos” denominada FavoritePage. En caso de que decidas que el gatito no te guste tendrás que tocar nuevamente la imagen para que el corazón vuelva a su estado de corazón vacío.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
[RelayCommand]
public async Task ManageFavoriteKittenAsync()
{
    IsBusy = true;
    bool isAddingFavorite = (LayoutState == LayoutState.None);
    await ToggleFavoriteKittenAsync(isAddingFavorite);
    IsBusy = false;
}

private async Task ToggleFavoriteKittenAsync(bool isAdding)
{
    if (isAdding)
    {
        await _catService.AddFavoriteKitten(Cats.FirstOrDefault().Id);
        ImageHeart = "icon_heart_solid.png";
        LayoutState = LayoutState.Success;
        Progress = TimeSpan.Zero;
        IsAnimation = true;
    }
    else
    {
        await _catService.RemoveFavoriteKitten(Cats.FirstOrDefault().Id);
        ImageHeart = "icon_heart_outline.png";
        LayoutState = LayoutState.None;
    }
}

Estructura y finalidad de BreedPage

En la página de BreedPage, podrás explorar las diversas razas de gatitos que existen en todo el mundo, desde las más comunes hasta las exóticas que quizás no conozcas. La interfaz te ofrece un vistazo completo a esta diversidad felina.

10-1-component-spacing-in-breedspage Component spacing in BreedPage

El método InitializeDataAsync desempeña un papel fundamental. Carga información sobre las razas de gatitos de forma asincrónica, y mientras lo hace, actualiza algunas variables clave como IsBusy, Breeds, SelectedBreed y KittensByBreed. Comienza mostrando un indicador de carga que desaparece a medida que se completan las operaciones, logrando que IsBusy pase a ser “false”.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[ObservableProperty]
private Breed selectedBreed;

public BreedsViewModel(ICatService catService)
{
    _catService = catService;
    InitializeDataAsync();
}

public async Task InitializeDataAsync()
{
    IsBusy = true;
    Breeds = await _catService.GetBreeds();
    SelectedBreed = Breeds.FirstOrDefault();
    KittensByBreed = await _catService.GetRandomKittensByBreed(SelectedBreed.Id);
    IsBusy = false;
}

Cuando desees cambiar la raza de tu gatito, puedes hacerlo con facilidad. La propiedad SelectedItem="{Binding SelectedBreed}" del collectionview de razas es la encargada de añadir el nuevo valor. Gracias a los métodos parciales que MVVM Community Toolkit proporciona, este proceso se vuelve sencillo.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<CollectionView
    Grid.Row="0"
    ItemsSource="{Binding Breeds}"
    SelectedItem="{Binding SelectedBreed}"
    SelectionMode="Single">
    <CollectionView.ItemsLayout>
        <GridItemsLayout
            HorizontalItemSpacing="12"
            Orientation="Horizontal" />
    </CollectionView.ItemsLayout>
    <CollectionView.ItemTemplate>
        <DataTemplate x:DataType="model:Breed">
            <Border
                Padding="14"
                StrokeShape="{RoundRectangle CornerRadius='8,8,8,8'}"
                StrokeThickness="0">
                <Label
                    Style="{StaticResource TxtSubtitle1_1}"
                    Text="{Binding Name}" />
            </Border>
        </DataTemplate>
    </CollectionView.ItemTemplate>
</CollectionView>

Para realizar el cambio de raza, simplemente crea un método parcial llamado OnSelectedBreedChanged derivado del objeto SelectedBreed que toma como parámetro el valor de la raza seleccionada. Luego, implementa este nuevo valor en la lista de KittensByBreed usando el servicio GetRandomKittensByBreed. Como resultado, la colección de KittensByBreed se actualiza, y podrás ver un máximo de 10 fotos de la nueva raza seleccionada, con sus respectivos títulos, descripciones y enlaces a través de la propiedad visible asociada a SelectedBreed.

1
2
3
4
5
6
7
8
9
10
11
12
[ObservableProperty]
private Breed selectedBreed;

partial void OnSelectedBreedChanged(Breed value)
{
    SelectedBreedAsync(value.Id);
}

public async Task SelectedBreedAsync(string id)
{
    KittensByBreed = await _catService.GetRandomKittensByBreed(id);
}

Estructura y finalidad de FavoritePage

Esta página registra los “me gusta” de los gatitos favoritos.

10-1-component-spacing-in-favoritepage Component spacing in FavoritePage

En esta página, utilizamos un “CollectionView” cuyo origen de elementos es FavoriteCats. Esta lista se actualiza de manera asincrónica a través del servicio GetFavoriteKittens(). Cuando un usuario hace clic en una de las imágenes, el gatito seleccionado se elimina gracias a la propiedad SelectionChangedCommand que está vinculado a DeleteFavoriteKittenCommand. El método asociado a este comando se encarga de utilizar el servicio DeleteFavoriteKitten() para eliminar al gato seleccionado y para luego actualizar la lista de gatitos de FavoriteCats con el servicio de GetFavoriteKittens().

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public FavoriteViewModel(ICatService catService)
{
    _catService = catService;
    InitializeDataAsync();
}

public async Task InitializeDataAsync()
{
    await PerformOperationAsync(async () =>
    {
        FavoriteCats = await _catService.GetFavoriteKittens();
    });
}

[RelayCommand]
public async Task DeleteFavoriteKittenAsync()
{
    await PerformOperationAsync(async () =>
    {
        await _catService.DeleteFavoriteKitten(SelectedFavoriteCat.Id);
        FavoriteCats = await _catService.GetFavoriteKittens();
    });
}

private async Task PerformOperationAsync(Func<Task> operation)
{
    IsBusy = true;
    await operation.Invoke();
    IsBusy = false;
}

Comtroles personalizados

CustomStringSplitterView

El control personalizado CustomStringSplitterView se utiliza para mostrar una cadena de texto dividida en palabras, donde cada palabra está contenida en un borde con un fondo y se muestran horizontalmente dentro de un ScrollView. Los colores de fondo y texto pueden estar vinculados a temas de la aplicación mediante el método SetAppThemeColor, lo que permite que la apariencia se ajuste automáticamente a los modos de luz y oscuridad de la aplicación.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
public class CustomStringSplitterView : ContentView
{
    private StackLayout itemsLayout;

    public static readonly BindableProperty InputTextProperty =
        BindableProperty.Create(nameof(InputText), typeof(string), typeof(CustomStringSplitterView), null, propertyChanged: OnInputTextChanged);

    public string InputText
    {
        get { return (string)GetValue(InputTextProperty); }
        set { SetValue(InputTextProperty, value); }
    }

    public CustomStringSplitterView()
    {
        itemsLayout = new StackLayout
        {
            Spacing = 6,
            Orientation = StackOrientation.Horizontal
        };

        Content = new ScrollView
        {
            HorizontalScrollBarVisibility = ScrollBarVisibility.Never,
            Orientation = ScrollOrientation.Horizontal,
            Content = itemsLayout
        };
    }

    private static void OnInputTextChanged(BindableObject bindable, object oldValue, object newValue)
    {
        if (bindable is CustomStringSplitterView splitter)
        {
            splitter.UpdateItems();
        }
    }

    private void UpdateItems()
    {
        itemsLayout.Children.Clear();

        if (!string.IsNullOrWhiteSpace(InputText))
        {
            string cleanedText = InputText.Replace(",", "");
            string[] items = cleanedText.Split(' ');

            foreach (string item in items)
            {
                Label label = new()
                {
                    Text = item,
                    HorizontalTextAlignment = TextAlignment.Center,
                    VerticalTextAlignment = TextAlignment.Center

                };
                label.SetAppThemeColor(Label.TextColorProperty, Color.FromArgb("#703EDB"), Color.FromArgb("#9F79F1"));

                Border border = new()
                {
                    Content = label,
                    Padding = new Thickness(8, 6, 8, 6),
                    StrokeShape = new RoundRectangle
                    {
                        CornerRadius = new CornerRadius(7, 7, 7, 7)
                    },
                    StrokeThickness = 0,
                };
                border.SetAppThemeColor(Border.BackgroundColorProperty, Color.FromArgb("#F4F0FC"), Color.FromArgb("#1C0F37"));

                itemsLayout.Children.Add(border);
            }
        }
    }
}

RatingView by Naweed Akram

El RatingView, desarrollado por Naweed Akram, posibilita la exhibición de calificaciones mediante estrellas predefinidas o a través de la implementación de figuras personalizadas. Sus propiedades enlazables permiten una personalización flexible tanto en términos de apariencia como de comportamiento de la vista. La lógica de actualización de estas propiedades se encarga de asegurar la coherencia visual de la calificación en la vista


Resources


Publication in Spanish


Conlusiones

La modesta aplicación Meow ha sido creada con el propósito de destacar el inmenso potencial de .NET MAUI. Al aprovechar de manera eficaz herramientas como .NET MAUI Community Toolkit y MVVM Toolkit, logra crear una aplicación que es tanto sencilla de desarrollar como altamente escalable.

Feel free to give me your opinion and with the help of my repository, draw your own conclusions. If you have any constructive questions or suggestions, I would very much like to read them. Thanks for your time.

This post is licensed under CC BY 4.0 by the author.