Prism para Xamarin [Primera parte]

Prism es de los frameworks más fáciles de usar y potentes que tenemos actualmente, no solo porque Miguel de Caza, cofundador de la plataforma Xamarin recomiende su uso, si no por que es muy completo, práctico y sobre todo porque nos simplifica la creación de aplicaciones móviles.

Proporciona una implementación muy completa del patrón MVVM, incluyendo inyección de dependencias, comandos, EventAggregator entre otras cosas, permitiéndonos realizar aplicaciones bien estructuradas y mantenibles a lo largo del tiempo por su bajo nivel de acoplamiento (loose coupling). También dispone de comunidad bastante grande con mucha ayuda y muchas mas ventajas que veremos a continuación.

En este post (y más que estarán por venir ) os mostraremos en que consiste, algunos conceptos básicos y cómo usar Prism de una manera sencilla.

Instalando Prism

Para añadir Prism en nuestras aplicaciones tendremos que buscar en la librería e instalar en todos los proyectos su paquete NuGet.

Prism ofrece distintos frameworks de inyección de dependencias, en mi caso usare Unity:

Prism es de los frameworks más fáciles de usar y potentes que tenemos actualmente, no solo porque Miguel de Caza, cofundador de la plataforma Xamarin recomiende su uso, si no por que es muy completo, práctico y sobre todo porque nos simplifica la creación de aplicaciones móviles. Proporciona una implementación muy completa del patrón MVVM, incluyendo inyección de dependencias, comandos, EventAggregator entre otras cosas, permitiéndonos realizar aplicaciones bien estructuradas y mantenibles a lo largo del tiempo por su bajo nivel de acoplamiento (loose coupling). También dispone de comunidad bastante grande con mucha ayuda y muchas mas ventajas que veremos a continuación. En este post (y más que estarán por venir ) os mostraremos en que consiste, algunos conceptos básicos y cómo usar Prism de una manera sencilla. Instalando Prism Para añadir Prism en nuestras aplicaciones tendremos que buscar en la librería e instalar en todos los proyectos su paquete NuGet. Prism ofrece distintos frameworks de inyección de dependencias, en mi caso usare Unity:

Si creamos un proyecto existe la opción de usar la plantilla que nos ofrece Prism Prism Template Pack que tendremos que descargarla previamente desde las extensiones de Visual Studio.

Después de instalar el paquete NuGet (si hemos creado un proyecto desde la plantilla ya estará configurado) tenemos que hacer que App herede de PrismApplication añadiendo la referencia Prism y en los archivos App.xaml y App.xaml.cs:

______


    public partial class App : PrismApplication
    {
        public App(IPlatformInitializer initilizer = null): base(initilizer) { }

        protected override async void OnInitialized()
        {
            InitializeComponent();
        }

        protected override void RegisterTypes(IContainerRegistry containerRegistry)
        {

        }
    }

Una cosa a tener en cuenta si usamos el previsualizador de XAML en Visual Studio tendremos que añadir un constructor predeterminado, ya que la clase System Activator CreateInstance no entiende App(IPlatformInitializer initializer = null):

______
public App() : this(null) { } 

Para terminar con la configuración en ambas plataformas tenemos que pasarle como parámetro una clase que herede de IPlatformInitializer:

