Search code examples
c#reactiveuiavaloniaui

Call async method from WhenActivated in IActivatableViewModel


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.


Solution

  • 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.