I have 2 serializable objects that I want to retain after my fragment is rebuilt. Therefore I saved them during onSaveInstanceState (the objects are not null here):
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putSerializable(RENDERER, renderer);
outState.putSerializable(SERIES, series);
}
In the onCreate method of the fragment I try to get them out of the Bundle:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState != null) {
renderer = (DefaultRenderer) savedInstanceState.getSerializable(RENDERER);
series = (CategorySeries) savedInstanceState.getSerializable(SERIES);
}
}
The problem is that "renderer" and "series" are always null after calling getSerializable. Any ideas why?
Did you override onSaveInstanceState method in FragmentActivity that hosts this fragment? If you're, then make sure that it should call super.onSaveInstanceState(Bundle).
Related
In one of Activities I have HashSet<Integer> mSelectedPositions. I want to save state of this set on screen rotation.
#Override
protected void onSaveInstanceState(#NotNull Bundle outState) {
super.onSaveInstanceState(outState);
outState.putSerializable(SELECTED_TYPES_POSITIONS, mSelectedPositions);
}
And restore it
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if(savedInstanceState!= null && savedInstanceState.containsKey(SELECTED_TYPES_POSITIONS)){
mSelectedPositions = (HashSet<Integer>) savedInstanceState.getSerializable(SELECTED_TYPES_POSITIONS);
}
...
}
The problem is, getSerializable(..) returns an empty HashSet, even then it wasn't empty in putSerializable(..).
What's even weirder, I have almost the same code (with other keys) in other Fragments, and it works fine.
Don't know if matters, but activity in question is a child of MainActivity.
Upd
Part of the problem is in selection flow. On destroy of activity action mode is finished.
#Override
protected void onDestroy() {
if(mActionMode != null){
mActionMode.finish();
}
super.onDestroy();
}
Which triggers
#Override
public void onDestroyActionMode(ActionMode mode) {
mAdapter.clearSelections();
mActivity.nullifyActionMode();
}
in SelectionCallback.
I think, next thing happens:
1. I put mSelectedPositions in outState Bundle, it stores reference
2. Activity is destroyed
3. SelectionCallback clears mSelectedPositions
4. Actual serialization happens with empty HashSet.
So I made some changes — new HashSet with copy of mSelectedPositions data
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putSerializable(SELECTED_TYPES_POSITIONS, new HashSet<>(mSelectedPositions));
}
And it works like it should.
Upd2
In Fragments I call mActionMode.finish() in onDetach(), which is not called on screen rotation, so mSelectedPositions there remains intact.
Try putting a Json instead of raw HashMap
#Override
protected void onSaveInstanceState(#NotNull Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString(SELECTED_TYPES_POSITIONS,new Gson().toJson(mSelectedPositions));
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if(savedInstanceState!= null && savedInstanceState.containsKey(SELECTED_TYPES_POSITIONS)){
String data = savedInstanceState.getString(SELECTED_TYPES_POSITIONS);
if(data != null){
mSelectedPositions = new Gson().fromJson(str, new TypeToken<HashSet<Integer>>() { }.getType())
}
}
...
}
The funny thing is that serialization of outState happens after onDestroy() call. And I was erasing data passed to outState in onDestroy.
So, there are two ways to fix that:
• pass copy of data to outState
• don't erase data in onDestroy
Opted for second option. Erasing might be required in some cases in onDetach() of Fragments, but it is not necessary with Activity.
How to get the bundle that is given to this method on request (Bundle outState)?
#Override
protected void onSaveInstanceState(***Bundle outState***) {
super.onSaveInstanceState(outState);
// Only if you need to restore open/close state when
// the orientation is changed
if (adapter != null) {
adapter.saveStates(outState);
}
}
in Bundle instance you can pass the data and get it back in onCreate() method. like following
outState.putString("key1", "data1");
outState.putBoolean("key2", "data2");
outState.putInt("key3", "data3");
and in onCreate get it like following
if (savedInstanceState != null){
data_1 = savedInstanceState.getString("keys1");
data_2 = savedInstanceState.getBoolean("keys2");
data_3 = savedInstanceState.getInt("keys3");
}
You can get saved bundle back using below method:
#Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
// get your saved bundles back here
}
Just refer this developer page, you will get clear idea about this
It's the Bundle sent in the OnCreate method in your activity.
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState != null) {
savedInstanceState.getString("bla");
}
}
Note that you'll have to check for null, because the first time you create your activity it will be null, since there was no previous state.
see https://developer.android.com/guide/components/activities/activity-lifecycle.html#oncreate for more information
Here is a re-occurring problem that I haven't found a good solution for in the past.
My application is based on a single activity that has multiple child fragments.
What i want to do:
In some of my fragments, I want to take a picture with the phones own camera-app and both show the image for the user and then upload it to my server.
What i do now
Now, i am calling StartActivityForResult with my camera intent which works fine. Then i receive what i need from onActivityResult and are able to show the taken image in an image view and also send it to my server.
The problem
Some times when my onActivityResult is called. My fragment has been uninitiated or just flushed from memory by the OS (As i understand it).
This means that variables now has null-references.
What i have read from similar issues is that OnCreateView() is supposedly to be called before OnActivityResult().
So what I am trying to do here is to save the fragments state to its Arguments in my onDestroyView() and onSaveInstanceState() and then try to restore variables such as the temporary Camera Image FilePath. Here however, the fragment seems to initiate the Fragment with a new Bundle and not the one i've created for it, and causes my app to crash due to my camera file is null.
This is also hard to test as this just happens some times at random.
Code
saveState() is called from onDestroyView() and onSaveInstanceState()
#Override
protected Bundle saveState() {
Bundle state = new Bundle();
state.putSerializable("tempCameraFile", tempCameraFile);
return state;
}
restoreStates() is called in the end by onCreateView()
private void restoreStates(){
tempCameraFile = (File)savedState.getSerializable("tempCameraFile");
}
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == 7777 && resultCode == Activity.RESULT_OK) {
setPendingImage(tempCameraFile);
}
}
private void setPendingImage(File imageFile){
try {
Bitmap bitmap = PhotoUtils.decodeFile(imageFile.getPath(), Utils.convertDpToPixel(40, mActivity));
if(bitmap != null) {
buttonImageChooser.setImageBitmap(bitmap);
}
} catch(NullPointerException npe){
npe.printStackTrace();
Log.d(getClass().getSimpleName(), "imageFile NULLPOINTER!!!! WHYYYY!?");
}
}
#Override
public void onSaveInstanceState(Bundle outState){
super.onSaveInstanceState(outState);
saveStateToArguments();
}
#Override
public void onDestroyView() {
super.onDestroyView();
saveStateToArguments();
}
private void saveStateToArguments() {
if (getView() != null)
savedState = saveState();
if (savedState != null) {
Bundle b = getArguments();
b.putBundle("savedState", savedState);
}
}
I really hope there is an obvious thing I am doing wrong when using fragments and that someone are able to help me out.
This has been a reoccurring problem that I have solved with a really ugly implementation of destroying and re-creating fragments from my Activity, but I now want to do this the right way.
The problem is you are not saving your state at all.
#Override
public void onSaveInstanceState(Bundle outState){
super.onSaveInstanceState(outState);
saveStateToArguments();
}
The bundle outState contains all values which will be saved, but your method
saveStateToArguments(); saves the values in another bundle.
The outState and your bundle are not related, so nothing will be saved.
Besides there is no need to call the saveStateToArguments(); in the onDestroyView 'cause the onSaveInstanceState will be called.
So simply change your code to the following:
#Override
public void onSaveInstanceState(Bundle outState){
super.onSaveInstanceState(outState);
outState.putSerializable("tempCameraFile", tempCameraFile);
}
And restore the state in the method onRestoreInstanceState
#Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
if (savedInstanceState != null) {
tempCameraFile = (File) savedInstanceState.getSerializable("tempCameraFile");
}
}
Because the lifecycle is the following:
onCreate
onStart
onRestoreInstanceState
onActivityResult
onResume
See State of Activity while in onActivityResult question
Use activity to start the camera app and use activity's onActivityResult to make sure if the fragment exists. If it doesn't reinitialise the fragment.
getActivity().startActivityForResult(intent);
The problem is the system kill background activities on low memory.
Solution :
Save state of Activity or Fragment
/**
* Handled take camera photo on low memory maybe activity on background killed, need to save state.
*/
private Uri cameraMediaOutputUri;
#Override
public Uri getCameraMediaOutputUri() {
return cameraMediaOutputUri;
}
#Override
public void onSaveInstanceState(#NonNull Bundle outState) {
outState.putParcelable("cameraMediaOutputUri", cameraMediaOutputUri);
super.onSaveInstanceState(outState);
}
Restore the state and here is the trick as restoring state on Activity is different from Fragment.
Restoring Activity State
#Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
if (savedInstanceState !=null && savedInstanceState.containsKey("cameraMediaOutputUri"))
cameraMediaOutputUri = savedInstanceState.getParcelable("cameraMediaOutputUri");
}
Restoring Fragment State Fragment Life Cycle
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (savedInstanceState !=null && savedInstanceState.containsKey("cameraMediaOutputUri"))
cameraMediaOutputUri = savedInstanceState.getParcelable("cameraMediaOutputUri");
}
I have a few CheckBox elements inside one of my Fragments.
Every time I leave this Fragment it seems to nor save or restore the checked state of each one provided by the user.
In the FragmentList example you can find:
CheckBox check1;
boolean active;
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean("state1", check1.isChecked());
}
Which you can use later like this:
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (savedInstanceState != null) {
// Restore last state for checked position.
check1.setChecked(savedInstanceState.getBoolean("state1"));
}
}
But somehow the CheckBox elements don`t save their state.
Is this the correct approach I should take?
Unless you're keeping your Fragment reference alive through the lifecycle of the application, it should be fine to save its state in onSaveInstanceState and restore it in onActivityCreated.
One important point, though, is also to save and restore that state in the Activity level by doing like:
public void onCreate(Bundle savedInstanceState) {
...
if (savedInstanceState != null) {
// Restore the fragment's instance
mFragment = getSupportFragmentManager().getFragment(
savedInstanceState, "fragKey");
...
}
...
}
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
// Save the fragment's instance
getSupportFragmentManager().putFragment(outState, "fragKey", mContent);
}
Please check to see how your Activity is behaving in your scenario.
I have an Activity that calls setContentView with this XML:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="horizontal"
>
<fragment android:name="org.vt.indiatab.GroupFragment"
android:id="#+id/home_groups"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="1" />
<..some other fragments ...>
</LinearLayout>
The GroupFragment extends Fragment, and all is well there. However, I show a DialogFragment from within GroupFragment. This shows correctly, HOWEVER when the screen rotates, I get a Force Close.
What's the proper way to display a DialogFragment from within another Fragment other than DialogFragment.show(FragmentManager, String)?
There's a bug in the compatibility library that can cause this. Try putting this in you dialogfragment:
#Override
public void onDestroyView() {
if (getDialog() != null && getRetainInstance())
getDialog().setOnDismissListener(null);
super.onDestroyView();
}
I also suggest setting your dialogfragment as retained, so it won't get dismissed after the rotation. Put "setRetainInstance(true);" e.g. in the onCreate() method.
OK, while Zsombor's method works, this is due to me being inexperienced with Fragments and his solution causes issues with the saveInstanceState Bundle.
Apparently (at least for a DialogFragment), it should be a public static class. You also MUST write your own static DialogFragment newInstance() method. This is because the Fragment class calls the newInstance method in its instantiate() method.
So in conclusion, you MUST write your DialogFragments like so:
public static class MyDialogFragment extends DialogFragment {
static MyDialogFragment newInstance() {
MyDialogFragment d = new MyDialogFragment();
return d;
}
#Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
...
}
}
And show them with:
private void showMyDialog() {
MyDialogFragment d = MyDialogFragment.newInstance();
d.show(getFragmentManager(), "dialog");
}
This may be unique to the ActionBarSherlock Library, but the official samples in the SDK documentation use this paradigm also.
To overcome the Bundle always being null, I save it to a static field in onSaveInstanceState. It's a code smell, but the only solution I found for both restoring the dialog and saving the state.
The Bundle reference should be nulled in onDestroy.
#Override
public void onCreate(Bundle savedInstanceState)
{
if (savedInstanceState == null)
savedInstanceState = HackishSavedState.savedInstanceState;
setRetainInstance(true);
}
#Override
public Dialog onCreateDialog(Bundle savedInstanceState)
{
if (savedInstanceState == null)
savedInstanceState = HackishSavedState.savedInstanceState;
...
}
#Override
public void onDestroyView() // necessary for restoring the dialog
{
if (getDialog() != null && getRetainInstance())
getDialog().setOnDismissListener(null);
super.onDestroyView();
}
#Override
public void onSaveInstanceState(Bundle outState)
{
...
HackishSavedState.savedInstanceState = outState;
super.onSaveInstanceState(outState);
}
#Override
public void onDestroy()
{
HackishSavedState.savedInstanceState = null;
super.onDestroy();
}
private static class HackishSavedState
{
static Bundle savedInstanceState;
}
I used a mix of the presented solutions and added one more thing.
This is my final solution:
I used setRetainInstance(true) in the onCreateDialog;
I used this:
public void onDestroyView() {
if (getDialog() != null && getRetainInstance())
getDialog().setDismissMessage(null);
super.onDestroyView();
}
And as a workaround of the savedInstanceState not working, I created a private class called StateHolder (the same way a holder is create for a listView):
private class StateHolder {
String name;
String quantity;
}
I save the state this way:
#Override
public void onSaveInstanceState(Bundle savedInstanceState) {
super.onSaveInstanceState(savedInstanceState);
stateHolder = new StateHolder();
stateHolder.name = actvProductName.getText().toString();
stateHolder.quantity = etProductQuantity.getText().toString();
}
In the onDismiss method I set the stateHolder back to null. When the dialog is created, it verifies if the stateHolder isn't null to recover the state or just initialize everything normally.
I solved this issue with answers of #ZsomborErdődy-Nagy and #AndyDennie . You must subclass this class and in you parent fragment call setRetainInstance(true), and dialogFragment.show(getFragmentManager(), "Dialog");
public class AbstractDialogFragment extends DialogFragment {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
#Override
public void onDestroyView() {
if (getDialog() != null && getRetainInstance())
getDialog().setDismissMessage(null);
super.onDestroyView();
}
}
I had a similar issue, however none of the above worked for me. In the end I needed to create the fragment in code instead of in the XML layout.
See: Replacing fragments and orientation change
I ran into this on my project and none of the above solutions helped.
If the exception looks something like
java.lang.RuntimeException: Unable to start activity ComponentInfo{
...
Caused by: java.lang.IllegalStateException: Fragment....
did not create a view.
It's caused by an issue with a fallback container Id that gets used after rotation. See this ticket for more details:
https://code.google.com/p/android/issues/detail?id=18529
Basically you can prevent the crash by making sure all of your xml fragments have a tag defined in the layout. This prevents the fallback condition from occurring if you rotate when a fragment is visible.
In my case I was able to apply this fix without having to override onDestroyView() or setRetainInstance(true), which is the common recommendation for this situation.
I encountered this problem and the onDestroyView() trick wasn't working. It turned out that it was because I was doing some rather intensive dialog creation in onCreate(). This included saving a reference to the AlertDialog, which I would then return in onCreateDialog().
When I moved all of this code to onCreateDialog() and stopped retaining a reference to the dialog, it started working again. I expect I was violating one of the invariants DialogFragment has about managing its dialog.
In onCreate() call setRetainInstance(true) and then include this:
#Override
public void onDestroyView() {
if (getDialog() != null && getRetainInstance()) {
getDialog().setOnDismissMessage(null);
}
super.onDestroyView();
}
When you call setRetainInstance(true) in onCreate(), onCreate() will no longer be called across orientation changes, but onCreateView() will still be called.
So you can still save the state to your bundle in onSaveInstanceState() and then retrieve it in onCreateView():
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt("myInt", myInt);
}
#Override
public View onCreateView(LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.my_layout, container);
if (savedInstanceState != null) {
myInt = savedInstanceState.getInt("myInt");
}
...
return view;
}