Fragments backstack working

I am having troules understanding how fragments work in saving and restoring data and other things.

  1. what would be the difference between fragmentTransaction.add(container,frag,tag) and fragmentTransaction.replace() . From what i have understood, replace( container,frag,tag) is like calling transaction.remove(frag) for all the fragments (in fragManager’s backstack i guess?) and then calling fragManager.add(container,frag,tag) .
    Also for most of the times, the add() has been troubling for me since it keeps on adding multiple fragments on screen whenever i rotate . what could be its possible use case?

  2. what actually is this term backstack ? I am guessing this would be some way of storing fragments, and their data. For eg if an activity’s ui is like this:

<FrameLayout> // root of activity

<LinearLayout>
//--- fragment container
</LinearLayout>

<Button1/> <Button2/>

</FrameLayout >

then on the press of a button1 in activity, if this query runs:

// query_x
fragmentmanager.beginTransaction()
               .____(R.id.container,firFragInstance,"FFS_TAG")  /* ____= add/replace() */
               .addTobackstack("FFS_NAME")
               .commit()

Then i was assuming our firFragInstance is saved somewhere by the fragment manager, and could be retrieved *with its full data being saved* and methods like constructor/ onCreateview()/ onViewCreated() where the ui is initialized and data is set would** not** be called the next time *we retrieve it* , even if some other fragment is added over the top/replaced the top fragment . But a few more observations:

  • firstly by *we retrieved it* , i meant using Fragment1 firFragInstance= (Fragment1) manager.findFragmentByTag(FFS_TAG) . I then added a check to weather this function gives firFragInstance == null or some instance. Truly enough, it was giving me null on the first run of above query x and the fragment instance on subsequent runs . But i observed that every time time this query ran, both my constructor & onCreateview() was being called again.
    I guess This means that all previous data got destroyed and a new instance of my fragment got created .I obviously did not want that and want to save users data when transitioning between different fragments. is addtobackstack not enough for this task?

  • additionally, this addTobackstack() method fails when used with the findfragmentbyTAG(..) check and add(...) method:

