question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Advanced Avalonia+ReactiveUI+Routing example

See original GitHub issue

Hello,

I want to say that I’m excited to work with Avalonia, this is a fantastic experience to write cross-platform applications which actually works the same on Windows and Linux!

However, to write some advanced applications which uses the full power of chain Avalonia+ReactiveUI+Routing+Asyncs we definitely need some advanced example of code which can show us how to use all these things properly. Also, the current docs about integration with ReactiveUI shows us the very simple use-cases and docs for ReactiveUI do not cover enough to be sure that you are writing the leaks-free code in proper style.

Long story short, so, I have started this issue because I want to understand the following concepts.

VM Reactive Commands and Routing

  • Where the ReactiveCommand should be created - in VM constructor or in WhenActivated block? And why?
  • What is the best way to create the async commands with async/await logic? Or may be they should be created in other way?
  • How to properly lock the view (aka make it ReadOnly) when the command is executing?
  • How to properly setup and use Routing - for example if I have a command which performs some async operation (like request to some REST API) how should I handle properly:
    • The making UI readonly during the request (related to previous point)
    • Return errors to UI textblock
    • Navigate somewhere using Router.Navigate (call it in subscription/call it in async/await source method/in other way)

Binding VMs to Views

  • What is the best way of binding - via XAML or via WhenActivated in view with DisposeWith call? And how I should decide which way should be chosen?
  • How to avoid memory leaks on bindings?

Observable examples

  • Good use-cases with observing SourceCache<TModel, TKey> from singleton service via Connect with covering the following topics:
    • How to use Connect?
    • How to Bind connected SourceCache to ObservableCollectionExtended
    • How it should be disposed?
  • Advanced example of Oaph usage - assign from multiple sources, etc. (And how to properly dispose them/Do we need to dispose them?)
  • Cover most common use cases with Subscription and proper disposing of them
  • Example of this.WhenAnyValue usage and proper disposing of it (or when and how it should be disposed?)

