Elbrinner da Silva Fernandes

Blog sobre Xamarin, MVVMCROSS y UWP (Plataforma universal de Windows)

Tutorial de como crear una aplicación con Xamarin Forms y MvvmCross

Tutorial de como crear una aplicación con Xamarin Forms y MvvmCross

Tutorial de como crear una aplicación con Xamarin Forms y MvvmCross

En uno de los encuentros que hemos realizado en Xamarin Madrid, algunos miembros han pedido una charla de introducción de Xamarin Forms con MvvmCross. Hemos creado un taller explicando cómo realizar los primeros pasos con Xamarin Forms y MvvmCross 5.6.

Como siempre, el código está disponible en github:

Paso 1 - Crear un proyecto Xamarin Forms

Abra visual studio y selecciona la opción crear nueva solución => Multiplataforma => Aplicación de Forms en blanco.

Paso 2 - Configurar MvvmCross

  1. Agregue .Core al nombre del proyecto PCL.
  2. Elimine la página que fue creada por los formularios de Xamarin.
  3. Agregue el paquete MvvmCross Forms StarterPack a los proyectos específicos de la plataforma y .Core
  4. En Android: elimine MainActivity.cs.
  5. En los proyectos específicos de la plataforma, elimine la referencia al proyecto principal y haga clic en Aceptar. Luego agrega la referencia nuevamente. (Esto tiene que hacerse porque los proyectos específicos de la plataforma no reconocerán el proyecto central renombrado)
  6. Cambie la acción de compilación de los siguientes archivos de 'Página' a 'Recurso integrado'(Embedded Resource): * Core|Pages|MainPage.xaml * Core|App.xaml

Seguiendo los pasos anteriores, MvvmCross ya estaría configurado. Al compilar se debe ver algo similar a la siguiente captura

Paso 3 - Archivo de arranque de la aplicación. CoreApp


 public override void Initialize()
        {
            CreatableTypes()
                .EndingWith("Service")
                .AsInterfaces()
                .RegisterAsLazySingleton();

            //No es necesario registrar las instancias que termina como Service, se registra automaticamente
            //Mvx.LazyConstructAndRegisterSingleton();

            //Configuramos la página de arranque, en nuestro caso será MainViewModel
            RegisterNavigationServiceAppStart();
        }


No es necesario aplicar ningún cambio. Si tenemos un servicio que el nombre no termine por Service, tenemos que registrar de la siguiente forma:

Mvx.LazyConstructAndRegisterSingleton();

Paso 4 - Crear un archivo de configuración

Creamos una carpeta con el nombre de Constants y dentro de ella, un CLASS estatica ConfigConstants.cs



using System;
namespace BaseForms.Constants
{
    public static class ConfigConstants
    {
        public const string keyApi = ""; // Código de la api themoviedb

        public const string baseUrl = "https://api.themoviedb.org/3/movie/upcoming?api_key="+keyApi+"&language=es-ES&page=1";

        public const string imgSmall = "http://image.tmdb.org/t/p/w185";

        public const string imgBig = "http://image.tmdb.org/t/p/w780";
    }
}


Paso 5 - Crear las interfaces de los servicios

En el proyecto de la PCL, vamos a crear una carpeta Interfaces y dentro de la misma 2 carpetas más Iconnectors y IWebServices

Dentro de la carpeta Iconnectors, crearemos la interfaz para acceder a HttpClient

IWebClientService.cs


using System;
using System.Net.Http;

namespace BaseForms.Interfaces.IConnectors
{
    public interface IWebClientService
    {
        HttpClient Client();   
    }
}


* Es ejemplo es muy básico, haga la interfaz según tu necesidad

La implementación de este archivo sería



using System;
using System.Net.Http;
using BaseForms.Interfaces.IConnectors;
using ModernHttpClient;

namespace BaseForms.Service.Connectors
{
    public class WebClientService : IDisposable, IWebClientService
    {
        private HttpClient client;

        public NativeCookieHandler CookieContainer { get; set; }

        public WebClientService()
        {
            this.client = this.GenerateHttpClient();
        }

