Retain Current Visible Fragment After Orientation Changes - android

In my application I have 3 Fragments. suppose fragment1, fragment2, and Fragment3. these three fragments hosted on single activity. there is a Navigation drawer available to navigate each fragment. and Fragment1 is the default fragment, ie., Fragment1 is visible when the application starts. the problem is when the orientation of phone changes the current visible fragment goes out and the default fragment shows, because the activity restarts. I am keeping a the tag of current visible fragment's tag in bundle, and checks in onCreate method. but I cant create Fragment object of corresponding fragment by tag.
onCreate
if (savedInstanceState == null) {
mFragment = new Fragment1();
showFragment(mFragment, FRAGMENT1_TAG);
} else {
String tag = savedInstanceState.getString(CURRENT_FRAGMENT_TAG);
mFragment = mFragmentManager.findFragmentByTag(tag);
if (mFragment != null)
mFragmentManager.beginTransaction().replace(R.id.content_frame, mFragment, tag).commit();
else
Toast.makeText(getBaseContext(), "Something went wrong, please restart application", Toast.LENGTH_SHORT).show();
}
onSaveInstanceState
String fragmentTag = mFragmentManager.findFragmentById(R.id.content_frame).getTag();
dataOut.putString(CURRENT_FRAGMENT_TAG, fragmentTag);
when the orientation changes I am getting a null pointer exception, that is the mFragment is null, how can I resolve this
UPDATE
public static void showFragment(Fragment fragment, String tag) {
mFragmentTag = tag;
FragmentTransaction fragmentTransaction = mFragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.content_frame, fragment, mFragmentTag);
fragmentTransaction.addToBackStack(null);
fragmentTransaction.commit();
}

Keep the name of the class of your Fragments in the Bundle.
Then recreate your Fragment with the name of the class, using the static method instantiate.
String name = savedInstanceState.getString(CURRENT_FRAGMENT_TAG);
Fragment fragment = Fragment.instantiate (Activity.this, "com.example." + name);
The method expects the full name (package included).

The easiest way to do it is to prevent the activity from recreating by setting
android:configChanges="screenSize|orientation" in your manifest file.
UPDATE
You need to call to: super.onSaveInstanceState(outState); when overriding the onSaveInstanceState method

Related

Back in fragment and nested fragment

i have app like this
one activity and inside it >
fragment a (loaded when run app also from menu can open it )
fragment b (open it from just menu)
fragment c (can open it from fragment a and also can open it from menu)
also inside fragment c there are 4 child fragments
in main activity(using navigation drawer as source) i call fragment a in oncreate like this
FragmentManager fragmentManager=getSupportFragmentManager();
fragmentManager.beginTransaction().replace(R.id.fragment_place,new First_Fragment()).addToBackStack("First").commit();
my problem is how to control back button to always back to fragment a and when fragment a is open close app
i was using addToBackStack(null) but is not what i want because will show all history of fragments that i opened
When adding fragment a to the back stack "addToBackStack(String name)" pass in a name.
Then listen for on back presses in your fragments
FragmentManager fm = getFragmentManager();
fm.addOnBackStackChangedListener(new OnBackStackChangedListener() {
#Override
public void onBackStackChanged() {
}
});
make sure to stop listening when each fragment is not being shown.
Then you can pop back to the named fragment added to the back stack
FragmentManager fm = getActivity()
.getSupportFragmentManager();
fm.popBackStack ("name", FragmentManager.POP_BACK_STACK_INCLUSIVE);
Make sure the rest of your fragment transactions are not added to the back stack. This should give you the behavior you want.
addToBackStack(String tag) is used to add the fragment to backstack and it contains the string as parameter. This parameter can be null or have some value.
If you pass null, it will add your fragment to backstack with tag null. addToBackStack(null) doesn't mean that your fragment is not added to backstack.
If you want your fragment will not be added to backstack, then just delete this line.
If you are adding your fragment to backstack and want it to be visible onBackPressed, then you can use
getSupportFragmentManager().popBackStackImmediate(/* Fragment TAG */,0);
CODE:- Try the below code and let me know.
Copy the below function in your main Activity.
/**
* function to show the fragment
*
* #param name fragment to be shown
* #param tag fragment tag
*/
public void showFragment(Fragment name, String tag) {
FragmentManager fragmentManager = getSupportFragmentManager();
// check if the fragment is in back stack
boolean fragmentPopped = fragmentManager.popBackStackImmediate(tag, 0);
if (fragmentPopped) {
// fragment is pop from backStack
} else {
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.fragment_container, name, tag);
fragmentTransaction.addToBackStack(tag);
fragmentTransaction.commit();
}}
Show fragment using the below code.
showFragment(yourFragment, yourFragmentTag);
In mainActivity onBackPressed.
#override
public void onBackPressed(){
FragmentTransaction fts = getSupportFragmentManager().beginTransaction();
FragmentManager fragmentManager = getSupportFragmentManager();
if (fragmentManager.getBackStackEntryCount() >= 2) {
// always show your fragment a here
showFragment(new FragmentA(), FragmentA.class.getSimpleName());
} else {
// finish your activity
}
}

