3

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.

Furedal
  • 523
  • 2
  • 8
  • 19
  • please show the code for saveinstanceState and restore also add the stacktrace perhaps the buttonImageChooser is null not your bitmap. – Zelldon Jul 13 '15 at 11:39
  • I added the exception just to put a breakpoint so i could detect when it actually crashes. The code is cleaned for this post and it is `imageFile` that is NULL. 100% I'll edit my post with the code in 2 minutes. – Furedal Jul 13 '15 at 12:42

3 Answers3

1

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

Community
  • 1
  • 1
Zelldon
  • 5,396
  • 3
  • 34
  • 46
  • I want to remember that this is the first thing i tried when i was struggling with this problem. As I am working with Fragments, `onRestoreInstanceState ` does not exists BUT you should be able to use the `savedInstanceState-variable ` provided by `onCreate()`, `onCreateView()` or `onActivityCreated()`. However savedInstanceState is always null, even when i save my state to the bundle of`onSaveInstaceState()`. This is why i started saving the state to `getArguments()`, as it usually never got destroyed. I will give this a try anyway just to clarify. – Furedal Jul 13 '15 at 15:08
0

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);
Simar
  • 600
  • 4
  • 17
  • Yes, this is actually an ugly solution I have been using before. There is however a problem here. If i keep a reference to a fragment in my Activity and that fragment is being flushed by the OS. The Activitys reference to the fragment will `NOT` be NULL, even if it is uninitialized. So then i need to expect it to always be uninitialized which makes me think that there is a better solution. – Furedal Jul 13 '15 at 12:39
  • You don't need to keep the reference to the fragment in your activity. You could use fragment manager to find that fragment. – Simar Jul 13 '15 at 12:58
  • You are right, i am using a really bad practice implementation right there! But shouldn't the application try to reinitiate the fragments of its own? I mean, there is still a fragment that receives the `OnActivityResult()` so the problem is just to preserve its state. – Furedal Jul 13 '15 at 15:11
  • If you want to save state, use onViesStateRestored than onRestoreInstanceState – Simar Jul 13 '15 at 15:12
0

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");
}
Khaled Lela
  • 7,831
  • 6
  • 45
  • 73