Search code examples
c#iosxamarinmvvmcross

MvxTabBarViewController as first ViewController doesn't appear


In my Xamarin.iOS app (which uses MvvmCross), I have registered a custom AppStart that starts either the login screen or the main screen based on whether the user has already logged in. I'm using EntityFrameworkCore to store user data and loading information from the database at startup works fine, the problem comes about after calling await NavigationService.Navigate<MainViewModel() from AppStart.

I receive the message in the debugger that MvvmCross navigated (iOSNavigation) which is immediately followed by Request is null - assuming this is a TabBar type situation where ViewDidLoad is called during construction... patching the request now - but watch out for problems with virtual calls during construction, which as far as I could tell from my research online, is normal. However, the view never appears and the app remains stuck on the launch/splash screen.

My MainViewController (which corresponds to MainViewModel) inherits from MvxTabBarViewController and has the following presentation attribute: [MvxRootPresentation(AnimationOptions = UIViewAnimationOptions.TransitionCrossDissolve | UIViewAnimationOptions.CurveEaseInOut, WrapInNavigationController = true)].

MainViewController's only constructor is:

public MainViewController()
    : base()
{
    // No call to ViewDidLoad here as base() seems to do it for me.
}

Everything works fine in my Xamarin.Android project so I'm guessing it's on the iOS side.

MvvmCross 6.3.1.

EDIT

The tabs that are to be displayed are created inside MainViewController's ViewDidLoad:

public override void ViewDidLoad()
{
    base.ViewDidLoad();

    if (ViewModel == null)
        return;

    // There are 3 ViewControllers, all created this way.
    var viewControllerOne = new ViewControllerOne 
    {
        ViewModel = ViewModel.ViewModelOne,
        TabBarItem = new UITabBarItem(ViewModel.ViewModelOne.Title, UIImage.FromBundle("Icon1"), 0)
    };

    ViewControllers = new UIViewController[]
    {
        viewControllerOne,
        viewControllerTwo,
        viewControllerThree
    };
}

Each tab inherits from MvxViewController and has the [MvxTabPresentation] presentation attribute. The constructor for each of the tabs is:

public ViewControllerOne()    // One, Two, Three
    : base("ViewOne", null)    // One, Two, Three
{
    // None of the tab views currently have any bindings to ViewModels,
just a UILabel constrained to the centre of the view for testing purposes.
}

I tried running the initial navigation logic on the main thread and no difference was made. This is how I did it inside MvxAppStart.NavigateToFirstViewModel:

await Mvx.IoCProvider.Resolve<IMvxMainThreadAsyncDispatcher>().ExecuteOnMainThreadAsync(() =>
{
    if (isLoggedIn)
        NavigationService.Navigate<MainViewModel>().GetAwaiter().GetResult();
    else
        NavigationService.Navigate<LoginViewModel>().GetAwaiter().GetResult();
});

Solution

  • The issue was that my ViewModel logic was blocking the main thread.

    In my MainViewModel I have overridden the Initialize task to asynchronously load data from an API for the individual tabs to display.

    Originally, I was using IMvxMainThreadAsyncDispatcher to run things on the main thread when I should have been using the MvxViewModel methods InvokeOnMainThread and InvokeOnMainThreadAsync. Now that I'm using these the app launches without issue.

    Thank you to everyone who tried to help and special thanks to Cheesebaron for pointing me in the right direction regarding blocking the main thread.