I have a strange behaviour in my application on orientation change.
The normal behaviour:
When I open my app my home activity starts. When I go to the next activity (a gallery) it is started normally (with an sliding-in animation from right to left).
When I go back using the back key, the current activity (the gallery) is finished (with an sliding-out animation from left to right).
The strange behaviour:
When I'm starting the app in portrait mode and change the orientation to landscape. Then there's something like a second instance of the home activity. Because then pressing the back button in landscape mode doesn't close the app like it would without orienation change (the home activity is the first activity in my app) but rahter make a sliding animation from left to right (like starting a new activity) and shows the home activity (but I think another instance) again. Pressing the back button a second time closes the app.
When I'm starting the app in landscape mode and change the orienation to portrait mode, press then the back button results in a slide animation from right to left (like closing an activity) and shows the home activity again.
When I start the app and make two orientation changes portrait-landscape-portrait, then the back button closes the app like it should be.
So it's like the landscape and the portrait mode are treated like two different activities.
I don't use android:configChanges="orientation|keyboardHidden|screenSize", so an orientation change should follow the normal android activity lifecycle and destroy the "old" portrait (or landscape) version of the activity.
My activites inherit from FragmentActivity.
I'm using onSaveInstanceState to pass a parceable (which does not contain any reference to the activity) and I'm using onRetainCustomNonConfigurationInstance (read here) to pass several AsyncTasks. But all references in these tasks (if they have any) are destroyed in onRetainCustomNonConfigurationInstance and restored (with the newly created activity) after getLastCustomNonConfigurationInstance.
Any ideas what could cause this behaviour?
EDIT:
Activity Declaration in Manifest-File:
<activity android:name=".activities.smartphone.HomeSmartphone" android:label="#string/app_name"></activity>
HomeSmartphone extends Home
Home extends MyFragmentActivity
MyFragmentActivity extends android.support.v4.app.FragmentActivity
In MyFragmentActivity I just do some logging/tracking stuff in onCreate, onRestart, onStart, onSaveInstanceState, onPause, onResume, onStop, onDestroy by calling some static methods of a tracking class, which just holds a reference to the application context. Not to the activity context.
Home is an abstract class which is extended by HomeSmartphone and HomeTablet. These two classes do just some special loading/refreshing/initalizing in the different layouts.
The most task are done in the abstract Home class.
public HomeRetainedObjects retained = new HomeRetainedObjects();
public boolean adIsShown = false;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.setContentView(R.layout.home);
Log.i("DEBUG", "onCreate(Home)");
if (savedInstanceState != null) {
this.adIsShown = savedInstanceState.getBoolean("adIsShown");
}
// write/update values in shared preferences
this.initPreferences();
// recover retained objects (mostly AsyncTasks)
this.recoverRetained();
// show content / refresh content
this.init();
}
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean("adIsShown", this.adIsShown);
Log.i("DEBUG", "onSaveInstanceState(Home)");
}
public void recoverRetained() {
Object retained = this.getLastCustomNonConfigurationInstance();
if (retained instanceof HomeRetainedObjects) {
this.retained = (HomeRetainedObjects) retained;
if (this.retained.loadMessageTask != null) {
this.retained.loadMessageTask.restoreContext(this);
}
}
}
#Override
public Object onRetainCustomNonConfigurationInstance() {
if (this.retained.loadMessageTask != null) {
this.retained.loadMessageTask.destroyContext();
}
return this.retained;
}
I hope this helps?!
I ran into a similar issue, and it appears the cause of your problems is overriding onRetainCustomNonConfigurationInstance().
I reviewed my retention state object, and found that it was holding a reference to a Context. I wrapped it in a WeakReference and the normal behavior returned and no strange double instances lingered about.
Perhaps your retention state is also holding a reference to a Context?
Related
I have seen a lot of questions and answers about recreating the current activity after changing the application's Night Mode, but I have seen nothing on how to refresh the back stack Activities.
Say I have the backstack A > B > C. Activity C allows to change the night mode by calling AppCompatDelegate.setDefaultNightMode(). After this call, the current Activity (C), can refresh its theme with delegate.applyDayNight() or recreate().
However, when the user navigates back to B or A, the activities are still using the "old" mode, either day or night.
I tried to add something like that to the Activities:
override fun onResume() {
super.onResume()
delegate.applyDayNight()
}
But it does not seem to work.
I did multiple attempts to fix this:
One idea would be to recreate the backstack completely like suggested here or here, but since the backstack is not static, it's not doable for me.
Another idea would be to have a class that handles the night mode change and provides a LiveData. Each Activity would listen to the LiveData for a mode change and call recreate(). However, we are stuck in an infinite loop because the Activity would recreate directly after starting to listen to the LiveData.
I find it hard to believe that I am the first one trying to refresh the Activities from the backstack after changing the night mode. What did I miss?
Thanks!
If you can detect when the day/night mode has changed, you can simply recreate an activity that is resumed when the back stack is popped.
In the following demo, there are three activities: A, B and C. A creates B and B creates C. Activity C can change the day/night mode. When C is popped, activity B sees the change in the day/night mode and calls reCreate() to recreate the activity. The same happens in activity A when activity B is popped.
The video below shows the effect. The light-colored background is the "day" mode and the dark is "night" mode.
I have created a GitHub project for this demo app. If this works as a solution, I can incorporate more text into the answer from the project.
Refreshing your back stack completely is probably overkill and may add some overhead/lag to the UX; and as you mentioned, most applications will not have access to a full, static back stack.
You are essentially describing a more general issue: global changes to the theme or WindowManager itself affect the subsequent drawing of views. But previous layouts for Activities in the stack may not be redrawn. It might seem odd for you in this situation, but there could also be many good reasons why one would not want to redraw an Activity in the stack if once the user goes back to it. And so this is not an automatic feature.
I can think of a couple of options:
1) Write a custom class inheriting from Activity that invalidates all it's views when it moves to the front of the stack again. E.g. in onResume() or onRestart(), call (if in Fragment)
View view = getActivity().findViewById(R.id.viewid);
view.invalidate();
Use this custom Activity for all your activities that you want to keep consistent with the current day/night mode.
2) Use ActivityLifecycleCallbacks. This helps keep all your logic in one place, and avoids the need for custom inheritance as above. You could invalidate your views here as needed as activities are paused/resumed. You could include a Listener, if it is your app that is changing the theme, and record as SharedPreference, for example.
To use, add the callbacks to your Application class:
registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
#Override
public void
onActivityCreated(Activity activity, Bundle savedInstanceState) {
//can check type of Activity for custom behaviour, if using inheritance
if(activity instanceof MainActivity) {
mMainActivities.put(activity, new MainActivityEntry((MainActivity)activity));
//...
}
}
#Override
public void
onActivityDestroyed(Activity activity) {
}
#Override
public void
onActivityPaused(Activity activity) {
}
#Override
public void
onActivityResumed(Activity activity) {
if(activity instanceof MainActivity) {
//...
}
//can update Entry properties too
final MainActivityEntry activityEntry = mMainActivities.get(activity);
if(activityEntry != null) {
//record state /perform action
}
}
#Override
public void
onActivitySaveInstanceState(Activity activity, Bundle outState) {
}
#Override
public void
onActivityStarted(Activity activity) {
}
#Override
public void
onActivityStopped(Activity activity) {
}
});
Quick answer:
#Override
protected void onRestart() {
super.onRestart();
recreate();
}
You add the above codes to your MainActivity and it will work.
create a static boolean variable in the project and in each activity check if the boolean is true or false, then apply daylight and night based on value.
In an Android app-
Say I am in an Activity - MyActivity which holds one Fragment at a time.
First I loaded Fragment A to it (With no tags I added it to back stack of the FragmentManager)
Then at some point I loaded Fragment B (Again with no tags I added it to back stack of the FragmentManager)
Then at some point i loaded Fragment C (Again with no tags I added it to back stack of the FragmentManager)
I am using popBackStack() to enable back button behavior so whenever I press back from Fragment C the flow is like:
Fragment C -> Fragment B -> Fragment A -> Close MyActivity..
Everything is perfect :-)
But if I am in Fragment C and the app gets killed in background (I used "do not keep activity flag" from Settings)
and come back online Fragment C is loaded in MyActivity
but the FragmentManager's back stack contains only Fragment C..
The Back button is messing it up
Fragment C -> Close MyActivity..
Why is it so?
How to properly restore FragmentManager's back stack within an Activity?
Try using alwaysRetainTaskState on your root activity. Android automatically clears the Activity backstack because it assumes that it has been a long time since you used the app and that the user wants to start again from the start.
<activity android:alwaysRetainTaskState="true"/>
This setting will prevent that behaviour and it may follow that the behaviour is inherited by the Fragment Manager.
While developing your app, I recommend you to test restore/saved states of the activities, fragments with ADB:
Open app
Navigate between activities
Press home
ADB -> Kill (stop) app
Press the application stack (menu button from the device) and resume the application
This way you can debug the saved/restore states.
If you don't have a complex application, I suggest you to handle the saved/restore state in the activity:
private Fragment1 mFragment;
#Override
protected void onCreate(Bundle savedState) {
super.onCreate(savedState);
// ...
if (savedState == null) {
mFragment = new Fragment1();
getFragmentManger().beginTransacation().add(mFragment, TAG).addToBackStack(TAG).commit();
}
else {
mFragment = getFragmentMananager().findFragmentByTag(TAG);
}
}
If you have several Fragments or a ViewPager or nested fragments, then things can get really complicated. I suggest you to restart the whole application:
#Override
protected void onCreate(Bundle savedState) {
super.onCreate(savedState);
if (savedState != null) {
Intent intent = new Intent(ActivityMain.this, ActivityMain.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(intent);
return;
}
}
If you want to handle each saved/restore state, please read this post: http://www.androiddesignpatterns.com/2013/08/fragment-transaction-commit-state-loss.html
try this method
public void setRetainInstance (boolean retain)
Control whether a fragment instance is retained across Activity re-creation (such as from a configuration change). This can only be used with fragments not in the back stack. If set, the fragment lifecycle will be slightly different when an activity is recreated:
from developer website
http://developer.android.com/reference/android/app/Fragment.html#setRetainInstance%28boolean%29
Fragment C should never be loaded after your application dies. Do you have an Init Fragment in your application ? Ideally when you are implementing a pop of fragments there should be an Init screen. If the application dies or is killed for memory reasons you application should start from Fragment A (Init Fragment). Not from Fragment C.
If your problem demands this solution, then you should have to save each fragment persistently when a new fragment come on top. Which ideally means you are persisting your backstack in a preference or a database to achieve this.
I am developing a (custom) bottom action bar, managed through a fragment. The bottom bar is made of 5 buttons, and is shared by 3 activities (I want each individual activity to present this bar at the bottom of the screen).
I would like to save/restore the state of the fragment in order to save buttons' state across activity changes (through startActivity(Intent) and so on).
However, when starting a child activity from a parent one, the first one is not destroyed and the state of the activity (and it's inner fragments) is not saved. In consequence, when my child activity starts, the state of it's bottom bar fragment is not restored.
The code
The link between my activities :
AndroidManifest.xml
<activity
android:name=".MainActivity"
android:screenOrientation="portrait"
android:theme="#style/Theme.TicketPromo" />
<activity
android:name=".SavingsActivity"
android:parentActivityName=".MainActivity"
android:screenOrientation="portrait"
android:theme="#style/Theme.TicketPromo" />
My save / restore code :
public class BottomBarFragment extends Fragment {
...
// Indicates whether the buttons are enabled or not
protected boolean isPromoFlashEnabled;
protected boolean isCouponsModeEnabled;
protected boolean isSavingsModeEnabled;
protected boolean isSearchModeEnabled;
protected boolean isAccountModeEnabled;
...
/*
* Save the state of buttons so they can be restored on the next screen.
*/
public void onSaveInstanceState(Bundle state) {
super.onSaveInstanceState(state);
// Save buttons' state
state.putBoolean("isPromoFlashEnabled", isPromoFlashEnabled);
state.putBoolean("isCouponsModeEnabled", isCouponsModeEnabled);
state.putBoolean("isSavingsModeEnabled", isSavingsModeEnabled);
state.putBoolean("isSearchModeEnabled", isSearchModeEnabled);
state.putBoolean("isAccountModeEnabled", isAccountModeEnabled);
}
/*
* Restore the state of buttons and call their trigger on the screen opener.
*/
public void onViewStateRestored(Bundle state) {
super.onViewStateRestored(state);
if (state == null) { return; }
// Promo flash
isPromoFlashEnabled = state.getBoolean("isPromoFlashEnabled", false);
toggleActionButton(ActionButton.PromoFlash, isPromoFlashEnabled);
screenOpenener.togglePromoFlashButton(isPromoFlashEnabled);
// My coupons
isCouponsModeEnabled = state.getBoolean("isCouponsModeEnabled", false);
toggleActionButton(ActionButton.Coupons, isCouponsModeEnabled);
screenOpenener.toggleMyCouponsButton(isCouponsModeEnabled);
// My savings
isSavingsModeEnabled = state.getBoolean("isSavingsModeEnabled", false);
toggleActionButton(ActionButton.Savings, isSavingsModeEnabled);
screenOpenener.toggleMySavingsButton(isSavingsModeEnabled);
// Search mode
isSearchModeEnabled = state.getBoolean("isSearchModeEnabled", false);
toggleActionButton(ActionButton.Search, isSearchModeEnabled);
screenOpenener.toggleSearchButton(isSearchModeEnabled);
// My account
isAccountModeEnabled = state.getBoolean("isAccountModeEnabled", false);
toggleActionButton(ActionButton.Account, isAccountModeEnabled);
screenOpenener.toggleMyAccountButton(isAccountModeEnabled);
}
...
}
The code to obtain a reference on the fragment when creating the activities (the fragment is directly inserted in the activity's layout with <fragment .../> ) :
FragmentManager fm = getSupportFragmentManager();
bottomBar = (BottomBarFragment) fm.findFragmentById(R.id.bottom_bar);
THE question
How to save the state of a fragment that is still attached to a paused activity
How to restore this state
Is there a link with setRetainInstance(true) or a hack regarding this?
Edit, reworded question :
How to share a single fragment between two activities so the fragment is not destroyed, or at least so that it can be saved/restored across activities' lifecyle ?
1- If your main activity gets in pause state your fragment should get in pause state to.
This corresponds to Activity.onSaveInstanceState(Bundle) and most of
the discussion there applies here as well. Note however: this method
may be called at any time before onDestroy(). There are many
situations where a fragment may be mostly torn down (such as when
placed on the back stack with no UI showing), but its state will not
be saved until its owning activity actually needs to save its state.
http://developer.android.com/reference/android/app/Fragment.html#onSaveInstanceState(android.os.Bundle)
2- You could get the saved bundle at onViewStateRestored.
Called when all saved state has been restored into the view hierarchy
of the fragment. This can be used to do initialization based on saved
state that you are letting the view hierarchy track itself, such as
whether check box widgets are currently checked. This is called after
onActivityCreated(Bundle) and before onStart().
http://developer.android.com/reference/android/app/Fragment.html#onViewStateRestored(android.os.Bundle)
3- If you set retain instance to TRUE the save and restore methods will only be called when the fragment was realy create/destroyed because when the flag is TRUE the first instance is "never" lost.
Control whether a fragment instance is retained across Activity
re-creation (such as from a configuration change). This can only be
used with fragments not in the back stack. If set, the fragment
lifecycle will be slightly different when an activity is recreated:
onDestroy() will not be called (but onDetach() still will be, because the fragment is being detached from its current activity).
onCreate(Bundle) will not be called since the fragment is not being re-created.
onAttach(Activity) and onActivityCreated(Bundle) will still be called.
http://developer.android.com/reference/android/app/Fragment.html#setRetainInstance(boolean)
I solved the problem using SharedPreferences when the first activity comes to be paused.
Not a great deal, just sad I couldn't use the native life cycle to handle this.
I have an activity the same as the following image:
FragmentA is a listview and has a SearchWidget as menu-item (which is not displayed on old devices, only API11 and above).
FragmentB is a detail view and has several menu-items.
When ActivityA runs on a tablet, the menu-items of FragmentA + FragmentB are visible in the actionbar. This is correct and works perfect.
Now on a Nexus 7 I want a mix of those:
In portrait only use the handset layout
When I rotate the device, the tablet layout is loaded
The only thing which I can't seem to get working is the actionbar. When I rotate the device from landscape mode (tablet view) back to portrait (handset view), still the actionbar shows the menu-items of FragmentA + FragmentB.
I've tried calling the invalidateOptionsMenu() from onResume() in both ActivityA as FragmentA, but without luck.
Does anyone has an idea?
I think this is due activity re-creation process.
When screen is rotated your activity is destroyed (by default).
But before it is destroyed it saves state including states of all currently active fragments.
Later, when activity is creating after orientation change it restores saved state (with both fragments). As details fragment restores it appends menu items.
You can check this by adding log statements or using debugger in onCreateView of DetailsFragment.
If it's your case then you have next solutions:
Suppress saving state (must be avoided if DetailsFragment should keep track of last displayed item or something similar) by removing this fragment before activity save its state.
Suppress any initialization of DetailsFragment if it will not be displayed. Activity should give answer about visibility.
Your variant? I think two approaches above isn't good enough...
Sorry if my answer didn't help you at all
This works for me.
In Activity A try find Fragment B and set setHasOptionsMenu(mTwoPane)
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_document_list);
Fragment fragment = getFragmentManager().findFragmentByTag(TAG_FRAGMENT_DETAIL);
if (getResources().getBoolean(R.bool.has_detail)){
mTwoPane = true;
....
}
if (fragment != null) {
fragment.setHasOptionsMenu(mTwoPane);
invalidateOptionsMenu();
}
}
Well basically, I press a button, this opens up your default camera app by using the camera intent. After a picture is taken, it will save the things needed and redirect to another activity.
In this activity, I have an AsyncTask that can succesfully upload pictures. So what is my problem you may ask. My problem is that it re-creates my activity and therefore reset my ProgressDialog together with it. ( It runs the activity, does the aSyncTask, dies before it can finish it and re-creates my Activity to do the asynctask once again. )
It does not always do this. I think it does this because it changes the Orientation from the phone from Landscape to Portrait. ( I have a Samsung. When I go to the Camera it changes to landscape and when I finish it, it goes back to portrait. )
I've already done my homework and added these things to my manifest:
android:configChanges="orientation|keyboardHidden"
android:screenOrientation="portrait" >
I've made sure to "lock" my app in the portrait orientation but I still see my app change orientation and I believe this is why my activity gets re-created.
I was planning to add all kinds of checks but I believe this is not the right way to handle this situation, since it sometimes does not re-create the activity.
The check I am talking about is to use:
protected void onSaveInstanceState(Bundle outState) {
outState.putString("started", "1");
}
Anyway, can somebody help me out? I just want it to load the activity without it self-destructing on me.
PS: The VM doesn't have any problems. The VM loads the activity and finishes it without re-creating it.
PPS: Did extra testing, on my Samsung if I keep it on landscape-mode it will work. So it is definately the camera that is destroying my activity with it's orientation change.
I had the same issue, turns out you also need to listen for screen size changes in API level 13 or higher as explained here; https://stackoverflow.com/a/11483806
android:configChanges="orientation|screenSize"
For this to fix, I had to use following in my manifest file:
android:screenOrientation="portrait"
android:launchMode="singleTop"
android:configChanges="keyboardHidden|orientation|screenSize"
Try creating a fragment activity to handle displaying and updating the progress dialog
In the fragment activity make sure and set "setRetainInstance(true);" This will make sure it isn't destroyed when the main activity gets created/destroyed.
It's probably a good idea to put the entire image capture process inside this fragment, including the asynctask. Make sure you don't reference the parent activity's context from within the doInBackground() in the AsyncTask. If you do this and the orientation changes (i.e. the activity is destroyed) it will throw an error.
here's a rough example:
public class MyFragment extends FragmentActivity {
private ProgressBar mProgressBar;
private boolean mAsyncTaskActive = false;
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
setRetainInstance(true);
// grab reference to progress bar
mProgressBar = (ProgressBar) getActivity().findViewById(R.id.my_progress_bar);
// check to see if the async task is active and set the progress bar visibility accordingly
if (mAsyncTaskActive) {
mProgressBar.setVisibility(View.VISIBLE);
mProgressBarText.setVisibility(View.VISIBLE);
}
}
// this method is called from your main activity when the user does something (i.e. clicks a button)
// make sure you have already instantiated the fragment
public void startSomething() {
if (mAsyncTaskActive == false) {
mProgressBar.setVisibility(View.VISIBLE);
new MyAsyncTask().execute();
mAsyncTaskActive = true;
}
}
private class MyAsyncTask extends AsyncTask<Void, Void, Void> {
Context applicationContext;
#Override
protected Void doInBackground(String... params) {
// do stuff
publishProgress(//some number);
return null;
}
#Override
protected void onProgressUpdate(Integer... progress) {
setProgressPercent(progress[0]);
}
}
You should also take a look at how to implement fragments if you're not already familiar. The Android dev blog has a good post on DialogFragments, same priniciples. http://android-developers.blogspot.com/2012/05/using-dialogfragments.html