What I want to achieve is that the app starts with a login page and after login the Main page should be show. From there other pages can be opened and normal navigation is allowed.
However, I do not want the users to navigate back to the login page. After the login the main page must be the root of the navigation.
I found lots of information on google on how to do it, but they all don't seem to work for me. Mainly I've been told to make my main page the root by setting MainPage directly to my PageMain that is also in my code now, but it does not works.
Other method should be to remove the login page from the navigation stack, but I can't get that to work. The samples I find compile but on runtime they crash my application saying either I cannot remove the current page or the page I am removing is not found.
Here is my code:
My app starts with PageLogin, for now it just has a button and when you click on it then it opens my PageMain
private void ButtonLogin_Clicked(object sender, EventArgs e)
{
// almost does what I want
Application.Current.MainPage = new PageMain();
// almost does what I want
// make PageMain the new main page, so you cannot go back to the login screen
//Application.Current.MainPage = new NavigationPage(new PageMain());
// error you cannot remove the page you are on
//var _navigation = Application.Current.MainPage.Navigation;
//var _lastPage = _navigation.NavigationStack.LastOrDefault();
////Remove last page
//_navigation.RemovePage(_lastPage);
////Go back
//_navigation.PopAsync();
// error page does not exists
//Application.Current.MainPage.Navigation.RemovePage(this);
//Navigation.PopAsync(); not supported on android
//Navigation.RemovePage(this); not supported on android
}
The MainPage is set in App.xaml.cs like this
public partial class App : Application
{
public App()
{
InitializeComponent();
//MainPage = new NavigationPage(new Pages.PageLogin());
MainPage = new Pages.PageLogin();
}
The code above opens my page PageMain so far so good.
Now when I click on the back button of the device, my app minimizes (or whatever it does on android to hide itself)
This is good because I don't want the user to go back to the login form
But, when I now click on recent apps button on the device, and click on my app to get it back in foreground, it moves back to the login form.
See this vid
How can I avoid that ?
EDIT
I tried setting IsTabStop to false, but also no result
public PageLogin()
{
InitializeComponent();
this.IsTabStop = false;
ButtonLogin.Clicked += ButtonLogin_Clicked;
}
This is a pure Android behavior and has nothing to do with Xamarin.Forms, when pressing the back button while your navigation stack of the app is empty, depending on which Android version is running, it will behave like follow:
Android 11 and lower: The system finishes the activity.
Android 12 and higher:
The system moves the activity and its task to the background instead of finishing the activity. This behavior matches the default system behavior when navigating out of an app using the Home button or gesture.
In most cases, this behavior means that users can more quickly resume your app from a warm state, instead of having to completely restart the app from a cold state...
Source: Back press behavior for root launcher activities.
In your case when you press the back button on the main screen, Android finishes the activity, if you want to confirm that, set a breakpoint on your AppShell.cs constructor or MainActivity.cs/OnCreate() you will notice that:
Home button pressed on main screen and restore back the app from android apps stack: none of the breakpoints will be hit because the app activity was conserved. In fact Android will call MainActivity.OnResume().
Back button press you will hit the breakpoints, because the activity was terminated and you are starting it over.
Some potential solutions
Save and keep an updated record of the logging state on a local DB (SQLite) or a file, at the app startup read this bool and accordingly show or no the login page (set the mainpage).
If you don't want your app to exit upon clicking back button, override OnBackPressed() in your MainActivity with an empty code:
public override void OnBackPressed()
{
}
Send your app to the back (pause it) rather than terminate it:
public override void OnBackPressed() => MoveTaskToBack(true);
More on OnBackPressed()
Related questions
What happens when back button or home or application removed from recent apps
How can you restrict/control the navigation routes the user can visit based on login status/role?
I worked out a method base on CFun's answer that seems to work fine during my tests.
I will show my implementation of this answer here, so it can be used by other people with the same problem.
And by doing so I also give a change to everybody to comment on this implementation of that answer.
The idea is to keep a reference to the prior page, everytime another page is opened. Then in the MainActivity.cs in the OnBackPressed method I can check if I am on the Root Page (which is my PageMain) or not.
When on the root page I can do MoveTaskToBack and otherwise I can set the current page to priorPage.
Here is the code:
What I did, first in my App.xaml.cs I put this static variable priorPage
public partial class App : Application
{
public static Page priorPage;
public App()
{
InitializeComponent();
MainPage = new Pages.PageLogin();
}
Then when my app starts (first page is the login page) and the user clicks on login, this code will be executed. The variable priorPage will be set to the page that currently is active, before opening a new page
private void ButtonLogin_Clicked(object sender, EventArgs e)
{
App.priorPage = this;
Application.Current.MainPage = new PageMain();
}
The same principle will be used for every page that is opened
private void ButtonSettings_Clicked(object sender, EventArgs e)
{
App.priorPage = this;
Application.Current.MainPage = new PageSettings();
}
And finally, in the MainActivity.cs I can now do this
public override void OnBackPressed()
{
if (App.Current.MainPage is PageMain)
{
MoveTaskToBack(true); // you are on the root, hide the app
}
else if (App.priorPage != null)
{
App.Current.MainPage = App.priorPage; // navigate back to you prior page
}
else
{
base.OnBackPressed();
}
}
Related
I have had similar issues with back button before.
In my app I have a login page where the user submits their phone number and then they are taken to a page where they enter the verification code that hey have received. If the user pressed the back button on that page, the app gets minimized but when it's opened again the login page is shown instead of the page for submitting the verification code, even though I've specified clearhistory: true.
This is how I navigate:
this.$navigateTo(ConfirmSMS, {
transition: {name: 'slideLeft'},
clearHistory: true
});
You must use clearHistory only if you don't want use to go back to Login back upon pressing back button.
When you press back button and there are no Pages in back stack, application will terminate. It will still appear in recent application but unlike iOS tapping on the recent application will restart it unless it was paused but another activity / home button.
You may override back button to pause application instead of terminating it.
import { isAndroid } from "#nativescript/core/platform";
import * as application from "#nativescript/core/application";
import { Frame } from "#nativescript/core/ui/frame";
if (isAndroid) {
application.android.on(application.AndroidApplication.activityBackPressedEvent, function (args) {
const frame = Frame.topmost();
if (frame && !frame.canGoBack()) {
args.cancel = true;
var startMain = new android.content.Intent(
android.content.Intent.ACTION_MAIN
);
startMain.addCategory(android.content.Intent.CATEGORY_HOME);
startMain.setFlags(android.content.Intent.FLAG_ACTIVITY_NEW_TASK);
application.android.foregroundActivity.startActivity(startMain);
}
});
}
Playground Sample
I have tried this both using Prism as well as with Xamarin Forms inbuilt Navigation.
I have PageA and PageB (both using MVVM). During my app startup (App.xaml.cs), I Navigate to PageA. During PageA initialization I encounter an error and want to send the App to PageB, so within PageA I perform navigation to PageB.
Once I reach PageB, I have the back arrow displayed to navigate back, but clicking on it never sends me back to the previous page (PageA). How do I achieve this ?
Thanks in advance.
Edit: Added code below (Prism based)
App.Xaml.cs:
public App(IPlatformInitializer initializer = null):base(initializer)
{
NavigationService.NavigateAsync("NavigationPage/PageA");
}
PageAVM:
public PageAVM(INavigationService navigationService)
{
_navigationService = navigationService;
}
public async void OnAppearing()
{
// Perform some actions and if there is an error navigate to PageB
await _navigationService.NavigateAsync("PageB");
}
Expectation is that I am navigated to PageB, with the back button available and able to press it to move back to PageA.
In actuality, pressing the back button or the hardware back button does not take me back to PageA.
If I move the navigation to within the constructor of PageA, I am able to navigate back, but any toolbar items on PageA get retained on PageB.
You need to use the namespace to change the root.
>
await NavigationService.NavigateAsync("{Namespace}:///PageB");
I am developing cross platform mobile application using NativeScript + Angular 2.
I want to handle back button click of the current View, i.e. when user clicks on back button in android device, i want to perform an action like killing / removing the current view from the stack.
For ex: In android platform (Native Development) we can use finish() method of the activity for removing it from the stack. We can handle onBackPressed() like -
#Override
public void onBackPressed()
{
finish(); //Removes current Activity from stack
}
So is there any way to handle onBackPressed() and finish() method in NativeScript + angular 2? I googled a lot but didn't find any solution and also tried Frame.goBack() in NativeScript + Angular 2, but didn't worked for me. It works great in NativeScript + JavaScript.
UPDATE
I want to remove view permanently from the stack as it is not needed any more in application. It should display first time when app install.
For ex :
Like Login Screen
1) When app installs then Login screen should display and on next launch of the application, App will automatically skip the Login Screen and move to the Home Screen(This is working fine)
2) But the problem is when i press back button from Home Screen then app navigates to Login screen every time because Login screen still present in STACK. So that's why i want to remove login screen permanently from stack.
What you need to implement going back is to inject Location and use the method back()
import {Location} from '#angular/common';
#Component({ ... })
export class MyComponent {
constructor(private location: Location) { }
public goBack() {
this.location.back();
}
}
At this point when the user goes back, you shouldn't worry about explicitly destroying the view as there is no mobile option for going "forward"
Basicly, i have 3 activities in my app. call them A,B and Login, A and B shows the user very sensitive data. a legit flow has the form of launch->Login->A->B->A->B...->A->quit what i really want is that if for some reason the app got to the background ( by pressing back or home or whatever ) while it was still on A, or on B, then no metter what way the app relaunches ( or traced in some way including by the long home press menu ) it would not show A or B content. he can see either the login page content, or nothing at all.
noHistory is almost what i was looking for, but isnt a good solution since i want to allow intuative navigation from A to B and back to A. so how is it done?
Check out the "clearTaskOnLaunch" Activity attribute.
When the value is "true", every time users start the task again, they
are brought to its root activity regardless of what they were last
doing in the task and regardless of whether they used the Back or Home
button to leave it.
try the following pseudo:
public A extends Activity
{
boolean just_launched = true;
void onPause()
{
just_launched = false;
}
void onResume()
{
if (!just_launched)
finish();
}
}
Let's consider simple DB access application with two activities:
A - list of entries from DB
B - input form to enter new data to DB, with two buttons: Save / Cancel
Application starts with A (list) and from A user may go to B (input form).
To make entering new data more efficient I created a widget to jump directly to B (PendingIntent).
The observed behaviour of the application is like that:
If the first action of the user is widget (empty back stack) => the application opens B and when user click Save or Cancel activity is finished and focus goes back to Android desktop.
If main application was started before (A is on back stack) => B is still properly opened from widget however when user click Save or Cancel focus goes back to A
The behaviour described in 2 is OK when user starts B from A. However I would like to avoid it when B is started from widget.
Any hints ?
I have a situation where I need to do something similar. My quick fix was to add a "EXTRA_LAUNCHED_BY_WIDGET" Extra to the Intent launched by the widget. Then, in my Activity I treat that as a special case.
I needed to override the Back button behaviour, but you could just as easily use this case elsewhere, e.g. in other overridden Activity methods.
#Override
public void onBackPressed()
{
Bundle extras = getIntent().getExtras();
boolean launchedFromWidget = false;
if (extras.containsKey("EXTRA_LAUNCHED_BY_WIDGET"))
{
launchedFromWidget = extras.getBoolean("EXTRA_LAUNCHED_BY_WIDGET");
}
if (launchedFromWidget)
{
// Launched from widget, handle as special case
}
else
{
// Not launched from widget, handle as normal
}
}