I’m not sure what is the best way how to communicate about it. Ideally, I want to write some example (and possibly exemplary application) after understanding all described concepts. Hope that this future sample application can be used as reference to get started and include it into samples in Avalonia (or as reference here https://github.com/AvaloniaCommunity/awesome-avalonia).

In best case I want to help to extend the Avalonia documentation with all these info.

Hope that you will be able to answer on my questions or provide some links to samples which covers all described cases.

Thanks!

Issue Analytics

  • State:closed
  • Created 4 years ago
  • Reactions:1
  • Comments:8 (3 by maintainers)

github_iconTop GitHub Comments

3reactions
worldbeatercommented, Feb 24, 2020

Generally most of the design patterns folks tend to use in WPF are also applicable to Avalonia. There is an online ReactiveUI Handbook which covers almost every aspect of the use cases of the reactive framework, including commanding, routing, activation and memory management. There is also a book by Kent Boogaart which includes a lot of ReactiveUI and System.Reactive usage examples. There is also a bunch of open-source samples for various platforms.

VM Reactive Commands and Routing

On every platform ReactiveUI supports folks usually create the commands in the constructor, and asynchronous logic is usually extracted into separate services which are injected into the view model via the constructor. A button bound to a command will be locked automatically, and to lock the whole view you could declare an OAPH and bind it to the IsEnabled property of any Avalonia XAML control, or of a window.

private readonly ReactiveCommand<Unit, Unit> _command;
private readonly ObservableAsPropertyHelper<bool> _isEnabled;
public bool IsEnabled => _isEnabled.Value;

_command = ReactiveCommand.CreateFromTask(() => Task.Delay(1000));
_isEnabled = command.IsExecuting.ToProperty(this, x => x.IsEnabled);
<TextBox Text="Hello!" IsEnabled="{Binding IsEnabled}" />

A feature unique to Avalonia is, that the GUI framework allows us to bind to an IObservable<T> directly without using OAPHs. Subscriptions to such observables will get disposed automatically. See the Binding to Tasks and Observables section for more info on this topic. Binding to an IObservable<T> directly using Avalonia bindings is generally more perfomant.

private readonly ReactiveCommand<Unit, Unit> _command;
public IObservable<bool> IsEnabled => _command.IsExecuting;

_command = ReactiveCommand.CreateFromTask(() => Task.Delay(1000));
<TextBox Text="Hello!" IsEnabled="{Binding IsEnabled^}" />

In case if you are sharing your view models across different platforms that don’t support this feature (e.g. WPF, WinForms or XF), use the WhenAnyObservable call inside a WhenActivated block on the view side, to bind IObservable<T> to a property manually, e.g.

this.WhenActivated(disposables => {
    this.WhenAnyObservable(x => x.ViewModel.StatusChanged)
        .BindTo(this, x => x.StatusControl)
        .DisposeWith(disposables);
});

Binding VMs to Views

AFAIK, the code-behind bindings were developed to allow the bindings to be compile-time-checked, and aren’t necessarily needed in Avalonia, because its XAML markup is compiled. The memory won’t leak as well if you bind only to view model properties and not to the properties of injected services that can potentially outlive both the view and the view model. In short, if an object subscribes to its own properties, there is no need to dispose such subscriptions. For example, here the memory won’t leak. See When should I bother disposing IDisposable objects?

// No need to dispose the subscription.
this.WhenAnyValue(x => x.SearchQuery)
    .Throttle(TimeSpan.FromSeconds(1))
    .InvokeCommand(this, x => x.PerformSearch);

// Disposing the subscription is a must.
this.WhenActivated(disposables => {
  // e.g. the memory could leak if the dependency
  // is a singleton, and the view model is not.
  injectedViewModelDependency
    .ObservableSearchResults
    .ObserveOn(RxApp.MainThreadScheduler)
    .Subscribe(results => SearchResults = results)
    .DisposeWith(disposables);
});

If you have a ReadOnlyObservableCollection<SomeDisposableViewModel> of view models with subscriptions and you need those subscriptions to be disposed automatically once an element is removed from the collection, take a look at the DisposeMany DynamicData operator. With its help, you can use a different approach — simply implement the IDisposable interface as usual, or, using a CompositeDisposable; and add a call to .DisposeMany() to your IObservable<IChangeSet<T>> pipeline.

Observable examples

To combine multiple observable streams into a single stream either use CombineLatest or Zip depending on what you are willing to achieve. For better understanding of such operators, as Connect and RefCount, read a note about Hot and Cold Observables.

For DynamicData questions consider joining ReactiveUI Slack, the #dynamicdata channel. Generally you don’t want to bind a SourceCache to an ObservableCollectionExtended as it is a mutable collection, and the thing you are looking for is a ReadOnlyObservableCollection and some kind of a Merge operator. See DynamicData Blog for more info about idiomatic usage. See also the DynamicData.Snippets project which contains a lot of code examples and a blog post which explains the differences between the typical IObservable<T> and IObservable<IChangeSet<T>>. Thanks for your interest!

1reaction
worldbeatercommented, Mar 8, 2020

@cheprogrammer didn’t have time to do a full profiling session, but probably the issue is in here https://github.com/cheprogrammer/SampleAvaloniaApplication/blob/001d741a15af7284b0960f51bb910b1c580cc8e0/Client/SampleAvaloniaApplication.Client.Core/Models/EmployeeModel.cs All the properties of the dto are marked as [Reactive]. Then, those properties are observed in the EditEmployeeViewModel and never unsubscribed, and the referenced model is kept inside the EmployeesService. Try using NavigateAndReset instead of Navigate here, because otherwise the view model is left in the navigation stack as well https://github.com/cheprogrammer/SampleAvaloniaApplication/blob/001d741a15af7284b0960f51bb910b1c580cc8e0/Client/SampleAvaloniaApplication.Client.Core/ViewModels/Employees/EmployeesViewModel.cs#L56 But still worth doing a profiling session, probably will have time this weekend.

A common pattern is to proxy the properties of a model to the view model, so the dto which is saved to the database is never subscribed to, and as a result it never introduces a potential for a memory leak. If a view model observes its own properties, then it won’t leak. So another suggestion is to keep your database entities as plain C# objects without Reactive attributes.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Routing
Sextant library for advanced XF routing; AvaloniaUI routing guide · Universal Windows Platform routing samples. Using Visual Studio, create a new WPF project ......
Read more >
ReactiveUI
The package includes helpers specifically for Avalonia U_I to handle the _ReactiveUI tasks of view model-based routing, view activation and scheduling. (see the ......
Read more >
MVVM design help (Avalonia + ReactiveUI) : r/csharp
I was already looking for any solution and found ReactiveUI Routing, but as far as I understand it, it looks like it's for...
Read more >
Multiplatform Avalonia .NET Framework Programming ...
The purpose of this article is to continue explaining advanced Avalonia concepts using simple coding samples.
Read more >
zhouyang/ReactiveUI
An advanced, composable, functional reactive model-view-viewmodel framework for all .NET platforms that is inspired by functional reactive programming.
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found