App.Current.MainPage NullReferenceException in Constructor of MainPageModel - c#

On the loading of MainPage, I am pushing another page from Constructor of MainPageModel. It throws NullReferenceException in MainPageModel. This is my code
MainPage constructor
public MainPage()
{
InitializeComponent();
Title = "MainPage";
BindingContext = new MainPageViewModel();
}
MainPageModel constructor
public MainPageViewModel()
{
App.Current.MainPage.Navigation.PushAsync(new HomePage()); //Exception
//CommandMenu1 = new Command(async () => await NavigateNext());
}
How can I solve this issue?

What probably happens is this:
public class App
{
MainPage = new MainPage();
}
The MainPage() constructor has to be completed before it is assigned to the MainPage property of your App. Therefore, trying to access the App.Current.MainPage before then, you will get a NullReferenceException. This means you will have to find another way to achieve what you are trying to do here.

You are trying to access MainPage in your View Model before it has been fully initialized. Instead of binding in your MainPage constructor bind in App.xaml.cs
public App() {
InitializeComponent();
MainPage = new MainPage();
MainPage.BindingContext = new MainPageViewModel();
}

Use OnAppearing() instead of MainPageViewModel() to give it time to load fully,
Code :
protected override async void OnAppearing()
{
App.Current.MainPage.Navigation.PushAsync(new HomePage());
}
Hope it helps!

You can try the code below:
await Navigation.PushAsync(new NewPage());
on New Page.Cs
InitializeComponent();
NavigationPage.SetHasBackButton(this, false);

Related

Why doesn't navigation work for me after calling Navigation.InsertPageBefore() in Xamarin Forms?

I'm trying to implement logging in on Xamarin Forms (5.0.0, using ActiveDirectory's built in Login page). Any ideas on how to make this work?
In the constructor of App.xaml.cs, I have:
public App()
{
InitializeComponent();
MainPage = new NavigationPage(new LoginPage());
}
I implement the Login page w/ a view model, in which I pass in a callback that should (according to the documentation), set my navigation root to my HomePage:
public partial class LoginPage : ContentPage
{
private async Task _handleLoginAsync()
{
Navigation.InsertPageBefore(new HomePage(), this);
await Navigation.PopAsync();
}
public LoginPage()
{
InitializeComponent();
BindingContext = new LoginPageViewModel(_handleLoginAsync);
}
}
In the view model, I try to login using Device.BeginInvokeOnMainThread, calling my(note, I didn't include login logic for brevity/cleanliness)
public Command LoginCommand => new Command(LoginUsingAzureAsync);
private Func<Task>_handleLoginAsync;
public LoginPageViewModel(Func<Task> handleLoginAsync)
{
_handleLoginAsync = handleLoginAsync;
LoginCommand.Execute(null);
}
internal void LoginUsingAzureAsync()
{
Device.BeginInvokeOnMainThread(async () =>
{
try
{
if (await Login()) == true)
{
UserDialogs.Instance.HideLoading();
await _handleLoginAsync();
return;
}
}
catch (Exception ex)
{
Debug.WriteLine(ex.ToString());
UserDialogs.Instance.Alert("The login has failed.");
}
});
}
It successfully goes to the homepage, but navigating to other pages afterwards doesn't work. When I call the following, it enters the OtherPage() constructor, but fails to render the new page.
Navigation.PushAsync(new OtherPage());
Note, the navigation works as expected if I use PushAsync(new HomePage()) rather than removing the login, but I'd prefer to remove the login page from the navigation stack.
Thanks in advance!
Update: Here's the initial HomeViewModel:
public class HomeViewModel
{
private readonly INavigation _navigation;
public Command GoToOtherPageCommand => new Command(GoToOtherPage);
public async void GoToOtherPage()
{
await App.Navigation.PushAsync(new OtherPage());
}
}
The problem wasn't in the login, it was in the HomePageViewModel, which was initially referencing App.Navigation (see update in question).
Passing in the navigation into my ViewModel did the trick:
public class HomeViewModel
{
private readonly INavigation _navigation;
public Command GoToOtherPageCommand => new Command(GoToOtherPage);
public async void GoToOtherPage()
{
await _navigation.PushAsync(new OtherPage());
}
public HomeViewModel(INavigation navigation)
{
_navigation = navigation;
}
}
public partial class HomePage : ContentPage
{
public HomePage()
{
InitializeComponent();
BindingContext = new HomeViewModel(Navigation);
}
}

Xamarin re-navigation to mainpage from viewmodel displays empty page

