WP7 Navigation - NullReferenceException - windows-phone-7

I need to navigate to a certain page the first time my app is run, to gather login details etc. I'm using IsloatedStorageSettings to save a value to determine if this is the first run of the app or not, which works fine.
My problem is actually navigating to my 'first run' page when the app is run for the first time, using NavigationService, it seems NavigationService is not created at this point so is still null. When is NavigationService created or how can I work around this?
My code (in the constructor of my main page:
if ((bool)settings["firstRun"])
{
if (NavigationService != null)
{
NavigationService.Navigate(new Uri("/FirstRun.xaml", UriKind.Relative));
}
else
{
MessageBox.Show("Navigation service must be null?"); //always prompts
}
}
else
{
InitializeComponent();
}

Peter Torr has a great blog post on the ins and outs of redirecting for the initial navigation, though for user login I'd suggest that you either use a full screen popup or have a login control on your "normal" start page and toggle visibility based on your first run condition.

Add in class
private bool m_onNavigatedToCalled = false;
In ctor
this.LayoutUpdated += new EventHandler(MainPage_LayoutUpdated);
Then in code
void MainPage_LayoutUpdated(object sender, EventArgs e)
{
if (m_onNavigatedToCalled)
{
m_onNavigatedToCalled = false;
Dispatcher.BeginInvoke(() =>
{
if (NavigationService != null)
{
MessageBox.Show("Navigation not null?"); //always prompts
}
else
{
MessageBox.Show("Navigation service must be null?");
}
//StartApp(); do all stuff here to keep the ctor lightweight
}
);
}
}

Related

How to handle back button when at the starting destination of the navigation component

I've started working with the new navigation component and I'm really digging it! I do have one issue though - How am I supposed to handle the back button when I'm at the starting destination of the graph?
This is the code I'm using now:
findNavController(this, R.id.my_nav_host_fragment)
.navigateUp()
When I'm anywhere on my graph, it's working great, it send me back, but when I'm at the start of it - the app crashes since the backstack is empty.
This all makes sense to me, I'm just not sure how to handle it.
While I can check if the current fragment's ID is the same as the one that I know to be the root of the graph, I'm looking for a more elegant solution like some bool flag of wether or not the current location in the graph is the starting location or not.
Ideas?
I had a similar scenario where I wanted to finish the activity when I was at the start destination and do a regular 'navigateUp' when I was further down the navigation graph. I solved this through a simple extension function:
fun NavController.navigateUpOrFinish(activity: AppCompatActivity): Boolean {
return if (navigateUp()) {
true
} else {
activity.finish()
true
}
}
And then call it like:
override fun onSupportNavigateUp() =
findNavController(R.id.nav_fragment).navigateUpOrFinish(this)
However I was unable to use NavigationUI as this would hide the back arrow whenever I was at the start destination. So instead of:
NavigationUI.setupActionBarWithNavController(this, controller)
I manually controlled the home icon:
setSupportActionBar(toolbar)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setHomeAsUpIndicator(R.drawable.ic_navigation_back)
Override onBackPressed in your activity and check if the current destination is the start destination or not.
Practically it looks like this:
#Override
public void onBackPressed() {
if (Navigation.findNavController(this,R.id.nav_host_fragment)
.getCurrentDestination().getId() == R.id.your_start_destination) {
// handle back button the way you want here
return;
}
super.onBackPressed();
}
You shouldn't override "onBackPressed", you should override "onSupportNavigateUp" and put there
findNavController(this, R.id.my_nav_host_fragment)
.navigateUp()
From the official documentation:
You will also overwrite AppCompatActivity.onSupportNavigateUp() and call NavController.navigateUp
https://developer.android.com/topic/libraries/architecture/navigation/navigation-implementing
In Jetpack Navigation Component, if you want to perform some operation when fragment is poped then you need to override following functions.
Add OnBackPressedCallback in fragment to run your special operation when back present in system navigation bar at bottom is pressed .
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
onBackPressedCallback = object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
//perform your operation and call navigateUp
findNavController().navigateUp()
}
}
requireActivity().onBackPressedDispatcher.addCallback(onBackPressedCallback)
}
Add onOptionsItemMenu in fragment to handle back arrow press present at top left corner within the app.
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setHasOptionsMenu(true)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == android.R.id.home) {
//perform your operation and call navigateUp
findNavController().navigateUp()
return true
}
return super.onOptionsItemSelected(item)
}
If there is no special code to be run when back is pressed on host fragment then use onSupportNavigateUp in Activity.
override fun onSupportNavigateUp(): Boolean {
if (navController.navigateUp() == false){
//navigateUp() returns false if there are no more fragments to pop
onBackPressed()
}
return navController.navigateUp()
}
Note that onSupportNavigateUp() is not called if the fragment contains onOptionsItemSelected()
As my back button works correctly, and using NavController.navigateUp() crashed on start destination back button. I have changed this code to something like this. Other possibility will be to just check if currentDestination == startDestination.id but I want to close Activity and go back to other Activity.
override fun onSupportNavigateUp() : Boolean {
//return findNavController(R.id.wizard_nav_host_fragment).navigateUp()
onBackPressed()
return true
}
/** in your activity **/
private boolean doubleBackToExitPressedOnce = false;
#RequiresApi(api = Build.VERSION_CODES.M)
#Override
public void onBackPressed() {
int start = Navigation.findNavController(this, R.id.nav_host_fragment).getCurrentDestination().getId();
if (start == R.id.nav_home) {
if (doubleBackToExitPressedOnce) {
super.onBackPressed();
return;
}
this.doubleBackToExitPressedOnce = true;
Toast.makeText(MainActivity.this, "Press back again to exits", Toast.LENGTH_SHORT).show();
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
doubleBackToExitPressedOnce = false;
}
}, 2000);
} else {
super.onBackPressed();
}
}
If you mean the start of your "root" navigation graph (just incase you have nested navigation graphs) then you shouldn't be showing an up button at all, at least according to the navigation principles.
Just call this in your back button Onclick
requireActivity().finish()

