locked
Xamarin.Forms iOS Error: NavigationPage must have a root Page before being used. RRS feed

  • Question

  • User391091 posted

    I'm using MVVM in my project, and there is a class (named NavigationService) which is responsible for navigation within the app. When I'm running the app for Android platform, everything works well. But when I'm running the app in iOS platform, I'm getting this error.

    Some background: The first screen is NavigationPage for login and forgotpassword pages.

    After successful login, NavigationService replaces the NavigationPage with a MasterDetail where the Detail is a new NavigationPage.

    After I login the logic follows these steps:

    1. Create a MasterDetail page, bind to ViewModel and set it to be the CurrentApplication.MainPage
    2. In the MasterDetail Init, use NavigationService to navigate to the navigationPage (in this case the NavigationService will check and create the Navigation page and assign it to be the MasterDetail.Detail in case its not created yet.)

    But the crash happens when I am assigning the MasterDetail page to be the MainPage. I cant understand why it thinks that I'm trying to put a NavigationPage....

    This is the code for the NavigationService (The main navigation logic happens in InternalNavigateToAsync function):

    using MoblieCP.Bootstrap; using MoblieCP.Contracts.Services.General; using MoblieCP.Pages; using MoblieCP.ViewModels; using MoblieCP.ViewModels.Base; using System; using System.Collections.Generic; using System.Threading.Tasks; using Xamarin.Forms;

    namespace MoblieCP.Services.General { class NavigationService : INavigationService { private readonly Dictionary _mappings;

        protected Application CurrentApplication => Application.Current;
    
        public NavigationService()
        {
            _mappings = new Dictionary<Type, Type>();
    
            CreatePageViewModelMappings();
        }
    
        public async Task InitializeAsync()
        {
            bool loginTestNavigation = false;
    
            if (loginTestNavigation)
            {
                await NavigateToAsync<MainViewModel>();
            }
            else
            {
                await NavigateToAsync<LoginViewModel>();
            }
        }
    
        public Task NavigateToAsync<TViewModel>() where TViewModel : ViewModelBase
        {
            return InternalNavigateToAsync(typeof(TViewModel), null);
        }
    
        public Task NavigateToAsync<TViewModel>(object parameter) where TViewModel : ViewModelBase
        {
            return InternalNavigateToAsync(typeof(TViewModel), parameter);
        }       
    
        public Task NavigateToAsync(Type viewModelType)
        {
            return InternalNavigateToAsync(viewModelType, null);
        }
    
        public async Task ClearBackStack()
        {
            await CurrentApplication.MainPage.Navigation.PopToRootAsync();
        }
    
        public Task NavigateToAsync(Type viewModelType, object parameter)
        {
            return InternalNavigateToAsync(viewModelType, parameter);
        }
    
        public async Task NavigateBackAsync()
        {
            if (CurrentApplication.MainPage is MainPage mainPage)
            {
                await mainPage.Detail.Navigation.PopAsync();
            }
            else if (CurrentApplication.MainPage != null)
            {
                await CurrentApplication.MainPage.Navigation.PopAsync();
            }
        }
    
        public virtual Task RemoveLastFromBackStackAsync()
        {
            if (CurrentApplication.MainPage is MainPage mainPage)
            {
                mainPage.Detail.Navigation.RemovePage(
                    mainPage.Detail.Navigation.NavigationStack[mainPage.Detail.Navigation.NavigationStack.Count - 2]);
            }
    
            return Task.FromResult(true);
        }
    
        public async Task PopToRootAsync()
        {
            if (CurrentApplication.MainPage is MainPage mainPage)
            {
                await mainPage.Detail.Navigation.PopToRootAsync();
            }
        }
    
        protected virtual async Task InternalNavigateToAsync(Type viewModelType, object parameter)
        {
            Page page = CreateAndBindPage(viewModelType, parameter);
    
            if (page is MainPage)
            {
                try
                {
                    CurrentApplication.MainPage = page;  //<---- EXCEPTION HAPPENS HERE IN IOS!!!
                }
                catch(Exception ex)
                {
                    var msg = ex.Message;
                }
            }
            else if (page is LoginPage)
            {
                CurrentApplication.MainPage = new MobileCpLoginNavigationPage(page);
            }
            else if (CurrentApplication.MainPage is MobileCpLoginNavigationPage loginNavigationPage)
            {
                if (page is ForgotPasswordPage)
                {
                    var currentLoginPage = loginNavigationPage.CurrentPage;
    
                    if (currentLoginPage.GetType() != page.GetType())
                    {
                        await loginNavigationPage.PushAsync(page);
                    }
                }
            }
            else if (CurrentApplication.MainPage is MainPage)
            {
                var mainPage = CurrentApplication.MainPage as MainPage;
    
                if (mainPage.Detail is MobileCpNavigationPage navigationPage)
                {
                    var currentPage = navigationPage.CurrentPage;
    
                    if (currentPage.GetType() != page.GetType())
                    {
                        await navigationPage.PushAsync(page);
                    }
                }
                else
                {
                    navigationPage = new MobileCpNavigationPage(page);
                    mainPage.Detail = navigationPage;
                }
    
                mainPage.IsPresented = false;
            }
            else
            {
                var navigationPage = CurrentApplication.MainPage as MobileCpNavigationPage;
    
                if (navigationPage != null)
                {
                    await navigationPage.PushAsync(page);
                }
                else
                {
                    CurrentApplication.MainPage = new MobileCpNavigationPage(page);
                }
            }
    
            await (page.BindingContext as ViewModelBase).InitializeAsync(parameter);
        }
    
        protected Type GetPageTypeForViewModel(Type viewModelType)
        {
            if (!_mappings.ContainsKey(viewModelType))
            {
                throw new KeyNotFoundException($"No map for ${viewModelType} was found on navigation mappings");
            }
    
            return _mappings[viewModelType];
        }
    
        protected Page CreateAndBindPage(Type viewModelType, object parameter)
        {
            Type pageType = GetPageTypeForViewModel(viewModelType);
    
            if (pageType == null)
            {
                throw new Exception($"Mapping type for {viewModelType} is not a page");
            }
    
            Page page = Activator.CreateInstance(pageType) as Page;
            ViewModelBase viewModel = AppContainer.Resolve(viewModelType) as ViewModelBase;
            page.BindingContext = viewModel;
    
            return page;
        }
    
        private void CreatePageViewModelMappings()
        {
            _mappings.Add(typeof(LoginViewModel), typeof(LoginPage));
            _mappings.Add(typeof(MainViewModel), typeof(MainPage));
            _mappings.Add(typeof(MenuViewModel), typeof(MenuPage));
            _mappings.Add(typeof(HomeViewModel), typeof(HomePage));
            _mappings.Add(typeof(ChangePasswordViewModel), typeof(ChangePasswordPage));
            _mappings.Add(typeof(MeterDetailViewModel), typeof(MeterDetailPage));
            _mappings.Add(typeof(ContactUsViewModel), typeof(ContactUsPage));            
            _mappings.Add(typeof(UpdateUserInfoViewModel), typeof(UpdateUserInfoPage));            
            _mappings.Add(typeof(TransactionHistoryViewModel), typeof(TransactionHistoryPage));            
            _mappings.Add(typeof(MeterTariffsViewModel), typeof(MeterTariffsPage));            
            _mappings.Add(typeof(COTTokenViewModel), typeof(COTTokenPage));            
            _mappings.Add(typeof(MeterNotificationsViewModel), typeof(MeterNotificationsPage));            
            _mappings.Add(typeof(MeterPaymentInfoViewModel), typeof(MeterPaymentInfoPage));            
            _mappings.Add(typeof(CreditCardInfoViewModel), typeof(CreditCardInfoPage));            
            _mappings.Add(typeof(ReadingsAndConsumptionViewModel), typeof(ReadingsAndConsumptionPage));            
            _mappings.Add(typeof(ForgotPasswordViewModel), typeof(ForgotPasswordPage));            
            _mappings.Add(typeof(PaymentRedirectViewModel), typeof(PaymentRedirectPage));            
        }
    }
    

    }

    This is the code for MainPage ViewModel - which is the MasterDetail:

    public class MainViewModel : ViewModelBase
    {
        private MenuViewModel _menuViewModel;
    
        public MainViewModel(INavigationService navigationService, 
            IDialogService dialogService, 
            IConnectionService connectionService,    
            MenuViewModel menuViewModel) : base(navigationService, dialogService, connectionService)
        {
            _menuViewModel = menuViewModel;
        }
    
        public MenuViewModel MenuViewModel
        {
            get => _menuViewModel;
            set
            {
                _menuViewModel = value;
                OnPropertyChanged();
            }
        }
    
        public override async Task InitializeAsync(object data)
        {
            await Task.WhenAll
                (
                    _menuViewModel.InitializeAsync(data), 
                    _navigationService.NavigateToAsync<HomeViewModel>() //<---HERE THE LOGIC OF CREATING THE NAV PAGE HAPPENS
                );
        }
    }
    
    Wednesday, December 18, 2019 10:40 AM

Answers

  • User391091 posted

    Turns out that the MasterDetail had an empty NavigationPage tag inside its detail. Changing it to , Fixed the exception.... I still wonder why in iOS it complained about it white android ran it like a boss.

    • Marked as answer by Anonymous Thursday, June 3, 2021 12:00 AM
    Wednesday, December 18, 2019 10:52 AM