When I re-navigate to my first page (Called Mainpage) from a viewmodel it displays an empty page.
I understand that this is related to the stack but no matter what I try it stays empty. Does anyone has any ideas? I find related threads but not in the same way.
Mainpage constructor:
public Login()
{
InitializeComponent();
}
Code in the viewmodel
await Application.Current.MainPage.Navigation.PopToRootAsync();
await Application.Current.MainPage.Navigation.PushAsync(new Login());
Ok so for extra info:
Setting my rootpage:
public partial class App : Application
{
public App()
{
InitializeComponent();
NavigationPage navigationPage = new NavigationPage(new Login()); << Renamed mainpage to login after the suggestion over here
MainPage = navigationPage;
}
I call my rootpage after login. I want to make sure my code logs-out my user after the person his/hers ADAL token has expired. It would happen like this in my viewmodel:
var auth = DependencyService.Get<IAuthenticator>();
auth.logout();
await Application.Current.MainPage.Navigation.PopToRootAsync(true);
(Without pushAsync is does not navigate at all and stays at the current page. This is called straight after the new page is loaded and the user is logged-in)
In your App.cs you should have defined a NavigationPage passing the root of your navigation stack.
Let's say:
public class App : Application
{
public App ()
{
NavigationPage navigationPage = new NavigationPage (new MainPage ());
MainPage = navigationPage;
}
}
The root of your application will be MainPage.
Wherever in your application will call the Navigation.PopToRootAsync(); method, you'll go back to your root page. (MainPage in this case)
You don't need to push in the stack the MainPage because it's already there, you're popping out all the pages excluding the root.
The constructor of the page won't be called but you can rely on the Appearing event that is fired when the page is displayed.
initialize static NavigationPage variable in App.cs
public class App : Application
{
public static NavigationPage NavPage;
public App ()
{
NavPage= new NavigationPage (new MainPage ());
MainPage = NavPage;
}
}
use NavigationPage variable of App.cs for navigate main page from third page
public partial class thirdPage : ContentPage
{
public thirdPage ()
{
InitializeComponent ();
main.Clicked += Main_Clicked;
}
private void Main_Clicked(object sender, EventArgs e)
{
App.NavPage.PopToRootAsync(true);
}
}

Xamarin portable, how to navigate back from selected list item to MainPage and pass value

My previous question was, how to navigate from ToolbarItem to another page and keep navigation bar existing. : Xamarin portable project navigate with ToolbarItem to another page by using MasterDetailPage
Now I have troubles when I select item from item list and I want to back to my MainPage, but I am getting such a error: System.Exception: Android only allows one navigation page on screen at a time and MainPage page appear, but it freeze and I see now 2 buttons on navigation bar. But it should be only one.Here in MainPage I am calling Cities:
public partial class MainPage : MasterDetailPage
{ public MainPage()
{
InitializeComponent();
masterPage.ListView.ItemSelected += OnItemSelected;
CityClick.Clicked += async (sender, e) =>
{
await Detail.Navigation.PushAsync(new Cities());
};}}
Then when I am at Cities.xaml.cs I want to back to MainPage(). And also I want pass my selected item value from list to my label in MainPage.xaml navigation. Second problem is when I am returning from Cities.xaml.cs after item select to MainPage() I am getting that error which I mentioned before. This is my Cities class:
public partial class Cities : ContentPage
{
public Cities()
{
InitializeComponent();
Label header = new Label
{
Text = ...
};
List<City> cities = new List<City>
{new City("City1"),
new City("City2")};
ListView listView = new ListView
{ItemsSource = cities,
ItemTemplate = new DataTemplate(() =>
{
Label nameLabel = new Label();
nameLabel.SetBinding(Label.TextProperty, "Name");
BoxView boxView = new BoxView();
return new ViewCell
{
...
};
...
{
Children =
{
...
}
};
listView.ItemSelected += async (sender, e) =>
{
if (e.SelectedItem == null)
{return;}
else
{
await Navigation.PushAsync(new MainPage());
}
};
}
class City
{public City(string name)
{
this.Name = name;
}
public string Name { private set; get; }
};}
And this is how looks when returns to MainPage() it freeze everything and appear another label:
EDIT : Regarding #AkashAmin comments I changed from await Navigation.PushAsync(new MainPage()); to await Navigation.PopAsync(); and it is worked very well. Now I still have dilemma with pasiing value from City class to MainPage class.
I navigated back from my Cities class to my MainPage by changing from PushAsync to PopAsync(); . Thank you #Akash Amin for your information. This awaited task also solved duplicated labels problem in Navigation ToolbarItems place .
Also I solved value passing from Cities to MainPage class problem. In this link you can see my all walkthrough of solving this problem: https://stackoverflow.com/a/37350738/3727758

Data Binding ObservableCollections with an IoC

I am writing a Universal app targeting Windows 8.1, and am re-writing it to use an IoC container. However, I found something that is puzzling me a bit.
Before I used the IoC, I would create an instance of my VM in the code-behind and bind to it, like this:
MainPage.xaml
<ListBox ItemsSource="{Binding ItemList}" DisplayMemberPath="Title" />
MainPage.xaml.cs
private MainPageVM Data = new MainPageVM();
public MainPage()
{
this.InitializeComponent();
this.DataContext = Data;
}
MainPageVM.cs
public ObservableCollection<MenuItem> ItemList { get; set; }
public MainPageVM()
{
ItemList = new ObservableCollection<MenuItem>();
}
This worked just fine. However, now I am setting up the app very differently, like this:
App.xaml.cs
private IUnityContainer _Container;
public App()
{
_Container = new UnityContainer();
this.InitializeComponent();
}
protected override void OnLaunched(LaunchActivatedEventArgs e)
{
_Container.RegisterType<MainPage>(new ContainerControlledLifetimeManager());
_Container.RegisterType<Frame>(new ContainerControlledLifetimeManager());
_Container.RegisterType<MainPageVM>(
new ContainerControlledLifetimeManager(),
new InjectionConstructor(typeof(Frame),
typeof(MainPage)));
Frame rootFrame = Window.Current.Content as Frame;
if (rootFrame == null)
{
rootFrame = _Container.Resolve<Frame>();
rootFrame.CacheSize = 1;
}
if (rootFrame.Content == null)
{
var mainPageVM = _Container.Resolve<MainPageVM>();
mainPageVM.Show();
}
Window.Current.Activate();
}
This creates an instance of MainPageVM():
MainPageVM.cs
private Frame _Frame;
private Page _View;
public ObservableCollection<MenuItem> ItemList { get; set; }
public MainPageVM(
Frame frame,
Page view)
{
_Frame = frame;
_View = view;
_View.DataContext = this;
ItemList = new ObservableCollection<MenuItem>();
OnPropertyChanged("ItemList");
}
public void Show()
{
_Frame.Content = _View;
Window.Current.Content = _Frame;
}
MainPage.xaml.cs
public MainPage()
{
this.InitializeComponent();
}
MainPage.xaml is not changed.
My question is, why do I have to signal OnPropertyChanged("ItemList"); to activate the binding in the second version, but not the first? Am I doing something wrong here?
My entire repository can be found on GitHub: Learn OneNote.
Switching between these two lines should fix this:
_View.DataContext = this;
and
ItemList = new ObservableCollection<MenuItem>();
What happens is that you currently first set the DataContext, which causes all of the view's bindings to reevaluate, and only then you modify ItemsList. Since ItemsList's setter doesn't call OnPropertyChanged, the view is not updated when it's changed. By switching between the lines, you first initialize the data context and only then reevaluate bindings.
A more readable solution would be to include a call to OnPropertyChanged in your setter (then you don't have to switch the lines):
private ObservableCollection<MenuItem> itemList
public ObservableCollection<MenuItem> ItemList
{
get { return itemsList; }
set
{
if (itemsList != value)
{
itemsList = value;
OnPropertyChanged("ItemsList");
}
}
}
Although it's always a good practice to first initialize your data context and only then set it to avoid having controls to attempt binding twice.
That's because in the first version, ItemList instantiated in view-model constructor which itself constructed in view's initialization, before data binding. But in the second version, data get's bound first, then you instantiate the ItemList.

How to open new TabbedPage from ContentPage?

I am trying to open (using any method) the TabbedPage from ContentPage.
My main App code:
public class App : Application
{
public App ()
{
MainPage = new ConnectPage ();
}
}
My ConnectPage uses XAML, code:
.cs file:
public partial class ConnectPage : ContentPage
{
public ConnectPage ()
{
InitializeComponent ();
}
void connectDevice(object sender, EventArgs args){
connect_btn.Text = "Connecting...";
connect_btn.IsEnabled = false;
var mainapp_page = new MainApp ();
Navigation.PushAsync (mainapp_page);
}
}
XAML file:
<Button x:Name="connect_btn" Text="Connect Now" Clicked="connectDevice"></Button>
Above method throws error:
PushAsync is not supported globally on iOS, please use a
NavigationPage
My MainApp.cs (which contain tabs):
public class MainApp : ContentPage
{
public MainApp ()
{
var tabs = new TabbedPage ();
tabs.Children.Add (new Tab1Page{Title="Tab1" });
tabs.Children.Add (new Tab2Page{Title="Tab2" });
tabs.Children.Add (new Tab3Page{Title="Tab3" });
}
}
You can either update your'r app's MainPage property and set that to the page you want to display, use stack navigation or present a page modally.
Setting a new main page will provide no way for the user to go back:
App.Current.MainPage = new SomeOtherPage ();
If you want to use stack navigation, you will have to wrap your initial page into a NavigationPage:
public partial class App : Application
{
public App ()
{
InitializeComponent ();
this.MainPage = new NavigationPage (new FirstPage ());
}
}
Than you can use Navigation.PushAsync().
If you want to present a page modally, so it is shown on top of your current page and can be dismissed, use:
Navgiation.PushModalAsync(new Page());
However, this will still require to wrap your current page into a NavigationPage.
There are other ways too, like CarouselPage or MasterDetailPage. I recommend you look at the documentation for all of your options.

Categories

Resources