Api Dragon Ball Super

Este es un proyecto desarrollado para plataformas móviles y en este blog muestro de cómo consumir una api(Application Programming Interface) y poder utilizar la información. Implementando Repository Pattern y Model View ViewModel como buenas prácticas y tener un código limpio. Aplicacion: Proyecto desarrollado con .Net C# MAUI 8.0 Especificamos el PageContent con el que inicializaría la aplicación al ejecutarse: using AppDragonBallZ.View; namespace AppDragonBallZ { public partial class App : Application { public App() { InitializeComponent(); MainPage = new NavigationPage(new Characters()); } } } View(Vista): Creamos un nuevo archivo ContentPage con el nombre 'Characters' que será la vista para mostrar los personajes. Métodos de Interfaz: ICharactersRepository. En esta interfáz creamos 3 métodos y de los cuáles solo utilizaremos 2. Una interfaz define un contrato o conjunto de reglas que las clases que lo implementen deben cumplirlos. using AppDragonBallZ.Model; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace AppDragonBallZ.RepositoryPattern.IRepositoryPattern { public interface ICharactersRepository { Task Characters(string uriApi); Task GetCharacterById(long characterId); Task GetCharacterByName(string name); } } Repository Pattern: CharactersRepository, implementa los métodos de la Interfaz: ICharactersRepository. Este contiene únicamente la lógica para consumir la api y poder deserealizarlo y retornar los datos para luego ser utilizado de acuerdo a la información que necesitaríamos visualizar en vista de la aplicación. using AppDragonBallZ.Model; using AppDragonBallZ.Model.DBZmodel; using AppDragonBallZ.RepositoryPattern.IRepositoryPattern; using AppDragonBallZ.Generico; using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace AppDragonBallZ.RepositoryPattern { public class CharactersRepository : ICharactersRepository { public async Task Characters(string uriApi) { Respuesta respuesta = new(); try { string urlApi = uriApi; var httpClient = new HttpClient(); Pagination pagination = new(); var response = await httpClient.GetAsync(urlApi); if (response.IsSuccessStatusCode) { var content = await response.Content.ReadAsStringAsync(); var result = JsonConvert.DeserializeObject(content); pagination = result; } respuesta.Data = pagination; respuesta.Resultado = true; respuesta.Mensaje = "Consulta exitosa"; } catch (Exception ex) { respuesta.Resultado = false; respuesta.Mensaje = "Error en método: Characters - > " + ex.Message.ToString(); } return respuesta; }

Apr 28, 2025 - 07:31
 0
Api Dragon Ball Super

Este es un proyecto desarrollado para plataformas móviles y en este blog muestro de cómo consumir una api(Application Programming Interface) y poder utilizar la información. Implementando Repository Pattern y Model View ViewModel como buenas prácticas y tener un código limpio.

Aplicacion:

Image description

Proyecto desarrollado con .Net C# MAUI 8.0
Image description

Especificamos el PageContent con el que inicializaría la aplicación al ejecutarse:

using AppDragonBallZ.View;

namespace AppDragonBallZ
{
    public partial class App : Application
    {
        public App()
        {
            InitializeComponent();
            MainPage = new NavigationPage(new Characters());
        }
    }
}

