Xamarin Forms Application, consuming REST API https://pokeapi.co with design pattern MVVM and CLEAN CODE principles. Available on Android, iOS and Windows platforms
- 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
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"/>
public class BaseBusiness<TModel> : ObservableObject where TModel : BaseModel, new()
{
...
public IList<ValidationFailure> Erros { get; set; }
public AbstractValidator<TModel> Validator { get; set; }
...
}
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()
{ ... }
public partial class BasePage<TViewModel> : ContentPage where TViewModel : ViewModels.BaseViewModel, new()
{ ... }
public class Database : DbContext
{
...
public void Initialize()
{
//Database.EnsureDeleted();
Database.Migrate();
}
}
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);
}
Strategy used to use icons, in the action bar, from true type fonts.
- icofont.ttf;
- material.ttf;
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;
}
}
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);
}
Focus on best programming practices in Xamarin Forms applications.