I have three fragments F1 F2 F3 F4 all are accessible from sidebar.
all four can be called at any time and in any order,
Now I want if, F1 is already clicked(created) then never again create F1, but only bring back fragment F1 to front using fragment manager. Same for all other fragment
So far i tried this for every fragment in my container (FRAGMENT ACTIVITY)
if (fragmentManager.findFragmentByTag("apps")==null) {
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
Fragment newFragment = new CategoriesFragment();
transaction.replace(R.id.content_frame, newFragment, "apps");
transaction.addToBackStack("apps");
transaction.commit();
} else{
}
If part ensures me NO fragment is recreated (If its created already) again, but what should i write in else part so that already created fragment can be brought to front in View Hierarchy
Please Help, i'm stuck at this for 2 days.
I would put this code in activity class, that must have FrameLayout with id R.id.fragment_container.
private Fragment1 F1;
private Fragment2 F2;
private Fragment3 F3;
private Fragment4 F4;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
F1 = new Fragment1();
getSupportFragmentManager().beginTransaction().add(R.id.fragment_container, F1).commit();
F2 = new Fragment2();
getSupportFragmentManager().beginTransaction().add(R.id.fragment_container, F2).commit();
F3 = new Fragment3();
getSupportFragmentManager().beginTransaction().add(R.id.fragment_container, F3).commit();
F4 = new Fragment4();
getSupportFragmentManager().beginTransaction().add(R.id.fragment_container, F4).commit();
//if needed show F1
getSupportFragmentManager().beginTransaction().show(F1).commit();
}
And add this for button click:
public void onBtnClick(View view){
if(mShowF1){
getSupportFragmentManager().beginTransaction().show(F1).commit();
getSupportFragmentManager().beginTransaction().hide(F2).commit();
getSupportFragmentManager().beginTransaction().hide(F3).commit();
getSupportFragmentManager().beginTransaction().hide(F4).commit();
}
//...
}
On button click(s) you can show, that fragment that you want and hide others.
NOTE (#developer1011):
For use after activity save state call commitAllowingStateLoss (). Use with care, because fragment is not restored with activity restoration.
NOTE:
MainActivity should implement OnFragmentInteractionListener for each Fragment.
public class MainActivity extends FragmentActivity implements Fragment1.OnFragmentInteractionListener, Fragment2.OnFragmentInteractionListener, Fragment3.OnFragmentInteractionListener, Fragment4.OnFragmentInteractionListener {//..
#Override
public void onFragmentInteraction(Uri uri) {
//
}
}
Get the fragment by tag and replace it in the container,
else{
Fragment existingFragment = (CategoriesFragment)fragmentManager.findFragmentByTag("apps");
transaction.replace(R.id.content_frame,existingFragment, "apps");
transaction.addToBackStack("apps");
transaction.commit();
}
UPDATE:
you can use hide and show fragment to avoid recreation.instead of using "transaction.replace()"
fragmentTransaction.hide(<oldFragment>);
fragmentTransaction.show(<newFragment>);
JAVA:
If you are just trying to add a Fragment without having to worry about recreating it then I think this method I have wrote to add Fragment will do you job.
public static void attachFragment ( int fragmentHolderLayoutId, Fragment fragment, Context context, String tag ) {
FragmentManager manager = ( (AppCompatActivity) context ).getSupportFragmentManager ();
manager.findFragmentByTag ( tag );
FragmentTransaction ft = manager.beginTransaction ();
if (manager.findFragmentByTag ( tag ) == null) { // No fragment in backStack with same tag..
ft.add ( fragmentHolderLayoutId, fragment, tag );
ft.addToBackStack ( tag );
ft.commit ();
}
else {
for (Fragment frag : manager.getFragments()){
ft.hide(frag)
}
ft.show ( manager.findFragmentByTag ( tag ) ).commit ();
}
}
Kotlin:
fun attachFragment(fragmentHolderLayoutId: Int, fragment: Fragment?, tag: String?) {
val manager: FragmentManager = supportFragmentManager
val ft: FragmentTransaction = manager.beginTransaction()
if (manager.findFragmentByTag(tag) == null) { // No fragment in backStack with same tag..
ft.add(fragmentHolderLayoutId, fragment!!, tag)
ft.addToBackStack(tag)
ft.commit()
} else {
//Hide other fragments
for (frag in manager.fragments){
ft.hide(frag)
}
//Shows the selected fragment.
ft.show(manager.findFragmentByTag(tag)!!).commit()
}
}
Use a simple ArrayList<Fragment> for your Fragments, and add them in order, so that you know get(0) will get F1, get(1) gets F2, etc.
Create the Fragments as singletons. In each fragment add a static field and method:
private static Fragment mMyInstance = null;
public static Fragment newInstance() {
if (mMyInstance == null) {
mMyInstance = new F1();
}
return mMyInstance;
}
Create the Fragments with the static method and add them to the ArrayList.
In each Fragment add the setRetainInstance(true); command to the onCreate() method.
Now when you add the Fragment with the FragmentManager, onCreate() will only be called the first time, but onCreateView() will be called every time. You want to inflate the view and wire the widgets each time, just en case your Activity got recreated because of a configuration change. But you can check something you add to see if it's the first time or not, and reset the widgets to their previous state if not. So, you will need member variables in your Fragments to keep track of their state. Override onStop() to save state, and reapply it in onCreateView() after wiring up the widgets.
Then when the sidebar button is pressed, you get the Fragment that corresponds to that button, remove the previous Fragment, and add the current one with the FragmentManager (or just use the replace() command instead of remov()/add()).
If you are using the Support Fragment, then this static method does the job.
/**
* Takes a Fragment TAG and tries to find the fragment in the manager if it exists and bring it to front.
* if not, will return false;
* #param manager
* #param tag
*/
public static boolean resurfaceFragment(FragmentManager manager, String tag ){
Fragment fragment = manager.findFragmentByTag(tag);
FragmentTransaction transaction = manager.beginTransaction();
if (fragment!=null){
for (int i = 0; i < manager.getFragments().size(); i++) {
Fragment f = manager.getFragments().get(i);
transaction.hide(f);
}
transaction.show(fragment).commit();
return true;
}
return false;
}
Related
Problem 1
I have a Navigation Drawer and most of my fragment transactions happens from here.
So say I have 4 Items in my drawer and I am doing the transaction from all of them. So if I am at the fragment [A] and now I click on the fragment [B], I need to come back to the previous fragment i.e. [A]. But if I keep clicking on the Item B of the navigation drawer that opens the fragment [B], I keep adding it to the backstack and when I press the back button, I am still at the same fragment.
Problem 2
How do I achieve the Clear Top behavior that is used for the intents for the fragments. As intents have the power to clear the activities from the stack from the top only, I want to achieve the same behavior.
Problem 1 & 2 solution Idea:
Create an Interface say FragmentInstanceHandler
public interface FragmentInstanceHandler {
public void openFragment(Fragment fragment, String fragmentTag);
}
Create a BaseFragment like below and extend this to all your Fragment classes:
public BaseFragment extends Fragment {
public FragmentInstanceHandler fragmentInstanceHandler;
public void setFragmentInstanceHandler(FragmentInstanceHandler fragmentInstanceHandler) {
this.fragmentInstanceHandler = fragmentInstanceHandler;
}
}
Implement the FragmentInstanceHandler interface to the Activity in which you are going to open all the Fragments. Let's say Activity is MainActivity:
public MainActivity extends Activity implements FragmentInstanceHandler {
private BaseFragment currentFragment;
#Override
public void openFragment(BaseFragment fragment, String fragmentTag) {
FragmentManager fragmentManager = getSupportFragmentManager();
Fragment oldFragmentInstance = fragmentManager .findFragmentByTag(fragmentTag);
boolean fragmentPopped = fragmentManager.popBackStackImmediate (fragmentTag, 0);
if (!fragmentPopped && oldFragmentInstance == null) {
fragment.setFragmentInstanceHandler(this);
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.add(R.id.container, fragment, fragmentTag);
fragmentTransaction.addToBackStack(fragmentTag);
fragmentTransaction.commit();
currentFragment = fragment;
} else if(fragmentPopped ){
currentFragment = oldFragmentInstance;
}
if(mDrawerLayout!= null)
mDrawerLayout.closeDrawers();
}
}
Now whenever you want to open a new Fragment even from any other Fragment you can call method like below, It is advised to provide new tag if you want to have new instance of same Fragment:
fragmentInstanceHandler.openFragment(new MyFragment(), "FragmentNewInstance");
You can tweak FragmentInstanceHandlerto add your own method to replace the current Fragment instead of adding. Above solution just gives you an idea how you can acheive your solution by putting and mananging all your code from one place.
Hi I have read this Difference between add(), replace(), and addToBackStack(). I have a confusion that If I add multiple fragments like below then If I press back button from fragment2 then will fragment1 will open ? If so then what is the use of addToBackStack as add already maintaining a stack.
FragmentTransaction ft = fragmentManager.beginTransaction();
Fragment fragment1 = new Fragment();
ft.add(R.id.llContainer, fragment1, "fragment_one");
Fragment fragment2 = new Fragment();
ft.add(R.id.llContainer, fragment2, "fragment_two");
ft.commit();
Well if you call multiple times add method on FragmentTransaction like this
FragmentTransaction ft = fragmentManager.beginTransaction();
Fragment fragment1 = new Fragment();
ft.add(R.id.llContainer, fragment1, "fragment_one");
Fragment fragment2 = new Fragment();
ft.add(R.id.llContainer, fragment2, "fragment_two");
ft.commit();
then both the fragments that been added to FragmentTransaction will be shown as overlapping.
Now clicking back will close the application. It won't start the previous fragment.
Hope this is what you were looking for.
Add method will not add your Fragment in BackStack. You need to verify once again.
While looking into code of addToBackStack
#Override
public FragmentTransaction addToBackStack(String name) {
if (!mAllowAddToBackStack) {
throw new IllegalStateException(
"This FragmentTransaction is not allowed to be added to the back stack.");
}
mAddToBackStack = true;
mName = name;
return this;
}
Flag mAddToBackStack = true; enabled which value is false by default. And this is the flag which is being used to add fragment into backstack. Have a look into below methods calls
#Override
public int commit() {
return commitInternal(false);
}
int commitInternal(boolean allowStateLoss) {
......
if (mAddToBackStack) {
mIndex = mManager.allocBackStackIndex(this);
} else {
mIndex = -1;
}
.....
}
So what you observed is not correct. Something you are missing
In my android activity I am using multiple fragments, I am successfully switching these fragments according to my requirement but problem is that when I am switching Fragment 1 to Fragment 2 and back from Fragment 2 to Fragment 1, Fragment 1 not showing previous data Fragment 1 start from stretch, but I want to show previous data same as I was selected.
Here is my code for go Fragment 1 (fragment_search_customerProfile) to Fragment 2 (fragment_CustomerInfo):
FragmentManager manager = getActivity().getSupportFragmentManager();
FragmentTransaction transaction = manager.beginTransaction();
transaction.replace(R.id.filterFram, new fragment_Search());
FrameLayout layout = (FrameLayout) rootView.findViewById(R.id.cpFrame);
layout.removeAllViewsInLayout();
transaction.remove(new fragment_search_customerProfile()).commit();
Here is my code for back Fragment 1 (fragment_search_customerProfile) fromFragment 2 (fragment_CustomerInfo):
FragmentManager manager = getActivity().getSupportFragmentManager();
FragmentTransaction transaction = manager.beginTransaction();
Log.d("fragment_Search", fromSearch + "");
transaction.replace(R.id.custIndoFram, new fragment_search_customerProfile());
FrameLayout layout = (FrameLayout) rootView.findViewById(R.id.custIndoFram);
layout.removeAllViewsInLayout();
transaction.remove(new fragment_CustomerInfo()).commit();
Can anyone explain me how can I stay save my fragment data?
Instead of transaction.replace(R.id.filterFram, new fragment_Search());
you can use transaction.add to add fragment while having the data of first one
transaction.add(R.id.filterFram, new fragment_Search());
transaction.addToBackStack(null);
From fragment two you can use .show instead of replace to show the first fragment
and hide fragment two from the first one. transaction.show(getSupportFragmentManager().findFragmentByTag("firstFragmentTag")).commit();
You can simple keep the instances of the fragments, so in your Activity you would have fields like that.
private Fragment fragment1;
private Fragment fragment2;
And now you can use the sexy replace() option,
To add Fragment1
if (fragment1 == null) {
fragment1 = new fragment_Search();
}
FragmentManager manager = getActivity().getSupportFragmentManager();
FragmentTransaction transaction = manager.beginTransaction();
transaction.replace(R.id.filterFram, fragment1);
and same for Fragment2
And you can hold the state of the fragment (data it retrieves) in the Fragment itself, and in your onViewCreated() you check, if the state is not null, you update the view immediately.
class Fragment1 extends Fragment {
private State state;
public void onViewCreated(View v) {
if (state != null) {
// Update UI here
}
}
}
UPDATE
Using your own code
private Fragment fragment1; // This is a field outside of below method
if (fragment1 == null) {
fragment1 = new fragment_Search();
}
FragmentManager manager = getChildFragmentManager();
FragmentTransaction transaction = manager.beginTransaction();
transaction.replace(R.id.filterFram, fragment1).commit();
FrameLayout layout = (FrameLayout) rootView.findViewById(R.id.cpFrame);
layout.removeAllViewsInLayout();
Going to fragment2 should be the same, and this code is used to go to Fragment1 and Fragment2, doesn't matter if you are going back or first time.
You have to add fragment instead of remove or replace
The app consists of an activity (containing a FrameLayout only) and three Fragments (all of them same in structure, have a button but the only difference being different background color).
When the activity is first created, all fragments are put into the FrameLayout one by one by replacing. When we click the button (of the fragment) on the screen, it replaces the current fragment with another fragment.
The problem being that on screen rotation, the activity shows the fragment which was displayed when the app was first started, irrespective of the fragment that was being displayed just before rotation.
Why does this happen? Why isn't the last displayed fragment restored on the screen?
I know that I can use onSavedInstanceState, but more importantly, I want to learn how the FragmentManager works.
The Main Activity:
public class MainActivity extends AppCompatActivity implements ActivityInstance {
fraga a;
fragb b;
fragc c;
FragmentManager fm;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
fm = getSupportFragmentManager();
if (fm.findFragmentByTag("a")!=null){
a = (fraga) fm.findFragmentByTag("a");
}
else {
a = new fraga();
FragmentTransaction ft = fm.beginTransaction();
ft.add(R.id.frame, a, "a" );
ft.commit();
}
if (fm.findFragmentByTag("b")!=null){
b = (fragb) fm.findFragmentByTag("b");
}
else {
b = new fragb();
FragmentTransaction fx = fm.beginTransaction();
fx.add(R.id.frame, b, "b" );
fx.commit();
}
if (fm.findFragmentByTag("c")!=null){
c = (fragc) fm.findFragmentByTag("c");
}
else {
c = new fragc();
FragmentTransaction fl = fm.beginTransaction();
fl.add(R.id.frame, c, "c" );
fl.commit();
}
}
public void changefrag(int i) { //This method is called by the fragment
using the ActivityInstance interface
switch (i){
case 1: FragmentTransaction f1 = fm.beginTransaction().replace(R.id.frame, a, "a" );
f1.commit();
break;
case 2: FragmentTransaction f2 = fm.beginTransaction().replace(R.id.frame, b, "b" );
f2.commit();
break;
case 3: FragmentTransaction f3 = fm.beginTransaction().replace(R.id.frame, c, "c" );
f3.commit();
break;
default:
{Toast.makeText(this, "default", Toast.LENGTH_SHORT).show();}
}
}
}
The FragmentManager restores the last displayed fragment after orientation change. Looks like you are adding a new fragment after every orientation change from the Activity. So a new fragment is added over the last fragment, which covers the last fragment.
You should add your fragment in onCreate only if the savedInstanceState is null. savedInstanceState will be null only the first time, it won't be null after orientation change.
if (savedInstanceState == null) {
// add your fragment
}
// else - don't add fragment, because all the fragments are restored automatically
First of all, I know that I can retain a single fragment with setRetainInstance(true); and retrieving from FragmentManager when savedInstanceState.
But my situation is that I have three fragments in my Activity, which I "swap" using transaction, depending on user actions.
I can also recreate the current fragment, saving the flag within onSaveInstanceState(Bundle savedInstanceState) to recover it when the user rotates the screen.
My problem comes when the user rotates the screen, being in one fragment and then click to go to previous fragment. Since the only fragment I can recover is the active fragment, I cannot show to the previous fragment without recreaing it (loosing all the information I had).
Here is my code. Also if this is not a good sollution, I would apreciate any tips.
private Fragment1 fragment1;
private Fragment2 fragment2;
private Fragment3 fragment3;
private String currentFragmentTAG;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_weight);
...
mFragmentManager = getSupportFragmentManager();
if (savedInstanceState != null) {
// HOW TO RECOVER ALL FRAGMENTS??
fragment1 = ??;
fragment2 = ??;
fragment3 = ??;
String tag = savedInstanceState.getString(SAVED_FRAGMENT);
Fragment savedFragment = mFragmentManager.findFragmentByTag(tag);
replaceFragment(savedFragment, tag, null);
}
}
// Here, in other funcions, I initialize fragments and show one of them depending of user actions
// I use replaceFragment function to "swap" fragments.
private void replaceFragment(Fragment fragment, String tag, Map<String, Parcelable> objectsToBundle) {
if (fragment != null && !tag.equals(currentFragmentTAG)) {
FragmentTransaction mFragmentTransaction = mFragmentManager.beginTransaction();
if (objectsToBundle != null && !objectsToBundle.isEmpty()) {
Bundle bundle = new Bundle();
for (Map.Entry<String, Parcelable> entry : objectsToBundle.entrySet()) {
bundle.putParcelable(entry.getKey(), entry.getValue());
}
fragment.setArguments(bundle);
}
mFragmentTransaction.replace(R.id.fragment_weight_container, fragment, tag);
mFragmentTransaction.commit();
currentFragmentTAG = tag;
}
}
In my fragments I use setRetainInstance(true)
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// retain this fragment
setRetainInstance(true);
}
Thank you so much!
The solution was quite simple. I just need to use FragmentTransaction.addToBackStack(tag) when perform the transaction in my replaceFragment function. This way all the fragments used in my activity will be retaines and could be recovered from FragmentManager
...
FragmentTransaction mFragmentTransaction = mFragmentManager.beginTransaction();
mFragmentTransaction.addToBackStack(tag);
...
So this is the way I can recover them (if already instantiated)
if (savedInstanceState != null) {
mWeightMainFragment = (WeightMainFragment) mFragmentManager.findFragmentByTag(WeightMainFragment.TAG);
mWeightAddFragment = (WeightAddFragment) mFragmentManager.findFragmentByTag(WeightAddFragment.TAG);
mWeightChartFragment = (WeightChartFragment) mFragmentManager.findFragmentByTag(WeightChartFragment.TAG);
currentFragmentTAG = savedInstanceState.getString(SAVED_FRAGMENT);
Fragment savedFragment = mFragmentManager.findFragmentByTag(currentFragmentTAG);
replaceFragment(savedFragment, currentFragmentTAG, null);
}
Bonus: I'm also using popBackStack() to undo transcactions instead of replace again with the back stack fragment.