0
votes

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.

1
Make sure the thread used to capture the synchronization context (where you capture the UiScheduler) is actually the main UI thread. If the Scheduler class is a singleton (as it appears to be) and the UiScheduler captured lazily (i.e. when the property is first used) then it may first be being accessed from a background thread and therefore not actually be the UI thread. Oh, and SelectMany has an overload that can handle a Task so you don't need the inner 'Observable.FromAsync'.ibebbs
@ibebbs if I push the subscription into another thread then I get the exception stating it is not on the UI thread so I'm assuming that my capture of the synchronisation context is correct. I'm still waiting on someone else to confirm that my capture approach is also valid. Scheduler is a property from an injected scheduler so it is not a singleton! (ugh!)The Senator
My ChangePageAsync method doesn't actually return anything, so I don't think I can use the overload for the SelectMany unless I modify my return type. It's really a side effect of the select - just another attempt at making it work. I had a go and tried changing it. Still no joy.The Senator

1 Answers

1
votes

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