Trouble with saving fragments state after orientation is changed - android

I have a trouble with saving state of current fragment after changing orientation.
I've got fragments with gridview that i replace in navigation process on fragments.
Actions after starting app.
1) Starting "MainActivity".
2) Adding "CenterPanelFragment" (this is a container for inner fragment).
3) Replacing in "CenterPanelFragment" fragment "FragmentCatalog"(GridView). Picture 1.
4) If i click on gridview item of "FragmentCatalog", it'll replece for fragment "FragmentCatalogStone"(Gridview). Picture 2.
5) After that i change orientation on a device,as you can see i've got the fragment "FragmentCatalog" in Landscape orientation instead of fragment "FragmentCatalogStone" in Landscape orientation. Picture 3
What things i do wrong?
I attached necessary class files.
Sorry for my poor english.
Thanks a lot!
MainActivity.java
CenterPanelFragment.java
FragmentCatalog.java
FragmentCatalogStone.java

I found a way to resolve the problem.
I simply added android:configChanges="orientation|screenSize" in manifest for my Activity, and it works!

if (savedInstanceState != null)
{
fragmentCatalog = (FragmentCatalog)getFragmentManager().getFragment(savedInstanceState, FragmentCatalog.class.getName());
}
else
{
fragmentCatalog = new FragmentCatalog();
}