View(Vista): Creamos un nuevo archivo ContentPage con el nombre 'Characters' que será la vista para mostrar los personajes.


 xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="AppDragonBallZ.View.Characters"
             NavigationPage.HasNavigationBar="False"
             Title="Characters">


    
     BackgroundColor="White">

         >
            
             Margin="5,5">
                 Source="dragonballsuper.png"/>
                 Margin="10">
                    
                         Brush="Black"
                                Opacity="0.9"
                                Offset="5,5"
                                Radius="10" />
                    
                

            

            
             Orientation="Horizontal"
                        HorizontalScrollBarVisibility="Never">
                 HorizontalOptions="Center"
                                       Spacing="5"
                                       Margin="10,10">
                     Text="{Binding Pagination.Meta.TotalItems, StringFormat='Total personajes: {0}'}"
                            FontAttributes="Bold"
                            BackgroundColor="#ba0f42"
                            CornerRadius="20"
                            HeightRequest="35"
                            Padding="15,0,15,0" />
                     Text="{Binding Pagination.Meta.TotalPages, StringFormat='Total página: {0}'}"
                            FontAttributes="Bold"
                            BackgroundColor="#30915f"
                            CornerRadius="20"
                            HeightRequest="35"
                            Padding="15,0,15,0" />
                     Text="{Binding Pagination.Meta.CurrentPage, StringFormat='Página actual: {0}'}"
                            FontAttributes="Bold"
                            BackgroundColor="#2c74ab"
                            CornerRadius="20"
                            HeightRequest="35"
                            Padding="15,0,15,0" />
                     Text="{Binding Pagination.Meta.ItemsPerPage, StringFormat='Cantidad personajes por página: {0}'}"
                            FontAttributes="Bold"
                            BackgroundColor="#2c74ab"
                            CornerRadius="20"
                            HeightRequest="35"
                            Padding="15,0,15,0" />
                
            

                           
             ItemsSource="{Binding Pagination.Characters}"
                            x:Name="characters">
                
                     Orientation="Vertical"
                                        Span="2" />
                
                
                    
                         Padding="20,0,20,0"
                                RowDefinitions="*,*"
                                Margin="0,10">
                            
                             Grid.Row="0">
                                 HeightRequest="280"
                                        Stroke="#87878a"
                                        StrokeShape="RoundRectangle 20,20,20,20"
                                        StrokeThickness="0">
                                    
                                    
                                         StartPoint="0,0"
                                                                EndPoint="1,1">
                                             Color="#5da1a3"
                                                            Offset="0.0" />
                                             Color="#ffffff"
                                                            Offset="0.5" />
                                             Color="#ffffff"
                                                            Offset="1.0" />
                                        
                                    
                                    
                                    
                                         Brush="Black"
                                                Opacity="0.5"
                                                Offset="5,5"
                                                Radius="10" />
                                    
                                
                                
                                 Aspect="AspectFit"
                                       HeightRequest="300"
                                       Source="{Binding Image}" />
                                
                                     Command="{Binding Path=BindingContext.SeleccionarPersonajeCommand, Source={x:Reference characters}}"
                                                          CommandParameter="{Binding Id}" />
                                
                            

                            
                             Grid.Row="1"
                                    Margin="0,0,0,0">
                                 HorizontalOptions="Center"
                                        TextColor="Black"
                                        Text="{Binding Name}"
                                        FontAttributes="Bold"
                                        FontSize="15"
                                        VerticalOptions="End" />
                                 HeightRequest="1"
                                            BackgroundColor="#d1d1d1"
                                            Margin="0,25,0,0" />
                            
                        
                    
                
            

            
             HorizontalOptions="Center"
                                    Margin="0,10"
                                    BindableLayout.ItemsSource="{Binding Pagination}"
                                    x:Name="paginationCharacters">
                 Text="Anterior"
                        IsEnabled="{Binding BtnAnterior}"
                        FontAttributes="Bold"
                        TextColor="White"
                        BackgroundColor="#262626"
                        CornerRadius="20"
                        HeightRequest="35"
                        Margin="0,0,5,0"
                        Padding="15,0,15,0">
                    
                         Command="{Binding Path=BindingContext.AnteriorPaginaCommand, Source={x:Reference paginationCharacters}}"
                                                CommandParameter="{Binding Pagination}" />
                    
                

                 Text="Siguiente"   
                        IsEnabled="{Binding BtnSiguiente}"
                        FontAttributes="Bold"
                        TextColor="White"
                        BackgroundColor="#262626"
                        CornerRadius="20"
                        HeightRequest="35"
                        Margin="5,0,0,0"
                        Padding="15,0,15,0">
                    
                         Command="{Binding Path=BindingContext.SiguientePaginaCommand, Source={x:Reference paginationCharacters}}"
                                                CommandParameter="{Binding Pagination}" />
                    
                
            

        
    


Métodos de Interfaz: ICharactersRepository. En esta interfáz creamos 3 métodos y de los cuáles solo utilizaremos 2. Una interfaz define un contrato o conjunto de reglas que las clases que lo implementen deben cumplirlos.

using AppDragonBallZ.Model;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace AppDragonBallZ.RepositoryPattern.IRepositoryPattern
{
    public interface ICharactersRepository
    {
        Task<Respuesta> Characters(string uriApi);
        Task<Respuesta> GetCharacterById(long characterId);
        Task<Respuesta> GetCharacterByName(string name);

    }
}

