reactiveui-un-framework-mvvm-para-net-basado-en-reactivex

ReactiveUI, un framework MVVM para .NET basado en ReactiveX

Hoy vamos a ver ReactiveUI, un poderoso framework para implementar un patrón MVVM en aplicaciones en .NET, basado en el patrón Observable-Observador.

En la entrada anterior vimos MVVM Light como una alternativa sencilla y poco intrusiva con las ayudas necesarias para implementar un patrón MVVM.

En esta ocasión veremos el potente ReactiveUI, que más que un simple framework representa casi un cambio de filosofía a la hora de programar.

ReactiveUI está disponible para aplicaciones de WinForms (con funciones algo limitadas), WPF, UWP y Xamarin Forms. Es Open Source y su código está disponible en este enlace.

Hay que decir que ReactiveUI es un framework con una curva de aprendizaje dura. Además, la documentación es relativamente escasa, y muchos de los tutoriales hacen referencia a versiones anteriores.

No obstante, su enorme potencia hace que sea un framework realmente interesante para aprender e incorporar a nuestros proyectos. Vamos a ver algunas de las características de este interesante framework para .NET.

¿Por qué ReactiveUI?

ReactiveUI está basado en las conocidas ReactiveX, una librería multilenguaje para facilitar el trabajo asíncrono y la gestión de eventos, que se basa en lo mejor del patrón Observador-Observable, los iteradores, y la programación funcional.

ReactiveX basa su funcionamiento en el uso intensivo de Observables. Un Observable es un objeto que emite notificaciones de forma asíncrona y que son recibidas por los Observadores o suscriptores, según un patrón PubSub.

Lo Observables representan un cambio a una filología “Push”, frente a un iterador que representa una filosofía “Pull”. Así, típicamente en un iterador recorremos una colección ejecutando acciones en cada elemento. Por el contrario, al usar un Observable este emite notificaciones cuando es oportuno, y los Observadores ejecutan las acciones.

Además, suponen una mejor aproximación a una definición funcional y declarativa del software frente la clásica definición imperativa. En lugar de ‘hacer algo’, declaramos las relaciones en el constructor. Esto tiene ventajas, especialmente desde el punto de vista de escalabilidad y mantenibilidad.

Observables en ReactiveX

El funcionamiento de los Observables en ReactiveUI es el mismo que en ReactiveX, en el que se basa. Pero ReactiveUI añade métodos adicionales que facilitan su empleo en el UI, como veremos más adelante. Antes, repasaremos brevemente el uso de Observables en ReactiveX.

Como hemos dicho, un Observable es un objeto que emite notificaciones que son recibidas por los Observadores según un patrón PubSub.

La metodología de trabajo con Observables consiste en:

  • Generar un Observable
  • Aplicar operadores o filtros a los mensajes emitidos
  • Consumir los mensajes en los Observadores

Podemos crear Observables de muchísimas formas. Como ejemplos sencillos, podemos crear un Observable que emita los números en un rango,

IObservable<int> myObservable = Observable.Range(1, 10);

O un Observable que emita una notificación cada N segundos.

IObservable<int> myObservable = Observable.Timer(TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(1));

A continuación, empleamos operadores y filtros en las notificaciones emitidas por los Observables. También tenemos muchos métodos disponibles como, por ejemplo, Debounce, Distinct, Fist, Last, SkipUntil, TakeUntil, TimeInterval, TimeOut, Merge, entre muchos otros.

Finalmente, nos suscribiríamos a las notificaciones