I might not fully have understood your code but I suspect this bit in your MainActivity's onCreate:
frAdapter = new FragmentAdapter(getSupportFragmentManager());
vPager = (ViewPager)findViewById(R.id.pager);
vPager.setAdapter(frAdapter);
vPager.setCurrentItem(1);
vPager.setOnPageChangeListener(this);
vPager.setOffscreenPageLimit(3);
Think you should save currentItem in onSaveInstance and retrive it:
vPager.setCurrentItem(value_before_orientation_change);
Not 100% sure if the vPager actually overwrites your fragment as I suspect though.
Edit:
Even more likely, in your CenterPanelFragment.java in onActivityCreated you commit the catalog:
fragmentTransaction.replace(R.id.fragment_container, fragmentCatalog);
fragmentTransaction.addToBackStack("FragmentCatalog");
fragmentTransaction.commit();
Maybe something as simple as setRetainInstance(true) in onCreate at CenterPanelFragment could resolve this problem.
I am pretty confident that the orientation change relaunches your CenterPanelFragment, causing a new call to onActivityCreated.
Think you could try in your FragmentCatalog:
And change this line:
fragmentTransaction.replace(R.id.fragment_container, fcAllStone);
To:
fragmentTransaction.replace(R.id.fragment_container, fcAllStone, "fragment_stone");
Now in CentralPanelFragment:
if (savedInstanceState != null)
{
Log.d(MainActivity.tag, "CenterPanelFragment not null");
fragmentCatalog = (FragmentCatalog)getFragmentManager().getFragment(savedInstanceState, FragmentCatalog.class.getName());
// see if stone is open
if (savedInstanceState != null) {
FragmentCatalogStone fragmentStone = (FragmentCatalogStone) getFragmentManager()
.findFragmentByTag("fragment_stone");
if (FragmentCatalogStone != null) {
/*
* maybe recommit it, dont actually think anything is needed
* since moving the below code inside the else statement
* prevents it from overwriting the FragmentCatalogStone
*/
}
}
else
{
Log.d(MainActivity.tag, "CenterPanelFragment null");
fragmentCatalog = new FragmentCatalog();
android.support.v4.app.FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
fragmentTransaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
fragmentTransaction.replace(R.id.fragment_container, fragmentCatalog);
fragmentTransaction.addToBackStack("FragmentCatalog");
fragmentTransaction.commit();
}

Related

How to prevent displaying multiple fragments over each other

I have a multiple-dialog-fragment layout. In the large layout, I show them as dialogs and there's no problem:
fragment.show(fragmentManager, "fragment_dialog");
But in normal devices, I am using a fragment transaction and replace fragments as below:
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
transaction.replace(R.id.fragment_container, fragment).addToBackStack(null).commit();
The problem is that in normal devices, when I press the menu button twice (or more), the same fragment will be shown over the previous one. Is there a way to find out which fragment is visible right now and prevent it from opening again?
First of all, add tag to when you replace
transaction.replace(R.id.fragment_container, fragment, "fragment_foo")
.commit(); // etc
then make a control
if (fragmentManager.findFragmentByTag("fragment_foo") == null){
// do something
}
You can find fragment by tag:
if (fragmentManager.findFragmentByTag("fragment_dialog") == null) {
//show dialog here
}
// Replace fragmentCotainer with your container id
Fragment currentFragment = fragmentManager.findFragmentById(R.id.fragmentCotainer);
// Return if the class are the same
if(currentFragment.getClass().equals(fragment.getClass())) return;

Android how to compare fragments?

I have been using jfeinstein's SlidingMenu. I am currently trying to find if a certain fragment is visible to the user. I first tried:
if(mainfrag.isVisible()){
Log.d("Frag","Main is visible");
}else{
Log.d("Frag","Main is NOT visible");
}
Which always printed that the fragment was NOT visible. I then tried:
android.support.v4.app.FragmentManager fragmentManager = getActivity().getSupportFragmentManager();
Log.d("Frag","CurFrag: "+fragmentManager.findFragmentById(R.id.content_frame).toString());
MainFragment mf = new MainFragment();
if(fragmentManager.findFragmentById(R.id.content_frame) == mf){
Log.d("Frag","This is Main");
}else{
Log.d("Frag","This is NOT Main :(");
}
This prints
So I know that the findFragmentById will tell me the current fragment but I don't know how I can logically compare it so I can do things only if it is visible.
I have never dived into the details of SlidingMenu and couldn't tell you what's wrong in the first problem.
But in your second problem, you are comparing two different objects.
MainFragment mf = new MainFragment();
fragmentManager.findFragmentById(R.id.content_frame) == mf
Here you create a new MainFragment, and try to compare it with a old instance. It can never be true. When comparing Objects, the address are compared. It will only return true if they are the same objects.
If you just want to check the class of object, use the following code:
Fragment f = fragmentManager.findFragmentById(R.id.content_frame);
if(f instanceof MainFragment)
// code here.
Get the fragment by tag or by I'd
Get the fragment's view
Get window visibility on the view will provide visibility

Android Fragment - move from one View to another?

Can i first add a Fragment to a View, then "detach" it, and then "re-attach" it to another View?
In code, i want to:
fragOne one = new fragOne();
getSupportFragmentManager().beginTransaction()
.add(R.id.left, one, "tag").commit();
getSupportFragmentManager().beginTransaction()
.detach(one).commit(); // or .remove(), or .addToBackStack(null).remove()
getSupportFragmentManager().executePendingTransactions();
getSupportFragmentManager().beginTransaction()
.add(R.id.right, one).commit();
But it throws error:
04-05 13:28:03.492: E/AndroidRuntime(7195): java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.trybackstack/com.example.trybackstack.MainActivity}: java.lang.IllegalStateException: Can't change container ID of fragment fragOne{40523130 #0 id=0x7f080000 tag}: was 2131230720 now 2131230721
Thanks for help!
I had the same problem but I found that what I really needed was to reparent the fragment's view and not the fragment itself, thus avoiding any fragmentManager transaction.
View vv = fragment.getView();
ViewGroup parent = (ViewGroup)vv.getParent();
parent.removeView(vv);
newparent.addView(vv, layoutParams);
after trying all the answers from similar questions, looks like i've found a way to do the trick.
First issue - you really have to commit and execute remove transaction before trying to add fragment to another container. Thanks for that goes to nave's answer
But this doesn't work every time. The second issue is a back stack. It somehow blocks the transaction.
So the complete code, that works for me looks like:
manager.popBackStackImmediate(null, manager.POP_BACK_STACK_INCLUSIVE);
manager.beginTransaction().remove(detailFragment).commit();
manager.executePendingTransactions();
manager.beginTransaction()
.replace(R.id.content, masterFragment, masterTag)
.add(R.id.detail, detailFragment, activeTag)
.commit();
I guess you would have this figured out by now, but i dont't see any satisfactory answer.
So, I'm posting this for others who may refer to this in the future.
If you want to move a fragment from one view to another you do the following:
android.app.FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.remove(fragment1);
fragmentTransaction.commit();
fragmentManager.executePendingTransactions();
fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.add(R.id.containerToMoveTo, fragment1);
fragmentTransaction.commit();
This way you do not have to duplicate the fragment.
Please check the solution,you need to create the new instance of the same fragment and instantiate it with state of the old fragment if you want to save the state of the old fragment.
FragmentTransaction ft = mFragmentManager.beginTransaction();
ft.remove(one);
Fragment newInstance = fetchOldState(one);
ft.add(R.id.right, newInstance);
ft.commit();
//TO fetch the old state
private Fragment fetchOldState(Fragment f)
{
try {
Fragment.SavedState oldState= mFragmentManager.saveFragmentInstanceState(f);
Fragment newInstance = f.getClass().newInstance();
newInstance.setInitialSavedState(oldState);
return newInstance;
}
catch (Exception e) // InstantiationException, IllegalAccessException
{
}
}
I have run into that problem as well. Sometimes moving fragments works, sometimes you have to create a new instance. It seems that moving fragments does not work, if the fragment keeps being in the "isRemoving" state.
Being removed also seems to be prevented by having a fragment in the backstack.
Adding to the Yan. Yurkin's answer. Make sure not to have any transitions applied to the transaction as these seem to delay the fragment from being detached.