Repository Pattern: CharactersRepository, implementa los métodos de la Interfaz: ICharactersRepository. Este contiene únicamente la lógica para consumir la api y poder deserealizarlo y retornar los datos para luego ser utilizado de acuerdo a la información que necesitaríamos visualizar en vista de la aplicación.

using AppDragonBallZ.Model;
using AppDragonBallZ.Model.DBZmodel;
using AppDragonBallZ.RepositoryPattern.IRepositoryPattern;
using AppDragonBallZ.Generico;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace AppDragonBallZ.RepositoryPattern
{
    public class CharactersRepository : ICharactersRepository
    {
        public async Task<Respuesta> Characters(string uriApi)
        {
            Respuesta respuesta = new();
            try
            {                
                string urlApi = uriApi;

                var httpClient = new HttpClient();
                Pagination pagination = new();

                var response = await httpClient.GetAsync(urlApi);
                if (response.IsSuccessStatusCode)
                {
                    var content = await response.Content.ReadAsStringAsync();
                    var result = JsonConvert.DeserializeObject<Pagination>(content);
                    pagination = result;
                }

                respuesta.Data = pagination;
                respuesta.Resultado = true;
                respuesta.Mensaje = "Consulta exitosa";

            }
            catch (Exception ex)
            {
                respuesta.Resultado = false;
                respuesta.Mensaje = "Error en método: Characters - > " + ex.Message.ToString();
            }

            return respuesta;
        }

        public async Task<Respuesta> GetCharacterById(long characterId)
        {
            Respuesta respuesta = new();
            try
            {
                string urlApi = $"{Utilidades.UrlApiDBZ}/{characterId}";

                var httpClient = new HttpClient();
                Character character = new();

                var response = await httpClient.GetAsync(urlApi);
                if (response.IsSuccessStatusCode)
                {
                    var content = await response.Content.ReadAsStringAsync();
                    var result = JsonConvert.DeserializeObject<Character>(content);
                    character = result;
                }

                respuesta.Data = character;
                respuesta.Resultado = true;
                respuesta.Mensaje = "Consulta exitosa";

            }
            catch (Exception ex)
            {
                respuesta.Resultado = false;
                respuesta.Mensaje = "Error en método: GetCharacterById - > " + ex.Message.ToString();
            }

            return respuesta;
        }

        public Task<Respuesta> GetCharacterByName(string name)
        {
            throw new NotImplementedException();
        }
    }
}

ViewModel(Modelo de Vista): CharactersViewModel.
Es una clase que encapsula la lógica de la interfaz de usuario, separándola de la vista (la interfaz de usuario XAML) y del modelo (los datos y la lógica de negocio). Esto facilita el desarrollo de aplicaciones más organizadas, testables y mantenibles.

using AppDragonBallZ.RepositoryPattern;
using AppDragonBallZ.Generico;
using AppDragonBallZ.Model;
using AppDragonBallZ.Model.DBZmodel;
using AppDragonBallZ.RepositoryPattern.IRepositoryPattern;
using AppDragonBallZ.View;
using System.Windows.Input;

namespace AppDragonBallZ.ViewModel
{
    public class CharactersViewModel : BaseViewModel
    {
        #region VARIABLES
        private readonly ICharactersRepository charactersRepository = new CharactersRepository();

        Pagination _pagination = new();
        public bool IsRefreshing { get; set; }

        int _page =1;
        int _limit=10;
        bool _btnAnterior;
        bool _btnSiguiente;

        #endregion

        #region OBJETOS
        public Pagination Pagination
        {
            get { return _pagination; }
            set { SetValue(ref _pagination, value); }
        }
        public bool BtnAnterior
        {
            get { return _btnAnterior; }
            set { SetValue(ref _btnAnterior, value); }
        }
        public bool BtnSiguiente
        {
            get { return _btnSiguiente; }
            set { SetValue(ref _btnSiguiente, value); }
        }
        public int Page
        {
            get { return _page; }
            set { SetValue(ref _page, value); }
        }

        public int Limit
        {
            get { return _limit; }
            set { SetValue(ref _limit, value); }
        }
        #endregion

        #region CONSTRUCTOR
        public CharactersViewModel(INavigation navigation)
        {
            Navigation = navigation;
            BtnSiguiente = true;
            ObtenerPersonajes();
        }
        #endregion

