
I’ve sometimes posted about configuration changes, screen rotations and etc, but now I will write about a mistake that I made at the beginning when this handling involves Activity and Fragments.
Let’s take as example a simple Activity with a Fragment. We have to handle the configuration changes of the activity and the fragment and this relationship brings some extra details. That’s our Activity:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class MyObjectActivity extends AppCompatActivity { | |
@Override | |
protected void onCreate(Bundle savedInstanceState) { | |
super.onCreate(savedInstanceState); | |
setContentView(R.layout.activity_myobject); | |
int id = 10; | |
getSupportFragmentManager() | |
.beginTransaction() | |
.replace(R.id.fragment_showmyobject, ShowMyObjectFragment.newInstance(id)) | |
.commit(); | |
} | |
} |
There is any code where the state is saved and configuration changes is handle in this Activity because I want to focus on Fragment. But you can imagine it happening even that I haven’t done. And that’s our Fragment:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class ShowMyObjectFragment extends Fragment { | |
private MyObject myObject; | |
private static final String MYOBJECT_BUNDLE_KEY = "myobject_bundlekey" | |
private static final String MYOBJECTID_BUNDLE_KEY = "myobjectid_bundlekey" | |
@Override | |
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { | |
View view = inflater.inflate(R.layout.fragmentlayout, container, false); | |
if (savedInstanceState != null) { | |
myObject = (MyObject) savedInstanceState.getParcelable(MYOBJECT_BUNDLE_KEY); | |
if (myObject != null) { | |
show(myObject); | |
} | |
} else { | |
Integer myObjectId = getArguments().getParcelable(MYOBJECTID_BUNDLE_KEY); | |
if (myObjectId != null) { | |
myObject = //Search the MyObject with this ID | |
show(myObject); | |
} | |
} | |
return view; | |
} | |
@Override | |
public void onSaveInstanceState(Bundle outState) { | |
if (myObject != null) { | |
outState.putParcelable(MYOBJECT_BUNDLE_KEY, myObject ); | |
} | |
super.onSaveInstanceState(outState); | |
} | |
public static ShowMyObjectFragment newInstance(Integer myObjectId) { | |
ShowMyObjectFragment showMyObjectFragment = new ShowMyObjectFragment (); | |
Bundle bundle = new Bundle(); | |
bundle.putInt(MYOBJECTID_BUNDLE_KEY, myObjectId); | |
showMyObjectFragment.setArguments(bundle); | |
return showMyObjectFragment; | |
} | |
public void show(MyObject myObject) { | |
//… | |
} | |
} |
Let’s analyse when the Activity starts the Fragment. Inside the Fragment, the savedInstanceState param is obviously null, because the method onSaveInstanceState() has not yet been called and we don’t have a bundle with all of the states saved. So, the Fragment will deal with the Arguments received from the Activity, calling the method getArguments(). Inside the Bundle of Arguments we have the ID of the MyObject and we will use it to search the specific MyObject.
If the screen is rotated, this Fragment will save the MyObject instance because we don’t want to search it again. After it, the Fragment will be destroyed and recreated. At this time, the savedInstanceState is not null and the Fragment will deal with it, pulling out the saved MyObject instance of it.
But we have a problem. When the Activity is destroyed and recreated, the onCreate() method will replace that Fragment for a new instance and all of our work of save the state keeping the MyObject instance saved will go in vain!
So, we have to do some modifications on our Activity to avoid this situation. The tip is: We will check if the Bundle received in the method onCreate of the Activity is null. If it is null, is because the Activity is being created for the first time. If it is not null, mean that the Activity was destroyed and is been recreated.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class MyObjectActivity extends AppCompatActivity { | |
@Override | |
protected void onCreate(Bundle savedInstanceState) { | |
super.onCreate(savedInstanceState); | |
setContentView(R.layout.activity_myobject); | |
int id = 10; | |
if (savedInstanceState == null) { | |
getSupportFragmentManager() | |
.beginTransaction() | |
.replace(R.id.fragment_showmyobject, ShowMyObjectFragment.newInstance(id)) | |
.commit(); | |
} | |
} | |
} |
OR we can do this check using the tag to reference our fragment on the FragmentManager:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class MyObjectActivity extends AppCompatActivity { | |
private static final String TAG_MYOBJECT_FRAGMENT = "myobject_fragment"; | |
private ShowMyObjectFragment showMyObjectFragment; | |
@Override | |
protected void onCreate(Bundle savedInstanceState) { | |
super.onCreate(savedInstanceState); | |
setContentView(R.layout.main); | |
FragmentManager fragmentManager = getFragmentManager(); | |
showMyObjectFragment = (ShowMyObjectFragment) fragmentManager.findFragmentByTag(TAG_MYOBJECT_FRAGMENT); | |
if (showMyObjectFragment == null) { | |
int id = 10; | |
showMyObjectFragment = ShowMyObjectFragment.newInstance(id); | |
fragmentManager.beginTransaction().replace(R.id.fragment_showmyobject, showMyObjectFragment, TAG_MYOBJECT_FRAGMENT).commit(); | |
} | |
} | |
} |
Now it’s respecting the Fragment life cycle. Take care about it. Even if you handle the configuration changes in the Activity and in the Fragment, you can kill the lifecycle with some mistakes like that.