Xamarin forms : Stop page being accessible from hardware back button - android

I am using xamarin forms prism.
Quick info : I have a page that contains information, this page the user can delete along with its information, this page is an instance of a page called saved conversation, think of each instance as a saved email, you may have many of the one type and are made dynamically.
The user deletes one saved conversation and this also removes an instance for that page, after they have deleted it, it will send them back to one of two pages every time. But they are still able to access this deleted saved conversation page using the hardware back button (Android), either by clicking it straight after they were force navigated back or navigating the app a bit and then pressing the back button multiple times. I would use something such as...
protected override bool OnBackButtonPressed()
{
return true;
}
But I want the back button to work for other pages and only not work if the previous page is an instance of the Saved conversation page, regardless if it has been deleted or not (As I think I may be able to figure out if it was deleted or not myself, just need to detect the page the hardware back button is going to send the user to, I think).

This might fall under a user experience decision over a technical workaround; but it sounds to me like this page you have might be a good candidate to be a Modal Page.
Navigating away from the modal page (popping it from the stack) should accomplish the same goal you are aiming for: not being able to navigate "back" to that page.

What I have done was add this code to all the pages in which I did not want the user to be able to go back. But it would only limit them from going back if the previous page is one of the two shown below. If it isn't then the back button works.
What I did note was you have to make sure modalNavigation is set to false when navigating to the pages (in my case conversation and saved conversation) otherwise they won't appear on the navigation stack to check against.
/// <summary>
/// Will override the harware back button.
/// </summary>
/// <returns> True if it can't go back false if it can.</returns>
protected override bool OnBackButtonPressed()
{
int count = this.Navigation.NavigationStack.Count;
bool shouldntGoBack = false;
if (count > 1)
{
string previousPage = this.Navigation.NavigationStack[count - 2].ToString();
if (previousPage == "NGT.Views.ConversationPage" || previousPage == "NGT.Views.SavedConversationPage")
{
shouldntGoBack = true;
}
}
return shouldntGoBack;
}

Related

Handle onViewCreated() of the previous fragment when the back button pressed

I have the ResultFragment. The onViewCreated() method has some logic regarding saving the result. If a user has earned an achievement, a button appears and he can go to the AchievementFragment. However, when the user presses the back button from there, he goes back to ResultFragment and triggers the saving again and that leads to a result duplicate.
How can I handle this behavior? I can check if the result is a duplicate, but this is going to hide a symptom and not solve the problem.
If you want to prevent the application from triggering the saving of the result, which leads to duplications when going back, make use of Boolean.
Below there is a simplified pseudo code of what you could use and adapt to your project:
ResultFragment:
Boolean resultObtained = false // place it somewhere that is not reset when going back
if (resultObtained == true) {
// do not save result
}
else {
// save result
}
The Boolean can be reset to false when you go back to the homepage. While is true, the ResultFragment should not duplicate the result if you nest the function inside the if statement using the Boolean.
For the Boolean, you could add it somewhere else and make it static, so it is easily accessible from all the fragments. Additionally, you can keep in the same fragment, but you would need to write more code to ensure that it is not reset to false again when going back. Alternatively, you may place it directly in the AchievementFragment and make it static so it stays true when you go back. However, it is up to you how you want to adapt it in your code.

How to set the root of the navigation stack

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();
}
}

Handling the back button in a meteor android app

I have built a Meteor app with a mobile app interface (using Ratchet), only designed to be run as an app. On each page, there is a "back" link that takes you back to the parent page. For example, if I go deep inside a hierarchy of page such as this one:
Home > Category > Post
I can use a link in the Post page that will take me back to the Category page. Now, the problem is, if from this Category page I hit the back button, it should have the same behaviour as clicking the back link on the page. (in this case, take me to the Home page) Sadly, this doesn't happen and I get taken back to the Post page.
On iPhone it is not a problem (as far as I know) since there is no actual back button built-in to the device. But on Android it gives me headaches, for example:
Say a user goes to a Post page that he can delete using a button shown on the page. When the post-deletion method finishes, I take the user back to the Category page:
Router.go('categoryPage', {'_id': categoryId});
Problem is: if the user hits the back button after deleting a post, he or she gets taken to a "not found" page, since the previous post has been deleted. Now I can avoid that by adding replaceState: true like this:
Router.go('categoryPage', {'_id': categoryId}, {replaceState: true});
But now when the user hits the back button from the Category page, he or she gets taken to the page that was there before the Post page, which was... the same Category page. So the button just does nothing on the first press.
I also tried to pushState the url of the desired page in each of my template's `rendered̀€ function, to no avail (and what would I put in there for the Home page?):
Template.categoryPage.rendered = function () {
history.pushState(null, null, Router.url('home'));
};
Template.postPage.rendered = function () {
history.pushState(null, null, Router.url('categoryPage', {'_id': this.data.categoryId}));
};
Has anyone tackled this issue and/or would be able to drop some knowledge?
As soon as the back button is pressed, Cordova trigger a "backbutton" event, see there.
By listening to this event you should be able to override the default behavior and do what you want:
document.addEventListener("backbutton", onBackButtonDown, false);
function onBackButtonDown(event) {
event.preventDefault();
event.stopPropagation();
// Do what you want
...
}

Back button between fragments

