Xamarin Pillar navigation - xamarin.forms

What is the correct way to handle model navigation?
The first case below works as expected but I can then go back to the login page which I don't want. The second case I can see in the debugger that that page is loaded but never shown. Basically the model page stays on top. I am thinking I need to either close the model page before changing pages or I need to handle this differently. I don't won't to pop to root because the root is no longer home but main.
What I really won't to do is change the root, how?
[Edit] This seems to help but there is still a flicker when I unload the modal page.
await _navigator.PushAsync(vm => { vm.NoHistory = true; });
Case 1:
return viewFactory.Resolve<HomeVM>(); - from APP.cs
await _navigator.PushAsync<LoginVM>();
await _navigator.PushAsync<MainVM>();
Case 2:
return viewFactory.Resolve<HomeVM>(); - from APP.cs
await _navigator.PushModalAsync<LoginVM>();
await _navigator.PushAsync<MainVM>(); - Never works.

One way is to reset MainPage completely with the new page when login in successfully.
if (string.IsNullOrEmpty(authLoginToken))
MainPage = new LoginPage();
else
MainPage = new RootPage();
More detailed info, you can take a look the following thread:
https://github.com/asthanarht/CPXamarin/blob/master/CPMobile/CPMobile/App.cs
https://forums.xamarin.com/discussion/48634/from-login-page-to-main-page

Related

Xamarin Forms AppShell How return to previous page

I see a lot of threads on this sort of subject but none seem to apply to my question. I don't have a navigation page, I have a hamburger menu -- so Push/PopAsync() would not appear to be the answer. And I don't want to go to a specific page, I want to go back to the previous page (whatever it was) so GoToAsync() would not appear to be the answer.
Xamarin app for Android and UWP with iOS somewhere in the future. The description of the problem below is specific to Android; it works a little differently on UWP.
I want to put a Help entry in the hamburger menu that will take the user to a help page in the default browser. Hamburger menu seems to only go to an app page, so I defined a "dummy" View page that displays "Loading ..." and issues Browser.OpenAsync() in its OnAppearing() method, and that pretty much works. The problem is that the user would expect that the Back button would take him or her to the page they were on before clicking Help. I tried a couple of things. I have gotten close with the following but it does not quite work correctly:
In each of my other Views' OnAppearing() I call a method that saves the value of Current.MainPage.CurrentItem in a static. Then in the Help page after the OpenAsync() I set Current.MainPage.CurrentItem to its setting from the last page before the Help page.
Console.WriteLine("#### HelpPage loading Web Help");
_ = State.DisplayHelpPage(this, "MainHelp"); // _ = await Browser.OpenAsync(uri, blo);
Console.WriteLine("#### HelpPage returning to previous page");
State.ReloadPreviousPage(); // Current.MainPage).CurrentItem = lastFlyoutItem;
It almost works. The first time I click Help in the hamburger menu I get
#### HelpPage loading Web Help
#### HelpPage returning to previous page
#### HelpPage loading Web Help
#### HelpPage returning to previous page
The Web page loads perfectly. But when I click the Back button it displays again. Obviously my OnAppearing() method has been driven twice, which I do not understand.
If I click the Back button again I come back to the previous page in the application just as I wanted. The next time I click Help in the Hamburger menu it takes me to my dummy View page with no Web page. Obviously, my OnAppearing() is not being driven at all. But after that it works perfectly. I can go to any app page, and click Help in the menu. I get the Web page, and the Back button takes me back to the app and the page. In UWP of course the browser does not load on top of the app Views, and I seem to see it being loaded twice every time.
So ... what should I be doing differently? And why is my OnAppearing() being driven twice and then not at all ... and thereafter as I would expect?
There are several parts to this answer:
Get the previous page on to the Navigation stack. This is done by intercepting the Route "//HelpPage", and replacing it with a route that ISN'T a child of Shell.
Remember "FakePageVisible", so we know to do "PopAsync" in OnResume, when app returns from browser.
(Optional) "Entering" flag prevents going to browser twice.
App.xaml.cs:
public partial class App : Application
{
public App()
{
InitializeComponent();
MainPage = new AppShell();
}
protected override void OnResume()
{
if (HelpPage.FakePageVisible) {
HelpPage.FakePageVisible = false;
var shell = MainPage as AppShell;
if (shell != null) {
shell.Navigation.PopAsync();
}
}
}
}
AppShell.xaml.cs:
public partial class AppShell : Xamarin.Forms.Shell
{
public AppShell()
{
InitializeComponent();
// Define a route that isn't a child of Shell.
Routing.RegisterRoute("Help2", typeof(HelpPage));
}
protected override void OnNavigating(ShellNavigatingEventArgs args)
{
base.OnNavigating(args);
if (args.Current != null) {
if (args.Source == ShellNavigationSource.ShellItemChanged) {
if (args.Target.Location.OriginalString == "//HelpPage") {
// Cancel the original route.
args.Cancel();
Device.BeginInvokeOnMainThread(() => {
// Used by the next OnAppearing.
HelpPage.Entering = true;
// Go there by a route that isn't a child of Shell.
// Doing so, pushes our previous location on to Navigation stack.
Shell.Current.GoToAsync("Help2");
});
}
}
}
}
}
HelpPage.xaml.cs:
public partial class HelpPage : ContentPage
{
public static bool Entering;
public static bool FakePageVisible;
protected override void OnAppearing
{
// Make sure this only happens once (just in case).
if (Entering) {
Entering = false;
FakePageVisible = true;
Xamarin.Essentials.Browser.OpenAsync("https://aka.ms/xamarin-quickstart");
}
}
}
For a simple demo, this code communicates via static variables in HelpPage. Re-factor as appropriate for your situation.

