My Activity has a one-pane layout in portrait and a two-pane layout in landscape, using Fragments. I'd like the system to restore all the views after an orientation change, but I don't know where to start.
Is this even possible considering the layout is different (but similar) in the two orientations?
If anyone has implemented this (or at least tried), please share you experience.
Maybe I know what the problem is. Hope my answer helps someone want to solve such problem.
In case there are a Fragment in your Activity, you config that Activity in AndroidManifest.xml with
android:configChanges="keyboardHidden|orientation|screenSize"
then, you want to change the layout of the Activity when onConfigurationChanged triggered. Once the Activity inflate a new layout or using setContentView(R.layout.new_layout);, the fragment will disappear, however, that fragment still running and just disappear from the view.
So the problem is that fragment is attached to the previous layout, what you need to do:
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction transaction = fm.beginTransaction();
if (fm.findFragmentById(R.id.pane_list) == null) {
// instantiate fragment and add to view
mFragment = new ItemFragment();
transaction.add(R.id.pane_list, mFragment );
} else {
// fragment already exists, we re-attahced it
mFragment = fm.findFragmentById(R.id.pane_list);
transaction.detach(mFragment);
transaction.attach(mFragment);
}
transaction.commit();
After detached from old layout and attached to new layout, problem solved.
Correct me if there any wrong :)
I urge you to use a Fragment with setRetainInstance(true) in its onCreate method. This makes the fragment retain its member variables across Activity configuration changes. Please read this blog post which explains the information you need:
http://www.androiddesignpatterns.com/2013/04/retaining-objects-across-config-changes.html
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt("curChoice", mCurCheckPosition);
}
Which you can use later like this:
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (savedInstanceState != null) {
// Restore last state for checked position.
mCurCheckPosition = savedInstanceState.getInt("curChoice", 0);
}
}
Related
I am working on an application and there is one specific thing that is bothering me. Let's just say I have one activity and 2 fragments.FragmentA and FragmentB and FragmentA gets attached when activity starts.
I want to save the fragment data and fragment state when orientation changes occur.I have successfully saved fragment data using OnSavedInstanceState method. Now I want to save fragment state in the activity so that if orientation change occurs I want to be on the fragment I was (in my case either FragmentA or FragmentB depends on which was showing before config changes occur).
This is how I am saving the fragment state in the Activity:
#Override
protected void onSaveInstanceState(Bundle outState) {
// Save the values you need into "outState"
super.onSaveInstanceState(outState);
outState.putLong(SS_DATE, userDate.getTime());
android.support.v4.app.FragmentManager manager = getSupportFragmentManager();
Fragment currentFragment = this.getSupportFragmentManager().findFragmentById(R.id.content_container);
manager.putFragment(outState, "currentFragment", currentFragment);
}
And this is how I am retrieving on which fragment I was when the orientation change occurred:
#Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
FragmentManager manager = getSupportFragmentManager();
#SuppressLint("CommitTransaction")
FragmentTransaction transaction = manager.beginTransaction();
if (savedInstanceState != null) {
Fragment MyFragment = (Fragment) manager.getFragment(savedInstanceState, "currentFragment");
if (MyFragment instanceof FragListStudentsAttendance) {
Log.v("onRestore", FragListStudentsAttendance.TAG);
}else if (MyFragment instanceof FragGetClassesForAttendance){
Log.v("onRestore", FragGetClassesForAttendance.TAG);
if(MyFragment!=null) {
mFragGetClassesForAttendance = (FragGetClassesForAttendance) MyFragment;
}else{
mFragGetClassesForAttendance = new FragGetClassesForAttendance();
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
// mFragGetClassesForAttendanceNew.setRetainInstance(true);
// transaction.replace(R.id.content_ssadmin_container, mFragGetClassesForAttendanceNew, "FragGetClassesForAttendance").addToBackStack(null);
transaction.add(R.id.content_ssadmin_container, mFragGetClassesForAttendance, FragGetClassesForAttendance.TAG);
//transaction.replace(R.id.newEnrollmentMainContainer, mFragNewEnrollmentResults).addToBackStack("FragNewEnrollments");
transaction.commit();
mFragGetClassesForAttendance.setDate(userDate);
}
}
}
}
Now
Scenario 1:
If I am on fragment A and I rotate the device every thing works fine as it should. Like fragment have web services which loads the data into listview so I check if data exist then there is no need to run the web service and that working for now
Scenario 2:
If I am on fragment B and orientation change occurs everything works fine as it is supposed to be on fragment B. Now When I press back button Fragment A gets called again and all the data also comes from service. I think this shouldn't happen because it was supposed to be in BackStack and it's data was saved. So what Should I do now here?
Scenario 3: On FragmentB I have noticed that when I rotates the device the saveInstanceState function of FragmentA also gets called. Why it is so? where as I was replacing the FragmentB with FragmentA ?
Some Confusions:
Let me talk about some of the confusions also , maybe someone clear it to me although I have searched and read a lot about fragment and activity life cycle,
Actually I want to save the data per activity and fragment on device rotation. I know how to do it with activity(how to save states) so I also know how to do it in the fragment (save state of fragment views) now I am confused how to tell activity which fragment was showing and which to go after config changes(rotation) ? also what happens to FragmentA if I am on FragmentB Does its get attach and detach again and again in background?
I got your problems and confusions. I think the life cycle of fragment is confusing you. and indeed it will confuse you.
You need to learn different situations.
1. Fragment Life cycle when it is in foreground (attaching and detaching with activity) . Please keenly observe all the methods that will call i.e OnSaveInstance,onCreateView,OnDestroyView,onDestroy
2. Fragment life cycle when it is in background (observe the methods stated above)
3. Fragment life cycle when it is added to backstack (and not in foreground)
I am quite sure you are confused with the point number 3. As when the fragment is added to backstack it never gets destroy. So rotating device twice will set the ffragment data to null. I think you are restoring data on ActivityCreated or on onViewCreated ,
Ill suggest you to restore the fragment data in the oncreate. this will work for you when your fragment is coming back to foreground from the backstack .
Example
private List<String> mCountries;</pre>
#Override
public void onCreate(Bundle savedInstanceState)
{
if (savedInstanceState != null)
{
// Populate countries from bundle
}
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
View view = inflater.inflate(R.layout.fragment_countries, container, false);
if (mCountries == null)
{
// Populate countries by calling AsyncTask
}
return view;
}
public void onSaveInstanceState(Bundle outState)
{
// Save countries into bundle
}
Hope this will clear your confusions.
I have an Activity with a FrameLayout and need to show different fragments based on user input.
The code I use for showing a fragment is this:
private void showFragment(Fragment fragment, Bundle args, boolean addToBackStack) {
if (args != null) {
fragment.setArguments(args);
}
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.setCustomAnimations(R.anim.activity_open_translate, R.anim.activity_close_scale);
fragmentTransaction.replace(R.id.main_frame, fragment);
if (addToBackStack) {
fragmentTransaction.addToBackStack(fragment.getClass().getName());
}
fragmentTransaction.commit();
}
This is called as :
if (contactPickFragment == null) {
contactPickFragment = new ContactPickFragment();
}
showFragment(contactPickFragment, args, true);
All this works fine. Now if the user goes into one fragment presses back and returns back to the same fragment, all my views inside stay the same. For example, I have an EditText inside the fragment and the user edits something inside. If the user comes back to the fragment, the same text persists. I do not want this to happen. How do I reset everything in the view?
I have added code within the Fragment's onCreateView() to clear the text, and from debugging I see that this is being called, but the text never gets cleared. What am I doing wrong here?
If you don't want the data from the previous instance to appear, simply create a new instance of ContactPickFragment each time you show it.
Clearing data in onCreateView() has no effect because view state is restored AFTER onCreateView(). Your Fragment has no view before onCreateView() and so Android cannot possibly apply the previous state any earlier. Values set on the views during onCreateView() will be overwritten by their previous values.
As a general answer, there is no way to "refresh" the view of a Fragment, other than replacing the fragment with another instance of itself (possibly initialized with the parameters that you want to refresh/update).
You can reuse your fragments and refresh the state of your views. You just can't do it from onCreateView as #antonyt correctly points out.
Instead, override onViewStateRestored and set up the state of your views the way you'd like from there.
Something like:
#Override
public void onViewStateRestored(Bundle savedInstanceState) {
super.onViewStateRestored(savedInstanceState);
View view = getView();
// Code to call view.findViewById to grab the views you want
// and set them to a specific state goes here
}
There are advantages to reusing fragments. Not the least of which is that if you have a memory leak with your fragment (which is easier than you may think to accomplish,) you will exacerbate the problem by creating myriads of them.
Starting Android 4.2, Android supports nested Fragments. The doc doesn't give a lot of explanations regarding nested Fragment lifecycles but from experience, it appears their lifecycle is really similar to "regular" Fragments.
It looks like there is one big difference though: child Fragments are not restored when the parent Fragment onCreate method is called. As a consequence, it seems impossible to save/restore a reference to a particular Fragment:
Using getChildFragmentManager.findFragmentByTag(String) always returns null in parent Fragment onCreate(Bundle) because mActive is null.
Using putFragment/getFragment results in a NullPointerException because getFragment looks for the size of a null mActive ArrayList.
So, my question is quite simple. Is there a correct way to retrieve a reference to a child Fragment in the parent Fragment onCreate method?
I don't think you can in onCreate as the view isn't constructed at that time. You can in onViewCreated() though. The logic I used is:
Check if there is saved state in onViewCreated(), if there is, try to get the child fragment
Then check if the child fragment is null, if it is, add it using the child fragment manager.
By "checking" I mean looking up the fragment by id. I guess by tag should work too.
AFAIK you can't get a child fragment before the view hierarchy is restored or created, but you could do the same at later time, for example in onActivityCreated()
What about setRetainInstanceState(true) on your fragment?
Could it solve your problem? It solved some problems when I have ChildFragments in a Fragment. I only have to keep a reference to the childfragment in the fragment.
But I allways did that in onCreateView(). Not sure if it will work in onCreate()
Or do you mean something completely different?
are u using FragmentPagerAdapter?
if not try FragmentPagerAdapter instead of FragmentStatePagerAdapter
I realised that have some bug when using FragmentStatePagerAdapter when i have 4 level nest.
Sorry my english is poor.
#Override
public void onCreate(Bundle savedInstanceState) {
if (savedInstanceState != null) {
mFragment1 = getFragmentManager().getFragment(savedInstanceState, STATE_Fragment1);
mFragment2 = getFragmentManager().getFragment(savedInstanceState, STATE_Fragment2);
mFragment3 = getFragmentManager().getFragment(savedInstanceState, STATE_Fragment3);
} else {
mFragment1 = SomeFragment.newInstance("param1");
mFragment2 = SomeFragment.newInstance("param2");
mFragment3 = SomeFragment.newInstance("param3");
}
super.onCreate(savedInstanceState);
mMyPagerAdapter = new MyPagerAdapter(getChildFragmentManager(), mFragment1, mFragment2, mFragment3);
}
#Override
public void onSaveInstanceState(Bundle outState) {
if (mFragment1 != null) {
getFragmentManager().putFragment(outState, STATE_Fragment1,
mFragment1);
}
if (mFragment2 != null) {
getFragmentManager().putFragment(outState, STATE_Fragment2,
mFragment2);
}
if (mFragment3 != null) {
getFragmentManager().putFragment(outState, STATE_Fragment3,
mFragment3);
}
super.onSaveInstanceState(outState);
}
I am building a one activity-multiple fragments application. I add to the backstack after every transaction. After a couple of hiding and showing fragments and then I rotate the phone, all the fragments added on the container were restored and every fragment is on top of the other.
What can be the problem? Why is my activity showing the fragments I have previously hidden?
I am thinking of hiding all the previously-hidden-now-shown fragments but is there a more 'graceful' way of doing this?
Use setRetainInstance(true) on each fragment and your problem will disappear.
Warning: setting this to true will change the Fragments life-cycle.
While setRetainInstance(true) resolves the issue, there may be cases where you don't want to use it.
To fix that, setup a boolean attribute on the Fragment and restore the visibility:
private boolean mVisible = true;
#Override
public void onCreate(Bundle _savedInstanceState) {
super.onCreate(_savedInstanceState);
if (_savedInstanceState!=null) {
mVisible = _savedInstanceState.getBoolean("mVisible");
}
if (!mVisible) {
getFragmentManager().beginTransaction().hide(this).commit();
}
// Hey! no setRetainInstance(true) used here.
}
#Override
public void onHiddenChanged(boolean _hidden) {
super.onHiddenChanged(_hidden);
mVisible = !_hidden;
}
#Override
public void onSaveInstanceState(Bundle _outState) {
super.onSaveInstanceState(_outState);
if (_outState!=null) {
_outState.putBoolean("mVisible", mVisible);
}
}
Once the configuration changes (e.g. screen orientation), the instance will be destroyed, but the Bundle will be stored and injected to the new Fragment instance.
I had the same problem. you should check source code in the function onCreateView() of your activity.
public class MainActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
setContentView(R.layout.activity_main);
if(savedInstanceState == null){//for the first time
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
FragmentExample fragment = new FragmentExample();
fragmentTransaction.add(R.id.layout_main, fragment);
fragmentTransaction.commit();
}else{//savedInstanceState != null
//for configuration change or Activity UI is destroyed by OS to get memory
//no need to add Fragment to container view R.id.layout_main again
//because FragmentManager supported add the existed Fragment to R.id.layout_main if R.id.layout_main is existed.
//here is one different between Fragment and View
}
}
activity_main.xml:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/layout_main">
You might want to try to use the replace() function rather than hide and show. I had the same problem when I started using Fragments and using the replace function really helped manage the Fragments better. Here is a quick example:
fragmentManager.replace(R.id.fragmentContainer, desiredFragment, DESIRED_FRAGMENT_TAG)
.addToBackStack(null)
.commit();
Can I use savedInstanceState() to save the state when removing a fragment, then restore the state when I pop the fragment off the back stack? When I restore the fragment from the back stack, savedInstanceState bundle is always null.
Right now, the app flow is: fragment created -> fragment removed (added to back stack) -> fragment restored from back stack (savedInstanceState bundle is null).
Here is the relevant code:
public void onActivityCreated(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Bundle bundle = getArguments();
Long playlistId = bundle.getLong(Constants.PLAYLIST_ID);
int playlistItemId = bundle.getInt(Constants.PLAYLISTITEM_ID);
if (savedInstanceState == null) {
selectedVideoNumber = playlistItemId;
} else {
selectedVideoNumber = savedInstanceState.getInt("SELECTED_VIDEO");
}
}
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt(Constants.SELECTED_VIDEO, selectedVideoNumber);
}
I think the problem is that onSavedInstanceState() is never called when being removed and being added to back stack. If I cant use onsavedInstanceState(), is there another way to fix this?
onSaveInstanceState is (unfortunately) not called in normal back-stack re-creation of a fragment. Check out http://developer.android.com/guide/components/fragments.html#Creating and the answer on How can I maintain fragment state when added to the back stack?
I like to store the View I return in onCreateView as a global variable and then when I return I simply check this:
if(mBaseView != null) {
// Remove the view from the parent
((ViewGroup)mBaseView.getParent()).removeView(mBaseView);
// Return it
return mBaseView;
}
The problem is that the fragment needs to have an Id or Tag associated with it in order for the FragmentManager to keep track of it.
There are at least 3 ways to do this:
In xml layout declare an Id for your fragment:
android:id=#+id/<Id>
If your fragments container View has an Id, use FragmentTransaction:
FragmentTransaction add (int containerViewId, Fragment fragment)
If your fragment is not associated with any View (e.g. headless fragment), give it a Tag:
FragmentTransaction add (Fragment fragment, String tag)
Also, see this SO answer.
FWIW, I hit this as well, but in my case onSaveInstanceState was called properly and I pushed in my state data when a new activity fragment was brought up on the smartphone. Same as you, the onActivityCreated was called w/ savedInstanceState always null. IMHO, I think it's a bug.
I worked around it by creating a static MyApplication state and putting the data there for the equivalent of "global variables"...