Fragment Transaction load empty view but fragment is shown after rotating device

I am building a navigation drawer as designed by the google documentation however I have an issue where the fragment is not being replaced. http://developer.android.com/training/implementing-navigation/nav-drawer.html
When the app first loads, the default fragment is loaded.
Clicking on another item on the drawer list leaves an empty view
However on rotating the device, loads the fragment chosen.
public void selectNavActivty(int position){
// TODO Changing between the different screens selection
fragment = null;
switch (position) {
case 0:
fragment = OverLay.newInstance();
break;
case 1:
fragment = Dummy.newInstance();
break;
}
if(fragment != null) {
// attach added to handle viewpager fragments
FragmentTransaction trans = getSupportFragmentManager().beginTransaction();
trans.replace(R.id.content_frame, fragment).attach(fragment)
.addToBackStack(null);
trans.commit();
getFragmentManager().executePendingTransactions();
} else {
Log.d("Drawer Activity","Error in creating Fragment");
}
}
For navigation menu fragment transactions I use the following approach, this way the fragment will be added and placed on top.
String name = "myFragment";
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.content_frame, fragment, name)
.commit();
Look up the attach() function. It follows a different fragment lifecycle.
Also make sure that your layout files framelayout is visible.
Modify your code as below:
if(fragment != null) {
// attach added to handle viewpager fragments
FragmentTransaction trans = getSupportFragmentManager().beginTransaction();
trans.replace(R.id.content_frame, fragment);
trans.addToBackStack(null);
trans.commit();
} else {
Log.d("Drawer Activity","Error in creating Fragment");
}
If the solution doesn't work for you, share the xml code along with your fragment code.
After adding Fragment it will be added to activity state and its view
will be added to defined Container view. But by attaching nothing will
be displayed if fragment was not already added to UI. It just attaches
to fragment manager. However if view was already added to a container
in UI and detached after that, by attaching it will be displayed again
in its container. Finally you can use attach and detach if you want to
destroy fragment View temporarily and want to display and build its
view on future without losing its state inside activity.
https://stackoverflow.com/a/18979024/3329488
My solution is to tag all the fragment with unique tag on fragment replacement. Make sure you also assign a unique tag to the default fragment during it creation. A more efficient way is to identify the fragment before you recreate the same one.
public void selectNavActivty(int position){
// TODO Changing between the different screens selection
FragmentManager fragmentManager = getSupportFragmentManager();
fragment = fragmentManager.findFragmentById(R.id.content_frame);
String fragmentTag = null;
switch (position) {
case 0:
fragmentTag = "case0Tag"; // please change to better tag name
break;
case 1:
fragmentTag = "case1Tag"; // please change to better tag name
break;
default:
Log.d("Drawer Activity","Error in creating Fragment");
return;
}
if (fragmentTag != null && !fragment.getTag().equals(fragmentTag))
fragmentManager.beginTransaction().replace(R.id.content_fragment, fragment, tag).commit();
}
In my case after rotating a device a blank fragment was shown. I understood that in an Activity.onCreate() I always called creating a blank Fragment and after that a needed one. So I changed it's behaviour to this:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (savedInstanceState == null) {
openEmptyFragment()
openAnotherFragment()
}
}
I recommend to check savedInstanceState != null before adding new fragments, as written in Why won't Fragment retain state when screen is rotated?.

GetFragmentManager.findFragmentByTag() returns null

