1
votes

I have 2 Views and 2 ViewModels:

First View:

public partial class FirstView : Page
{
    FirstViewModel ViewModel;
    public FirstView()
    {
        ViewModel = new FirstViewModel();
        ViewModel.ShowSecondView.Subscribe(_ =>
        {              
            NavigationService.Navigate(new SecondView(ViewModel.ChildViewModel));
        });            

        this.DataContext = ViewModel;
        InitializeComponent();            
    }        
}

First ViewModel:

public class FirstViewModel
{
    SecondViewModel ChildViewModel;
    public ReactiveCommand<Unit, Unit> ShowSecondView { get; set; }
    public FirstViewModel()
    {
        ChildViewModel = new SecondViewModel();
        ShowSecondView = ReactiveCommand.Create(() => 
        {
             ChildViewModel.Reconfigure(...);  
        });          
    }        
}

Second View:

public partial class SecondView : Page
{
    SecondViewModel ViewModel;
    public SecondView(SecondViewModel viewModel)
    {
        ViewModel = viewModel;
        ViewModel.GoBack.Subscribe(_ => 
        {
            DoSomethingHard();
            if(NavigationService != null)  NavigationService.GoBack();
        });
        this.DataContext = ViewModel;
        InitializeComponent();            
    }        
}

Second ViewModel:

public class SecondViewModel
{
    public ReactiveCommand<Unit, Unit> GoBack { get; set; }
    public FirstViewModel()
    {
        VeryLongInitialization();
        GoBack = ReactiveCommand.Create(() => { });          
    }        
    public void Reconfigure(...)
    { ... }
}

So, when I run FirstViewModel.ShowSecondView several times and run SecondViewModel.GoBack several times, DoSomethingHard() execute also several times on each created SecondView.

Why do I want create ChildViewModel in FilstViewModel once? Because creating of SecondViewModel takes long time. And I don't recreate every time SecondViewModel but only reconfigure it.

My question is how can I unsubscribe ViewModel.GoBack.Subscribe in SecondView?

P.S. Maybe I should not recreate SecondView in FirstView but reconfigure it as well as SecondViewModel?

UPDATE 1 (thanks to Julien Mialon)

I add IDisposable goBackSubscr and it works! Am I right to implement it?

public partial class SecondView : Page
{
    SecondViewModel ViewModel;
    IDisposable goBackSubscr;
    public SecondView(SecondViewModel viewModel)
    {
        ViewModel = viewModel;
        goBackSubscr = ViewModel.GoBack.Subscribe(_ => 
        {
            DoSomethingHard();
            if(NavigationService != null)  NavigationService.GoBack();
            goBackSubscr.Dispose();
        });
        this.DataContext = ViewModel;
        InitializeComponent();            
    }        
}
5

5 Answers

2
votes

use WhenAcitvated on your view: in constructor of your page (it has to be IViewFor):

 this.WhenActivated(
     disposables =>
     {
         ViewModel.Command.Subscribe(...).(disposables);
     });
2
votes

The subscribe method return an IDisposable, you should store it and dispose it when you want to unsubscribe.

2
votes

Thanks to @Krzysztof Skowronek

I once again read about IViewFor and resolve my problem

public partial class SecondView : Page, IViewFor<SecondViewModel>
{
    public SecondViewModel ViewModel { get; set; }
    object IViewFor.ViewModel { get => ViewModel; set { ViewModel = (SecondViewModel)value; } }
    public SecondView(SecondViewModel viewModel)
    {
        ViewModel = viewModel;
        this.WhenActivated(disposables =>
        {
            ViewModel.GoBack.Subscribe(_ =>
                {
                    DoSomethingHard();
                    if (NavigationService != null) NavigationService.GoBack();
                })
                .DisposeWith(disposables);               

            this.WhenAnyValue(p => p.ViewModel)
                .BindTo(this, x => x.DataContext)
                .DisposeWith(disposables);
        });        
    InitializeComponent();            
    }        
}
0
votes

I think I know what you are trying to do. If I am right, you are trying to create a Wizard of some sort or something that displays one view model that can move to the next, and then that can move prior? If that sis the case then maybe you want to re-think how you are doing it. For example, Why not manage the back and forth buttons in a view model that houses the two view models and have the child view models only create once when they are first required. Then, your navigation buttons can determine which view models are currently on and be enabled depending upon the logic. In a Wizard type scenario, consider basing your child view models on a Base view model that may have a property or method called something like CanMoveNext() and CanMovePrior(). Internally, this can return true if it is ready. Much easier.

0
votes

I would also recommend to enable and disable the commands with the activations and deactivations of the ViewModel.

Using a small helper:

    public static class DisabledCommand
    {
        public static readonly ReactiveCommand<Unit, Unit> Instance
            = ReactiveCommand.Create<Unit, Unit>(_ => Unit.Default, Observable.Return(false));
    }

You can do it like this:

public sealed class MyViewModel : ReactiveObject, IActivateableViewModel
{

    public MyViewModel()
    {
        this.WhenActivated(d =>
        {
            // Observable that returns boolean to define if button is enabled
            // ie. Observable.Create() or this.WhenAnyValue(...)
            var canDoSomething = ...;

            // enable command when VM gets activated
            DoSomething = ReactiveCommand.Create<Unit, Unit>(_ => Unit.Default, canDoSomething);

            // disable command when VM gets disabled
            Disposable.Create(() => DoSomething = DisabledCommand.Instance).DisposeWith(d);

            DoSomething
                .Select(_ => this) // select ViewModel
                .Do(vm =>
                { 
                    // ...
                })
                .ObserveOn(RxApp.MainThreadScheduler)
                .ExecuteOn(RxApp.TaskPoolScheduler)
                .Subscribe()
                .DisposeWith(d);
        });
    }

    [Reactive] public ReactiveCommand<Unit, Unit> DoSomething { get; set; } = DisabledCommand.Instance;
    public ViewModelActivator Activator { get; } = new ViewModelActivator();
}

Then in your view:

public partial class MyView : Page, IViewFor<MyViewModel>
{
    public MyView()
    {
        InitializeComponent();

        this.WhenActivated(d =>
        {
            if(ViewModel == null) return;

            IDisposable current = Disposable.Empty;
            ViewModel.WhenAnyValue(vm => vm.DoSomething)
                .Select(_ => ViewModel)
                .Subscribe(vm => 
                {
                    current.Dispose();
                    current = vm.DoSomething
                        .Select(_ => vm)
                        .Do(vm_ =>
                        {
                            // do something on the UI
                        })
                        .ObserveOn(RxApp.MainThreadScheduler)
                        .SubscribeOn(RxApp.MainThreadScheduler)
                        .Subscribe();
                })
                .ObserveOn(RxApp.MainThreadScheduler)
                .SubscribeOn(RxApp.MainThreadScheduler)
                .Subscribe()
                .DisposeWith(d);

              Disposable.Create(() => current.Dispose()).DisposeWith(d);
        });
    }

    object IViewFor.ViewModel
    {
        get => ViewModel;
        set => ViewModel = value as MyViewModel;
    }

    public MyViewModel ViewModel
    {
        get => DataContext as MyViewModel;
        set => DataContext = value;
    }
}