Android Fragment lifecycle issue (NullPointerException on onActivityResult)
Asked Answered
H

1

2

I get an issue which for I cannot find any explanation. I have a FragmentActivity that displays fragments using a TabManager, as follows :

public class WorkOrderFormTabFragmentActivity extends FragmentActivity {
TabHost mTabHost;
TabManager mTabManager;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    requestWindowFeature(Window.FEATURE_NO_TITLE);
    setContentView(R.layout.work_order_form_tab_new);
    mTabHost = (TabHost)findViewById(android.R.id.tabhost);
    mTabHost.setup();

    mTabManager = new TabManager(this, mTabHost, R.id.realtabcontent);

    mTabManager.addTab(mTabHost.newTabSpec("form").setIndicator("Form"),
            WorkOrderFormFragment.class, null);
    mTabManager.addTab(mTabHost.newTabSpec("pictures").setIndicator("Pictures"),
            PictureListFragment.class, null);

    if (savedInstanceState != null) {
        mTabHost.setCurrentTabByTag(savedInstanceState.getString("tab"));
    }
}

@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putString("tab", mTabHost.getCurrentTabTag());
}

public static class TabManager implements TabHost.OnTabChangeListener {
    private final FragmentActivity mActivity;
    private final TabHost mTabHost;
    private final int mContainerId;
    private final HashMap<String, TabInfo> mTabs = new HashMap<String, TabInfo>();
    TabInfo mLastTab;

    static final class TabInfo {
        private final String tag;
        private final Class<?> clss;
        private final Bundle args;
        private Fragment fragment;

        TabInfo(String _tag, Class<?> _class, Bundle _args) {
            tag = _tag;
            clss = _class;
            args = _args;
        }
    }

    static class DummyTabFactory implements TabHost.TabContentFactory {
        private final Context mContext;

        public DummyTabFactory(Context context) {
            mContext = context;
        }

        @Override
        public View createTabContent(String tag) {
            View v = new View(mContext);
            v.setMinimumWidth(0);
            v.setMinimumHeight(0);
            return v;
        }
    }

    public TabManager(FragmentActivity activity, TabHost tabHost, int containerId) {
        mActivity = activity;
        mTabHost = tabHost;
        mContainerId = containerId;
        mTabHost.setOnTabChangedListener(this);
    }

    public void addTab(TabHost.TabSpec tabSpec, Class<?> clss, Bundle args) {
        tabSpec.setContent(new DummyTabFactory(mActivity));
        String tag = tabSpec.getTag();

        TabInfo info = new TabInfo(tag, clss, args);

        info.fragment = mActivity.getSupportFragmentManager().findFragmentByTag(tag);
        if (info.fragment != null && !info.fragment.isDetached()) {
            FragmentTransaction ft = mActivity.getSupportFragmentManager().beginTransaction();
            ft.detach(info.fragment);
            ft.commit();
        }

        mTabs.put(tag, info);
        mTabHost.addTab(tabSpec);
    }

    @Override
    public void onTabChanged(String tabId) {
        TabInfo newTab = mTabs.get(tabId);
        if (mLastTab != newTab) {
            FragmentTransaction ft = mActivity.getSupportFragmentManager().beginTransaction();
            if (mLastTab != null) {
                if (mLastTab.fragment != null) {
                    //ft.detach(mLastTab.fragment);
                    ft.hide(mLastTab.fragment);
                }
            }
            if (newTab != null) {
                if (newTab.fragment == null) {
                    newTab.fragment = Fragment.instantiate(mActivity,
                            newTab.clss.getName(), newTab.args);
                    ft.add(mContainerId, newTab.fragment, newTab.tag);
                } else {
                    //ft.attach(newTab.fragment);
                    ft.show(newTab.fragment);
                }
            }

            mLastTab = newTab;
            ft.commit();
            mActivity.getSupportFragmentManager().executePendingTransactions();
        }
    }
}

In the second tab of this FragmentActivity user can manage a list of pictures and use the camera to add more pictures.

Fragment code :

