El patrón Modelo-Vista-ViewModel (MVVM) hace una separación de la interfaz de usuario (la vista) a partir de los datos subyacentes (el modelo) a través de una clase que sirve como intermediario entre la vista y el modelo (ViewModel). La vista y el ViewModel están conectados a través de enlaces de datos definidos en la vista.

El ViewModel recupera los datos del modelo invocando métodos en las clases del modelo y a continuación ofrece los datos del modelo a la vista (reformateándolos si es necesario) de forma que la vista los pueda utilizar fácilmente.

MVVM se utiliza frecuentemente con enlaces de datos de dos vías (TwoWay), para modelar este comportamiento los ViewModels implementan la interfaz INotifyPropertyChanged que lanza un evento PropertyChanged cada vez que una de sus propiedades cambia. El mecanismo de enlace de datos en Xamarin.Forms asocia un manejador de eventos a PropertyChanged para que pueda ser notificado cuando haya un cambio de propiedad y mantenerlo actualizado con el nuevo valor.

Para las colecciones se proporciona System.Collections.ObjectModel.ObservableCollection<T> que alivia al desarrollador de tener que implementar la interfaz INotifyCollectionChanged sobre las colecciones.

En muchos casos, el patrón MVVM se limita a la manipulación de los elementos de datos, sin embargo, el ViewModel también proporciona implementaciones de comandos que un usuario de la aplicación inicia en la vista. Por ejemplo, cuando un usuario hace clic en un botón en la interfaz de usuario, esta acción puede desencadenar un comando en el ViewModel. El ViewModel puede ser también responsable de definir los cambios de estado en la lógica que afectan a algún aspecto de la pantalla en la vista, como una indicación de que está pendiente de alguna operación.

Para permitir a los ViewModels ser independientes de la vista se usa el interfaz ICommand, que está soportado por los siguientes elementos: Button, MenuItem, ToolbarItem, SearchBar, TextCell (y también ImageCell), ListView, TapGestureRecognizer.

Con la excepción de SearchBar y ListView, estos elementos definen dos propiedades:

  • Command de tipo System.Windows.Input.ICommand
  • CommandParameter de tipo Object

Del mismo modo, la SearchBar define las propiedades SearchCommand y SearchCommandParameter, mientras que el ListView define una propiedad RefreshCommand de tipo ICommand.

La interfaz ICommand define dos métodos y un evento:

  • void Execute(objeto arg)
  • bool CanExecute(objeto arg)
  • event EventHandler CanExecuteChanged

La idea es que el ViewModel tenga una o más propiedades de tipo ICommand. Estas propiedades están enlazadas a las propiedades Command de cada Button (u otro elemento, o tal vez una vista personalizada que implementa esta interfaz).

La propiedad CommandParameter se usa opcionalmente para identificar cada Button en particular (o el elemento que sea) que se asocia a esta propiedad del ViewModel. El Button llama al método Execute cada vez que el usuario pulsa el botón, pasando al método Execute su CommandParameter.

El método CanExecute y el evento CanExecuteChanged se utilizan para las veces en que pulsar un botón pudiera no ser una operación válida, en cuyo caso el botón debe desactivarse. El botón llama a CanExecute cuando la propiedad de comando se establece por primera vez y cada vez que se dispara el evento CanExecuteChanged. Si CanExecute devuelve false, el botón se desactiva y no genera las llamadas a Execute.

Xamarin.Forms define dos clases que implementan ICommand: Command y Command<T> donde T es el tipo de los argumentos para Execute y CanExecute. Estas dos clases definen los constructores más un método ChangeCanExecute que el ViewModel puede llamar para forzar al objeto Command a lanzar el evento CanExecuteChanged.

Los comandos también pueden invocar métodos asíncronos. Esto se logra mediante el uso de async y await. Un ejemplo podría ser:

DescargarCommand = new Command (async () => await DescargarAsync ());

Esto indica que el método DescargarAsync es una tarea Task y debe ser esperado:

async Task DescargarAsync()

{

await Task.Run (() => Descargar());

}

void Descargar()

{

}