Johny19 Johny19 - 21 days ago 9
Android Question

Activity crashes when minimized and a dialog is opened

On my activity a show a Custom DialogFragement. But when I minimize the app while the dialog is opened i get this error

java.lang.RuntimeException: Parcelable encountered IOException writing serializable object (name = xxx.xxx.MyActivity$24)
at android.os.Parcel.writeSerializable(Parcel.java:1468)
at android.os.Parcel.writeValue(Parcel.java:1416)
at android.os.Parcel.writeArrayMapInternal(Parcel.java:686)
at android.os.BaseBundle.writeToParcelInner(BaseBundle.java:1330)
at android.os.Bundle.writeToParcel(Bundle.java:1079)
at android.os.Parcel.writeBundle(Parcel.java:711)
at android.support.v4.app.FragmentState.writeToParcel(Fragment.java:144)
at android.os.Parcel.writeTypedArray(Parcel.java:1254)
at ....
... 23 more
java.io.NotSerializableException: xxx.xxx.MyActivity
at java.io.ObjectOutputStream.writeNewObject(ObjectOutputStream.java:1344)
xxxxx


For the moment to fix this I have set this in the dialog fragment which seems to works but I don't like these kind of of dirty tricks

@Override
public void onPause() {
dismiss();
super.onPause();
}


Anyone can tell me what causes this exception in the first place ?

The onCreate of the custom DialogFragment:

public static MyCustomDialog newInstance(double lat, double lng, MyListener listener) {
MyCustomDialog dialogFragment = new MyCustomDialog();
Bundle bundle = new Bundle();
bundle.putDouble(ARG_LAT, lat);
bundle.putDouble(ARG_LNG, lng);
bundle.putSerializable(ARG_LISTENER, listener);
dialogFragment.setArguments(bundle);
return dialogFragment;
}

@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.dialog_layout, null, false);
final double lat = getArguments().getDouble(ARG_LAT);
final double lng = getArguments().getDouble(ARG_LNG);
listener = (MyListener) getArguments().getSerializable(ARG_LISTENER);
TextView saveBtn = (TextView) view.findViewById(R.id.save_btn);
saveBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
/* some processing */
listener.onSave();
}
});
return view;
}


MyListener is an interface. I thought at first that this could have been the problem but even if I dont put in the bundle I get the same error, so it must be from something else.

Activity side inside a button on click:

MyCustomDialog myCustomDialog = MmyCustomDialog.newInstance(lat, lon, listener);
myCustomDialog.show(getSupportFragmentManager(), "tag");

Answer

I see this line in your code:

listener = (MyListener) getArguments().getSerializable(ARG_LISTENER);

If I guess correctly, MyListener is implemented either by an inner class of your Activity or by the Activity itself. Either way, MyListener holds a reference to the Activity. Please note that if your are creating an anonymous inner class, that is:

MyListener listener = new MyListener() { ... }

it still holds a reference to your Activity.

When the Fragment needs to be destroyed, the arguments Bundle is stored in the fragment state Parcel. Android tries to serialize MyListener, however Activity is not Serializable and it fails.

The usual pattern is to declare your listener interface in your Fragment:

public class MyFragment extends Fragment {
    ...
    public interface Callbacks {
        void doSomething();
    }
    ...

Then an Activity that hosts the Fragment implements this interface:

public class MyActivity extends AppCompatActivity implements MyFragment.Callbacks {
    ...
    @Override
    public void doSomething() {
        // do your stuff
    }
    ...

In onAttach of your Fragment you cast the Fragment's host to your Callbacks and set the field.

@Override
public void onAttach(Context context) {
    super.onAttach(context);

    if (getHost() instanceof Callbacks) {
        mCallbacks = (Callbacks) getHost();
    } else {
        throw new AssertionError("Host does not implement Callbacks!");
    }
}

PS In the documentation you may see a version of onAttach method taking an Activity. The version above is deprecated from API 23. Until then, Fragments could be hosted only by Activities. Now, Fragments can be hosted by arbitrary objects and operate in any Context. That being said, you probably will host your Fragments inside Activities anyway, and your context and host will be the same.