//following code crashes
        Fragment1 frag1= (Fragment1) manager.findFragmentByTag(FRAG_TAG1);
        if (frag1 == null) {
            Log.e(TAG, "onCreate: frag1 not found in backstack, therefore creating new frag1" );
            frag1=new Fragment1();
        }
        else {
            Log.e( fragment 1 already exists" );
        }

        manager.beginTransaction()
                .add(R.id.fl_frag_container, frag1, FRAG_TAG1)
                .addToBackStack(null/*or some name*/)
                .commit()
        ;

I am assuming this is because, as my log says fragment 1 already exists in the back stack , and i should rather use replace(..), but again, not gettinga fragment with saved data.

  • what’s the point of adding any name to addToBackStack(...) except null , when we will be only using the tag name always for accessing the fragments?
  • what are the differences in the effect of commit(),commitNow() and commitAllowStateLoss() with context to my above questions?

I guess this is it for now. all i want to have is an activity with 2 fragments frag1 and frag2, each with a button , and edittext and textview, sending and recieving data while saving their state.
Here are some pics to illustrate:





Now i think i was able to do so when using 2 activities like that, with the use of intent.putExtra(…) ,getIntent().getStringExtra() and onSaveInstanceState(Bundle outState) , but that also required destroying and creation of 1 or both the activites from what i vaguely remember. But i thought maybe fragments could overcome this and allow a complete state/data saving mechanism while being alive and hidden at the same time?

No. It is like calling remove() for the fragment in the indicated container, then adding a fragment to that same container.

what actually is this term backstack ?

In a Web browser, you have a BACK button. It takes you to the previous Web page. In Android, you have a BACK button. It takes you to the previous UI state. That could be:

  • Closing the soft keyboard
  • Popping a fragment off the back stack, if there is one
  • Destroying the current activity

The back stack of fragments is simply a way of allowing the fragment system to help you handle BACK navigation.

is addtobackstack not enough for this task?

addToBackStack() is unrelated to that task.

what’s the point of adding any name to addToBackStack(...) except null , when we will be only using the tag name always for accessing the fragments?

In addition to the fragment system automatically handling BACK, you might have a need to go back programmatically, and sometimes you need to go back multiple levels.

For example, suppose you have an app like the ToDo app from Exploring Android. We have a list of to-do items (RosterListFragment), a screen to display details of a to-do item (DisplayFragment), and a screen to edit details of a to-do item (EditFragment). The user can tap on an item in RosterListFragment to bring it up in DisplayFragment, and the user can click an “edit” toolbar button to bring it up in EditFragment.

Now, suppose that the business rule is that when we save our changes in EditFragment, we are to return to the RosterListFragment. If we have been using addToBackStack(), we need to pop two fragments off of the stack (EditFragment and DisplayFragment) to get back to the RosterListFragment.

We could call popBackStack() twice. That meets the business rule, but it is fragile: if we change our navigation, we might need to change that count of popBackStack() calls. Or, we could name the transaction that we used to add RosterListFragment, then call popBackStack() with that name, to say “pop back to where this one was on the screen”.

Now, in Exploring Android, I am not doing any of that, because I am using the Navigation component, which hides a lot of this headache.

what are the differences in the effect of commit() , commitNow() and commitAllowStateLoss() with context to my above questions?

I only ever use commit(), to the extent that I manually commit fragment transactions (again: use the Navigation component). commit() works asynchronously, so the actual fragment changes will happen sometime later. commitNow() works synchronously. Both commit() and commitNow() might fail if you call them at an inappropriate time — the ...AllowStateLoss() variants ignore those checks, but it may leave your FragmentManager in a weird state.

I recommend using the Navigation component and avoiding all of this.

But i thought maybe fragments could overcome this and allow a complete state/data saving mechanism while being alive and hidden at the same time?

You are welcome to use hide() and show() on a FragmentTransaction to hide and show fragments. hide() does not remove or destroy the fragment; it simply removes its views from the view hierarchy.

So, apologies for late reply. I didn’t knew about navigation component, will look at it in later projects.Right now, i found your hide / show methods to be suitable for my case. But when i tried calling them, it didn’t work. please have a look on my code to why isn’t it adding any fragment in the main linear layout

public class DashboardActivity extends AppCompatActivity {

    Fragment fragLogs, fragMain, fragSettings;
    Button btLogs, btMain, btSettings;


    private enum ShowFrag{LOGS,DASHBOARD,SETTINGS}

    FragmentManager manager;

    @Override
    protected void onCreate(final Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_dashboard);

        initUI();
        initFragments(savedInstanceState,ShowFrag.DASHBOARD);
        
        btLogs.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                updateFragment(ShowFrag.LOGS, savedInstanceState);
            }
        });
        btMain.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                updateFragment(ShowFrag.DASHBOARD, savedInstanceState);
            }
        });
        btSettings.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                updateFragment(ShowFrag.SETTINGS, savedInstanceState);
            }
        });

    }


    private void initUI() {
        btLogs = findViewById(R.id.bt_logs);
        btMain = findViewById(R.id.bt_main);
        btSettings = findViewById(R.id.bt_settings);

    }

    private void initFragments(Bundle savedInstanceState, ShowFrag fragNum) {

        fragLogs = new DailyLogsFragment();//todo:change that
        fragMain = new DashboardFragment();
        fragSettings = new SettingsFragment();

        manager = getSupportFragmentManager();

        if (savedInstanceState != null) {
            manager.beginTransaction()
                    .add(R.id.layout_frag_container, fragLogs)
                    .add(R.id.layout_frag_container, fragMain)
                    .add(R.id.layout_frag_container, fragSettings)
                    .commitNow();
            updateFragment(fragNum, savedInstanceState);
        }
    }



    private void updateFragment(ShowFrag frag, Bundle savedInstanceState) {
        if (savedInstanceState == null) {
            switch (frag) {
                case LOGS:
                    
                    manager.beginTransaction().hide(fragMain).hide(fragSettings).show(fragLogs).commit();
                    break;

                case DASHBOARD:
                    manager.beginTransaction().hide(fragLogs).hide(fragSettings).show(fragMain).commit();
                    break;

                case SETTINGS:
                    manager.beginTransaction().hide(fragMain).hide(fragLogs).show(fragSettings).commit();
                    break;

                default:
                    manager.beginTransaction().hide(fragLogs).hide(fragSettings).show(fragMain).commit();
            }
        }
    }

    @Override
    protected void onPause() {
        super.onPause();

    }

    @Override
    public void onBackPressed() {
        finish();
    }

I am guessing that the chaining of multiple instructions is causing error?

I doubt it.

Here are two problems that I see:

  • In initFragment(), you try calling updateFragment() from within if (savedInstanceState != null). So, we know at that point that savedInstanceState != null, and you pass savedInstanceState into updateFragment(). updateFragment() only does work if savedInstanceState == null, so that initial updateFragment() call will never do anything.
  • Please use FrameLayout as the container for fragment transactions, not LinearLayout.

well i updated to using viewpager 2, so i guess i won’t ever know, but i think you are right