I'm currently working on a project that has an activity which is consisted of two fragments.
The first fragment shows a custom expandable list. Every row is created from a custom layout that has a checkbox in the right side of it.
The second fragment shows more details about the clicked row from the list. In order to open the second fragment, the user has to click on the row. The checkbox is used for another reason.
So, what I'm trying to do is to display these two fragments side by side only when the application runs in tablets. When the app runs in handsets and the user presses one row, the second fragment should be displayed on top.
Furthermore, I have an action bar at the top of the screen which has implemented the usual back button.
The problem exists when I open the second fragment when I have already selected some checkboxes. When I press the back button, which navigates me to the first fragment, the checkboxes will not be checked.
The onSaveInstanceSate method is obviously not called (as the parent activity is not getting paused), so I can't save the ArrayList that stores the checked rows.
Last but not least, the fragments are being added dynamically.
The question
How can I properly implement the back button so when the user uses a
tablet, the back button should be used in order to close the activity, or a
handset, so the back button should be used as a navigation back to the first fragment with the ability to restore it's previous state?
if (mFragmentManager.getBackStackEntryCount() == 0) {
LogUtil.d(TAG,
"home fragment" + mFragmentManager.getBackStackEntryCount());
this.finish();
} else {
mFragmentManager.popBackStackImmediate();
}
try this should work , happy coding
My first idea would be to create a boolean in the resources of your project: in the "values" directory, your boolean would be false, for example, and in your "values-sw600dp" and "values-sw720dp-land" directory, the boolean would be true.
Then, in your code, you would check the boolean (using R.boolean.your_boolean) to know if this is a tablet or a handset.
Then, with a simple if/else, you would implement your code, depending on the value of your boolean...
if(yourBoolean){
//We are on a tablet
finish();
}else{
//We are on a handset
//Your code to navigate back...
}
You need to (1) detect if the user is on a tablet and (2) control the back function accordingly. I'm not sure how you're currently detecting whether the device is a tablet but a very easy method is described here. It involves a boolean resource that you can access when customizing your back function to determine the device type.
What I would do is override onBackPressed in your hosting Activity and control back function from there
#Override
public void onBackPressed(){
boolean tabletSize = getResources().getBoolean(R.bool.isTablet);
if (tabletSize){
moveTaskToBack(true);
} else {
//handle fragment back stack
}
}
The info on handling the back stack and replacing the fragment is here in the android docs. I will update that section later but I have to run for now.

Manually Access Android Back Stack

I'm having problems coming up with a solution to this problem.
Basically I have a load of tabs in my ActionBar. When each is touched the fragments from the previous tab are detached and the fragments for the new tab are added using replace (if they haven't been instantiated yet) or attached (if they have). I think I got this method from Google and it was working fine until now.
Example of adding a tab's fragments:
if(tab.getText().equals(context.getString(R.string.title_class_tab))) {
if(browser == null) {
browser = CourseBrowserFragment.newInstance(false);
fragmentTransaction.replace(leftContainerId, browser);
} else {
fragmentTransaction.attach(browser);
}
if(lessonViewer == null) {
lessonViewer = LessonViewerFragment.newInstance(false);
fragmentTransaction.replace(rightContainerId, lessonViewer);
} else {
fragmentTransaction.attach(lessonViewer);
}
}
and removing:
if(tab.getText().equals(context.getString(R.string.title_class_tab))) {
if(browser != null) {
fragmentTransaction.detach(browser);
}
if(lessonViewer != null) {
fragmentTransaction.detach(lessonViewer);
}
}
The problem arises from the layout I need for one of the tabs. Basically it's like the Gmail app. There are two fragments (let's say Panel A and Panel B) and when you push a button Panel A slides out, Panel B slides to Panel A's old position and a new, third one (Panel C) slides in from the right.
I had this working fine but now I've added the sliding-in FragmentTransaction to the back stack so that the user can touch the back button and Panel C will slide back out and Panel A will come back. Again, like Gmail.
Except when the user goes to a different tab this transaction is still on the back stack and executes if the user presses back. The fragments end up in crazy places. What I need to do is remove it from the back stack when the user navigates to a different tab. Is there any way I can do this? FragmentManager doesn't seem to let you manually remove things from the back stack and using the popBackStack() method doesn't just remove the transaction, it executes it. I want to remove it when the user navigates away and put it back when the user returns.
I think I can get a hold of the "Back Stack Entry" for this transaction using "getBackStackEntryAt" but it's not much good if I can't remove it and put it back in place when the user comes back to the tab.
The only possible solution I can think of is not using the back stack and overriding onBackButtonPressed instead. From there I could just do a reverse of the transaction if necessary.
Thanks for any help and sorry if I'm being incoherent.
Not sure if this would qualify as a solution but I ended up just not adding the transaction to the back stack and just doing a fresh transaction when the user swiped or pressed back. The transaction just did the reverse of the original one with animations etc.
The way I managed the back button is I set a boolean to true if I was in the layout showing Panel C. If the user swipes back into the Panel A layout or navigates away the boolean is set to false. I then overrode the onBackButtonPressed method in the Activity and if the boolean was true (ie: we're in the Panel C layout) I run that reverse transaction otherwise I just call super.onBackButtonPressed() (ie: perform standard back button behaviour).

Categories

Resources