1

I'm using the AvaloniaUI framework to build an application.
I have a viewmodel that implements IActivatableViewModel and I am calling WhenActivated in the viewmodel constructor. I have a method defined HandleActivation that is called when the viewmodel is activated, and HandleDeactivation when the viewmodel is deactivated. Everything is being called properly, there's no issues there.

It looks like this:

public abstract class MyViewModel: ViewModelBase, IActivatableViewModel
    {
        public ViewModelActivator Activator { get; }

        protected MyViewModel() : base()
        {
            Activator = new ViewModelActivator();
            this.WhenActivated(disposables =>
            {
                this.HandleActivation();

                Disposable
                    .Create(this.HandleDeactivation)
                    .DisposeWith(disposables);
            });
        }

        private void HandleActivation()
        { }

        private void HandleDeactivation()
        { }
    }

I also have a data service that retrieves some data from a database. The method I want to call returns a Task<T>. It looks like this:

public interface IDataService 
{
     Task<IList<UserDto>> GetActiveUsers();
}

What I'd like to do is call GetActiveUsers from the HandleActivation method. I need to get the list of users and then do some post processing on them after retrieving them.
If I were doing this in an async method, I would do something like this

private async Task HandleActivation()
{
    var users = await _dataService.GetActiveUsers();
    foreach(var user in users)
    {
       //do stuff
    }
}

With HandleActivation not being an async method I'm a little confused on how to go about doing this.
Is there a way I can make HandleActivation async, is there a "reactive" approach to this that I am missing. I'm very new to AvaloniaUI and ReactiveUI and reactive programming itself, so I'm sure there is a "right" way to do this, but I'm having trouble figuring it out.

thornhill
  • 632
  • 7
  • 17

2 Answers2

2

The pattern about handling activation and deactivation in your first code fragment matches the examples in the ReactiveUI documentation, however no one prevents you from writing the HandleActivation method to be async so it would look like this:

    protected MyViewModel() : base()
    {
        Activator = new ViewModelActivator();

        this.WhenActivated(disposables =>
        {
            this.HandleActivation().ConfigureAwait(false);

            Disposable
                .Create(this.HandleDeactivation)
                .DisposeWith(disposables);
        });
    }

    private async Task HandleActivation()
    {
        var users = await Task<IEnumerable<UserDto>>.Factory.StartNew(() => _dataService.GetActiveUsers().Result);

        foreach (var user in users)
        {
            // do stuff
        }
    }

The more reactive approach using observables could look like this:

    protected MyViewModel() : base()
    {
        Activator = new ViewModelActivator();

        this.WhenActivated(disposables =>
        {
            this.HandleActivation(disposables);

            Disposable
                .Create(this.HandleDeactivation)
                .DisposeWith(disposables);
        });
    }


    private void HandleActivation(CompositeDisposable disposables)
    {
        Observable.Start(() => _dataService.GetActiveUsers().Result)
            .ObserveOn(RxApp.MainThreadScheduler) // schedule back to main scheduler only if the 'stuff to do' is on ui thread
            .Subscribe(users => DoStuffWithTheUsers(users))
            .DisposeWith(disposables);
    }

    private void DoStuffWithTheUsers(IEnumerable<UserDto> users)
    {
        foreach (var user in users)
        {
            // do stuff
        }
    }

This would even work asynchronously if GetActiveUsers itself was a synchronous method returning IList because Observable.Start already invokes the method asynchronously. The only difference in the code example would be that ".Result" would have to be removed.

Döharrrck
  • 687
  • 1
  • 4
  • 15
  • Do you need to add a `DisposeWith()` on the `Subscribe()` call, or is that unnecessary? – David Mercer Sep 07 '21 at 18:32
  • I only made the minimum changes to the original question to make it async but I think you are right. the `HandleActivation` method should take the `disposables` as parameter so they can be used to dispose the subscription. – Döharrrck Apr 24 '23 at 20:07
2

Another option of handling such patterns would be to use a ReactiveCommand<Unit, Unit>. Reactive commands are great, they give you superpower in managing long-running asynchronous operations. A reactive command allows you to subscribe to the IsExecuting and ThrownExceptions observables and keep your users informed about what your app is doing. Also, reactive commands support cancelation. So I'd probably suggest you to move the activation logic into a reactive command named e.g. Activate, to move deactivation logic into a reactive command named Deactivate, and then inside the WhenActivated block you can do somewhat like this:

this.WhenActivated(disposable => {
    Activate.Execute().Subscribe(); // Handle async activation
    Disposable
        .Create(() => Deactivate.Execute().Subscribe()) // Handle async deactivation
        .DisposeWith(disposable);
});

See also this StackOverflow question.

Artyom
  • 446
  • 4
  • 7