Prism navigation usage from Xamarin Forms App.OnAppLinkRequestReceived

I am building an app that makes use of some App links. In below example, I want to open a ResetPasswordPage when the user activates a link from an e-mail.
protected override async void OnInitialized()
{
InitializeComponent();
await NavigationService.NavigateAsync("/NavigationPage/LoginPage");
}
protected override void OnAppLinkRequestReceived(Uri uri)
{
if (uri.Host.EndsWith("site.nl", StringComparison.OrdinalIgnoreCase))
{
if (uri.AbsolutePath.StartsWith(#"/appname/resetpassword/"))
{
if (uri.Segments.Length == 4)
{
string resetCode = uri.Segments[3];
NavigationParameters param = new();
param.Add(NavConst.PasswordResetCode, resetCode);
await NavigationService.NavigateAsync("ResetPasswordPage", param);
}
}
}
}
This code works, but not what I would expect (for both iOS and Android). I would expect that ResetPasswordPage would be added to the navigation stack that was set in OnInitialized, so that it would contain a back button in the navigation bar, bringing the user back to the login page in this case.
But instead, it looks like the navigation stack gets replaced. When ResetPasswordPage is shown, and the user clicks the hardware back button, the app is closed. However, i want it to go back to where the user was before.
Am I not understanding some navigation concepts well or might this be a bug?
The NavigationService is a rather unique service within Prism for Xamarin.Forms. It is constructed new for each page as navigation and the navigation stack is dependent on where you are navigating from.
For instance when you Navigate from the context of a MasterDetailPage / FlyoutPage, it understands that you aren't trying to push a modal on top of that page but instead you are starting from the context of Detail/Flyout. If the NavigationService continually updated the Page context then the navigation service injected into that MasterDetailPage/FlyoutPage's ViewModel would have no idea that it needed that page as the context to navigate from.
Your issue here ultimately is that you are Navigating from the context of the Application which has a NavigationService with no Page set. As a result even a relative Navigation will still have the effect of an absolute Navigation thus resetting the Application.MainPage since that NavigationService does not have the contextual understanding of another page.
Depending on your business requirements you have a few different options. One is to do an absolute Navigation that gives you navigation stack that you're looking for.
Optionally you may want to get the currently displayed page and update the NavigationService to understand it. In the App class you might add something like:
protected override void OnAppLinkRequestReceived(Uri uri)
{
if (NavigationService is IPageAware pa)
{
page.Page = PageUtilities.GetCurrentPage(MainPage);
}
if (someCondition)
{
// This will now navigate from relatively from the page
// returned by GetCurrentPage.
NavigationService.NavigateAsync("SomeRelativeUrl")
.OnNavigationError(HandleNavigationError);
}
}

Confirmation on master detail page before exiting

I want to ask for confirmation before exiting page but i tried every way i can find but nothing worked so far.
I have a master detail page as Application Main page.
App.Current.MainPage = new NavigationPage(new FaceApp.MainPageActive());
In that page Detail page is set like following.
Detail = new NavigationPage((Page)Activator.CreateInstance(typeof(RecoveryForm)));
In Recovery form page i have following code
protected override bool OnBackButtonPressed()
{
Device.BeginInvokeOnMainThread(async () =>
{
if (await DisplayAlert("Alert", "Are you sure you want to go back ?", "Yes", "No"))
{
// Nothing works here. i tried many things including following
await Navigation.PopAsync();
}
});
return true;
}
Code runs successfully but nothing happens, if i remove code of running in Main thread application gets halted.

Xamarin Forms - Duplicate Screens

I'm writing an app using Xamarin Forms and I have an issue I was hoping someone can help with.
My app contains a screen which has multiple icons that can be pressed which would then open a new screen.
My issue is that if you press the icon twice really fast, the app opens up 2 instances of the same screen (it's not just related to a double press, if you press the icon 6 times very fast it will open up 6 duplicate screens). Pressing the Back button, closes the top screen to reveal the duplicate screen underneath. Pressing the Back button again navigates you back to the original screen.
This issue seems to occur on any screen within my app so I'm hoping other people will have experienced it and know of a solution to prevent duplicate screens being displayed.
This is a known issue in TapEvents.
My hack is, in the code-behind, have a bool variable _canTap.
Inside the method you are calling to push new page, first you check if canTap, then set to false, and only set to true after navigating to another page. This way all taps will be disregarded.
Example:
private bool _canTap = true;
public void YourMethod()
{
if(_canTap)
{
_canTap = false;
YourMethodToNavigate();
_canTap = true;
}
}
In the Icon_Pressed method add this,
this.IsEnabled = false;
await Navigation.PushAsync(new MyPage());
this.IsEnabled = true;
It disables the page until the current Icon pressed event is finished
This is known problem with Xamarin apps. I've used a private variable combined with a try-finally pattern to solve this. Ex:
bool allowTap = true;
public void ButtonTapped()
{
try
{
if(allowTap)
{
allowTap = false;
// Do whatever...
}
}
finally
{
allowTap = true;
}
}
The finally makes sure allowTap gets set back to true no matter what happens, short of a complete crash. Note that you can also use a catch block between the try and finally blocks to grab any errors if needed.

How to Navigateback from Xamarin.Forms Prism Popup

Xamarin.forms using Prism Navigation back is not working in below scenario :
App.xaml.cs in OnInitialized NavigationService.NavigateAsync(nameof(LoginView));
In LoginViewModel once login I am navigating to MainPage await _navigationService.NavigateAsync(new Uri("MainPage", UriKind.Relative),useModalNavigation:true, animated: false);
MainPaga has a Button once I click in Button it will Popup setting Page navigation Popup like : await _navigationService.NavigateAsync(new Uri("SettingView", UriKind.Relative), useModalNavigation: true, animated: false);
SettingView(Popup) has a Button(LogOut) once I click on Button(LogOut) it has to go back in LoginView here is the code navigating back.
await _navigationService.ClearPopupStackAsync(animated: false);
await _navigationService.GoBackAsync(useModalNavigation: true, animated: false);
first I am Clearing Popup then navigating back, but it wont navigate back to LoginView?
If you are using PRISM and want to 'clear' the current stack.
You better use the explicit uri navigation.
Meaning the code behind the logout button should look like this
await _navigationService.NavigateAsync("/NavigationPage/LoginView");
By adding the / at the beginning of the URI, the current stack will be cleared.
You should just use absolute uri to set Loginpage as Mainpage after user confirm to logout like this
await NavigationService.NavigateAsync(new System.Uri("/LoginPage",System.UriKind.Absolute));

Resources