getFragmentManager().beginTransaction()
.replace(R.id.graph_fragment_holder, new GraphFragment(), "GRAPH_FRAGMENT")
.commit();
getFragmentManager().beginTransaction()
.replace(R.id.list_fragment_holder, new ListFragment(), "LIST_FRAGMENT")
.commit();
//getFragmentManager().executePendingTransactions();
GraphFragment graphFragment = (GraphFragment) getFragmentManager().findFragmentByTag("GRAPH_FRAGMENT");
graphFragment.setData(data);
ListFragment listFragment = (ListFragment) getFragmentManager().findFragmentByTag("LIST_FRAGMENT");
listFragment.setData(data);
I've supplied a tag so I'm not sure why findFragmentByTag() returns null.
What I've tried from reading other questions:
this.setRetainInstance(true) in the oncreate of both fragments.
Both fragment constructors are empty public fragmentName(){}.
tried executePendingTransactions after adding the fragments.
tried add instead of replace on the fragments (edited)
I was confused about this for a long time. First, you need to save the fragment you are replacing by pushing it onto the back stack. The tag you supply is put on the fragment you are adding, not the one you are pushing onto the back stack. Later, when you do push it onto the back stack, that tag goes with it. Here's code with objects broken out to make it easier to trace. You must call 'addToBackStack' before 'commit'.
GraphFragment grFrag = new GraphFragment();
FragmentTransaction tr = getSupportFragmentManager().beginTransaction();
tr.replace(R.id.fragment_container, grFrag, "GRAPH_FRAGMENT");
// grFrag is about to become the current fragment, with the tag "GRAPH_FRAGMENT"
tr.addToBackStack(null);
// 'addToBackStack' also takes a string, which can be null, but this is not the tag
tr.commit();
// any previous fragment has now been pushed to the back stack, with it's tag
ListFragment liFrag = new ListFragment();
FragmentTransaction tr = getSupportFragmentManager().beginTransaction();
tr.replace(R.id.fragment_container, liFrag, "LIST_FRAGMENT");
// liFrag is is about to become the current fragment, with the tag "LIST_FRAGMENT"
tr.addToBackStack(null);
tr.commit();
// 'grFrag' has now been pushed to the back stack, with it's tag being "GRAPH_FRAGMENT"
Call getFragmentManager().executePendingTransactions() after fragment transaction.
getFragmentManager()
.beginTransaction()
.replace(R.id.container, new ExampleFragment(), "YOUR TAG HERE");
.commit();
//after transaction you must call the executePendingTransaction
getFragmentManager().executePendingTransactions();
//now you can get fragment which is added with tag
ExampleFragment exampleFragment = getFragmentManager().findFragmentByTag("YOUR TAG HERE");
I was having the same problem of findFragmentByTag() always returning null.
Eventually I tracked it down, I was overriding onSaveInstanceState() in my Activity but not calling super. As soon as I fixed that findFragmentByTag() returned the Fragment as expected.
You can use
fragmentTransaction.addToBackStack(yourFragmentTag);
After that you can reuse it with
getSupportFragmentManager().findFragmentByTag(yourFragmentTag);
Answered here, just need to call getSupportFragmentManager().executePendingTransactions(); after your findByTag or findById
In my case I had to create a class level FragmentManager object and then use it instead of using getSupportFragmentManager() directly.
public class Main extends BaseActivity {
FragmentManager fragmentManager;
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.fragmain);
fragmentManager = getSupportFragmentManager();
initFrag1();
}
private void initFrag1() {
String name = Frag1.class.getSimpleName();
if (fragmentManager.findFragmentByTag(name) == null) {
fragmentManager.beginTransaction()
.add(R.id.frag_container, new Frag1(), name)
.addToBackStack(name)
.commit();
}
}
}

Weird behavior in Fragment Backstack

