When a user opens preferences on my app he may make changes for example changing the app theme.
The documentation for ContextThemeWrapper.setTheme(int) says:
Set the base theme for this context. Note that this should be called before any views are instantiated in the Context (for example before calling setContentView(View) or inflate(int, ViewGroup)).
So my first thought was restarting the app onResume() when the user changed the preferences. However I noticed that sometimes the process of restarting the activity is seamless while other times the activity is closed, the home screen is seen and only after some seconds the app opens again.
I'm wondering if there is a way to change handle the preferences changes. Like for instance changing the theme after onResume without restarting the activity or restarting the activity on the background while the user is on preferences.
What's the right way to handle this?
Assuming that your Preference screen is an Activity, when the user navigated to it, MainActivity was placed on a paused state (and then probably on a stopped state). When the user navigates back to MainActivity onResume() will be called; here you can change the sate of MainActivity accordingly to reflect the preferences that were changed.
I'm assuming you are storing user's selected theme in App Preferences. So in your Activity's onCreate(), add setTheme().
public void onCreate(Bundle savedInstanceState) {
setTheme(android.R.style.Theme); // or whatever theme you're using
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
}
But changes don't take effect until the activity is restarted. So if you need to immediately apply theme changes, call recreate() afterwards applying theme:
// Might need to call them on getApplication() depending on Context
setTheme(userSelectedTheme);
recreate();
Also, as Attiq mentioned, re-creating an activity might result in loss of data fetched from network using Threads/AsyncTasks so you need to take that in to account as well.
When a user opens preferences on my app he may make changes that mean I have to restart my MainActivity however I don't want to user to notice anything happened.
User will not notice anything because, the activity life cycle will take care.
Tasks - EVENT 1 Main Activity (MA) TO Preference Activity (PA)
EVENT 2 Preference Activity (PA)TO Main Activity
Activity Life Cycle - MA onPause() TO PA onResume() ... PA onPause() TO MA onResume()
nor remove it from the back stack.
If you want the same activity from backstack to resume and not a new MainActivity to be created, this can be done using launchmodes
Code sample
intent = new Intent(this, MainActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT | Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP);
startActivity(intent);
Our application is offering multiple themes and we have already tried methods like updating themes from onResume or onStart[restart activity case] but it's requires onCreate() method calls to update the themes, you need to finish the activity and recreate it using selected themes.
Important point: Re-creating new activity might result in lose of data fetched from network using threads or asynctask in the activity so you need to take this issue as well.
In our application, we are saving the number of selected theme in the sharedpreferences and loading it during the creation of new activity.
To finish and restart the activity
public static void changeToTheme(Activity activity, int theme) {
sTheme = theme;
activity.finish();
activity.startActivity(new Intent(activity, activity.getClass()));
}
Related
I have 2 activities, a 4 digit pin style login Activity (MainActivity) and a content Activity.
This app stores private information and such that should not be able to be seen when resuming an activity, therefore i want the app to completely restart on the login activity each time it's launched, rather than picking up where it left off.
I've tried messing around within the content activity's onStop() and onResume methods, but these seem to be highly unreliable as sometimes when i have the onRestart setup to relaunch the login activity, it won't launch it at all, and calling for the login screen to be activated in the onStop() will prevent me from being able to finish up tasks in the background, such as saving data.
Is there anything i can add to the manifest file that will tell the app to restart from the login activity no matter what? One of the worst things that can happen when working on an app like this is that the information is accessible to someone other than the owner that wasn't forced to login..
What you need is to focus on two calls in your Content Activity:
onPause(): This is where you kill Content activity and remove it from the stack. You can do this easily:
#Override
public void onPause() {
super.onPause();
//Save your data here
finish(); //Kill Contect Activity.
}
onRestart(): This is where you redirect the user to the Main Activity.
#Override
public void onRestart() {
super.onRestart();
Intent i = new Intent(this, MainActivity.class);
startActivity(i);
finish(); //Kill Contect Activity.
}
This graph might help you in understanding the Activity lifecycle:
What you need is setting the flag android:clearTaskOnLaunch and android:excludeFromRecents for your activities in the Manifest.
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.
I have a quiet big application, and sometimes the user uses the HOME BUTTON to 'exit' the application or he receives a call etc. but when he clicks again on the icon, the application is resumed.
What I want is everytime somthing like this happens, the app restarts on the login activity (security process) before resuming the previous activity running before he exit the app.
When the HOME button is pushed, I believe your activity's onStop() and/or onPause() functions will be called. Override one of these methods and set a member variable to check if your activity was interrupted. Now override onResume() to check that variable to determine if you want to start your login activity.
Hopefully this idea gets you in the right direction.
You might also consider creating a super class that extends Activity and override the onStop()/onPause()/onResume() methods to exhibit this functionality. That way, all of your activities outside of your login activity can extend this class, allowing you to place the functionality that you desire in exactly one location.
Pass a Extra to any activity you call after login: intent.putExtra("isLogin", "Yes");
In every activity you call:
declare a field boolean isLogin;
In onCreate:
Intent sender = getIntent();
isLogin = sender.getStringExtra("isLogin","No") == "Yes";
In the onResume of every activity do this:
if(isLogin){
isLogin = false;
}else{
callActivityLogin();
}
The books I have are abysmal at explaining how to work with the lifecycle, there's a lot I'm missing that I'm hoping somebody can fill in.
My app structure is that when it's first started, it starts an activity full of legalbabble that the user has to accept. When he says 'ok', I start my main activity and then I call finish like this:
public void onClick(View view) { //as a result of "I accept"
Intent mainIntent = new Intent(mParent, EtMain.class);
startActivity(mainIntent); // Start the main program
finish();
}
Then in EtMain in the onCreate method, I've got some tabs and I instantiate some classes:
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mTabHost = (TabHost)findViewById(android.R.id.tabhost);
SetupTabs.setMyTabs(mTabHost, this);
mComData = new ComFields(this); // Create the objects
mDepWx = new WxFields(this, DepArr.Departure);
mArrWx = new WxFields(this, DepArr.Arrival);
mDepVs = new DepFields(this);
mArrVs = new ArrFields(this);
mTabHost.setOnTabChangedListener(new OnTabChangeListener(){
}
Questions:
The 'finish' in the first fragment should terminate the legalbabble activity so it'll never be restarted, right? And the EtMain one will remain forever (until killed externally), even if my app gets pushed to the background, right?
The way it is now, when EtMain gets pushed and later brought to the foreground (by tapping on the icon), it goes through the legalbabble screen as though it's a complete start - that's what I'd like to prevent - going thru the legalbabble screen again.
It would seem that I'd want to override onRestart in the second code fragment and put something in there to restart the app, right? That's the part I'm unclear about.
My question then is what needs to be done in onRestart. Do I have to recreate all the tabs and data in the tabs and all my object instantiations? Or is the memory state of the app saved someplace and then is restored back to the state that it was in before something else was brought to the foreground in which case not much needs to be done because all the objects and listeners will still be there?
Yes after the first activity has ended you shouldn't have to view that activity again. You could also write to the shared preferences that the user has previously seen legal info.
If you're UI object creation is in the onCreate method, this should only be called once. Pausing or resuming will not call the onCreate method again.
Unless you explicitly remove your objects and tabChangedListeners in the onPause method, you should not have to touch them in the onRestart method.
Correct, the state of the app is saved automatically. You shouldn't have to touch the onRestart method.
Hope this helps!
I think the problem is that the launch activity in your manifest is the legalbabble activity, so when you click on the icon, the system launches another one. A better architecture would be to launch the legalbabble activity it from your EtMain activity in the onCreate method of the latter, using startActivityForResult. From the docs:
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.
When you get the result in onActivityResult, you can call finish() if the legal stuff was declined; otherwise everything will proceed normally.
This avoids the problem that the launch activity defined in your manifest finishes when the legal stuff is accepted.
EtMain will not remain forever, if the user backs out (by pressing the BACK key) the Activity will be finished (onPause, then onStop, then onDestroy will be called).
In general you can ignore onRestore until you are doing something complicated.
Once the user has exited your application and re-enters (or presses the icon on the Homescreen), onCreate (followed by onStart and onResume) will be called for your first activity, so you do not need any logic in onRestart, your code in onCreate will do the setting up for you as it did the first time. Because of this your legal babble will appear again when the user starts the app after exiting unless you store a preference (in SharedPreferences or a database or file) to indicate you have already displayed it - in which case finish it straight away and start the main activity.
onRestart is only called when the application goes from the stopped state (onStop has been called but not onDestroy) to the started state (onStart is called but onResume has not yet).
For saving data - some components save their state automatically (e.g. EditTexts remember the text in them, TabHosts remember the currently selected tab etc). Some components will not. If you wish to save extra data then make use of onSaveInstanceState and onRestoreInstanceState. You should only use these methods to restore the state of your application or temporary data, not important things, e.g. the id of the resource what the user was looking at, what zoom level they were at etc. For things like contacts or actual data you should commit these changes to a database, SharedPreferences or other permanent storage (e.g. file) when onPause is called.
I recommend taking a look at the Android Activity lifecycle if you are confused. Or ask more questions!
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.