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();
}
Related
I already owe a lot of credit to a user of SO for getting me this far, but after 6+ hours of developer page reading I still cannot get this to work on an Android emulator.
I am simply trying to swap one fragment for another. What happens when the button is clicked is the initial frag animates quickly, but stays put. It only animates once and subsequent button presses do not produce subsequent animations. I don't know if that's because for some reason the onCreate code is somehow running again quickly replacing the initial frag over the "click frag" or if the issue is in the replaceFrag method itself or if something is executing incorrectly. If anyone sees the issue and could point to where it is (without a full detailed explanation), I'd be very appreciative. When I get it to work I will award that person the green check and post the solution as well.
Here is the Main_Activity:
public class MainActivity extends FragmentActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (findViewById(R.id.fragment_container) != null) {
if (savedInstanceState != null) {
return;
}
}
// Add the fragment to'fragment_container'
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
Menu_Fragment fragment = new Menu_Fragment();
fragment.setArguments(getIntent().getExtras());
ft.add(R.id.fragment_container, fragment, "INITIAL_FRAG");
ft.commit();
}
public void replaceFrag(View v) {
Fragment fragment = new Click_Fragment(); //Creates a new Click_Fragment when button selected
Fragment savedFragment = null; // creates an instance of savedFragment
if (fragment != null) { //check to see if fragment was instantiated, always true
savedFragment = getSupportFragmentManager().findFragmentByTag("FB");
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
if (savedFragment != null) {//searches to see if the Click_Fragment has already been created once
ft.replace(R.id.fragment_container, savedFragment, "FB");
} else {
ft.replace(R.id.fragment_container, fragment, "FB");
}
ft.addToBackStack(null);
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
ft.commit();
}
}
}
The button, which has its XML housed in the menu_fragment layout, has the attribute:
android:onClick="replaceFrag"
EDIT: This error appears in the SDK log when button is pressed:
mycompany.fragment_test W/OpenGLRenderer: Failed to set EGL_SWAP_BEHAVIOR on surface 0xab70a680, error=EGL_SUCCESS
Can you explain why you have both of these lines in replaceFrag():
ft.addToBackStack(null);
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
I recommend stepping through by either debugging or using log statements to see what's happening. You might even print off the backstack at the of replaceFrag(). Additionally, I would not set replaceFrag() in XML.
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();
}
I am having trouble figuring out the proper way to navigate through fragments without a pager and i am having problems during Configuration changes for screen orientation. I am using Show/Hide on the fragments to make them visible and functional but i am wondering if i should instead be using Detach/Attach. I am also having problems adding things to the back stack and i think it is also due to the use of show/hide. Is it better to use Attach/detatch or is there a way to override what the back button does to make it show/hide the last/current fragment.
The Behavior:
I have a map fragment and a List fragment along with a few others. everything starts up correctly and works initially with orientation changes. When i navigate to the list view it populates correctly but upon orientation change the list gets redrawn without the Data in it. The map view also gets redrawn and is visible behind my pager title indicator.
If anyone could please point me in right direction for solving this that would be awesome. I am suspecting that is is caused by the way that i am showing and hiding the fragments.
Here is where i create the Fragments and add them to the fragment manager. I have also shown where i show/hide fragments.
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_map_frags);
mapViewContainer = LayoutInflater.from(this)
.inflate(R.layout.map, null);
setupFragments();
showFragment(0);
}
public void setListData(String name) {
bName = name;
showFragment(1);
}
private void setupFragments() {
final FragmentManager fm = getSupportFragmentManager();
final FragmentTransaction ft = fm.beginTransaction();
mFragment1 = fm.findFragmentByTag("f1");
if (mFragment1 == null) {
mFragment1 = new MenuFragment();
ft.add(mFragment1, "f1");
ft.hide(mFragment1);
}
mMapFragment = (MapFragment) getSupportFragmentManager()
.findFragmentByTag(MapFragment.TAG);
if (mMapFragment == null) {
mMapFragment = MapFragment.newInstance(0);
ft.add(R.id.fragment_container, mMapFragment, MapFragment.TAG);
}
ft.hide(mMapFragment);
myListFragment = (ListFrag) getSupportFragmentManager()
.findFragmentByTag(ListFrag.TAG);
if (myListFragment == null) {
myListFragment = new ListFrag();
ft.add(R.id.fragment_container, myListFragment, ListFrag.TAG);
}
ft.hide(myListFragment);
frag = (frag) getSupportFragmentManager().findFragmentByTag(
frag.TAG);
if (frag == null) {
bacFrag = new frag();
ft.add(R.id.fragment_container, frag, frag.TAG);
}
ft.hide(bacFrag);
ft.commit();
}
public void showFragment(int fragIn) {
final FragmentTransaction ft = getSupportFragmentManager()
.beginTransaction();
ft.setCustomAnimations(android.R.anim.fade_in, android.R.anim.fade_out);
if (mVisible != null) {
if (mVisible == mListFragment) {
ft.remove(mListFragment);
} else {
ft.hide(mVisible);
}
}
switch (fragIn) {
case 0:
ft.show(mMapFragment);
ft.commit();
mVisible = mMapFragment;
break;
case 1:
mListFragment = (ListFragmentDisplay) getSupportFragmentManager()
.findFragmentByTag(ListFragmentDisplay.TAG);
Toast.makeText(this, "startListFrag", Toast.LENGTH_LONG).show();
if (mListFragment == null) {
mListFragment = new ListFragmentDisplay();
ft.add(R.id.fragment_container, mListFragment,
ListFragmentDisplay.TAG);
}
ft.show(mListFragment).commit();
mVisible = mListFragment;
break;
case 2:
ft.show(myfragment).commit();
mVisible = myfragment;
break;
case 3:
ft.show(frag).commit();
mVisible = frag;
break;
}
}
It's not your fault. The problem is that when the orientation changes all the Activity is Destroyed, even all the fragments added. So none of the data within it is retained.
It's not advised to use android:configChanges="orientation|keyboardHidden".
Rather, set for every fragment setRetainInstance(true) and it will work well with your current code.
If you want to have a better persistence (for example when the activity is temporarily destroyed for space issues) also remember to save the state of your fragments with onSaveInstanceState. setRetainInstance will work only when a configuration change is about to come.
I have a layout that has an EditText and a Button. I <include> it in my main layout.
I'm having a weird issue with the layout and rotation. It seems to duplicate itself when the device (physical) is rotated, messing up the text and layout.
Here it is on first open, after I add some extra garble:
DSC_0013 is in the EditText on launch of the fragment.
Then, I rotate the phone and add some different garble:
And you can see the issue pretty clearly. At first, I thought it was just the EditText messing up. But if I add enough text to make a new line:
I can see that the button gets messed up too.
I do override onSaveInstanceState, but in it I don't touch the EditText or its value, it's strictly used for something else.
What's happening and how do I fix it?
Fixed it!
Turns out it wasn't the view duplicating itself, or the EditText, or the Button. It was the entire fragment.
In my Activity's onCreate, I add the fragment to an xml layout:
private FileDetails fileDetailsFragment;
public void onCreate(Bundle savedInstanceState) {
...
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager
.beginTransaction();
fileDetailsFragment = new FileDetails(fileData);
fragmentTransaction.add(R.id.DetailsHolder, fileDetailsFragment);
fragmentTransaction.commit();
And onCreate was being called every time I rotated the phone (as it's meant to). So I put in a check to see if the activity is being run for the first time, and it works great.
private FileDetails fileDetailsFragment;
public void onCreate(Bundle savedInstanceState) {
...
if (savedInstanceState == null) {
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager
.beginTransaction();
fileDetailsFragment = new FileDetails(fileData);
fragmentTransaction.add(R.id.DetailsHolder, fileDetailsFragment);
fragmentTransaction.commit();
} else {
fileDetailsFragment = (FileDetails) getSupportFragmentManager().findFragmentById(R.id.DetailsHolder);
}
You can also setRetainedInstance(true) on your fragment, then try to get the Fragment form de FragmentManager.findFragmentById(int) or FragmentManager.findFragmentByTag(String), and if it returns null it meant you had to create a new instance of your Fragment.
private FileDetails fileDetailsFragment;
public void onCreate(Bundle savedInstanceState) {
...
FragmentManager fragmentManager = getSupportFragmentManager();
fileDetailsFragment = (FileDetails) getSupportFragmentManager().findFragmentById(R.id.DetailsHolder);
if (fileDetailsFragment == null) {
fileDetailsFragment = new FileDetails(FileData);
}
FragmentTransaction fragmentTransaction = fragmentManager
.beginTransaction();
fragmentTransaction.add(R.id.DetailsHolder, fileDetailsFragment);
fragmentTransaction.commit();
}
In some cases, the value of savedInstanceState may be null after rotation, so it is better to add another condition:
FragmentManager fragmentManager = getSupportFragmentManager();
if (savedInstanceState == null &&
fragmentManager.getFragments().size() == 0) {
FragmentTransaction fragmentTransaction = fragmentManager
.beginTransaction();
fileDetailsFragment = new FileDetails(fileData);
fragmentTransaction.add(R.id.DetailsHolder, fileDetailsFragment);
fragmentTransaction.commit();
} else {
fileDetailsFragment = (FileDetails)
getSupportFragmentManager().findFragmentById(R.id.DetailsHolder);
}
What is the correct way to handle an orientation change when using Fragments?
I have a landscape layout that contains 2 fragments (instantiated in code into FrameLayouts). When I switch to portrait mode (the layout of which contains only one FrameLayout that holds the left pane only), the right hand fragment is no longer required.
I am receiving an error:
E/AndroidRuntime(4519): Caused by: java.lang.IllegalArgumentException: No view found for id 0x7f060085 for fragment myFragment{418a2200 #2 id=0x7f060085}
which is assume is my activity trying to re-attach the fragment where it was before the orientation change but as the view that contains the fragment does not exist in portrait mode the error is thrown.
I have tried the following hide/remove/detach methods but still get the error. What is the correct way to tell a fragment it is not needed any more and do not try to display?
#Override
public void onCreate(Bundle b) {
super.onCreate(b);
Fragment f = getSupportFragmentManager().findFragmentById(R.id.fragholder2);
//rightPane is a framelayout that holds my fragment.
if (rightPane == null && f != null) {
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.hide(f); // This doesnt work
ft.remove(f); // neither does this
ft.detach(f); // or this
ft.commit;
}
}
I ran into the same problem and I think I figured out another solution. This solution may be better because you don't have to add the fragment to the back stack.
Remove the right hand side fragment from your activity in Activity.onSaveInstanceState() before calling super.onSaveInstanceState(). This works for me:
public MyActivity extends Activity
{
#Override
public onCreate(Bundle state)
{
super.onCreate(state);
// Set content view
setContentView(R.layout.my_activity);
// Store whether this is a dual pane layout
mDualPane = findViewById(R.id.rightFragHolder) != null;
// Other stuff, populate the left fragment, etc.
.
.
.
if (mDualPane)
{
mRightFragment = new RightFragment();
FragmentManager fm = getFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
ft.replace(R.id.rightFragHolder, mRightFragment);
ft.commit()
}
}
#Override
public void onSaveInstanceState(Bundle state)
{
if (mDualPane)
{
FragmentManager fm = getFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
ft.remove(mRightFragment);
ft.commit()
}
super.onSaveInstanceState(state);
}
private boolean mDualPane;
private Fragment mRightFragment;
}
If you are retaining the fragment, try not retaining it.
setRetainInstance(false)
instead of
setRetainInstance(true)
I think I resolved it.
I added the fragment to the back stack and then before the activity closes popped it off again which effectively gets rid of it. Seems to work so far.
Usually you'll have two fragments (left/right), one main activity and one container activity for the right fragment (only when shown on phone devices). This is described in this blog entry: The Android 3.0 Fragments API
public class MyActivity extends FragmentActivity
implements MyListFragment.MyContextItemSelectedListener {
#Override
public void onCreate(final Bundle bundle) {
super.onCreate(bundle);
setContentView(R.layout.activity);
}
// Callback from ListFragment
#Override
public void myContextItemSelected(final int action, final long id) {
if (action == R.id.men_show) {
processShow(id);
}
}
private void processShow(final long id) {
if (Tools.isXlargeLand(getApplicationContext())) {
Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.right);
if (fragment == null ||
fragment instanceof MyEditFragment ||
(fragment instanceof MyShowFragment && ((MyShowFragment) fragment).getCurrentId() != id)) {
fragment = new MyShowFragment(id);
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.right, fragment);
transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
transaction.commit();
}
} else {
Intent intent = new Intent();
intent.setClass(this, MyShowActivity.class);
intent.putExtra("ID", id);
startActivityForResult(intent, MyConstants.DLG_TABLE1SHOW);
}
}
private static boolean isXlargeLand(final Context context) {
Configuration configuration = context.getResources().getConfiguration();
return (((configuration.screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) == Configuration.SCREENLAYOUT_SIZE_XLARGE) &&
configuration.orientation == Configuration.ORIENTATION_LANDSCAPE);
}
}
If you have a two pane activity with a left and right pane and one of the panes (usually the right pane) is suppose to not show when the device switches to portrait mode, let Android do its thing and recreate the right pane. But during the onCreateView of the right pane, the first thing you should do is check if one of the layout elements used by the pane is even available. If it is not, remove the fragment using the FragmentManager and return immediately:
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState)
{
View myView = getActivity().findViewById(R.id.myView);
if (myView == null)
{
FragmentTransaction fragTransaction = getFragmentManager().beginTransaction();
fragTransaction.remove(this);
fragTransaction.commit();
return null;
}
}
Android does recreates both fragments during screen rotation. But if you add check below into onCreateView() it will prevent you from issues:
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container,
#Nullable Bundle savedInstanceState) {
if (container == null) {
// Currently in a layout without a container, so no
// reason to create our view.
return null;
}
// inflate view and do other stuff
}
I took this from Android Developers blog.
You do not need this activity anymore because the fragment will be shown in-line. So you can finish the activity
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
// we are in landscape so you do not need this activity anymore
finish();
return;
}
if (savedInstanceState == null) {
// plugin right pane fragment
YourFragment frgm = new YourFragment();
getSupportFragmentManager().beginTransaction()
.add(..., frgm).commit();
}
}