Show page only once when app is installed in UWP

I am developing a UWP windows 10 application and I want to have a page that is only shown at the start of launching application first time. It should not be shown when the app is opened second time in a system.
I have searched online about it but couldn't find any thing about it.
I know that my answer can be refined more, but i have done with spending 2 minutes and its working for me. I have added a page loaded event in my StartPage.xaml.cs file and added the following code inside it.
if (localSettings.Values["IsFirstTime"] == null)
{
localSettings.Values["IsFirstTime"] = true;
}
if ((bool)localSettings.Values["IsFirstTime"])
{
localSettings.Values["IsFirstTime"] = false;
this.Frame.Navigate(typeof(MainPage));
}
Make sure you make a localSettings object at class level (of type ApplicationDataContainer). Now inside App.xaml.cs, I have added global variable for local settings as follow.
Windows.Storage.ApplicationDataContainer localSettings = Windows.Storage.ApplicationData.Current.LocalSettings;
Inside app.xaml.cs file come to the if condition showing rootFrame.Content == null and replace every thing inside it with the following code.
object value = localSettings.Values["IsFirstTime"];
if (localSettings.Values["IsFirstTime"] != null)
{
if ((bool)value)
{
rootFrame.Navigate(typeof(StartPage), e.Arguments);
localSettings.Values["IsFirstTime"] = false;
}
else
{
rootFrame.Navigate(typeof(MainPage), e.Arguments);
}
}
else
{
rootFrame.Navigate(typeof(StartPage), e.Arguments);
localSettings.Values["IsFirstTime"] = false;
}
I have tried it by uninstalling my app and run again to see if start page is shown (and it shows up). Second time when i open, MainPage is shown).
In App.xaml.cs look for the OnLaunched handler. There are these lines of code for "switching" pages:
if (rootFrame.Content == null)
{
rootFrame.Navigate(typeof(MainPage), e.Arguments);
}
As #Romasz suggested in comments, you can add here additional logic with local (or better - roaming) storage to check whether app is launched for the first time:
var roamingSettings = ApplicationData.Current.RoamingSettings;
if (roamingSettings.Values.ContainsKey("NotFirstTimeLaunch"))
{
rootFrame.Navigate(typeof(MainPage), e.Arguments);
} else
{
roamingSettings.Values["NotFirstTimeLaunch"] = true;
rootFrame.Navigate(typeof(FirstLaunchPage), e.Arguments);
}

Windows Phone Back button and page instance creation

I need to recreate new page instance on every page load (also when user pressed Back button).
So I overrided OnBackKeyPress method:
protected override void OnBackKeyPress(CancelEventArgs e)
{
base.OnBackKeyPress(e);
if (NavigationService.CanGoBack) {
e.Cancel = true;
var j = NavigationService.RemoveBackEntry();
NavigationService.Navigate(j.Source);
NavigationService.RemoveBackEntry();
}
}
The problem is that I can't handle case when user press back button to close CustomMessageBox dialog. How can I check it? Or is there any way to force recreation of page instance when going back through history state?
Why do you need to recreate the page instance? If you are simply trying to re-read the data to be displayed, why not put the data loading logic into OnNavigatedTo()?
Assuming that is what you are actually trying to achieve, try something like this...
public partial class MainPage : PhoneApplicationPage
{
// Constructor
public MainPage()
{
InitializeComponent();
// don't do your data loading here. This will only be called on page creation.
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
LoadData();
base.OnNavigatedTo(e);
}
MyViewModel model;
async void LoadData()
{
model = new MyViewModel();
await model.LoadDataAsync();
}
}
If you also have specific logic that you need to run on first construction of the page vs. on a back key navigation, check the NavigationMode property of the NavigationEventArgs object that gets passed to OnNavigatedTo.
if(e.NavigationMode == NavigationMode.New)
{
//do what you need to do specifically for a new page instance
}
if (e.NavigationMode == NavigationMode.Back)
{
// do anything specific for back navigation here.
}
Ha, in the near thread, i have opposite question :)
What about MessageBox - it depends, which one are you using. It can be custom message box, for example. Anyway, try to check MessageBox.IsOpened (or alternative for your MessageBox) in your OnBackKeyPress().
Another solution is to use OnNavigatedTo() of the page you want to be new each time.
Third solution: in case you works with Mvvm Light, add some unique id in ViewModel getter, like
public MyViewModel MyViewModel
{
get
{
return ServiceLocator.Current.GetInstance<MyViewModel>((++Uid).ToString());
}
}
This would force to recreate new ViewModel each time, so you'd have different instance of VM, so you would have another data on the View.