Restoring Fragment state after configuration change if Activity layout changes

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);
}
}

Android Fragment No View Found for id

I'm trying to create a dual pane/ single pane setup for an application. It was working fine a while ago before I put all the inards into the fragments, and I don't know what caused it not to work. The problem is happening when I'm doing an orientation change. At first, it was not recreating the view for some reason. It would call onCreateView, but I was not able to get a handle on the listview inside of the view and was getting null (I know I have the layouts correct for landscape and portrait, as it works in both portrait and landscape if it starts there). So, what I did is a add setRetainInstance to true, thinking that would fix my problem. Well, that brought up a whole other issue of now I'm getting No View Found For Id.
What I think is happening now is that its trying to reattach itself to the ID it had before the orientation change, and it doesn't exist as were on a different layout now. I tried just creating a new fragment and adding it, but that doesn't work either. I'm borderline pulling my hair out trying to work with Android's have thought fragment system that almost impossible to work with. Any help would be appreciated. Here is the relevant code
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.setContentView(R.layout.study_guide);
//Create The Ad Service
//Check to see if the view is landscape or portrait
if (this.findViewById(R.id.singlePaneStudyGuide) != null) {
mDualPane = false;
} else if (this.findViewById(R.id.doublePaneStudyGuide) != null) {
mDualPane = true;
}
LinearLayout layout = (LinearLayout)findViewById(R.id.addbox);
adView = new AdView(this,AdSize.SMART_BANNER,"a1511f0352b42bb");
layout.addView(adView);
AdRequest r = new AdRequest();
r.setTesting(true);
adView.loadAd(r);
//Inflate Fragments depending on which is selected
//if (savedInstanceState == null) {
FragmentManager fm = this.getSupportFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
SGListFragment newFrag = new SGListFragment();
if (mDualPane == true) {
ft.add(R.id.sgScrollContainer, newFrag, "SgListView").commit();
} else {
ft.add(R.id.singlePaneStudyGuide, newFrag, "SgListView").commit();
}
fm.executePendingTransactions();
//}
}
Ive tried looking for the fragment with the fragment manager and reassigning it to a different layout but for some reason its still looking for the old one.
You have to rewrite your code as follow:
//Inflate Fragments depending on which is selected
//if (savedInstanceState == null) {
FragmentManager fm = this.getSupportFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
// here the trick starts
int oldId = (mDualPane == true) ? R.id.singlePaneStudyGuide
: R.id.sgScrollContainer;
Fragment oldFragment = fm.findFragmentById(oldId);
if(null != oldFragment){
ft.remove(oldFragment);
ft.commit();
fm.executePendingTransactions();
ft = fragmentManager.beginTransaction();
}
// end of tricks
SGListFragment newFrag = new SGListFragment();
if (mDualPane == true) {
ft.add(R.id.sgScrollContainer, newFrag, "SgListView").commit();
} else {
ft.add(R.id.singlePaneStudyGuide, newFrag, "SgListView").commit();
}

Categories

Resources