Skip to content

RobertoGFilho/Pokemon

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

38 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Pokémon Collection

Xamarin Forms Application, consuming REST API https://pokeapi.co with design pattern MVVM and CLEAN CODE principles. Available on Android, iOS and Windows platforms

Screenshot

Libraries

  • Microsoft.EntityFrameworkCore.Sqlite : database abstraction layer;
  • Microsoft.EntityFrameworkCore.Tools : used for data migration;
  • Refractored.MvvmHelpers : used for data binding and synchronous and asynchronous commands;
  • Xamarin.Essentials : internet connection verification;
  • Shell : page browsing;
  • FluentValidation: validation when editing data

Languages

Multilingual App Toolkit extension was used to generate the translation files available in \Resources folder for languages:

  • English;
  • Brazilian portuguese;

<Label Text="{extensions:Translate pokemonTypes}" FontSize="Caption"/>

Models

Three main classes Pokemon, PokemonTypes and PokemonTypesPokemon the last one representing the N:N connection between the first two

ModelsDiagram

Business

Layer between model and viewModel responsible for data validation and new business rules;

public class BaseBusiness<TModel> : ObservableObject where TModel : BaseModel, new()
{
    ...
    public IList<ValidationFailure> Erros { get; set; }
    public AbstractValidator<TModel> Validator { get; set; }
    ...
}

View Model

In this layer, inheritance and generics techniques combined were used, to define behavior patterns and maximum code reuse.

public abstract class BaseCollectionViewModel<TModel, TBusiness, TDataManager> : 
BaseDataViewModel<TModel, TBusiness, TDataManager> where TModel : BaseModel, new() where 
TBusiness : BaseBusiness<TModel>, new() where 
TDataManager : BaseManager<TModel>, new()
{ ... }

View

Layer representing the page and injecting the viewModel.

public partial class BasePage<TViewModel> : ContentPage where TViewModel : ViewModels.BaseViewModel, new()
{ ... }

Database

The Sqlite and Entity Framework were used as data caching strategy.

Migrations

Whenever model structures are changed, adding or removing fields or new models, the migration will be performed at startup restructuring the database;

public class Database : DbContext
{
    ...
    public void Initialize()
    {
        //Database.EnsureDeleted();
        Database.Migrate();
    }
}

Pagination

Strategy used to load data automatically, data pages, from local database after all records displayed new data pages are downloaded from REST API https://pokeapi.co e stored locally.

public async Task<IQueryable<Pokemon>> GetPokemons(int skip)
    {
        int limit = 10;
        
        var pokemons = Database.Pokemons
            .OrderBy(o => o.PokemonId)
            .Skip(skip)
            .Take(limit);

        if (pokemons.Count() == 0)
        {
            pokemons = (await Service.GetPokemonsAsync(skip, limit)).AsQueryable();

            if (pokemons.Count() > 0)
            {
                Database.Pokemons.AddRange(pokemons);
                var pokemonTypesManager = new PokemonTypesManager();
                var pokemonTypes = await pokemonTypesManager.GetAll();

                foreach (var pokemon in pokemons)
                {
                    var pokemonDetails = await Service.GetPokemonDetailsAsync(pokemon.Name);

                    if (pokemonDetails != null)
                    {
                        pokemon.PokemonId = pokemonDetails.Id;
                        pokemon.Height = pokemonDetails.Height;
                        pokemon.Weight = pokemonDetails.Weight;
                        pokemon.Image = pokemonDetails.Sprite?.Image;

                        var typeDetailsNames = pokemonDetails.TypeDetailsServices?
		    .Select(s => s.PokemonType?.Name);

                        if (typeDetailsNames.Count() > 0)
                        {
                            var newPokemonTypes = pokemonTypes.Where(p => typeDetailsNames.Contains(p.Name));

                            foreach (var newPokemonType in newPokemonTypes)
                            {
                                PokemonTypePokemon pokemonTypePokemon = new PokemonTypePokemon
                                {
                                    PokemonType = newPokemonType,
                                    Pokemon = pokemon
                                };

                                Database.PokemonTypesPokemons.Add(pokemonTypePokemon);
                            }
                        }
                    }
                }

                Database.SaveChanges();
            }
        }
        return GetIncludes(pokemons);
    }

Image Font

Strategy used to use icons, in the action bar, from true type fonts.

  • icofont.ttf;
  • material.ttf;

API REST

Class responsible for downloading and deserializing endpoint jason file https://pokeapi.co/api/v2/

public static class Service
{
    static HttpClient client = new HttpClient();
    
    public static async Task<IList<Pokemon>> GetPokemonsAsync(int offset, int limit)
    {
        try
        {
            var current = Connectivity.NetworkAccess;

            if (current == NetworkAccess.Internet)
            {
                var request = new HttpRequestMessage
                {
                    Method = HttpMethod.Get,
                    RequestUri = new Uri($"https://pokeapi.co/api/v2/pokemon/
                    ?offset={offset}&limit={limit}"),
                };

                using (var response = await client.SendAsync(request))
                {
                    if (response.IsSuccessStatusCode)
                    {
                        var jsonString = await response.Content.ReadAsStringAsync();
                        PokemonsService pokemonsService = 
                        JsonSerializer.Deserialize<PokemonsService>(jsonString);

                        if (pokemonsService.Pokemons?.Count > 0)
                        {
                            return pokemonsService.Pokemons;
                        }
                    }
                }
            }
        }
        catch (Exception) { }

        return null;
    }
    
}

Navigation

Injecting the "views" page navigation service through view model navigation

public partial class App : Application
{
    ...   
    public App(string dbPath)
    {
        ...
        DependencyService.Register<Interfaces.INavigation, Navigation>();
    }   
 }
 
 public class Navigation : Interfaces.INavigation
 {
    public async Task<TViewModel> GoToAsync<TViewModel>() where TViewModel : BaseViewModel
    {
        await Shell.Current.GoToAsync(typeof(TViewModel).Name);
        return Shell.Current.CurrentPage.BindingContext as TViewModel;
    }

    public Task GoToBackAsync()
    {
        return Shell.Current.GoToAsync("..");

    }
 }
 
public abstract class BaseViewModel : MvvmHelpers.BaseViewModel
{
    ...
    public Interfaces.INavigation Navigation => 
    DependencyService.Get<Interfaces.INavigation>(DependencyFetchTarget.GlobalInstance);
}

Conclusion

Focus on best programming practices in Xamarin Forms applications.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages