I have an app with some normal behaviour but I still dont know what I am doing wrong.
Activity A calls --> Activity B
I pass strings/ints from one activity to the other (Intent.putExtra())
I had declared my activities as single instances and declared who is parent of who in my manifest, but when I open other app and then go back to mine, the activity I was in is the only still alive.
If I remove the'singleInstance', then my navigation works but the ones that receive strings from the intent (previous activity) crashes.
I want to be able to:
When I am in my app, navigate up/back through my activities.
If I leave my app and come back, still be able to navigate up/back in my app.
Considering I pass values between activities with Intent.putExtra("key", "value")
I think is all related with the Back/Up Navigation and the android:launchMode=["multiple" | "singleTop" | "singleTask" | "singleInstance"] but I can't find the perfect solution.
Update:
A --> B --> C
A->B: A putExtra; B getExtra
B->C: B put Extra; C get extra
If I go to C, leave the App and then come back to the app, the app is in C, and if I try to navigate up to B, it crashes because I don't have the extras and the activity has been terminated.
What is the best behaviour? Keep them open? Recreate them?
the behaviour you're trying to achieve is the default behaviour on Android. So I believe that on your question, less is more.
AndroidManifest.xml:
remove all parentActivityName from it. It really does not much at all.
remove all launchMode those specific edge cases, unless you have a specific reason and a non-default behaviour, do not use them.
intent parameters you're passing to activities don't get messed by any of those manifest details, I don't think they have anything to do with your issue. On any activity you should be able to call getIntent().getExtras() and access any parameters passed to it, no matter how many times you exit and enter the app.
you do not need to save/restore the intent parameters during onSaveInstanceState and onRestoreInstanceState. Those callbacks are for current state, not for parameters passed to it. Those are different things.
back: that happens automatically. You cannot finish(); the previous activity when calling the next one.
up: it's just a matter of overriding the onOptionsItemSelected
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
// Respond to the action bar's Up/Home button
case android.R.id.home:
finish(); // this finishes and the previous will be shown.
return;
// other menu items ?
}
}
if I try to navigate up to B, it crashes because I don't have the
extras and the activity has been terminated. What is the best
behaviour? Keep them open? Recreate them?
do NEVER say "it crashes" on StackOverflow without providing a stacktrace and the lines of code around that stacktrace. That's one of the main reasons I downvote people.
If it crashes, ask about that crash, do not mess-up the automatically-default navigation to "try to fix it"
If i understand you must set condition in second activity example on String data:
if(intent != null){
yourString = intent.getStringExtra("count");
}
then you must provide saving instance like
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString("count", yourString);//or int or other look on developers
}
and restoring from instance
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
yourString = savedInstanceState.getString("count");
}
http://developer.android.com/design/patterns/navigation.html
The up button takes you to the previous activity hierarchically in the same app.
The back button takes you to the previous screen/app/activity that your phone was on, irregardless of which app it belonged to.
Related
I have two activities; let's say A and B. In activity A there is a broadcast receiver registered that listens for a particular event which will finish activity A. I am registering the broadcast receiver in onCreate(), and destroying it in onDestroy() of activity A.
For simplicity, there is one button in activity B named "Destroy Activity A". When a user clicks on button, activity A should be destroyed.
Normally all of this is running smoothly without any issues,but the problem occurs in following scenarios:
1) Suppose I am in activity B and i press the Home key to move the application to the background then if i use other resource-heavy applications, Android system will kill my application to free memory. Then If I open my application from recent tasks, activity B will be resumed, and it's onCreate(), onResume() etc method will be called. Now I press button to destroy activity A, but activity A has already been destroyed, so activity A's onCreate(), onResume() etc methods will not be called until and unless i go to activity A by pressing the back button. Thus broadcast receiver is not registered to listen for the event.
2) The same problem will arise when user has selected "Don't keep activities" from Developer options in the device's settings.
I have been looking to solve this issue for a long time, but i am unable to find a proper answer. What is the best way to handle this scenario? Is this an Android bug? There should be some solution for this issue.
Please help me.
If your Activity A has destroyed by Android OS itself then there are
no way to track.
Some people has suggested to track that Activity A by listning event in onDestroy method BUT if your Activity killed by system OS then note here it wont call those method .
This cannot be fixed while keeping your current broadcast logic.
Killing activities from the backstack, imo, is not a correct approach. You should strongly consider changing the logic of your navigation.
But if your project is big and time is a problem, and refactoring is out of the question, A.J. 's approach works, but you mentioned that you have lots of activities that needs to be killed, his solution becomes very tricky to implement.
What I suggest is the following. This might not be the best idea, but I cannot think of another. So maybe that could help.
You should have the following:
A Base Activity for all your activities.
A ArrayList<String> activitiesToKill object at the application level. (If you did not extend Application you can have it as static variable
First we have to make sure that the activitiesToKill is not lost when the OS kills the app in low memory. In the BaseActivity we save the list during onSaveInstanceState and restore it in the onRestoreInstanceState
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putSerializable("activitiesToKill", activitiesToKill);
}
private void onRestoreInstanceState(Bundle state) {
if (state != null) {
activitiesToKill = (ArrayList<String>) state.getSerializable("activitiesToKill");
super.onRestoreInstanceState(state);
}
}
The idea here is to save which activities should be killed in the list, by using their name.
The logic is as follow:
Let's say you have Activities A, B, C, D and E
From Activity E, you press the button and you want to kill B and D
When you press the Button in E, you add the names of B and D to the activitiesToKill object.
activitiesToKill.add(B.class.getSimpleName()
activitiesToKill.add(D.class.getSimpleName()
In the onCreate method of the BaseActivity, we have to check if the
if(savedInstanceState != null)
{
//The activity is being restored. We check if the it is in the lest to Kill and we finish it
if(activitiesToKill.contains(this.getClass().getSimpleName()))
{
activitiesToKill.remove(this.getClass().getSimpleName())
finish();
}
}
Make sure to remove the name of the activity if it is killed through the broadcast.
So basically this is what happens in every scenario.
If the app is running normally, and you click the button, the broadcast gets sent and B and D will get killed. Make sure to remove B and D from the activitiesToKill
If the app was killed and restored, you press the button, the broadcast will have no effect, but you have added B and D to the activitiesToKill object. So when you click back, the activity is created and the savedInstanceState is not null, the activity is finished.
This approach consider that activity E knows which activities it has to kill.
In case you DON'T know which activities to kill from E, you have to modify this logic slightly:
Instead of using an ArrayList use a HashMap<String, bool>
When Activity B is created, it will register it self to the hashmap:
activitiesToKill.put(this.class.getSimpleName(), false)
Then from Activity E, all you have to do is set all the entries to true
Then in the on create of the base activity you have to check if this activity is registered in the activitiesToKill (the hashmap contains the key) AND the boolean is true you kill it (don't forget to return it to false, or remove the key)
This ensure that each activity register itself to the HashMap and Activity E doesn't have top know all the activities to kill. And don't forget to remove them in case the broadcast kills them.
This approach also ensure that the activity is not killed when opened normally from an intent because in that case onSaveInstanceState would be null in the onCreate, so nothing will happen.
More advanced checks can be accomplished in case you have groups of activities that needs to be terminated through different conditions (not only a button click) so you can have a HashMap of a HashMap to divide them in categories.
Also note, that you can use getName instead of getSimpleName if you have multiple activities with same name but different bundles.
I hope my explanation is clear enough as I wrote it from my head, let me know if any area is not clear.
Best of luck
One of the main rules with Activities is you can't rely on any activity being alive except the foreground activity. The thing you're trying to do with broadcasts has nothing to do with back stack -- back stack doesn't guarantee all activities are alive at all times, but it will make sure they're recreated when it's time to go foreground.
In your example (if my understanding of what you're aiming to do) you need to navigate to something underneath A -- say, Activity Z, and the stack looks like this: Z-A-[B]. There's normal course of events where you hit back and it takes you to A, then after another hit -- to Z but in a certain case (say pressing a button) you want to move back to Z bypassing A -- this is a classic case to use FLAG_ACTIVITY_CLEAR_TOP and launch Z explicitly:
Intent intent = new Intent(this, ActivityZ.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);
This will finish both B and A, and deliver the intent to Z. You will, probably also need the FLAG_ACTIVITY_SINGLE_TOP flag, pay close attention to the description of FLAG_ACTIVITY_CLEAR_TOP, there's some trickery you should consider.
I don't know if it's possible to handle this on a "proper" way.
What it comes to my mind, is to flag the A activity in some way. You can't use startActivityForResult() because you will receive the result before onResume() is called i.e. UI was already inflated.
If you using an Otto, you could try with a sticky event. Otherwise you will need a singleton to handle the flag or save it to shared preferences.
You will have to check that flag on your onCreate() method before calling setContentView(), if the flag is true just finish the activity.
With the information you have given, how about you register the broadcast in onCreate of Activity B after checking if its registered already or not. If onDestroy of Activity A has been called in either of the scenarios you have mentioned, then the deregister of the Broadcast would have been called. So in that case, you can register your Broadcast in onCreate of Activity B, so that you can listen to it, even if you have only the Activity B in your backstack.
Have you considered using Sticky Broadcast?
Also you can register your receiver on application level (in manifest) and listen to this event regardless of Activity A state.
But, like already said Youssef, killing activities from the backstack is not a correct approach. You should strongly consider changing the logic of your navigation.
Many solutions came to my mind but as you have not provided much information about your app, so I think this should work in general.
Instead of firing a broadcast to kill Activity A, just execute the following code when the "Kill Activity A" button is pressed in Activity B.
Intent intent = new Intent(getApplicationContext(),
ActivityA.class);
intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
intent.putExtra("EXIT", true);
startActivity(intent);
Add the following code in activity A
#Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
if (intent.getBooleanExtra("EXIT", false)) {
finish();
}
}
protected void onCreate(Bundle savedInstanceState) {
//Ideally, there should not be anything before this
super.onCreate(savedInstanceState);
if(getIntent().getBooleanExtra("EXIT", false)){
finish();
return;
}
In the manifest set "singleTop" launch mode for activity A.
<activity
android:name=".ActivityA"
...
android:launchMode="singleTop" />
This will have the following consequences:
If Activity A is already running it will brought to the front of the activity stack and finished, thus removing it from the stack.
If Activity A has been destroyed but still present in the activity stack (to be launched when back button is pressed), it will be started, brought to front and finished, thus removing it from the activity stack.
If Activity A has already has already been destroyed and not present in the activity stack, and you still press the "Remove Activity A" button, it will be started, brought to front and finished.
Generally, you should not see any flicker.
Based on this idea, you may build a better performing solution for your particular app. For example, you may use the FLAG_ACTIVITY_CLEAR_TOP and finish the Activity A in onBackPressed() of Activity B.
This question has been answered numerous times it seems, but none of the answers solve the problem for me, so I thought I'd ask a new one.
My scenario is as follows:
I have added a navigation drawer to my app that holds list items for each section of the app. Tapping one of these launches the appropriate activity for that app section.
The way I wanted to do this was to have a main activity which had the drawer layout and then everything else would be fragments, but the app has a lot of activities already and the customer isn't willing to pay for the extra time that would be needed to convert these to fragments and get it all working. Therefore I'm keeping it as it is and have the nav drawer on each activity.
When the user clicks an item in the nav drawer, that section's activity is launched. If the user presses the back button on any of the activities that the drawer brings them to, I want the app to close (and go back to the Android app menu or home screen or whatever).
My problem is that pressing back will just pop the activity that the user was previously on when they clicked an item from the drawer. For example:
User is in Activity A.
User opens drawer and clicks list item.
Activity B is opened.
User presses the back button.
Activity A is shown.
I'm looking for a way to remove all previous activities from the stack, so that when a nav item is clicked, everything already in the stack is removed so that pressing back on the new activity will end the app because there won't be anything else to show.
The closest I've got is using the FLAG_ACTIVITY_NEW_TASK and FLAG_ACTIVITY_CLEAR_TASK flags on the intent that is launched on the nav list item click.
sectionIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
parentActivity.startActivity(sectionIntent);
(where sectionIntent is an Intent for the new activity and parentActivity is the activity hosting the drawer).
This actually works, but an empty (black) screen is displayed for half a second or so between the old activity closing and the new one showing.
I've also considered using LocalBroadcastManager to tell all activities to close when a "top level" (as such) activity is exited, but to be honest that approach seems like total overkill to me and surely a simpler option must exist?!
Does anyone know a way either to prevent this delay (like for the previous activities to be removed AFTER the new one appears) or another way to remove the activities in the stack?
Thanks in advance, fellow devs :)
-- SOLUTION --
Thanks to one of #Zielony 's suggestions, I managed to get this working.
I made a base top level activity and made it send a local broadcast to exit the app.
public class AppSectionHomeActivity extends BaseActivity {
#Override
public void onBackPressed() {
sendFinishActivityBroadcast();
super.onBackPressed();
}
#Override
public void finish() {
sendFinishActivityBroadcast();
super.finish();
}
private void sendFinishActivityBroadcast() {
// Send a local broadcast to all other activities to tell them to close.
Log.d("AppSectionHomeActivity.sendFinishActivityBroadcast", "Sending broadcast");
Intent intent = new Intent();
intent.setAction(Constants.EXIT_APP);
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
}
}
I then made a base activity for all other activities (the ones that weren't top level ones) so make them listen for the broadcast and then call finish() on themselves if they received it.
public class BaseActivity extends Activity {
// A broadcast receiver so that we can listen out for feed updated events.
private BroadcastReceiver exitAppReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
unregisterReceiver();
// When the activity receives the broadcast to finish up, then do so.
finish();
}
};
#Override
protected void onResume() {
super.onResume();
LocalBroadcastManager.getInstance(this).registerReceiver(exitAppReceiver, new IntentFilter(Constants.EXIT_APP));
}
#Override
protected void onDestroy() {
unregisterReceiver();
super.onDestroy();
}
private void unregisterReceiver() {
LocalBroadcastManager.getInstance(this).unregisterReceiver(exitAppReceiver);
}
}
This seems to work!
You have several choices:
use Broadcast to tell the stack to close itself and then add new Activity. It's quite good solution, but seems like you tried something similiar. Also, this shows best that you shouldn't attempt to close multiple activities. I don't know the thought process behind that.
use Process.kill() to kill your application when exiting most recent top-level Activity. It would look like exiting the app without the rest of the stack. It's not that bad either - Android will close unused Activities anyway, so it's not that resource demanding.
use Fragment stacks. I know your client wants you to do it quick and cheap, but fragment stacks are really good for this.
use Tasks to group Activities and close groups at once. Seems like it's not working for you and I don't really know why. This is the pre-fragment solution for your case.
Have you considered using fragments instead of activities? This allows a lot more flexibility when things will be shown/hidden/available etc.
Are you using a new nav drawer for each activity?
You can try like this. such as
sectionIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
parentActivity.startActivity(sectionIntent);
Best of luck!
In Manifest file, which ever activity you don't want in stack, in that activity make this changes
android:noHistory="true"
I hope this works for you
I'm working on an Android application that has 4 activities :
A -> B -> C -> D
When I'm going from A to B, from B to C, or from C to D, I put some extras in the Intent.
I guess navigation for something like that is quite simple: there's no need to redefine the back button, and for the "up" action bar button, a simple "finish()" would be to correct way to do it (?)
Now, the problem is that from A, I can also go directly to D. Still no need to redefine the back button, it will go back to A, and that's what I want, but for the "up" button, it must go to C, and from C, up will lead to B, ...
What is the correct way to do that?
Thanks for your answers
To control the flow between the various activities explicitly, I call finish() in each activity when I respond to user input by starting a new activity:
startActivity(intentForNewActivity);
finish();
That leads to that instance of the orignal Activity being destroyed. In each activity I create an Intent to start up the activity I want to go back to. Then write:
#Override public void onBackPressed() {
startActivity(intentWhereIWantToGoNext);
finish();
}
I originally wrote here that I called finish() in onStop(), which does work while the app remains running, but does not give the desired result if the app is stopped for any reason. Sorry for the confusion, and thanks to PravinCG, who, while his comments were not entirely on the right track, at least made me think more carefully.
One of the way is to actually have the same stack but use extras to perform the toggle.
For Instance: When you want to go from A -> D
go from A->B->C->D and use intents to handle whether you want to simply bypass activity or display it. Same is the case in reverse order user resultIntent for that.
Place the following code before the last bracket.
#Override
protected void onPause() {
// TODO Auto-generated method stub
super.onPause();
finish();
}
Right now an activity gets destroyed when the BACK key is pressed. How can I make it just stop ( i.e. keep all the variables, etc. alive ), rather then be destroyed?
Thanks!
Why is it that you need to keep the variables alive? Given the established lifecycle of an Android application, I'm not sure that preventing the activity from being destroyed "just to keep the variables" makes sense.
Even if you stop the application without destroying it, there is always the chance that Android will kill it to free up memory. You will have to account for this in your code anyway, and so preventing the application from destroying doesn't save you from writing code.
Variables can be saved and restored relatively easily and quickly using SharedPreferences in your onPause() and onResume() methods. Unless you are storing a ton of data, preventing the application from destroying might not make much of a difference.
It sounds like you want to keep the variables in memory because you intend to return to this activity. Typically, you don't use the back button to navigate away from activities that you intend to come back to. Instead you would create an Intent and start a new activity. When you do this, Android places the current activity on the Back Stack calling onPause() and onStop(), which seems like exactly the sort of behavior you are looking for.
So if you still really want to prevent your activity from being destroyed (at least until Android decides it's using too much memory and kills it on it's own) you could always use Sagar's code and start a new activity in onBackPressed().
#Override
public void onBackPressed()
{
Intent intent = new Intent(this, Other.class);
startActivity(intent);
}
Just be certain that that is what you really want to do.
Simple one line
#Override
public void onBackPressed() {
mActivity.moveTaskToBack(true);
}
Pressing the BACK key triggers the onBackPressed callback method of Activity class. The default implementation of this callback calls the finish() method.
http://developer.android.com/reference/android/app/Activity.html#onBackPressed()
You can override this method to move the activity to background (mimick the action of pressing the HOME key.
eg:
#Override
public void onBackPressed() {
onKeyDown(KeyEvent.KEYCODE_HOME);
}
You could also instead consider moveTaskToBackground() mentioned here:
Override back button to act like home button
I have managed to work out exactly what you want: switch between 2 activities using Back button and keep them all not to be destroyed!
For example: you have 2 activities A & B. A will be started first, then A calls B. When B is loaded, user press Back button and switches back to activity A from B. From now B should not be destroyed and just goes to background, and when user starts activity B from A again, activity B will be brought to foreground, instead of being re-created again or created new instance! How to implement this:
1. Override onBackPressed() of activity B:
#Override
public void onBackPressed() {
Intent backIntent = new Intent(this, ActivityA.class);
backIntent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
startActivity(backIntent);
}
2. On activity A, call activity B:
public void callActivityB() {
Intent toBintent = new Intent(this, ActivityB.class);
toBIntent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
startActivity(toBintent);
}
remember to add flag: Intent.FLAG_ACTIVITY_REORDER_TO_FRONT when you call A&B.
This is similar to this question that was asked earlier.
Hope this helps!
N.S.
First of all, sorry for not answering the question, cause, I still have no optimal answer for it.
But, I really like when people start asking "what do you need this for". And, very rarely, the person who asked the question, really deserves this kind of question. I think not this time, but ok, this is not the issue...
Anyway, I will try to point out why some of us are convinced that
going from Activity A to Activity B(creating UI based on some data fetching) AND
going back from B to A(destroying all the created UI and/or fetched data in B) is sometimes a bad concept. Better solution would be to keep the stack as it is, so using something like finish() in Activity B, but keeping the Activity B in Pause state, so later when calling it again from Activity A - it just goes in onResume = nothing recreated in UI, no additional data fetching. The bonus of course is a fast and responsive UI, and the difference is really if you have a more complicated UI layout.
Just specify in the manifest for the activity as
android:persistent="true"
That should prevent your activity getting destroyed. To know more about this please refer to these below links which were answered by me
How to prevent call of onDestroy() after onPause()?
Prevent activity from being destroyed as long as possible
In the above posts I have explained in detail with a use case
My application starts with a welcome screen Activity, but that screen has an option to skip that screen altogether in future launches.
What's the proper Android way to do this? Initially, I just automatically detected the skipWelcome preference and switched to the 2nd activity from Welcome. But this had the effect of allowing the user to hit the back button to the welcome screen we promised never to show again.
Right now, in the Welcome activity, I read the preference and call finish() on the current activity:
SharedPreferences preferences = getPreferences(MODE_PRIVATE);
boolean skipWelcome = preferences.getBoolean("skipWelcome", false);
if (skipWelcome) {
this.finish();
}
And then I implement onDestroy to move on to the next Activity:
#Override
public void onDestroy() {
super.onDestroy();
startActivity(new Intent(Welcome.this, StartFoo.class));
}
But this makes for some weird visual transitions. I'm starting to think that I need a base Activity that pops open Welcome only if proper, and then goes to StartFoo.
I can't comment on Mayra's answer or I would (not enough rep), but that's the correct approach.
Hidden in the Android documentation is this important phrase for Activity.startActivityForResult(),
"As a special case, if you call
startActivityForResult() with a
requestCode >= 0 during the initial
onCreate(Bundle
savedInstanceState)/onResume() of your
activity, then your window will not be
displayed until a result is returned
back from the started activity. This
is to avoid visible flickering when
redirecting to another activity."
Another important note is that this call does not block and execution continues, so you need to stop execution of the onCreate by returning
if (skipWelcome) {
// Create intent
// Launch intent with startActivityForResult()
return;
}
The final piece is to call finish immediately in the welcome activity's onActivityResult as Mayra says.
There are a few solutions to this.
Did you try just launching the activity and finishing? I vauguely remember that working, but I could be wrong.
More correctly, in if(skipWelcome) you can start the new activity for result, then when onActivityResult is called, immidiately finish the welcome activity.
Or, you can have your launcher activity not have a view (don't set content), and launch either the welcome activity or StartFoo.