        private HttpClient GenerateHttpClient()
        {
            this.CookieContainer = new NativeCookieHandler();

            NativeMessageHandler httpClientHandler = new NativeMessageHandler(
                false,
                true,
                this.CookieContainer
            );

            return new HttpClient(httpClientHandler);

        }

        public HttpClient Client()
        {
            return this.client;
        }


        public void Dispose()
        {
            this.client.Dispose();
        }
    }

}	



Para que funcione este código vamos a tener que instalar ModernHttpClient.

IMovieWebService

Agregaremos todos los servicios relacionados con las peliculas aqui


using System;
using System.Threading.Tasks;
using BaseForms.Models.Movie;

namespace BaseForms.Interfaces.IWebServices
{
    public interface IMovieWebService
    {
        Task Movie();
    }
}



	
using System;
using System.Globalization;
using System.Threading.Tasks;
using BaseForms.Constants;
using BaseForms.Interfaces.IConnectors;
using BaseForms.Interfaces.IWebServices;
using BaseForms.Models.Movie;
using Newtonsoft.Json;

namespace BaseForms.Service.WebServices
{
    public class MovieWebService : IMovieWebService
    {
        IWebClientService conection;

        public MovieWebService(IWebClientService conection)
        {
            this.conection = conection;
        }

        public async Task Movie()
        {
            var url = new Uri(string.Format(CultureInfo.InvariantCulture, ConfigConstants.baseUrl));
            var result = await conection.Client().GetAsync(url);
            if (result.IsSuccessStatusCode)
            {
                string content = await result.Content.ReadAsStringAsync();
                if (content != null)
                {
                    var response = JsonConvert.DeserializeObject(content);
                    response.IsCorrect = true;
                    return response;
                }
            }
            return new MovieResponse() { Mensage = "Servicio no disponible" };
        }
    }
}


Para que funcione y compile, tenemos que instalar newtonsoft y crear los modelos.

Paso 7 - Crear los modelos

Para manejar los datos devuelvo por el servicio, vamos a crear 3 Modelos.

  • BaseResponse.cs, para ayudar a tratar los errores
  • MovieResponse.cs, respuesta del servicio
  • ResultMovie.cs , es un objeto de MovieResponse, que va a devuelver el detalle por cada pelicula.

BaseResponse.cs

De forma predetermiada, inicializamos que la petición no ha sido bien realizada. Usaremos la propiedad IsCorrect para conocer si el resultado de la peticón ha sido código 200 o no.


namespace BaseForms.Models.Base
{
    public class BaseResponse
    {
        public string Mensage { get; set; }
        public bool IsCorrect { get; set; }
        public BaseResponse()
        {
            Mensage = "No se ha podido realizar la operación";
            IsCorrect = false;
        }
} 

}

 

MovieResponse.cs

Respuesta del servicio, trataremos solo algunos campos usando la propiedad JsonProperty para cambiar el nombre de la propiedad.


using System;
using System.Collections.Generic;
using BaseForms.Models.Base;
using Newtonsoft.Json;

namespace BaseForms.Models.Movie { public class MovieResponse : BaseResponse {

    [JsonProperty("results")]
    public List Movies { get; set; }

    [JsonProperty("page")]
    public int Page { get; set; }

    [JsonProperty("total_results")]
    public int TotalResults { get; set; }

    //public Dates dates { get; set; }

    [JsonProperty("total_pages")]
    public int TotalPages { get; set; }

}

}

ResultMovie.cs

Modelo que contiene la información a pintar en la pantalla.


using System;
using BaseForms.Constants;
using BaseForms.Converters.Json;
using Newtonsoft.Json;

namespace BaseForms.Models.Movie { public class ResultMovie { [JsonProperty("vote_count")] public int VoteCount { get; set; }

    [JsonProperty("id")]
    public int Id { get; set; }

    [JsonConverter(typeof(BoolConverter))]
    [JsonProperty("video")]
    public bool Video { get; set; }

    [JsonProperty("vote_average")]
    public double VoteAverage { get; set; }

    [JsonProperty("title")]
    public string Title { get; set; }

    [JsonProperty("original_title")]
    public string OriginalTitle { get; set; }

    [JsonProperty("overview")]
    public string Overview { get; set; }