How to Cleanup a ViewModel in Mvvm Light?

I have a list of items that goes to another page, That page is hooked up to a view model. In the constructor of this view model I have code that grabs data from the server for that particular item.
What I found is that when I hit the back button and choose another item fromt hat list and it goes to the other page the constructor does not get hit.
I think it is because the VM is now created and thinks it does not need a new one. I am wondering how do I force a cleanup so that a fresh one is always grabbed when I select from my list?
I faced the same issue, that's how i solved it.
Have a BaseView class, override OnNavigatedTo
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
if (NavigatedToCommand != null && NavigatedToCommand.CanExecute(null))
NavigatedToCommand.Execute(null);
}
add DependencyProperty.
public static readonly DependencyProperty NavigatedToCommandProperty =
DependencyProperty.Register("NavigatedToCommand", typeof(ICommand), typeof(BaseView), null);
public ICommand NavigatedToCommand
{
get { return (ICommand)GetValue(NavigatedToCommandProperty); }
set { SetValue(NavigatedToCommandProperty, value); }
}
On the necessary pages, add to xaml (and, of course, inherit BaseView )
NavigatedToCommand="{Binding OnNavigatedToCommand}"
In the ViewModel, make command itself
public RelayCommand OnNavigatedToCommand
{ get { return new RelayCommand(OnNavigatedTo); } }
and implement method you want to call to update list
public async void OnNavigatedTo()
{
var result = await myDataService.UpdateMyList();
if (result.Status == OK)
MyList = result.List;
}
So, now, every time you navigate to page with list, inside of overriden OnNavigatedTo(), a NavigatedToCommand would be executed, which would execute OnNavigatedToCommand (which you set in xaml), which would call OnNavigatedTo, which would update your list.
A bit messy, but MVVM :)
EDIT: What about cleanings, they can be done in OnNavigatedFrom(), which works the same. Or OnNavigatingFrom(), which also can be useful in some cases.

Windows phone 7: facing issue for passing parameters

Right now i am developing a Windows phone app, yes this is my first windows app. Right now i am facing an issue, don't know what's the silly mistake i made. Yes of course i have done debugging.
Now, what exactly i am doing?
Passing data from 1st page to 2nd page,
On the page, catching data inside onNavigateTo() method, yes i am receiving it correctly.
Based on the parameter/data (i.e. ID) i got, i am making web service call.
Problem:
If i move to the 3rd page from 2page and again came back to the 2nd page, its again making web call. i.e. calling DownloadStringAsync again in below code.
i.e. If 2nd page is having ListBox with 5 data, now clicking on particular item i am moving to 3rd page, if i came back to 2nd page from page 3, items get doubled i.e. 10 items (just because its making call again)
Here is the possible code for the reference:
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
type = Convert.ToInt32(NavigationContext.QueryString["Type"]);
if (type != 0)
{
if (NetworkInterface.GetIsNetworkAvailable())
{
client.DownloadStringAsync(new Uri(Utils.Constant.WebService_URL));
}
else
{
MessageBox.Show("Please check internet connection!!");
}
}
}
Can't you use the following code to test if the user used the back button?
e.NavigationMode == System.Windows.Navigation.NavigationMode.Back
sometimes this method(QueryString) create problems.
In back event it create problems
its betters to store the id(parameter) in isolatedstorage application key
IsolatedStorageSettings.ApplicationSettings["id"] = "your data";
OnNavigatedTo is called whenever you navigate to the page, either by a forward navigation, or a back navigation. That's why it's retriggered when you navigate back from page #3 to page #2.
You can avoid this by only triggering the network call on a forward navigation.
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
if (this.isInitialized) return;
type = Convert.ToInt32(NavigationContext.QueryString["Type"]);
if (type != 0)
{
if (NetworkInterface.GetIsNetworkAvailable())
{
client.DownloadStringAsync(new Uri(Utils.Constant.WebService_URL));
}
else
{
MessageBox.Show("Please check internet connection!!");
}
}
this.isInitialized = true;
}
Beware that NetworkInterface.GetIsNetworkAvailable can block (in this case the UI thread) for a long time in some scenarios, to work around this you can use network detection events.

Resources