Xamarin Forms / ReactiveUI Router - Показать дочернее представление и выполнить код родительской модели ViewModel после закрытия дочернего представления

В моей родительской ViewModel я использую следующий код для перехода к ChildView с помощью маршрутизации ReactiveUI.

appBootstrapper.Router.Navigate.Execute(new ChildViewModel(HostScreen)).Subscribe();

Этот код работает так, как ожидалось.

Однако, когда дочернее представление закрыто, я хочу выполнить некоторые дополнительные операции (давайте воспользуемся примером скрытия индикатора активности с помощью свойства ViewModel типа bool), например

ShowActivityIndicator = true;
appBootstrapper.Router.Navigate.Execute(new ChildViewModel(HostScreen)).Subscribe();
ShowActivityIndicator = false;

Этот код работает не так, как ожидалось, потому что отображается дочерний View, а затем сразу выполняется ShowActivityIndicator = false;.

Как мне переписать код так, чтобы ShowActivityIndicator = false; выполнялся после, ChildView закрывается? Спасибо!


ОБНОВЛЕНИЕ

Благодаря предложению Гленна я попытался использовать технику WhenNavigatingFromObservable().Subscribe(...), но лямбда-код никогда не выполняется. Мой код выглядит следующим образом ...

var microsoftSignInVM = new ViewModel.MicrosoftSignInVM(HostScreen);
//
microsoftSignInVM.WhenNavigatingFromObservable().Subscribe((_) =>
{
    ShowActivityIndicator = true;
    //
    Data.Synchronisation.SynchroniseLocalDataCacheAsync();
    //
    appBootstrapper.Router.Navigate.Execute(new ViewModel.SelectJobVM(HostScreen)).Subscribe();
    //
    ShowActivityIndicator = false;
});
//
appBootstrapper.Router.Navigate.Execute(microsoftSignInVM).Subscribe();

Я поместил точку останова в лямбду, и она никогда не срабатывает.


ОБНОВЛЕНИЕ 2 - ОБНОВЛЕНИЕ

Я создал пару минимальных тестовых классов. ЭТИ РАБОТАЮТ В ПРОЕКТЕ WPF, НО НЕ РАБОТАЮТ С ИСПОЛЬЗОВАНИЕМ ФОРМ XAMARIN ...

Отправлено

TestAV.xaml.cs

namespace TestApp.Test
{
    [XamlCompilation(XamlCompilationOptions.Compile)]
    public partial class TestAV : ReactiveContentPage<TestAVM>
    {
        public TestAV()
        {
            InitializeComponent();
                //
                this.WhenActivated(
                    disposables =>
                    {
                        this.BindCommand(this.ViewModel, x => x.ClickCommand, x => x.button)
                            .DisposeWith(disposables);
                    });
        }
    }
}

TestAVM.cs

namespace TestApp.Test
{
    public class TestAVM : ReactiveObject, IEnableLogger, IRoutableViewModel
    {
        public TestAVM(IScreen hostScreen)
        {
            _HostScreen = hostScreen;
            //
            TestBVM testBVM = new TestBVM(_HostScreen);
            //
            _ClickCommand = ReactiveCommand.CreateFromObservable(() => _HostScreen.Router.Navigate.Execute(testBVM).Select(_ => Unit.Default));
            //
            testBVM.WhenNavigatingFromObservable().Subscribe((_) =>
            {
                ShowActivityIndicator = true;
            });
        }
        //
        public bool ShowActivityIndicator { get; set; }
        //
        private readonly ReactiveCommand<Unit, Unit> _ClickCommand; public ReactiveCommand<Unit, Unit> ClickCommand => _ClickCommand;
        //
        private readonly IScreen _HostScreen; public IScreen HostScreen => _HostScreen;
        //
        public string UrlPathSegment => "TestA";
    }
}

TestBV.xaml.cs

namespace TestApp.Test
{
    [XamlCompilation(XamlCompilationOptions.Compile)]
    public partial class TestBV : ReactiveContentPage<TestBVM>
    {
        public TestBV()
        {
            InitializeComponent();
            //
            this.WhenActivated(
                disposables =>
                {
                    this
                        .BindCommand(this.ViewModel, x => x.ClickCommand, x => x.button)
                        .DisposeWith(disposables);
                });
        }
    }
}

TestBVM.cs

namespace TestApp.Test
{
    public class TestBVM : ReactiveObject, IEnableLogger, IRoutableViewModel
    {
        public TestBVM(IScreen hostScreen)
        {
            _HostScreen = hostScreen;
        }
        //
        public ReactiveCommand<Unit, Unit> ClickCommand => _HostScreen.Router.NavigateBack;
        //
        private readonly IScreen _HostScreen; public IScreen HostScreen => _HostScreen;
        //
        public string UrlPathSegment => "TestB";
    }
}

Файлы, специфичные для Xamarin

AppBootstrapper.cs

namespace TestApp
{
    public class AppBootstrapper : ReactiveObject, IScreen
    {
        public AppBootstrapper(IMutableDependencyResolver dependencyResolver = null, RoutingState router = null)
        {
            Router = router ?? new RoutingState();
            //
            RegisterParts(dependencyResolver ?? Locator.CurrentMutable);
            //
            Router.Navigate.Execute(new Test.TestAVM(this));
        }

        public RoutingState Router { get; private set; }

        private void RegisterParts(IMutableDependencyResolver dependencyResolver)
        {
            dependencyResolver.RegisterConstant(this, typeof(IScreen));
            //
            dependencyResolver.Register(() => new Test.TestAV(), typeof(IViewFor<Test.TestAVM>));
            dependencyResolver.Register(() => new Test.TestBV(), typeof(IViewFor<Test.TestBVM>));
        }

