Fragments backstack working
from the CommonsWare Community archivesAt August 2, 2019, 11:42pm, root-ansh asked:
I am having troules understanding how fragments work in saving and restoring data and other things.
-
what would be the difference between
fragmentTransaction.add(container,frag,tag)
andfragmentTransaction.replace()
. From what i have understood,replace( container,frag,tag)
is like callingtransaction.remove(frag)
for all the fragments (in fragManager’s backstack i guess?) and then callingfragManager.add(container,frag,tag)
.
Also for most of the times, theadd()
has been troubling for me since it keeps on adding multiple fragments on screen whenever i rotate . what could be its possible use case? -
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 givesfirFragInstance == null
or some instance. Truly enough, it was giving me null on the first run of abovequery x
and the fragment instance on subsequent runs . But i observed that every time time this query ran, both myconstructor
&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. isaddtobackstack
not enough for this task? -
additionally, this
addTobackstack()
method fails when used with thefindfragmentbyTAG(..)
check andadd(...)
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 thetag
name always for accessing the fragments? - what are the differences in the effect of
commit()
,commitNow()
andcommitAllowStateLoss()
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?
At August 3, 2019, 12:34am, mmurphy replied:
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 thetag
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()
andcommitAllowStateLoss()
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.
At August 16, 2019, 10:12am, root-ansh replied:
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?
At August 16, 2019, 10:56am, mmurphy replied:
I doubt it.
Here are two problems that I see:
- In
initFragment()
, you try callingupdateFragment()
from withinif (savedInstanceState != null)
. So, we know at that point thatsavedInstanceState != null
, and you passsavedInstanceState
intoupdateFragment()
.updateFragment()
only does work ifsavedInstanceState == null
, so that initialupdateFragment()
call will never do anything. - Please use
FrameLayout
as the container for fragment transactions, notLinearLayout
.
At August 19, 2019, 5:13pm, root-ansh replied:
well i updated to using viewpager 2, so i guess i won’t ever know, but i think you are right