public class PictureListFragment extends Fragment {

static final int TAKE_PICTURE_ACTIVITY = 1;
static final int EDIT_PICTURE_ACTIVITY = 2;

FormPictureListAdapter lvAdapter;

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup viewgrp,
    Bundle savedInstanceState) {
    // Inflate the layout for this fragment
    View cont = inflater.inflate(R.layout.form_picture_list, viewgrp, false);

    LinearLayout container = (LinearLayout)cont.findViewById(R.id.formPictureListLayout);
    try{

        final Context context = getActivity();
        ListView ls2 = new ListView(context);
        // clear previous results in the LV
        ls2.setAdapter(null);
        // populate
        ArrayList<MFPicture> pictures = new ArrayList<MFPicture>();
        //pictures.add(0, new MFPicture());
        pictures.addAll(((MFApplication)getActivity().getApplication()).getCurrentForm().getPictures());

        lvAdapter = new FormPictureListAdapter(context, pictures);
        ls2.setAdapter(lvAdapter);
        LinearLayout.LayoutParams Params = new LinearLayout.LayoutParams(FrameLayout.LayoutParams.FILL_PARENT, 0, 1f);
        ls2.setLayoutParams(Params);
        ls2.setOnItemClickListener(new OnItemClickListener() {
          public void onItemClick(AdapterView<?> parent, View view,
                    int position, long id) {
              final MFPicture picture = ((MFPictureView)view).getPicture();
              final int idx = position;
              DialogInterface.OnClickListener dialogClickListener = new DialogInterface.OnClickListener() {
                  @Override
                  public void onClick(DialogInterface dialog, int which) {
                      switch (which){
                      case DialogInterface.BUTTON_POSITIVE:
                          //Edit picture
                          EditPictureActivity.setPicture(picture);
                          Intent configurationOpen = new Intent(getActivity(), EditPictureActivity.class);
                          startActivityForResult(configurationOpen, EDIT_PICTURE_ACTIVITY);
                          break;

                      case DialogInterface.BUTTON_NEGATIVE:
                          //Delete picture
                          ((MFApplication)getActivity().getApplication()).getCurrentForm().getPictures().remove(idx);  
                          MFUtils.deleteFile(picture.getPath());
                          lvAdapter.updatePictureList(((MFApplication)getActivity().getApplication()).getCurrentForm().getPictures());
                          lvAdapter.notifyDataSetChanged();
                          break;
                      }
                  }
              };
              AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
              builder.setMessage(getResources().getString(R.string.wo_bem_regie_list_el_action)).setPositiveButton(getResources().getString(R.string.wo_bem_regie_list_el_edit), dialogClickListener)
                    .setNegativeButton(getResources().getString(R.string.wo_bem_regie_list_el_delete), dialogClickListener).show();
          }
         });
        container.addView(ls2);

        LayoutInflater layoutInflater = (LayoutInflater)getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        View view=layoutInflater.inflate(R.layout.add_btn_bottom,container);
        view.setBackgroundResource(R.drawable.list_selector_even);
        TextView text = (TextView)view.findViewById(R.id.title);
        text.setText(getResources().getString(R.string.wo_picturelist_add));
        view.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v){
                v.setBackgroundResource(R.drawable.list_selector_even);
                String pictureFile = ((MFApplication)getActivity().getApplication()).getNextPictureFile();
                String picPath = MFUtils.MF_STORAGE_PATH+"/"+pictureFile;
                Log.e("FormPictureListActivity", "PicturePath : "+picPath);
                //setBackgroundResource(android.R.drawable.list_selector_background);
                try {
                    Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
                    intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(new File(picPath)));
                    startActivityForResult(intent, TAKE_PICTURE_ACTIVITY);
                } catch (ActivityNotFoundException e) {
                    Log.e("FormPictureListActivity", "Call failed", e);
                }
            }
      });
    }
    catch(Exception e){
      e.printStackTrace();
      Log.e("FormPictureListActivity", "Error:", e);
    }

    return cont;
}

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    //super.onActivityResult(requestCode, resultCode, data);
    String pictureFile = ((MFApplication)getActivity().getApplication()).getPictureFile();
    Log.d("FormPictureListActivity", "ActivityResult:"+resultCode);
    Log.d("FormPictureListActivity", "ActivityResult-picFile:"+pictureFile);
    if (requestCode == TAKE_PICTURE_ACTIVITY){
        if(resultCode == getActivity().RESULT_OK){
            Log.d("FormPictureListActivity", "ActivityResult:OK");
            MFPicture picture = new MFPicture(MFPicture.TYPE_PICTURE, MFUtils.MF_STORAGE_PATH+"/"+pictureFile);
            ((MFApplication)getActivity().getApplication()).getCurrentForm().getPictures().add(picture);
            lvAdapter.updatePictureList(((MFApplication)getActivity().getApplication()).getCurrentForm().getPictures());
            lvAdapter.notifyDataSetChanged();
            EditPictureActivity.setPicture(picture);
            Intent configurationOpen = new Intent(getActivity(), EditPictureActivity.class);
            startActivityForResult(configurationOpen, EDIT_PICTURE_ACTIVITY);
        }
    }
    else if (requestCode == EDIT_PICTURE_ACTIVITY){
        EditPictureActivity.getPicture().setComment(EditPictureActivity.getPicture().getCommentUIValue());
        lvAdapter.updatePictureList(((MFApplication)getActivity().getApplication()).getCurrentForm().getPictures());
        lvAdapter.notifyDataSetChanged();
    }
}
}

