Start activity from widget & ignore back stack - android

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
}
}

Related

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

Make App Launch Activity not be visible in application manage after back is pressed

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

Android raise Notification when app goes to background (not single activity)

I have an Android app made of 3 activities A, B and C:
A is the application itself (a regular Activity)
B is the settings screen (made with PreferenceActivity)
C is the about screen (a regular Activity)
The relationship between the activities is as follows:
A is the main activity
B's parent is A
C's parent is B
Activity A raises a notification when going to background, this is accomplished by calling NotificationManager's notify() inside A's onStop() method.
When the user launches the app it starts from activity A, then if the user presses the home button the notification will be raised as soon as the home screen is shown. Clicking on the notification will get the user back to the app.
Everything seems to work but now there's a behaviour I tend not to like:
When the user launches the app and then goes to settings (therefore showing activity A and then B) the notification is also raised (because A has gone to background in order to show B).
This is not desired: The notification should be raised only when the application goes to background regardless of the activity the user was looking at.
How to implement this proper behaviour?
Thanks in advance,
Marco
just create a boolean flag shouldNotify and set it to false when you are opening one of your other Activities. Without your code I can't tell you exactly how to implement it but here is an example to get the idea accross:
private boolean shouldNotify = true;
#Override
public void onStart(){
super.onStart();
shouldNotify = true;
}
#Override
public void onStop(){
super.onStop();
if(shouldNotify){
sendNotification();
}
}
// Where you need to put these next snippets depends on how you've made your Activity
/*
* Launching settings activity
*/
shouldNotify = false;
Intent settingsIntent = new Intent(this, YourSettingsActivity.class);
startActivity(settingsIntent);
You could set a boolean flag which is checked before raising the notification and set it to "false" before startig Activity B.
Of course this way you would have to do the same in Activity B and C for them to raise the notification, too.
Or, as hinted to before you could implement an own application class and fetch the callback there.

How to use FLAG_ACTIVITY_REORDER_TO_FRONT and onBackPressed() Together

I'm creating a Wizard-style application, where I need to keep the user data between activities A and B alive. A has a Next soft button and B has a Back soft button.
When using FLAG_ACTIVITY_REORDER_TO_FRONT I can keep the user data alive when the soft buttons are used, because each activity is reused.
But, when the user presses the Back hard button from B, B dies, due to that hard button uses finish() implicitly.
Then, I tried overriding onBackPressed in B, adding to it the same behavior as my Back soft button, thinking that the Back hard button will behave exactly like the former button (not finish B).
Now, getting back from B to A with Back hard key, everything is fine. At this point with the focus in A, when the user presses the back hard button again, the expected behavior is that the application leaves.
The problem is that expected behavior does not occur, given that B is still alive; so that overriden onBackPressed in B is still listening, and some other behavior ocurr instead.
How can I finish listening with the overriden onBackPressed in B, so that when the focus is in A the application leaves?
Thanks in advance.
Consider doing as Krylez's comment. Or you might want to use fragments. If you target SDKs which are older than 3.x, see support library.
There are sample projects inside SDK folder, which use wizard style.
Well, I could solved my problem with a natural Android solution:
Following the Krylez tip, I've stopped using FLAG_ACTIVITY_REORDER_TO_FRONT, so I don't have conflicts with the hard button anymore, and now I'm recycling the Intent which starts my wizard.
In A, I have a very common method which is called when the user presses the continue soft button to go to B. Activity A is just informative, so it doesn't need to put Intent's extras with user's data when going to B, like this:
/** Called when the user presses the Continue button*/
public void continueButtonOnClick(View view) {
Intent intent = this.getIntent();
intent.setClass(this, StepOneRegisterWizardActivity.class);
startActivity(intent);
}
When activity B starts, it always must seek if there are user's data available in Intent's extras, like this:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_step_one_register_wizard);
// Get the components of the content layout
usernameEditText = (EditText)findViewById(R.id.usernameEditText);
passwordEditText = (EditText)findViewById(R.id.passwordEditText);
getIntentExtras();
}
private void getIntentExtras() {
Intent intent = this.getIntent();
Bundle bundle = intent.getExtras();
if (bundle != null) {
usernameEditText.setText(bundle.getCharSequence("usernameEditText"));
passwordEditText.setText(bundle.getCharSequence("passwordEditText"));
}
}
Now, maybe from B, the user presses any back button available (soft or hard) to back to A. In this case, we need to put the user's data in Intent's extras, like this:
/** Called when the user presses the Back soft button*/
public void backButtonOnClick(View view) {
onBackPressed();
}
#Override
/** Called when the user presses the Back hard button*/
public void onBackPressed() {
finish();
Intent intent = this.getIntent();
intent.setClass(this, StepZeroRegisterWizardActivity.class);
intent.putExtra("usernameEditText", usernameEditText.getText());
intent.putExtra("passwordEditText", passwordEditText.getText());
startActivity(intent);
}
Finally, when the user presses the continue soft button again, the new Activity B will have the data that user entered las time.
I hope it helps someone.

Start root activity if no other activity live for this android app when press back key

I have a message notification, when user select the notificaton, a message display activity will show.
When user read the message and press back key to close the activity, I want to check if the previous activity is the same app's, if so, just go ahead, if not, I want to start the home activity for this app.
How can I do that?
How about this: Have the notification launch the Home Activity in a state that immediately has the Home Activity launch the MessageDisplayActivity. That is: (1) The intent that the notification sends should target the HomeActivity and include an extra flag identifying the Intent as being from the notification. (2) The HomeActivity, in onCreate(), upon finding that flag then simply launches the MesssageDisplayActivity.
Thus the HomeActivity will exist for a brief moment of time, but probably not long enough to be visible. It will then remain upon the back stack so that the back key will bring you there.
You should consider changing the launch mode of your Activity, so that you never have this problem - using singleTask or singleInstance should make this Activity always be the only one open for your app.
Have Intents within your app that call the message display activity (MDA) pass a boolean value of "true" that's extracted and stored in a field in the MDA. Have the field set by default to "false." Thus, if the MDA is called from an activity within your app, the field will have a boolean value of "true," if it's called from anywhere else (such as a notification), it will be set to false.
Override public void onBackPressed() in the MDA as follows:
public void onBackPressed() {
if (wasCalledFromYourApp == true) {
super.onBackPressed();
} else {
//[code that launches your app's home activity here]
}
}
(This is assuming the notification will only be created by one app).

Categories

Resources