______
[Activity(Label = "MySeries", Icon = "@mipmap/icon", Theme = "@style/MainTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
    public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
    {
        protected override void OnCreate(Bundle savedInstanceState)
        {
            TabLayoutResource = Resource.Layout.Tabbar;
            ToolbarResource = Resource.Layout.Toolbar;

            base.OnCreate(savedInstanceState);

            Xamarin.Essentials.Platform.Init(this, savedInstanceState);
            global::Xamarin.Forms.Forms.Init(this, savedInstanceState);
            LoadApplication(new App(new AndroidInitializer()));
        }

        public class AndroidInitializer : IPlatformInitializer
        {
            public void RegisterTypes(IContainerRegistry containerRegistry)
            {

            }
        }
    }



    [Register("AppDelegate")]
    public partial class AppDelegate : global::Xamarin.Forms.Platform.iOS.FormsApplicationDelegate
    {
        public override bool FinishedLaunching(UIApplication app, NSDictionary options)
        {
            global::Xamarin.Forms.Forms.Init();
            LoadApplication(new App(new iOSInitializer()));

            return base.FinishedLaunching(app, options);
        }

        public class iOSInitializer : IPlatformInitializer
        {
            public void RegisterTypes(IContainerRegistry containerRegistry)
            {

            }
        }
    }

Conectar Views y ViewModels

En Prism existen dos maneras de conectar una View con su correspondiente ViewModel, de manera manual y automática. Ambas se tienen que hacer en la clase de App dentro del contenedor IoC RegisterTypes.

Para realizarlo de manera automática tenemos que seguir su convención de mapeo que es: crear cada archivo dentro de su respectiva carpeta para así tener esa estructura en su namespace y nombrar los archivos terminando en View o ViewModel, según sea el caso.

Si no seguimos esta convención tenemos que registrar manualmente escribiendo ambos View y ViewModel <View,ViewModel> public partial class App : PrismApplication

______
    {
        public App() : this(null) { }

        public App(IPlatformInitializer initilizer = null): base(initilizer) { }

        protected override async void OnInitialized()
        {
            InitializeComponent();

            await NavigationService.NavigateAsync($"{nameof(NavigationPage)}/{nameof(MainPage)}",);

        }

        protected override void RegisterTypes(IContainerRegistry containerRegistry)
        {
            containerRegistry.RegisterForNavigation<MainPage, MainPageViewModel>();
            containerRegistry.RegisterForNavigation();
        }
    }

Navegación

La navegación en Prism es muy sencilla y de la más potente en mi opinión ya que por defecto todos nuestros ViewModel tienen implementado el servicio NavigationService y solo tenemos que instanciar una variable en el constructor y ya tendremos el servicio listo para su uso.

______
await NavigationService.NavigateAsync($"{nameof(NavigationPage)}/{nameof(MainPage)}");

Para navegar entre pantallas solo tenemos que escribir la vista o concatenar vistas a las que queramos ir. Cuando concatenamos Prism realiza un deep Linking, es decir apila las vistas en el Navigation Stack:

______
            await NavigationService.NavigateAsync($"{nameof(NavigationPage)}/{nameof(MainPage)}/{nameof(SeriesView)}/{nameof(SeriesDetailView)}");

Para recibir el parametro en la vista destino tendremos que implementar la interfaz INavigarionAware para obtener los eventos de navegación OnNavigatingTo, OnNavigatedFrom y OnNavigatedTo.

Enviar parámetros

Para pasar parámetros Prism nos ofrece dos maneras: usando un NavigationParameters añadiendo el parámetro en la navegación o escribiéndolo en la misma cadena con la que navegamos como si una URL.

______
            await NavigationService.NavigateAsync($"{nameof(NavigationPage)}/{nameof(MainPage)}/{nameof(SeriesView)}/{nameof(SeriesDetailView)}?param=Peaky%20Blinders");

Para recibir el parametro en la vista destino tendremos que implementar la interfaz INavigarionAware para obtener los eventos de navegación OnNavigatingTo, OnNavigatedFrom y OnNavigatedTo.

public class SeriesDetailViewModel : INavigationAware
    {
        INavigationService _navigationService; 
        public SeriesDetailViewModel(INavigationService navigationService)
        {
            _navigationService = navigationService;
        }

        public void OnNavigatedFrom(INavigationParameters parameters)
        {

        }

        public void OnNavigatedTo(INavigationParameters parameters)
        {
            var title = parameters["param"];
        }
    }

En este fragmento de código se puede observar como en el método OnNavigatedTo recogemos el parámetro que le pasamos anteriormente en la cadena de navegación con el nombre de “param”.

Comandos

Prism proporciona los DelegateCommand con un estado CanExecute observable, esto significa que la propiedad o método asociado observara el estado de CanExecute todo el tiempo automáticamente.

public class SeriesDetailViewModel : BindableBase, INavigationAware
    {
        bool _canExit;
        public bool CanExit
        {
            get { return _canExit; }
            set { SetProperty(ref _canExit, value); }
        }

        public ICommand BackButtonCommand { get; set; }

        INavigationService _navigationService; 
        public SeriesDetailViewModel(INavigationService navigationService)
        {
            _navigationService = navigationService;

            BackButtonCommand = new DelegateCommand(async () => await OnBackButton()).ObservesCanExecute(() => CanExit);
        }

        async Task OnBackButton()
        {
            await _navigationService.GoBackAsync();        
        }

        public void OnNavigatedFrom(INavigationParameters parameters)
        {

        }

        public void OnNavigatedTo(INavigationParameters parameters)
        {
            var title = parameters["param"];
        }
    }

Como contenido extra en este fragmento de código podemos observar el enlace de datos con la creación de un Binding. Para ello tenemos que heredar de BindableBase para poder informar su estado por medio de SetProperty.

Registro de Servicios

Todos los servicios tienen que registrarse al igual que las View y ViewModels en RegisterTypes para poder ser inyectarlos en el constructor:

 containerRegistry.Register<ISeriesService, SeriesService>();

Se registra en la clase App.xaml.cs 

public class SeriesViewModel
    {
        ISeriesService _seriesService;
        INavigationService _navigationService;

        public SeriesViewModel(ISeriesService seriesService,
                               INavigationService navigationService,
                               IPageDialogService pageDialogService,
                               EventAggregator eventAggrator)
        {
            _seriesService = seriesService;
            _navigationService = navigationService;
        }
    }

Por defecto los servicios NavigationService, PageDialogService, EventAggregator estarán a nuestra disposición sin tener que ser registrados.

Servicios por plataforma

Prism nos permite utilizar el Servicio de Depencias de Xamarin DependencyService de forma desacoplada mediante la inyección de dependencias una vez más en el constructor.

Para ello únicamente tendremos que registrar los servicios en los contenedores de cada plataforma desde la interfaz IPlatformInitializer:

public class AndroidInitializer : IPlatformInitializer
        {
            public void RegisterTypes(IContainerRegistry containerRegistry)
            {
                containerRegistry.RegisterSingleton<IDeviceOrientationService, DeviceOrientationService>();
            }
        }

______________

public class iOSInitializer : IPlatformInitializer
        {
            public void RegisterTypes(IContainerRegistry containerRegistry)
            {
                containerRegistry.RegisterSingleton<IDeviceOrientationService, DeviceOrientationService>();
            }
        }

Por último, solo quedaría inyectar el servicio en el constructor, evitando así llamar a DependencyService de Xamarin.

public class MainPageViewModel
    {
        IDeviceOrientationService _deviceOrientationService;
        public MainPageViewModel(IDeviceOrientationService deviceOrientationService)
        {
            _deviceOrientationService = deviceOrientationService;
        }
    }

Consideraciones

En un futuro seguiremos publicando más posts ampliando el contenido y de la misma forma actualizando este, también subiremos el código de los ejemplos a GitHub de Bravent.

Escrito por: Rebeca Garnacho, parte del equipo de Movilidad en Bravent.

Para recibir el parametro en la vista destino tendremos que implementar la interfaz INavigarionAware para obtener los eventos de navegación OnNavigatingTo, OnNavigatedFrom y OnNavigatedTo.

public class SeriesDetailViewModel : INavigationAware
    {
        INavigationService _navigationService; 
        public SeriesDetailViewModel(INavigationService navigationService)
        {
            _navigationService = navigationService;
        }

        public void OnNavigatedFrom(INavigationParameters parameters)
        {

        }

        public void OnNavigatedTo(INavigationParameters parameters)
        {
            var title = parameters["param"];
        }
    }

En este fragmento de código se puede observar como en el método OnNavigatedTo recogemos el parámetro que le pasamos anteriormente en la cadena de navegación con el nombre de “param”.

Comandos

Prism proporciona los DelegateCommand con un estado CanExecute observable, esto significa que la propiedad o método asociado observara el estado de CanExecute todo el tiempo automáticamente.

public class SeriesDetailViewModel : BindableBase, INavigationAware
    {
        bool _canExit;
        public bool CanExit
        {
            get { return _canExit; }
            set { SetProperty(ref _canExit, value); }
        }

        public ICommand BackButtonCommand { get; set; }

        INavigationService _navigationService; 
        public SeriesDetailViewModel(INavigationService navigationService)
        {
            _navigationService = navigationService;

            BackButtonCommand = new DelegateCommand(async () => await OnBackButton()).ObservesCanExecute(() => CanExit);
        }

        async Task OnBackButton()
        {
            await _navigationService.GoBackAsync();        
        }

        public void OnNavigatedFrom(INavigationParameters parameters)
        {

        }

        public void OnNavigatedTo(INavigationParameters parameters)
        {
            var title = parameters["param"];
        }
    }

Como contenido extra en este fragmento de código podemos observar el enlace de datos con la creación de un Binding. Para ello tenemos que heredar de BindableBase para poder informar su estado por medio de SetProperty.

Registro de Servicios

Todos los servicios tienen que registrarse al igual que las View y ViewModels en RegisterTypes para poder ser inyectarlos en el constructor:

 containerRegistry.Register<ISeriesService, SeriesService>();

Se registra en la clase App.xaml.cs 

public class SeriesViewModel
    {
        ISeriesService _seriesService;
        INavigationService _navigationService;

        public SeriesViewModel(ISeriesService seriesService,
                               INavigationService navigationService,
                               IPageDialogService pageDialogService,
                               EventAggregator eventAggrator)
        {
            _seriesService = seriesService;
            _navigationService = navigationService;
        }
    }

Por defecto los servicios NavigationService, PageDialogService, EventAggregator estarán a nuestra disposición sin tener que ser registrados.

Servicios por plataforma

Prism nos permite utilizar el Servicio de Depencias de Xamarin DependencyService de forma desacoplada mediante la inyección de dependencias una vez más en el constructor.

Para ello únicamente tendremos que registrar los servicios en los contenedores de cada plataforma desde la interfaz IPlatformInitializer:

public class AndroidInitializer : IPlatformInitializer
        {
            public void RegisterTypes(IContainerRegistry containerRegistry)
            {
                containerRegistry.RegisterSingleton<IDeviceOrientationService, DeviceOrientationService>();
            }
        }

______________

public class iOSInitializer : IPlatformInitializer
        {
            public void RegisterTypes(IContainerRegistry containerRegistry)
            {
                containerRegistry.RegisterSingleton<IDeviceOrientationService, DeviceOrientationService>();
            }
        }

Por último, solo quedaría inyectar el servicio en el constructor, evitando así llamar a DependencyService de Xamarin.

public class MainPageViewModel
    {
        IDeviceOrientationService _deviceOrientationService;
        public MainPageViewModel(IDeviceOrientationService deviceOrientationService)
        {
            _deviceOrientationService = deviceOrientationService;
        }
    }

Consideraciones

En un futuro seguiremos publicando más posts ampliando el contenido y de la misma forma actualizando este, también subiremos el código de los ejemplos a GitHub de Bravent.

Escrito por: Rebeca Garnacho, parte del equipo de Movilidad en Bravent.