        #region PROCESOS
        public async void ObtenerPersonajes()
        {
            try
            {               
                Respuesta respuesta = await charactersRepository.Characters($"{Utilidades.UrlApiDBZ}?page={Page}&limit={Limit}");

                if (respuesta.Resultado!=true)
                {
                    await DisplayAlert("Error", respuesta.Mensaje, "Ok");
                }

                Pagination = (Pagination)respuesta.Data;

            }
            catch (Exception ex)
            {
                await DisplayAlert("Error en método: ObtenerPersonajes -> ", ex.Message.ToString(), "Ok");
            }
        }

        public async void SiguientePagina(Pagination pagination)
        {
            try
            {              
                Respuesta respuesta = await charactersRepository.Characters(pagination.Links.Next.ToString());

                if (respuesta.Resultado != true)
                {
                    await DisplayAlert("Error", respuesta.Mensaje, "Ok");
                }

                Pagination = (Pagination)respuesta.Data;
                  ValidarEstadoBoton("Siguiente");
            }
            catch (Exception ex)
            {
                await DisplayAlert("Error en método: SiguientePagina -> ", ex.Message.ToString(), "Ok");
            }
        }

        public async void AnteriorPagina(Pagination pagination)
        {
            try
            {

                Respuesta respuesta = await charactersRepository.Characters(pagination.Links.Previous.ToString());

                if (respuesta.Resultado != true)
                {
                    await DisplayAlert("Error", respuesta.Mensaje, "Ok");
                }

                Pagination = (Pagination)respuesta.Data;
                ValidarEstadoBoton("Anterior");
            }
            catch (Exception ex)
            {
                await DisplayAlert("Error en método: AnteriorPagina -> ", ex.Message.ToString(), "Ok");
            }
        }

        public async void ValidarEstadoBoton(string boton)
        {
            try
            {
                if (boton.Equals("Siguiente"))
                {
                    BtnSiguiente = Pagination.Links.Next == null ? false : true;
                    BtnAnterior = Pagination.Links.Previous == null ? false : true;
                }
                else if (boton.Equals("Anterior"))
                {
                    BtnAnterior = Pagination.Links.Previous == null ? false : true;
                    BtnSiguiente = Pagination.Links.Next == null ? false : true;
                }
            }
            catch (Exception ex)
            {
                await DisplayAlert("Error en método: ValidarEstadoBoton -> ", ex.Message.ToString(), "Ok");
            }

        }

        public async void SeleccionarPersonaje(long characterId)
        {
            try
            {

                await Navigation.PushAsync(new CharacterDetail(characterId));
            }
            catch (Exception ex)
            {

                await DisplayAlert("Error en método: SeleccionarPersonaje -> ", ex.Message.ToString(), "Ok");
            }
        }
        #endregion

        #region COMANDOS        
        public ICommand SiguientePaginaCommand => new Command<Pagination>(SiguientePagina);
        public ICommand AnteriorPaginaCommand => new Command<Pagination>(AnteriorPagina);
        public ICommand SeleccionarPersonajeCommand => new Command<long>(SeleccionarPersonaje);
        #endregion
    }
}

Model(Modelo): En estas clases están todas las propiedades de acuerdo a la información de la API obtenida; estos nos servirá para setear los datos despues de ser deserealizados.

Image description

BindingContext:, creamos una instancia hacia CharactersViewModel permitiendo así que los elementos de la Interfaz de usuario se vinculen a sus propiedades, métodos, comandos etc.

using AppDragonBallZ.ViewModel;

namespace AppDragonBallZ.View;

public partial class Characters : ContentPage
{
    public Characters()
    {
        InitializeComponent();
        BindingContext = new CharactersViewModel(Navigation);
    }
}

Beneficion de utilizar este patrón:

  • Separación de responsabilidades: La vista no necesita conocer la lógica del modelo de vista.
  • Actualización automática: Cuando una propiedad cambia en el modelo de vista, la interfaz de usuario se actualiza automáticamente.
  • Facilita pruebas: Permite probar la lógica de negocio sin depender de la interfaz gráfica.

Nota:
Descargar el nugget: Newtonsoft.Json. Esta biblioteca .NET permite serializar (convertir objetos .NET a JSON o viceversa).

Image description

Código completo del proyecto:
[https://github.com/12AbelCabreraMiranda/AppDragonBallZ]

Referencias:
[https://learn.microsoft.com/es-es/dotnet/architecture/maui/mvvm]
[https://web.dragonball-api.com/documentation]