    [JsonProperty("backdrop_path")]
    public string Backdrop { get; set; }

    [JsonProperty("original_language")]
    public string OriginalLanguage { get; set; }

    public string ImgSmall => string.Format("{0}{1}", ConfigConstants.imgSmall, Backdrop);

    public string ImgBig => string.Format("{0}{1}", ConfigConstants.imgBig, Backdrop);

    /*
     * Propiedades sin tratar
    public double popularity { get; set; }
    public string poster_path { get; set; }
    public List genre_ids { get; set; }
    public string backdrop_path { get; set; }
    public bool adult { get; set; }
    public string release_date { get; set; }
    */

}

}

 

Cabe destacar que se puede crear converters para tratar la información retornada por el servicios y que tambien se puede unir y crear nuevas propiedades igual a ImgSmall y ImgBig.

Paso 8 - Crear el converter JSON/newsoft


using System;
using Newtonsoft.Json;

namespace BaseForms.Converters.Json
{
    public class BoolConverter : JsonConverter
    {
        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            writer.WriteValue(((bool)value) ? 1 : 0);
        }

        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            return reader.Value.ToString() == "1";
        }

        public override bool CanConvert(Type objectType)
        {
            return objectType == typeof(bool);
        }
    }
}

Paso 9 - Crear el ViewModel Base

Todo lo que es generico en todos los viewmodels, lo meteremos aqui.En nuestro caso sería:

  • La navegación: navigationService
  • El web services de las peliculas: webMovieService
  • IsBusy , responsable por indicar si está esperando la respuesta del servicio o no.
  • Title , responsable por el título de la página

using System;
using BaseForms.Interfaces.IWebServices;
using MvvmCross.Core.Navigation;
using MvvmCross.Core.ViewModels;

namespace BaseForms.ViewModels
{
    public class BaseViewModel : MvxViewModel
    {
        protected readonly IMvxNavigationService navigationService;
        protected readonly IMovieWebService webMovieService;
        protected bool isBusy;
        protected string title;
        public BaseViewModel(IMvxNavigationService navigationService,IMovieWebService webMovieService)
        {
            this.navigationService = navigationService;
            this.webMovieService = webMovieService;
        }

        public bool IsBusy
        {
            get
            {
                return this.isBusy;
            }
            set
            {
                this.isBusy = value;
                this.RaisePropertyChanged(() => this.IsBusy);
            }
        }

        public string Title
        {
            get
            {
                return this.title;
            }
            set
            {
                this.title = value;
                this.RaisePropertyChanged(() => this.Title);
            }
        }



        public override void Prepare(object parameter)
        {

        }
    }
}




Paso 10 - Crear el ViewModel MainViewModel

Este viewmodel va a contener:

  • Herenda de BaseViewModel
  • Movies , listado de peliculas
  • ResultMovie, pelicula seleccionada
  • ViewAppeared, relacionado con el ciclo de vida de la aplicación. Se entra en está función cuando la vista se carga.
  • OpenModal , navega a otra página con los valores de la pelicula seleccionada

using System.Collections.Generic;
using System.Threading.Tasks;
using BaseForms.Interfaces.IWebServices;
using BaseForms.Models.Movie;
using BaseForms.ViewModels;
using MvvmCross.Core.Navigation;
using MvvmCross.Core.ViewModels;

namespace BaseForms.Core.ViewModels
{
    public class MainViewModel : BaseViewModel
    {
      
        public MainViewModel(IMvxNavigationService navigationService, IMovieWebService webMovieService) : base(navigationService, webMovieService)
        {
            
        }

        public override Task Initialize()
        {
            return base.Initialize();
        }

        public async override void ViewAppeared()
        {

            this.IsBusy = true;
            var response = await this.webMovieService.Movie();
            this.IsBusy = false;
            if (response.IsCorrect)
            {
                Movies = response.Movies;
            }
            else
            {
                //mostrar el mensaje de error
            }

        }

        private List movies;
        public List Movies
        {
            get
            {
                return this.movies;
            }

            set
            {
                this.movies = value;
                this.RaisePropertyChanged();

            }
        }
        private ResultMovie selectedMovie;
        public ResultMovie SelectedMovie
        {
            get
            {
                return this.selectedMovie;
            }

            set
            {
                this.selectedMovie = value;
                this.RaisePropertyChanged();
                this.OpenModal(value);
            }
        }

