So I have a fragment that is attached to an activity and I'm trying to make sure things go smoothly when the screen is rotated (or anything that would interrupt the activity). In order to do this, I use the methods onSaveInstanceState and onRestoreInstanceState in my activity to keep the information that my activity stores.
When the view for my fragment is created, the fragment asks the Activity for information (This is in onCreateView() for the fragment):
ArrayList<String> picList = mListener.getPics();
ArrayList<String> descripList = mListener.getDescriptions();
In order for the fragment to create the view, it needs access to picList and descripList, which are member variables for the activity. These member variables are stored and restored in onSaveInstanceState and onRestoreInstanceState.
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
if(photoFile != null)
outState.putString("photoFile", photoFile.getAbsolutePath());
outState.putString("currentFragTag", currentFragTag);
outState.putStringArrayList("picList", picList);
outState.putStringArrayList("descripList", descripList);
}
#Override
protected void onRestoreInstanceState(Bundle saved) {
super.onRestoreInstanceState(saved);
if(saved.getString("photoFile") != null)
photoFile = new File(saved.getString("photoFile"));
currentFragTag = saved.getString("currentFragTag");
picList = saved.getStringArrayList("picList");
descripList = saved.getStringArrayList("descripList");
currentFrag = getFragmentManager().findFragmentByTag(currentFragTag);
changeFrag(currentFrag, currentFragTag);
}
The problem is, onCreateView() is being called before onRestoreInstanceState() is being called in the activity. I tried using onActivityCreated() in the fragment instead, but that was also being called before onRestoreInstanceState(). With a debugger attached, when the screen is rotated, onRestoreInstanceState() is always called last. This means that the fragment does not have access to the activity's information when creating the view.
Is this supposed to happen? How can I have my fragment's view use information from the activity when the activity is being restored?
I think the most easiest way is using EventBus.
You can send a "msg" when your activity is recreated, and your fragment's "target method" will get this msg(the msg is Object, it can be a bundle).
Updated response:
Read the alternatives Passing data between a fragment and its container activity. Also see this.
Previous response revised:
See this and try to place your code in onResume() and invalidate the view or detach/attach the fragment as a quick solution but is not the best solution as Alex Lockwood said:
Fragments are re-usable UI components. They have their own lifecycle,
display their own view, and define their own behavior. You usually
don't need to have your Activity mess around with the internal
workings of a Fragment, as the Fragment's behavior should be
self-contained and independent of any particular Activity.
If you really need the code before, override the next methods and directly save/restore the required data in the fragment:
/**
* Called when the fragment's activity has been created and this
* fragment's view hierarchy instantiated. It can be used to do final
* initialization once these pieces are in place, such as retrieving
* views or restoring state. It is also useful for fragments that use
* {#link #setRetainInstance(boolean)} to retain their instance,
* as this callback tells the fragment when it is fully associated with
* the new activity instance. This is called after {#link #onCreateView}
* and before {#link #onViewStateRestored(Bundle)}.
*
* #param savedInstanceState If the fragment is being re-created from
* a previous saved state, this is the state.
*/
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (savedInstanceState != null) {
restoreInstanceState(savedInstanceState);
}
}
/**
* Called to ask the fragment to save its current dynamic state, so it
* can later be reconstructed in a new instance of its process is
* restarted. If a new instance of the fragment later needs to be
* created, the data you place in the Bundle here will be available
* in the Bundle given to {#link #onCreate(Bundle)},
* {#link #onCreateView(LayoutInflater, ViewGroup, Bundle)}, and
* {#link #onActivityCreated(Bundle)}.
*
* <p>This corresponds to {#link Activity#onSaveInstanceState(Bundle)
* Activity.onSaveInstanceState(Bundle)} and most of the discussion there
* applies here as well. Note however: <em>this method may be called
* at any time before {#link #onDestroy()}</em>. 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.
*
* #param outState Bundle in which to place your saved state.
*/
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.put...;
}
And create this one, used to retrieve the needed data from bundle:
public void restoreInstanceState(Bundle savedInstanceState) {
... = savedInstanceState.get...
}
or use getActivity() method to directly access to some method or field from here if you need the code on your activity for some reason.
/**
* Return the {#link FragmentActivity} this fragment is currently associated with.
* May return {#code null} if the fragment is associated with a {#link Context}
* instead.
*/
final public FragmentActivity getActivity() {
return mHost == null ? null : (FragmentActivity) mHost.getActivity();
}
For example: ((YourActivity) getActivity()).getPics();
And add the getPics() method to the activity.
Further information here and an alternative solution defining an interface here.
Related
I have fragments A, B, C which I add with add() method.
When I reach fragment C, at some point I want to go back to fragment A and remove B and C.
My approach:
val backStateName = FragmentA::class.java.name
activity.fragmentManager.popBackStackImmediate(backStateName, FragmentManager.POP_BACK_STACK_INCLUSIVE)
I also have a specialTag added to my fragment A, so I did a check to make sure, that before I try my approach, fragment A is still in back stack.
val fragmentToGoTo = activity.fragmentManager.findFragmentByTag(specialTag)
and it doesn't return null - which means fragment is still available in back stack. popBackStackImmediate returns false. Why?
I had the same behaviour. Make sure that you call popBackStackImmediate on the same Thread as you used to add it to your backstack.
Also verify that you use .add() instead of .replace()
Anyway, it's never guaranteed that the backstack is not cleared/destroyed while doing this. I solved this behaviour by just using popBackStack() until you reach the fragment which you want to have.
You may try something like:
fun popStack(tag: String) {
var isPopped = fragmentManager.popBackStackImmediate(tag, FragmentManager.POP_BACK_STACK_INCLUSIVE)
if (!isPopped) {
fragmentManager.popBackStack()
//maybe a loop until you reached your goal.
}
}
When you attach a fragment (or perform any other action s.a. add/remove/detach etc.), you have an option to add it to the backstack with a name String:
FragmentA fragmentA = (FragmentA) fragmentManager.findFragmentByTag("A");
FragmentTransaction transaction = fragmentManager.beginTransaction();
if (fragmentA != null) {
transaction.attach(fragmentA);
transaction.addToBackStack("attachA");
transaction.commit();
}
Notice the "attachA" String we passed to the addToBackStack() method. We'll later use it to go back. Assume we've performed other transactions - added/removed/attached/detached some other fragments. Now to get back to the state we were call one of the popBackStack() methods:
fragmentManager.popBackStack("attachA", FragmentManager.POP_BACK_STACK_INCLUSIVE);
If there was a transaction added to the back stack with the name "attachA" - the method will take us back to that state.
Regarding your question about the return case - you've probably read the documentation about these methods and what values they return. I prefer to use the popBackStack() since it
/**
* Pop the last fragment transition from the manager's fragment
* back stack. If there is nothing to pop, false is returned.
* This function is asynchronous -- it enqueues the
* request to pop, but the action will not be performed until the application
* returns to its event loop.
*
* #param name If non-null, this is the name of a previous back state
* to look for; if found, all states up to that state will be popped. The
* {#link #POP_BACK_STACK_INCLUSIVE} flag can be used to control whether
* the named state itself is popped. If null, only the top state is popped.
* #param flags Either 0 or {#link #POP_BACK_STACK_INCLUSIVE}.
*/
public abstract void popBackStack(String name, int flags);
/**
* Like {#link #popBackStack(String, int)}, but performs the operation immediately
* inside of the call. This is like calling {#link #executePendingTransactions()}
* afterwards.
* #return Returns true if there was something popped, else false.
*/
public abstract boolean popBackStackImmediate(String name, int flags);
I hold two Fragment instance in the activity ,add first fragment to activity , then replace second fragment to activity with setArgument() and addBackStack(), then press back button. now we return the first fragment , then we replace first to the second fragment which activity has hold once again , as the same with setArgument(), and it throws out a Exception ---- Fragment already active .
what's wrong with this process?
As per setArguments() source documentation, arguments supplied will be retained across fragment destroy and creation. So use getArguments() and then put bundle values to change the fields.
You can call it more than once or twice IF a Fragment is not attached to any Activity.
The code below is copied from Fragment.java
/**
* Supply the construction arguments for this fragment. This can only
* be called before the fragment has been attached to its activity; that
* is, you should call it immediately after constructing the fragment. The
* arguments supplied here will be retained across fragment destroy and
* creation.
*/
public void setArguments(Bundle args) {
if (mIndex >= 0) {
throw new IllegalStateException("Fragment already active");
}
mArguments = args;
}
You can call the method as long as you want IF not attached to the activity
I have an Android app, composed of Fragments, that I have saving state correctly. The problem is it works a bit too well ;-). I'll enter some input into a couple of EditText elements that I then save via SharedPrefs in the "onSaveInstanceState()" method, and then hit the "Task Manager" or "Switch App" button on the phone (there's two overlapping rectangles as the icon) and swipe left to close my application. If I then go to the App Drawer and re-run the application, that saved input will still be there. I am clearing the saved instance state in the "onDestroy()" method too, but apparently that is not being called when "closing" the app from that Task Manager (confirmed via logging).
Any suggestions here? Other apps I have do not exhibit this behavior. I'd like for the saved input to be cleared when a user closes the app via Task Manager as well as probably after a set amount of time. Any ideas of what the standard practice for state handling is here?
I tested some apps I have and noticed the default Contacts app actually saves a new contact if you start a new one and switch to another app before explicitly saving. I guess I could do this but I'd rather not.
Below is some relevant code for a particular Fragment; thank you very much in advance for any assistance.
#Override
public void onSaveInstanceState(Bundle savedInstanceState) {
super.onSaveInstanceState(savedInstanceState);
Log.v(Tag, "onSaveInstanceState()");
saveInstanceState();
}
#Override
public void onResume() {
super.onResume();
Log.v(Tag, "onResume()");
restoreInstanceState();
}
#Override
public void onDestroy() {
super.onDestroy();
Log.v(Tag, "onDestroy()");
clearInstanceState();
}
private void saveInstanceState() {
Log.v(Tag, "saveInstanceState()");
// get entered data
String name = mTxtName.getText().toString();
String notes = mTxtNotes.getText().toString();
// save data in Shared Prefs
PreferenceManager.getDefaultSharedPreferences(mContext)
.edit()
.putInt(KeyAmmunitionId, mAmmunitionId)
.putString(KeyName, name)
.putString(KeyNotes, notes)
.putString(StringUtils.CurrentFragmentKey, Tag)
.commit();
}
private void restoreInstanceState() {
Log.v(Tag, "restoreInstanceState()");
mTxtName = (EditText)getActivity().findViewById(R.id.frag_manage_ammunition_txtName);
mTxtNotes = (EditText)getActivity().findViewById(R.id.frag_manage_ammunition_txtNotes);
if (PreferenceManager.getDefaultSharedPreferences(mContext).contains(KeyName)) {
String ammunitionName = PreferenceManager.getDefaultSharedPreferences(mContext).getString(KeyName, StringUtils.EMPTY_STRING);
mTxtName.setText(ammunitionName);
}
if (PreferenceManager.getDefaultSharedPreferences(mContext).contains(KeyNotes)) {
String ammunitionNotes = PreferenceManager.getDefaultSharedPreferences(mContext).getString(KeyNotes, StringUtils.EMPTY_STRING);
mTxtNotes.setText(ammunitionNotes);
}
}
private void clearInstanceState() {
Log.v(Tag, "clearInstanceState()");
PreferenceManager.getDefaultSharedPreferences(mContext)
.edit()
.remove(KeyAmmunitionId)
.remove(KeyName)
.remove(KeyNotes)
.commit();
}
instead of using SharedPrefs, I've found that an internal headless fragment that retains its state is much easier to implement/cleaner.
Inside your fragment class you have this class....
/**
* "Headless" Fragment that retains state information between
* configuration changes.
*/
public class RetainedFragment extends Fragment {
/**
* internal storage to be kept put here
*/
// assuming the only 'view' in the parent fragment is this editText with some string value.
String editTextValue;
public RetainedFragment(){
editTextValue = ""; //init values on first time in.
}
/**
* Hook method called when a new instance of Fragment is
* created.
*
* #param savedInstanceState
* object that contains saved state information.
*/
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Ensure the data survives runtime configuration changes.
setRetainInstance(true);
}
// have getter/setter methods
Then in your outer fagment just 'create' the UI based off the values stored in the inner headless fragment.
OR...
You can use the RetainedFragmentManager that we figured out. Its a bit wonky and can be a bit confusing to figure out at first, but its a headless fragment that allows you to store java objects in hashmap-like manner, and they will persist across configuration changes, but won't exist if your app is fully closed, etc.
https://github.com/douglascraigschmidt/POSA-15/blob/master/ex/ImageDownloads/src/vandy/mooc/common/RetainedFragmentManager.java
I am currently doing a tutorial series on eclipse android development and am trying to copy the code to help memorize the functionality of everything but I am getting an error for onSaveInstanceState(Bundle), it says "The method onSaveInstanceState(Bundle) is undefined for the type Object". I have checked to see that everything is exact multiple times but have found nothing wrong.
protected void onSaveInstanceState(Bundle outState)
{
super.onSaveInstanceState(outState);
outState.putDouble(TOTAL_BILL, finalBill);
outState.putDouble(CURRENT_TIP, tipAmount);
outState.putDouble(BILL_WITHOUT_TIP, finalBill);
}
You'll want to use #Override and ensure that your class extendsActivity. As long as you have those two things you should be good.
class MyActivity extends Activity {
#Override // May be public depending on the class you are extending
protected void onSaveInstanceState(Bundle outState) {
outState.putDouble(TOTAL_BILL, finalBill);
outState.putDouble(CURRENT_TIP, tipAmount);
outState.putDouble(BILL_WITHOUT_TIP, finalBill);
// Wait till after you've added your items to pass the bundle
super.onSaveInstanceState(outState);
}
}
According to the Activity docs page other classes that extend Activity are
AccountAuthenticatorActivity, ActivityGroup, AliasActivity, ExpandableListActivity, FragmentActivity, ListActivity, NativeActivity, ActionBarActivity, LauncherActivity, PreferenceActivity and TabActivity.
The Android sources are also a great place to look if you're trying to figure out how something works. A lot of times the notes there are a lot more descriptive about how things are implemented and what they rely on.
Ref: core/java/android/app/Activity.java
/**
* Called to retrieve per-instance state from an activity before being killed
* so that the state can be restored in {#link #onCreate} or
* {#link #onRestoreInstanceState} (the {#link Bundle} populated by this method
* will be passed to both).
*
* <p>This method is called before an activity may be killed so that when it
* comes back some time in the future it can restore its state. For example,
* if activity B is launched in front of activity A, and at some point activity
* A is killed to reclaim resources, activity A will have a chance to save the
* current state of its user interface via this method so that when the user
* returns to activity A, the state of the user interface can be restored
* via {#link #onCreate} or {#link #onRestoreInstanceState}.
*
* <p>Do not confuse this method with activity lifecycle callbacks such as
* {#link #onPause}, which is always called when an activity is being placed
* in the background or on its way to destruction, or {#link #onStop} which
* is called before destruction. One example of when {#link #onPause} and
* {#link #onStop} is called and not this method is when a user navigates back
* from activity B to activity A: there is no need to call {#link #onSaveInstanceState}
* on B because that particular instance will never be restored, so the
* system avoids calling it. An example when {#link #onPause} is called and
* not {#link #onSaveInstanceState} is when activity B is launched in front of activity A:
* the system may avoid calling {#link #onSaveInstanceState} on activity A if it isn't
* killed during the lifetime of B since the state of the user interface of
* A will stay intact.
*
* <p>The default implementation takes care of most of the UI per-instance
* state for you by calling {#link android.view.View#onSaveInstanceState()} on each
* view in the hierarchy that has an id, and by saving the id of the currently
* focused view (all of which is restored by the default implementation of
* {#link #onRestoreInstanceState}). If you override this method to save additional
* information not captured by each individual view, you will likely want to
* call through to the default implementation, otherwise be prepared to save
* all of the state of each view yourself.
*
* <p>If called, this method will occur before {#link #onStop}. There are
* no guarantees about whether it will occur before or after {#link #onPause}.
*
* #param outState Bundle in which to place your saved state.
*
* #see #onCreate
* #see #onRestoreInstanceState
* #see #onPause
*/
protected void onSaveInstanceState(Bundle outState) {
outState.putBundle(WINDOW_HIERARCHY_TAG, mWindow.saveHierarchyState());
Parcelable p = mFragments.saveAllState();
if (p != null) {
outState.putParcelable(FRAGMENTS_TAG, p);
}
getApplication().dispatchActivitySaveInstanceState(this, outState);
}
Put the onSaveInstanceState(Bundle outState) method in the right location out of the textWatcher method.
Just put right after the semicolon, which marks end of the textWatcher method.
[};]
protected void onSaveInstanceState(Bundle outState){
super.onSaveInstanceState(outState);
outState.putDouble(Total_Bill, finalBill);
outState.putDouble(Tip_Amount, tipamount);
outState.putDouble(Bill_Without_Tip, Billbeforetip);
}
The error should go away.
I'm really new to android and I've created an app that has buttons which lead to other activities where data is selected to be sent back to the main activity by an intent with extras.
When I'm done with the data gathering activity I call the finish() method to return back to my main activity.
The user may wish to revisit the information gathering activity to enter new data but it doesn't matter if the previously entered data is not there when they return to the activity.
is this considered to be good or bad practice when writing an app?
Thanks,
M
Depends on your application.
If your application need a permament data like a options activity or a date enter activity you must to "save" the changes of your activity.
There are a a easy form to do that with the Overryde methods:
/**
* Method onSaveInstanceState.
* #param outState Bundle
*/
#Override
protected void onSaveInstanceState(Bundle outState) {
outState.putBoolean("ISCALLSMS", isOnCallingSms);
super.onSaveInstanceState(outState);
}
/**
* Method onRestoreInstanceState.
* #param savedInstanceState Bundle
*/
#Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
if ( (savedInstanceState != null) && (savedInstanceState.containsKey("ISCALLSMS")) ){
isOnCallingSms = savedInstanceState.getBoolean("ISCALLSMS");
}
super.onRestoreInstanceState(savedInstanceState);
}
*This are a example of my app. Yoy should decide what data you need to save. There are other methods to save your data, this is only one of them.
In other case, if your app dont need to save data its ok how you finish your activity