timer.Subscribe(x => // doWhatEver));

Tenéis un listado con los operadores disponibles en http://reactivex.io/documentation/operators.html

Por supuesto, también podemos crear nuestros propios Observables. Por ejemplo, a continuación, creamos un Observable que cuenta hasta 100 cada 10ms, y después de 100 a 0, y se repite indefinidamente.

IObservable<long> loop(int i)
{
  var obs1 = Observable.Timer(TimeSpan.Zero, TimeSpan.FromMilliseconds(10)).TakeUntil(x => x > 100);
  var obs2 = Observable.Timer(TimeSpan.Zero, TimeSpan.FromMilliseconds(10)).TakeUntil(x => x > 100).Select(x => 100 - x);
  return obs1.Concat(obs2).Repeat();
}

Para más información sobre el uso de ReactiveX consultar la documentación al respecto en la página del proyecto.

Uso de ReactiveUI

A continuación, veremos algunas de las funcionalidades que añade ReactiveUI para emplear Observables en el UI.

INotifyPropertyChanged

Uno de los puntos necesarios de cualquier Framework MVVM es facilitar una implementación de INotifyPropertyChanged. Efectivamente, ReactiveUI proporciona su propia implementación, que empleamos de la siguiente forma.

private string myProperty;  
public string MyProperty
{
    get { return myProperty; }
    set { this.RaiseAndSetIfChanged(ref myProperty, value); }
}

Nada nuevo, simplemente una implementación de muchas posibles de INotifyPropertyChanged que en el caso de ReactiveUI se denomina RaiseAndSetIfChanged(…).

Observables y propiedades

ReactiveUI va mucho más allá que un simple INotifyPropertyChanged, ya que combina la potencia los Observable junto con el mecanismo de Binding a propiedades.

Para ello disponemos los métodos:

  • WhenAny/WhenAnyValue, convierte una propiedad en un Observable.
  • ToProperty, convierte un Observable en una propiedad.

Estos dos métodos forman parte del núcleo de ReactiveUI, y los usaremos frecuentemente. Vamos a profundizar en su funcionamiento.

WhenAny y WhenAnyValue

Los métodos WhenAny y su variante WhenAnyValue sirven para crear suscripciones a cambios ante una propiedad en el VM. Estos métodos de extensión crean un nuevo IObservable que lanza una notificación cuando se modifica la propiedad.

El método WhenAny emplea un objeto IObservedChange, que contiene el emisor del cambio (Sender), la expresión del cambio (Expression) y el valor tras el cambio (Value). Por eso, el WhenAny requiere como segundo parámetro un selector.

this.WhenAny(x => x.MyProperty, x => x.Value)

En la mayoría de ocasiones, la sintaxis de WhenAny tendrá x.Value como selector. Por ello, se dispone de una versión simplificada WhenAnyValue que es la que usaremos la mayoría de veces.

this.WhenAnyValue(x => x.MyProperty)

WhenAny/WhenAnyValue, permiten supervisar a cambios en múltiples propiedades simultáneamente.

this.WhenAnyValue(x => x.Red, x => x.Green, x => x.Blue, 
                  (r, g, b) => new Color(r, g, b));

O en propiedades anidadas.

this.WhenAnyValue(x => x.Foo.Bar.Baz);

ToProperty

El caso opuesto de WhenAny/WhenAnyValue, es decir, proyectar un Observable en una propiedad. En muchas ocasiones necesitaremos convertir un Observable en una propiedad, para que sea Bindeada al UI.

Para ello, ReactiveUI proporciona el método ToProperty(…) y el objeto auxiliar ObservableToPropertyHelper. Como su nombre indica, ambos objetos permiten convertir un Observable en una propiedad.

Por ejemplo, si tenemos una propiedad readonly ‘Counter’, que queremos bindear al UI, y cuyo estado se actualiza desde un Observable, definiríamos lo siguiente.

private readonly ObservableAsPropertyHelper<string> counter;  
public string Counter  
{
    get { return counter.Value; }
}

Donde ObservableAsPropertyHelper es un objeto que responde a notificaciones de modificación de un Observable.

Posteriormente, en el constructor del VM realizaríamos la proyección a la propiedad que queramos, mediante el método ‘ToProperty’.

public MyViewModel()  
{
    Observable.Timer(TimeSpan.FromSeconds(0), TimeSpan.FromSeconds(2))
      .Select(x=> x.ToString())
      .ToProperty(this, vm => vm.Counter, out counter);
}

Ejemplo de Propiedades y Observables

Veamos todo esto en un ejemplo sencillo. Tenemos dos propiedades de texto modificables por el usuario, Firstname y LastName, y una propiedad FullName que depende de los dos anteriores.

Así definiríamos las propiedades:

private string firstName;  
public string FirstName  
{
    get { return firstName; }
    set { this.RaiseAndSetIfChanged(ref firstName, value); }
}

private string lastName;  
public string LastName  
{
    get { return lastName; }
    set { this.RaiseAndSetIfChanged(ref lastName, value); }
}

private readonly ObservableAsPropertyHelper<string> fullName;  
public string FullName  
{
    get { return fullName.Value; }
}

Por otro lado, en el constructor del VM definiríamos la lógica de forma declarativa. En este ejemplo, definimos un observable que responde a cambios en FirstName y LastName, emplea un selector para concatenar ambas propiedades, y finalmente las proyecta en la propiedad FullName.

public MyViewModel()  
{
    this.WhenAnyValue(vm => vm.FirstName, vm => vm.LastName,
            (first, last) => $"{first} {last}")
            .ToProperty(this, vm => vm.FullName, out fullName);
}

Simplificación con FODY

Como vemos, la sintaxis para ReactiveUI, si bien resulta bastante intuitiva, requiere mucho código repetitivo. Esto mejora sustancialmente si empleamos la extensión FODY y los atributos disponibles para ReactiveUI.

Así, el ejemplo anterior pasaría a ser.

[Reactive] public string FirstName { get; set; }

[Reactive] public string LastName { get; set; }

[ObservableAsProperty] public string FullName { get; }

public MyViewModel()  
{
    this.WhenAnyValue(vm => vm.FirstName, vm => vm.LastName,
    (first, last) => $"{first} {last}")
    .ToPropertyEx(this, vm => vm.FullName);
}

Como vemos, mucho más conciso y conveniente.

Remarcar que, al usar FODY, la propiedad ‘ToProperty(…)’ pasa a ser ‘ToPropertyEx(…)’, necesario porque el objeto ObservableAsPropertyHelper generado no está definido hasta que compilamos y FODY crea la variable por nosotros.

Commands

Por supuesto, ReactiveUI realiza una implementación de ICommand, llamada ‘ReativeCommand’. La declaración de un comando se realiza mediante:

public ReactiveCommand<TParam, TResult> MyCommand {get;}

Donde TParam es el tipo de parámetro que recibe el comando, y TResult el tipo de resultado que devuelve. En caso de que alguno, o ambos sea nulo, usaremos el tipo ‘Unit’ que proporciona ReactiveUI y que, a efectos prácticos, podemos considerar similar a ‘void’.

Posteriormente, para crear el Command en el constructor del VM empleamos uno de los siguientes métodos constructores,

  • CreateFromObservable() - Crear desde un Observable.
  • CreateFromTask() - Ejecutar una Task con async/await.
  • Create() - Ejecutar un Func o Action síncrono.
  • CreateCombined()

Por ejemplo, para crear un comando síncrono con un parámetro int lo declararíamos de la siguiente forma,

public ReactiveCommand<int, Unit> CommandParemeterless { get; private set;}

En el constructor crearíamos la instancia con

CommandParemeterless = ReactiveCommand.Create<int, Unit>(
    integer => Console.WriteLine(integer)
);

Y podríamos ejecutarlo con de la siguiente forma,

CommandParemeterless.Execute(42).Subscribe();

Como otro ejemplo, para crear un comando asíncrono sin parámetros lo declararíamos así,

public ReactiveCommand<Unit, Unit> AsyncCommand { get; private set; }

Y en el constructor lo instanciaríamos de la siguiente forma

AsyncCommand = ReactiveCommand.CreateFromTask(async () =>
  {
    await Task.Delay(2000);
    // do whatever
});

Message BUS

Otro componente habitual en frameworks de MVVM es el Message BUS que, por supuesto, también está incluido en ReactiveUI. Si bien no conviene abusar de su uso porque dificulta el debug y puede enmascarar dependencias entre clases, en ciertas ocasiones es una útil para mantener débil el acoplamiento entre clases.

Su uso es similar al resto de implementaciones de servicios de notificaciones. Para suscribirnos a mensajes de una determinada clase usaríamos

MessageBus.Current.Listen<BusMessageDemo>().Subscribe(x => Console.WriteLine($"{x.Time} Recieved: {x.Content}"));

Por otro lado, otra clase enviaría mensajes a través del BUS a los distintos suscriptores.

MessageBus.Current.SendMessage(new BusMessageDemo { Content = "DemoMessage Content" });

En este ejemplo hemos empleado esta clase auxiliar, que en vuestro proyecto debería ser sustituida por los mensajes correspondientes en vuestra aplicación.

// Clase auxiliar para ejemplo de Bus
public class BusMessageDemo
{
  public DateTime Time { get; set; } = DateTime.Now;

  public string Content { get; set; }
}

Service Location

Otra funcionalidad habitual es en la mayoría de frameworks MVVM es IoC y DI. ReactiveUI emplea Splat como service locator, un contenedor rápido y multiplataforma. Es sencillo, peo lo suficiente potente para la mayoría de usos.

Para registrar un nuevo servicio se emplea la propiedad CurrentMutable, que permite registrar los servicios con los siguientes métodos

// Crea un nuevo servicio cada que vez que se resuelve
Locator.CurrentMutable.Register(() => new MyService(), typeof(IMyService));

// Devuelve el mismo objeto, existente (o creado al registrar)
Locator.CurrentMutable.RegisterConstant(new MyService(), typeof(IMyService));

// Devuelve un objeto, que es creado la primera vez que se solicita
Locator.CurrentMutable.RegisterLazySingleton(() => new MyService(), typeof(IMyService));

Posteriormente, podemos resolver los servicios usando la propiedad Current.

var myService = Locator.Current.GetService<IMyService>();
var allMyServices = Locator.Current.GetServices<IMyService>();

Finalmente, ReactiveUI no proporciona por sí mismo un sistema DI para resolver las dependencias automáticamente por inyección desde constructor o desde propiedad, aunque es posible combinarlo con un DI existente como AutoFac.

No obstante, el uso recomendado por los desarrolladores para resolver las dependencias desde el constructor es el siguiente.

public class Foo : IFoo
{
  IService MyService;
  
  public Foo(IService myService)
  {
    MyService = myService ?? Locator.Current.GetService<IService>();
  }
}

Dispatcher

ReactiveUI también proporciona herramientas para simplificar lanzar acciones en el Thread del UI de la aplicación, mediante los ‘Schedule’. Así, el siguiente ejemplo muestra cómo ejecutar una tarea en el UI.

RxApp.MainThreadScheduler.Schedule(() => DoSomething());

No obstante, los Schedule se integran normalmente en la definición de los propios Observables.

MyObservable.Throttle(TimeSpan.FromSeconds(0.8))
    .Where(...)
    .DistinctUntilChanged()
    .ObserveOn(scheduler)
    .Subscribe(x => { });

O por ejemplo

var result = await Observable.Start(() => {
    int number = ThisCalculationTakesALongTime();
    return number;
}, RxApp.TaskpoolScheduler);

Colecciones Reactive

Finalmente, ReactiveUI también proporciona colecciones que complementan las ObservableCollection, para el uso de colecciones de Observables. Son muy potentes y su uso abarca muchas posibilidades, por lo que conviene leer la documentación de estos objetos.

Un ejemplo sencillo sería el siguiente, que crea una derivedList que contendría los elementos de myList filtrados según su Status.

var myDerivedList = myList
    .ToObservableChangeSet()
    .Filter(t => t.Status == "someStatus")
    .AsObservableList();

Hasta aquí esta presentación de ReactiveUI, un framework MVVM muy potente y con un sin fin de posibilidades. Cómo decíamos al principio, más que un framework ReactiveUI representa un cambio en la filosofía de programación.

La curva de aprendizaje es pronunciada pero, afortunadamente, la adopción de ReactiveUI es muy escalable. No hace falta que empleemos todo lo que ofrece este framework desde el principio. Podemos comenzar por las funcionalidades más básicas, y poco a poco ir incorporando y aprovechando las más avanzadas.

En cualquier caso merece la pena que le echéis un vistazo porque hace años que este tipo de patrones son la tendencia habitual en la programación, en los distintos lenguajes y plataformas, tanto en aplicaciones de escritorio como Web.