        private void OpenModal(ResultMovie value)
        {
            object parameter = value;
            navigationService.Navigate(parameter);

        }
    }
}


Paso 11 - Vista MainView

Preparando la vista, elementos a tener en cuenta:

  • MvxContentPage, fijate que estamos usando MvxContentPage en lugar de ContentPage
  • Que estamos relacionando el viewmodel con la vista. d:MvxContentPage x:TypeArguments="viewModels:MainViewModel
  • Que estamos cargando el template xmlns:templates="clr-namespace:BaseForms.Views.Template;assembly=BaseForms"
  • Que los valores que se pinta, viene de la propiedad Movies
  • SelectedMovie se usa para guardar el elemento seleccionado.

Código aqui Vista MainView




	
       

            
                    
                        
                            
                        
                    
                
            
        
	


Paso 12 - Crear template

Creamos una carpeta Views en el proyecto BaseForms.Core y luego otro carpeta más con el nombre de template dentro de la carpeta Views.

El próximo paso es crear nuestros templates distinto, para esto debemos crear una página contentView

Template 1 con imagen

Código aqui Template 1

Template 2 sin imagen

Código aqui Template 2

Los templates son buenas formas de reutilizar código, para modificar el aspecto de la lista lo único que debemos indicar ahora es el nombre del template

Paso 13 - Crear el ViewModel AboutViewModel

Este viewmodel contiene:

  • CloseCommand => Un Command para cerrar la modal
  • La propiedad SelectedMovie, para pintar los datos en la pantalla
  • Prepare , este metodo, recupera el valor pasando por el MainViewModel.

using System;
using BaseForms.Interfaces.IWebServices;
using BaseForms.Models.Movie;
using BaseForms.ViewModels;
using MvvmCross.Core.Navigation;
using MvvmCross.Core.ViewModels;

namespace BaseForms.Core.ViewModels
{

    public class AboutViewModel : BaseViewModel
    {
       

        private IMvxCommand closeCommand;
        public IMvxCommand CloseCommand => closeCommand = closeCommand ?? new MvxCommand(DoCloseHandler);

        private ResultMovie selectedMovie;

        public AboutViewModel(IMvxNavigationService navigationService, IMovieWebService webMovieService) : base(navigationService, webMovieService)
        {
        }

        public ResultMovie SelectedMovie
        {
            get
            {
                return this.selectedMovie;
            }

            set
            {
                this.selectedMovie = value;
                this.RaisePropertyChanged();
            }
        }

        private void DoCloseHandler()
        {
            Close(this);
        }

        public override void Prepare(object parameter)
        {
            this.SelectedMovie = (ResultMovie)Convert.ChangeType(parameter, typeof(ResultMovie));
            this.Title = this.SelectedMovie.Title;
        }
    }
}


Paso 14 - Crear la AboutView

Código aqui Vista AboutView





      
         
    
    
        
            
                       
            
            
        
     



Para que se visualize como modal, debemos agregar el tipo de presentación en la vista. (MvxModalPresentation)


using System;
using System.Collections.Generic;
using BaseForms.Core.ViewModels;
using MvvmCross.Forms.Views;
using MvvmCross.Forms.Views.Attributes;
using Xamarin.Forms;

namespace BaseForms.Core.Pages
{

    [MvxModalPresentation(WrapInNavigationPage = true, Title = "Modal")]
    public partial class AboutPage : MvxContentPage
    {
        public AboutPage()
        {
            InitializeComponent();
        }


    }
}


El resultado final de este turtorial, se puede ver en la carpeta "FIN" de este repositorio.

 

 

Elbrinner da Silva Fernandes Elbrinner da Silva Fernandes
Consultor Xamarin, experto en mobilidad en everis España.
Madrid Spain

Xamarin Certificado

Xamarin Master

Certificación Solutions developer App Builder

Certicación Solutions Associate Web applications

Microsoft Active Professional

Microsoft Professional

Specialist programaming in C#

Specialist programaming in HTML5 with JavaScript & CSS3

Planet Xamarin