The Senator The Senator - 1 month ago 9
C# Question

How should I execute a PushAsync navigation request from an Rx subscription on my ViewModel

I have an MVVM style application set up in Xamarin forms where I display my login page via a ViewModel first approach which itself is hosted within a NavigationPage.

I inject an INavigationService instance which establishes the correct Navigation property like this:

var navigationController = Application.Current.MainPage as NavigationPage;

if (navigationController != null)
return navigationController.Navigation;


Which when then gives me access to the PushAsync method like so:

public async Task PushAsync(INavigationPageModel page)
{
var view = _viewFactory.CreatePage(page);
await Navigation.PushAsync(view);
}


Now, if I use this method in a Command (for example) I find my view is correctly pushed onto the navigation stack and is viewable. However I'm using Rx to watch for a 'connected' status on my application via a BehaviourSubject which seems to be causing me some trouble.

I don't receive any errors, but what I find is the view itself appears to be pushed onto the stack but is not visible to me (I can see it on the stack in the debugger). I can only assume that this relates to the UI thread somehow, but I'm not sure.

My RX subscription has gone through a few iterations as it appears that I need to support the call for an async method. Currently it looks like this:

_application.ConnectionManager.State
.Where(s => s == ConnectionState.Connected)
.SelectMany(l => Observable.FromAsync(ChangePageAsync))
.SubscribeOn(Scheduler.UiScheduler)
.Subscribe();


My change page method looks like this:

private Task ChangePageAsync()
{
var viewModel = _viewModelFactory.CreateModel<DashboardPageViewModel>();
return Navigation.PushAsync(viewModel);
}


For completeness I capture the UI scheduler in the AppDelegate for the iOS application like this:

new SynchronizationContextScheduler(SynchronizationContext.Current);


As I say, I get no errors, but it just doesn't display me a new page via the Rx async action. Any ideas? Its driving me crazy.

Answer

Oh, sorry, hadn't fully read the code. You'll want to use ObserveOn rather than SubscribeOn and place it before the SelectMany. Change it to this:

_application.ConnectionManager.State
    .Where(s => s == ConnectionState.Connected)
    .ObserveOn(Scheduler.UiScheduler)
    .SelectMany(l => Observable.FromAsync(ChangePageAsync))
    .Subscribe();

This ensures it's on the UI thread before attempting to change the page. SubscribeOn is used to ensure the scaffolding of the subscription is performed on the specified scheduler (usually used when the subscription is blocking).