Everything works well on my test devices (Nexus 5, Galaxy Nexus, Galaxy Mini 2) but I receive from time to time errors from other devices I have no access to (mainly devices running Android 4.0.4) :

java.lang.RuntimeException: Unable to resume activity 

{com.mf.mobile.android/com.mf.mobile.android.WorkOrderFormTabFragmentActivity}: java.lang.RuntimeException: Failure delivering result ResultInfo{who=null, request=131073, result=-1, data=null} to activity {com.mf.mobile.android/com.mf.mobile.android.WorkOrderFormTabFragmentActivity}: java.lang.NullPointerException
at android.app.ActivityThread.performResumeActivity(ActivityThread.java:2616)
at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:2644)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2130)
at android.app.ActivityThread.access$600(ActivityThread.java:135)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1248)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:137)
at android.app.ActivityThread.main(ActivityThread.java:4645)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:809)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:576)
at dalvik.system.NativeStart.main(Native Method)
Caused by: java.lang.RuntimeException: Failure delivering result ResultInfo{who=null, request=131073, result=-1, data=null} to activity {com.mf.mobile.android/com.mf.mobile.android.WorkOrderFormTabFragmentActivity}: java.lang.NullPointerException
at android.app.ActivityThread.deliverResults(ActivityThread.java:3156)
at android.app.ActivityThread.performResumeActivity(ActivityThread.java:2599)
... 12 more
Caused by: java.lang.NullPointerException
at com.timewise.mobile.android.fragments.PictureListFragment.onActivityResult(PictureListFragment.java:138)
at android.support.v4.app.FragmentActivity.onActivityResult(FragmentActivity.java:166)
at android.app.Activity.dispatchActivityResult(Activity.java:4662)
at android.app.ActivityThread.deliverResults(ActivityThread.java:3152)
... 13 more

The NPE appears on this line of code : lvAdapter.updatePictureList(((MframeApplication)getActivity().getApplication()).getCurrentForm().getPictures());

This means that at this time lvAdapter variable is null. But this variable should have been set in onCreateView of Fragment... Which makes me think that, at some point Fragment may have been recreated without calling onCreateView.

I cannot find any explanation about this issue. Can you help me on this one?

Many thanks

Hullo answered 23/4, 2014 at 13:44 Comment(0)
F
2

Ok, I think this can happen if the user has your app open and then hits the home button, then either hits the show running applications button (the one that is to the right of the home button) or holds down the home button on some phones and selects your app to go back to it. Then, if you look at the lifecycle it will call onResume (which is what is in the stack trace above) without calling onCreateView again.

Fragment Lifecycle

Now, you should be able to mimic this to see it happening by going into the Developer options on your Nexus 5, and selecting Don't keep activities. Then open your app, go to that fragment hit the home button, then the running apps button and select your app and I think it will show you that exception.

Flavourful answered 23/4, 2014 at 14:8 Comment(3)
Hello Kaediil, and first 'tank you!' for making me reproduce this NPE! So handling the ListView adapter in the onResume function is the correct way to handle this, right? But why does the 'lvAdapter' variable become null on resume?Hullo
It becomes null because onDestroy is called or else the underlying activity has been destroyed by the OS to reclaim memory. As reference read up on the Activity lifecycle, but when someone navigates away from your app on low memory devices it becomes a candidate for being recycled/killed and thus nulled out from under you.Flavourful
If this helped can you please give the answer a vote and mark it as correct?Flavourful

© 2022 - 2024 — McMap. All rights reserved.