I have an Activity that handles my fragments. I created the following method to add/replace fragments and add them (or not) to the backstack:
public void startFragment(CCFragment fragment, boolean addToBackStack) {
final String fragmentTag = fragment.getClass().getSimpleName();
final FragmentManager fragmentManager = getSupportFragmentManager();
// If my fragment is already in the backstack, I don't want to add
// it again, but go back to it:
boolean fragmentPopped=false;
if(fragmentManager.findFragmentByTag(fragmentTag)!=null){
fragmentPopped=true;
fragmentManager.popBackStack(fragmentTag,0);
}
//If it is not, I want to add/replace it
if (!fragmentPopped) {
fragment.setFragmentDelegate(this);
FragmentTransaction fragmentTransaction = fragmentManager
.beginTransaction();
fragmentTransaction.replace(CONTENT_VIEW_ID, fragment, fragmentTag);
if (addToBackStack)
fragmentTransaction.addToBackStack(null);
fragmentTransaction.commit();
}
}
This works fine, until the following scenario happens:
startFragment(F1, false); //nothing in the backstack
startFragment(F2, true); //F1 in the backstack
startFragment(F3, false); //F1 in the backstack
startFragment(F1, false); -> when I call this, it enters the "if" and popBackStack won't work, so my app stays at the F3 instead of going back to F1. If I press the back button, then the app goes to F1...
So what am I doing wrong here? I already checked if the names are being stored right.
ft.addToBackStack(tag);
I'm not certain on this, but when you add a frag to the back stack, I believe you have to tag it (again). In your code, you're not supplying a tag, but using null, so there's no tag to search for, and even if it would otherwise use the original tag, you're overwriting it with null.
Edit: Use the following to verify the name of your tags on the backstack match the tags you originally assigned. I still believe your null is overwriting them.
FragmentManager mgr = getFragmentManager();
BackStackEntry be = mgr.getBackStackEntryAt(mgr.getBackStackEntryCount()-1);
String tag be.getName();
System.out.println("tag " + tag);

Can fragment be created with only one instance

I was just wondering, can fragment creation only have one instance or singleton?
I went through Google iosched project too. They simply create
Fragment a = new Fragment();
Whenever they want...
Suppose eg:
public static FragmentManager instance;
public static FragmentManager getInstance() {
if (instance == null) {
instance = new FragmentManager();
}
return instance;
}
public TestFragment getTestFragment() {
if (testFragment == null) {
testFragment = new TestFragment ();
}
return testFragment
}
}
Can I use everywhere
FragmentManager.getInstance().getTestFragment() for transaction?
eg:
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.content_frame, FragmentManager.getInstance().getTestFragment())
.commit();
Or OS automatically destroy the reference or some issues related to it?
When you use getSupportFragmentManager().beginTransaction().replace you can add a third parameter as a string that you can use as a tag, so in case you want to recover a previous fragment you can use getSupportFragmentManager().findFragmentByTag(String) so you won't have to create a new fragment.
So it would be like this
Check if the fragment exists using findFragmentByTag(String) if it not exists, create a new fragment and call getSupportFragmentManager().beginTransaction() .replace(R.id.content_frame,myFragment,myTag).commit(); where myTag is the String you'll use in your findFragmentByTag. This way you won't create more than one fragment of every type.
I hope it makes some sense :)
For more information check this and this
No such limitation. Though, two fragment objects must not have same tag or id.
Also, its good to re-attach an existing fragment, rather that creating a new one.
MyFragment f = (MyFragment) getFragmentManager().findFragmenByTag("my_fragment");
if(f == null){
f = Fragment.instantiate(context, MyFragment.class.getName());
}
if(!f.isAdded()){
//--do a fragment transaction to add fragment to activity, WITH UNIQUE TAG--
//--Optionally, add this transaction to back-stack as well--
}
If you are trying to make sure that you will not add or replace one or more of your fragments with the same "type" twice or more, then you can use the FragmentManager.BackStackEntry to know which of your fragments is currently on the top of the stack.
String TAG_FIRST_FRAGMENT = "com.example.name.FIRST.tag";
String TAG_SECOND_FRAGMENT = "com.example.name.SECOND.tag";
FragmentManager fragmentManager = getSupportFragmentManager();
if (fragmentManager.getBackStackEntryCount() == 0 ||
!fragmentManager.getBackStackEntryAt(
fragmentManager.getBackStackEntryCount() - 1)
.getName().equals(TAG_SECOND_FRAGMENT)) {
//Now it's safe to add the secondFragment instance
FragmentTransaction transaction = fragmentManager.beginTransaction();
//Hide the first fragment if you're sure this is the one on top of the stack
transaction.hide(getSupportFragmentManager().findFragmentByTag(TAG_FIRST_FRAGMENT));
SecondFragment secondFragment = new SecondFragment();
transaction.add(R.id.content_frame, secondFragment, TAG_SECOND_FRAGMENT);
//Add it to back stack so that you can press back once to return to the FirstFragment, and
//to make sure not to add it more than once.
transaction.addToBackStack(TAG_SECOND_FRAGMENT);
transaction.commit();
}

Categories

Resources