FragmentOne fragmentOne;
FragmentTwo fragmentTwo;
public void showFragmentOne() {
if(fragmentOne == null){
fragmentOne = FragmentOne();
}
// ...
}
public void showFragmentTwo(String name) {
if(fragmentTwo == null) {
fragmentTwo = new FragmentTwo();
}
// ...
This will break after low-memory condition, when Android recreates your Fragment instances using their no-arg constructor, rather than using the instances you're creating here.
Although technically in this particular case, you are using fragmentManager.beginTransaction().replace().commit()
, therefore your instance will win, and the one recreated by the system will be overridden. As you used replace
instead of add
, you also won't end up with "multiple overlapping fragments".
However, you lose the ability to restore the Fragment via Fragment.onSaveInstanceState
/Fragment.onCreate(Bundle)
, as you will be overwriting the system-recreated fragment using your own uninitialized fragment.
Basically, this approach results in state-loss.
Out of the box, the intended solution is to use
public class MyActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if(savedInstanceState == null) {
getSupportFragmentManager().beginTransaction().replace(R.id.fragment, new FragmentOne(), "one").commit();
}
}
public void showFragmentTwo(String name) {
FragmentTwo fragmentTwo = new FragmentTwo();
Bundle bundle = new Bundle();
bundle.putString("name", name);
fragmentTwo.setArguments(bundle);
getSupportFragmentManager().beginTransaction()
.replace(R.id.fragment, fragmentTwo, "two")
.addToBackStack(null)
.commit();
}
}
And you could potentially find the fragments using findFragmentByTag
.
Although I personally don't like the Fragment backstack, I believe it's much easier if you track identifiers that describe the fragments you should have, these identifiers can provide you the given tag, and you can set up the fragments to whatever state you want them to be without necessarily removing the fragment (and therefore losing its view + state) just because you navigated forward.
You can check out the approach I tend to use for fragments here.
Using the following code, I could set up any Fragment to be in any state that I wanted, without relying on addToBackStack
.
public class FragmentStateChanger {
private FragmentManager fragmentManager;
private int containerId;
public FragmentStateChanger(FragmentManager fragmentManager, int containerId) {
this.fragmentManager = fragmentManager;
this.containerId = containerId;
}
public void handleStateChange(StateChange stateChange) {
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.disallowAddToBackStack();
// here you could animate based on direction
List<Key> previousKeys = stateChange.getPreviousKeys();
List<Key> newKeys = stateChange.getNewKeys();
for(Key oldKey : previousKeys) {
Fragment fragment = fragmentManager.findFragmentByTag(oldKey.getFragmentTag());
if(fragment != null) {
if(!newState.contains(oldKey)) {
fragmentTransaction.remove(fragment);
} else if(!fragment.isDetached()) {
fragmentTransaction.detach(fragment);
}
}
}
for(Key newKey : newKeys) {
Fragment fragment = fragmentManager.findFragmentByTag(newKey.getFragmentTag());
if(newKey.equals(stateChange.topNewKey())) {
if(fragment != null) {
if(fragment.isRemoving()) {
fragment = newKey.createFragment();
fragmentTransaction.replace(containerId, fragment, newKey.getFragmentTag());
} else if(fragment.isDetached()) {
fragmentTransaction.attach(fragment);
}
} else {
fragment = newKey.createFragment();
fragmentTransaction.add(containerId, fragment, newKey.getFragmentTag());
}
} else {
if(fragment != null && !fragment.isDetached()) {
fragmentTransaction.detach(fragment);
}
}
}
fragmentTransaction.commitAllowingStateLoss();
}
}
And then I could use this like this:
public class MainActivity
extends AppCompatActivity
implements StateChanger {
private static final String TAG = "MainActivity";
@BindView(R.id.fragment)
ViewGroup root;
FragmentStateChanger fragmentStateChanger;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
fragmentStateChanger = new FragmentStateChanger(getSupportFragmentManager(), R.id.fragment);
Navigator.configure()
.setStateChanger(this)
.install(this, root, History.single(FragmentOneKey.create()));
}
@Override
public void onBackPressed() {
if(!Navigator.onBackPressed(this)) {
super.onBackPressed();
}
}
public void showSecondFragment(String data) {
Navigator.getBackstack(this).goTo(FragmentTwoKey.create(data));
}
// this handles navigation from any nav state to any other nav state
@Override
public void handleStateChange(@NonNull StateChange stateChange, @NonNull Callback completionCallback) {
if(stateChange.isTopNewKeyEqualToPrevious()) {
completionCallback.stateChangeComplete();
return;
}
fragmentStateChanger.handleStateChange(stateChange);
completionCallback.stateChangeComplete();
}
}
Can't really imagine Fragments anymore without the convenience of saying backstack.goTo(SomeScreen())
instead of juggling transactions.