Skip to content

Instantly share code, notes, and snippets.

@SuperJMN
Last active July 13, 2022 13:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save SuperJMN/9098373c493026b9890ffb897bb83fff to your computer and use it in GitHub Desktop.
Save SuperJMN/9098373c493026b9890ffb897bb83fff to your computer and use it in GitHub Desktop.
MVVM framework replacement

MVVM framework replacement

Goal

We want to replace ReactiveUI by another MVVM toolkit. The candidate for such move is the library called CommunityToolkit.Mvvm AKA Microsoft.Toolkit.Mvvm. This text will use “CTM” for short.

Evaluation

The library has been evaluated, focusing on features that can be useful for Wasabi Wallet.

Features

The main features of the library have been checked to see the viability of a transition from ReactiveUI:

  • Source generation. This library has attributes to help users avoid adding repetitive code that usually clutters ViewModels.
  • Optional base class for observable classes. Multiple inheritance can be simulated using attributes like [INotifyPropertyChanged].
  • Messenger pattern.
  • Support for IoC.
  • Validation.
  • Commands (including async commands).
  • Mechanism to expose Tasks as properties.

Source generation

CTM includes the [ObservableProperty] and [RelayCommand] attributes to simplify the code.

  • ObservablePropertyAttribute does exactly what the existing AutoNotifyAttribute.
  • RelayCommandAttribute is an attribute that can be added to a method. It will generate a command that invokes that method when the command is executed. There’s a property in the attribute to specify a Boolean property or method which controls executability. It also exposes options to control whether concurrent execution is allowed or not.

Optional observable base class

In order to make it easier to adapt existing code to CTM, it allows users to keep their current inheritance untouched thanks to source generation. Applying an attribute to a class will generate the appropriate members to, for example, implement INotifyPropertyChanged. This is important for us, since we aren’t removing RxUI’s ReactiveObject as base class for ViewModels, we can mix RxUI / CTM without breaking the current behavior.

Messenger pattern

This is built-in in RxUI. We don’t use it.

Support for IoC.

Built-in in RxUI. We don’t use it.

Validation

We have a validation system. Not evaluated as part of this assessment.

Commands

CTM supports the creation of both sync and asynchronous commands.

Mechanism to expose Tasks as properties

CTM use a wrapper class that can expose Tasks as properties. We don’t use it.

Assessment

The features that we currently use have been checked for viability and these are the results:

  • Source generation works perfectly to observe properties. The migration from AutoNotify to ObservableProperty should be trivial. However, it might be necessary to change ViewModelBase to accommodate ReactiveObject with the code that is autogenerated by CTM.
  • We could the RelayCommand annotation to generate most of the simple commands. More complex commands are rare but would force us to keep using ReactiveCommand from RxUI.
  • Validation hasn’t been assessed. We might consider migrating this part if is CTM’s validation is convenient.

NOTICE

This assessment has been done using the latest preview of CommunityToolkit.Mvvm: 8.0.0 Preview 4. The latest stable version (7.x) doesn’t support code generation for generic classes, as showed in CommunityToolkit/WindowsCommunityToolkit#4600

Suitability

There are 3 different aspects that have to be taken into account:

1. Wasabi is based on Reactive Programming

CTM is a very light MVVM framework that can cover the most common usage scenarios. We use reactive programming extensively in all our ViewModels and our Views. However, it hasn’t been designed to fit with reactive programming. This means that there’s no easy option to completely replace ReactiveUI. In fact, replacing it would be a bad move, since reactive programming has been helping us make our code more declarative and understandable. Doing the same with regular imperative programming has been proven error prone. If we replace RxUI by another framework, this other framework should offer us a simple way to convert regular properties to observables and vice versa. The same applies to Commands.

2. Code generation

This framework offers the code generation features we already have. It works as expected and might allow us to remove our own code generation classes.

  • A little inconvenience is that it’s not possible to specify the visibility of the setters in the generated properties, but that shouldn’t be a problem. However, we should take care because migration can be a bit more time consuming due to this.

3. Future of the library

This library looks like a basic MVVM framework with a low learning curve, and it seems it will continue to be so in the future.

  • The library is quite new and we still cannot foresee which features will come in the future, but it's unlikely that it will gain compatibility with System.Reactive or something similar.
  • Something to take into account is that CTM is maintained by the community, usually people from Microsoft of related to the .NET Foundation, with all the consequences.

Roadmap proposal

This is a list of steps that can be done with little effort and very low risk:

  1. Introduce CommunityToolkit.Mvvm 8.0, preferably when it’s been released.
  2. Replace [AutoNotify] by [ObservableProperty].
  3. Review each ReactiveCommand in the solution. Simple ones, that are most of them, should be easily replaceable by the [RelayCommand] annotation.
  4. Remove existing code generation classes related with [AutoNotify].
  5. Keep ReactiveUI and CTM coexisting.

General improvements to ViewModels

We need to review the code in our ViewModels.

  • Subscribe methods shouldn’t contain business logic. Most of times it should be empty: .Subscribe();
  • Simplify reactive pipelines: Select(_ => Unit.Default) can be converted to ToSignal()
  • Remove all calls to ObserveOn unless it’s necessary.
  • Bind to Observables when it’s possible. Avalonia can bind directly to them: Eg. {Binding IsVisible^} where IsVisible is IObservable<bool>

This will allow us to avoid ObservableAsPropertyHelper.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment