3

What is the proper usage of Activation/Deactivation in conjunction with ObservableAsPropertyHelper? Given a view and viewmodel that reflects long lived (hot) observables, the subscription would need to be disposed when the view and viewmodel is unloaded. However ObservableAsPropertyHelper, which is recommended to be readonly is assigned in the constructor of the viewmodel, and cannot be part of the activation/deactivation lifecycle. What is the right way to handle these kind of situations?

public interface ILongLivedObject
{
    IObservable<bool> Status { get; }
}


public class TestViewModel : ReactiveObject
{
    private readonly ObservableAsPropertyHelper<bool> _status;
    public bool Status => _status.Value;

    public TestViewModel(ILongLivedObject obj)
    {
         _status = obj.Status.ToProperty(this, vm => vm.Status); //how is the subscription disposed?
    }


}

This also gets me into a corner when trying to add commands that depends on this status. In my application, a common use case is to have some hardware that is on some specific status (e.g. IsOpen) and allow commands when it is true. Without knowing better, this is what I am trying to do:


public class TestViewModel : ReactiveObject
{
    private readonly ObservableAsPropertyHelper<bool> _status;
    public bool Status => _status.Value;

    public ReactiveCommand<Unit, Unit> DoStuff {get;}

    public TestViewModel(ILongLivedObject obj)
    {
         _status = obj.Status.ToProperty(this, vm => vm.Status); //how is the subscription disposed?
         DoStuff = ReactiveCommand.CreateFromTask(....., this.WhenAnyValue(this, x => x.Status);
    }


}

If I try to move the _status creation into this.WhenActivated, the app will crash as the command is trying to get the value of status before it is created. Am I supposed to (re)create the comand during activation? This seems wrong and pretty costly?

So far, it seems better to have a regular Status property with a protected setter and make a regular subscription in this.WhenActivated - but this is what the handbook tells to avoid for "readonly" properties.

Dom
  • 31
  • 2

1 Answers1

3

So one thing to be aware of in Reactive programming, disposing often means "unsubscribe".

You often don't need to unsubscribe since the garbage collector will take care of it for you, providing you create ObservableAsPropertyHelper (abbreviated as OAPH) only with observables generated from the current ViewModel.

In your case however, your observable/object, is related to a object outside the current ViewModel. The OAPH itself is a Disposable object.

So you can use ISupportsActivation (shortly going to have a replacement of IActivableViewModel) and pass your OAPH into it's Disposable property.

public class TestViewModel : ReactiveObject, ISupportsActivation
{
    private readonly ObservableAsPropertyHelper<bool> _status;
    public bool Status => _status.Value;
    public ViewModelActivator Activator { get; } = new ViewModelActivator();

    public TestViewModel(ILongLivedObject obj)
    {
         _status = obj.Status.ToProperty(this, vm => vm.Status);
         this.WhenActivated(disposables =>
         {
             disposables(_status);
         }
    }
}

The disposables parameter passed into the WhenActivated lambda is a Func that takes a IDisposable

In the view, make sure you derive off IActivatable (soon to be renamed IActivatableView) and use WhenActivated in the constructor of the view as well.

Glenn Watson
  • 2,758
  • 1
  • 20
  • 30
  • the readonly _status cannot be assigned in the WhenActivated lambda because it is not actually part of the constructor. This is the problem I hit when following the ReactiveUI handbook. It doesn't seem a way to boith follow the OAPH recommendation and Unsubscribing recommendation (for external observables at least)... – Dom May 10 '19 at 17:32
  • There is a version of WhenActivated that you can return a enumerable of in the Func<> form, so you can always make a CompositeDisposable and pass that in separate.. – Glenn Watson May 11 '19 at 04:59
  • This would work for the first activation but the status subscription would not be executed again on a second activation (such as a TabControl that load/unload the selected tab)? Is there some assumptions in ReactiveUI that views and view controls are one-time only use (which would be expensive in some scenarios)? – Dom May 14 '19 at 02:44
  • Well the OAPH I would argue maybe it shouldn't be readonly in that case since it's context can change every run. You could do it outside the WhenActivated but this could lead to memory leaks potentially. – Glenn Watson May 15 '19 at 01:13
  • So if I have reactive commands where CanExecute depends on such external observables, it would also require to recreate all the commands in WhenActivated, because if I create the command in the constructor using an an OAPH that is not yet created, it leads to a null reference exception as the command tries to get the initial CanExecute value? Are there samples or apps I can look into that has similar use cases? – Dom May 23 '19 at 16:49
  • You can mutate the external observable into a boolean observable by using Select() and not have to rely on the OAPH and pass in the boolean observable into your CanExecute. – Glenn Watson May 26 '19 at 08:10