        public Page CreateMainPage()
        {
            return new RoutedViewHost();
        }
    }
}

TestAV.xaml

<?xml version="1.0" encoding="utf-8" ?>
<rxui:ReactiveContentPage xmlns="http://xamarin.com/schemas/2014/forms"
                        xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                           xmlns:rxui="clr-namespace:ReactiveUI.XamForms;assembly=ReactiveUI.XamForms"
                          xmlns:vm="clr-namespace:TestApp.Test"
                          x:TypeArguments="vm:TestAVM"
                        x:Class="TestApp.Test.TestAV">
    <ContentPage.Content>
        <StackLayout>
            <Button x:Name="button" Text="click A" HorizontalOptions="CenterAndExpand" />
        </StackLayout>
    </ContentPage.Content>
</rxui:ReactiveContentPage>

TestBV.xaml

<?xml version="1.0" encoding="utf-8" ?>
<rxui:ReactiveContentPage xmlns="http://xamarin.com/schemas/2014/forms"
                        xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                           xmlns:rxui="clr-namespace:ReactiveUI.XamForms;assembly=ReactiveUI.XamForms"
                          xmlns:vm="clr-namespace:TestApp.Test"
                          x:TypeArguments="vm:TestBVM"
             x:Class="TestApp.Test.TestBV">
    <ContentPage.Content>
        <StackLayout>
            <Button x:Name="button" Text="click B" HorizontalOptions="CenterAndExpand" />
        </StackLayout>
    </ContentPage.Content>
</rxui:ReactiveContentPage>

Специальные файлы WPF

TestAV.xaml

<rxui:ReactiveUserControl x:Class="TestApp.TestAV"
            x:TypeArguments="vm:TestAVM"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:rxui="http://reactiveui.net"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:vm="clr-namespace:TestApp.Test">
    <Grid>
        <Button x:Name="button" Content="click A" />
    </Grid>
</rxui:ReactiveUserControl>

TestBV.xaml

<rxui:ReactiveUserControl x:Class="TestApp.TestBV"
            x:TypeArguments="vm:TestBVM"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:rxui="http://reactiveui.net"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:vm="clr-namespace:TestApp.Test">
    <Grid>
        <Button x:Name="button" Content="click B" />
    </Grid>
</rxui:ReactiveUserControl>

Стоит отметить, что если я использую

_ClickCommand = ReactiveCommand.CreateFromObservable(() => _HostScreen.Router.Navigate.Execute(new TestAVM()).Select(_ => Unit.Default));

в TestBVM.cs, лямбда срабатывает должным образом.

Похоже, есть проблема с реализацией Router.NavigateBack в Xamarin Forms, но я открыт для других наблюдений!


person 3-14159265358979323846264    schedule 15.05.2019    source источник


Ответы (1)


Пара разных способов подойти к этому.

Маршрутизатор имеет несколько наблюдаемых объектов, на которые вы можете подписаться при вызове операций в стеке навигации. В RoutableViewModelMixin есть также метод расширения под названием WhenNavigatingFromObservable, который вы можете использовать.

public void ShowChildView()
{
    ShowActivityIndicator = true;
    var childViewModel = new ChildViewModel(HostScreen);
    // Use the extension method to get an observable when the child view is navigated away from, subscribe and set the variable.
    childViewModel.WhenNavigatingFromObservable().Subscribe(x => ShowActivityIndicator = false);
    appBootstrapper.Router.Navigate.Execute(childViewModel).Subscribe();
} 

Второй вариант: если вы не возражаете против функциональности, выполняемой ребенком, вы можете использовать механику WhenActivated ().

public ChildView()
{
    this.WhenActivated(disposable => 
     {
        // do activation logic.
        return new[] { Disposable.Create(() => DoDeactivationLogic());
     }

Дополнительные сведения см. Здесь https://reactiveui.net/docs/handbook/when-activated/

person Glenn Watson    schedule 15.05.2019
comment
Спасибо за Ваш ответ. Я пытался реализовать вариант 1, но по какой-то причине lamdba в методе childViewModel.WhenNavigatingFromObservable().Subscribe(...) никогда не выполняется? - person 3-14159265358979323846264; 15.05.2019
comment
Я обновил свой ответ, включив в него фактический код, который я использую. - person 3-14159265358979323846264; 15.05.2019
comment
Он зависит от установленного свойства HostScreen. Таким образом, вы можете передать его или использовать DI для его хранения. HostScreen содержит маршрутизатор - person Glenn Watson; 16.05.2019
comment
Еще раз спасибо. Я добавил к своему вопросу минимальный, но полный пример, но лямбда все еще не запускается ... На самом деле не вижу, что я делаю не так! - person 3-14159265358979323846264; 16.05.2019
comment
Я снова обновил свой ответ ... похоже, проблема связана с реализацией Router.NavigateBack в Xamarin Forms - person 3-14159265358979323846264; 17.05.2019
comment
Возможно, стоит сообщить о проблеме на странице GitHub. Наверное, регресс какой-то. - person Glenn Watson; 17.05.2019
comment
Извини, Гленн. Я к этому подходил, обещаю ... мне пришлось проделать огромную работу, так как я провел 2 дня, экспериментируя и пытаясь выяснить, в чем проблема ... босс был недоволен! - person 3-14159265358979323846264; 21.05.2019
comment
Нет проблем, я проведу несколько модульных тестов на следующий день или около того и посмотрю, смогу ли я исправить для вас новую ошибку на этой неделе. - person Glenn Watson; 22.05.2019