Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

NullPointerException on Fragment Listener

I have a Activity with ViewPager, In ViewPager Adapter I provide Fragment for each position.

An example Fragment is DebugFragment. I have written the source code below.

public class DebugFragment extends android.support.v4.app.Fragment {

private OnFragmentInteractionListener mListener;

public interface OnFragmentInteractionListener {
    void onFragmentInteraction(int someValue);
}

public static DebugFragment newInstance() {
    DebugFragment fragment = new DebugFragment();
    Bundle args = new Bundle();
    fragment.setArguments(args);
    return fragment;
}

private BroadcastReceiver mMessageReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        mListener.onFragmentInteraction(0);
    }
};

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    LocalBroadcastManager.getInstance(getContext()).registerReceiver(mMessageReceiver,
            new IntentFilter("com.android.example.INITIAL_REQUEST"));
}

@Override
public void onDestroy() {
    LocalBroadcastManager.getInstance(getContext()).unregisterReceiver(mMessageReceiver);
    super.onDestroy();
}

@Override
public void onAttach(Context context) {
    super.onAttach(context);
    if (context instanceof OnFragmentInteractionListener) {
        mListener = (OnFragmentInteractionListener) context;
    } else {
        throw new RuntimeException(context.toString()
                + " must implement OnFragmentInteractionListener");
    }
}

@Override
public void onDetach() {
    super.onDetach();
    mListener = null;
}

@Override
public void onResume() {
    super.onResume();
    getUserData();
}

public void getUserData() {
 // Inside Background Thread
    if (getActivity() == null) {
        return;
    }
    getActivity().runOnUiThread(new Runnable() {
        @Override
        public void run() {
            mListener.onFragmentInteraction(0); // This line throws NPE
        }
    });
}

My Activity implementation below.

public class DebugActivity extends AppCompatActivity implements
    DebugFragment.OnFragmentInteractionListener {

    // Other Activity Callback

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == REQUEST_CODE) {
            if (resultCode == Activity.RESULT_OK) {
                DebugFragment debugFragment = ((DebugFragment) mViewPagerAdapter.getRegisteredFragment(2));
                if (debugFragment != null) {
                    debugFragment.getUserData();
                }
            }
        }
    }
}

I call getUserData of my DebugFragment from onResume of Fragment, BroadcastReceiver, OnActivityResult of Activity.

Sometime I get NullPointerException in getUserData while trying to access FragmentListener i.e mListener. I want to know Why?

As i am already checking for Activity null. Isn't this enough. Do i have to check for null of mListener also? It would be great if somebody will explain me the case where activity will not be null but my mListener will be null. I have kept my activity in Portrait mode only.

Edit

My Adapter code

public abstract class TabPagerAdapter extends FragmentPagerAdapter {

    public TabPagerAdapter(FragmentManager fm) {
        super(fm);
    }

    public abstract View getTabView(int position);
}

public class SecondaryPagerAdapter extends TabPagerAdapter {

    private static final int NUM_PAGES = 5;

    private String tabTitles[] = new String[] { "Today", "New", "Calendar", "In-progress", "Invoices" };
    private int[] imageResId = { R.drawable.ic_tab_hired_pro, R.drawable.ic_tab_history,
            R.drawable.ic_tab_today, R.drawable.ic_tab_inprogress, R.drawable.ic_tab_invoices };
    SparseArray<Fragment> registeredFragments = new SparseArray<Fragment>();

    public SecondaryPagerAdapter(FragmentManager fm) {
        super(fm);
    }

    @Override
    public Fragment getItem(int position) {
        switch (position) {
            case 0:
            case 1:
            case 3:
                return ServiceRequestFragment.newInstance(tabTitles[position]);
            case 2:
                return DebugFragment.newInstance();
            case 4:
                return InvoicesFragment.newInstance();
            default:
                throw new RuntimeException("No fragment for this position");
        }
    }

    @Override
    public int getCount() {
        return NUM_PAGES;
    }

    @Override
    public View getTabView(int position) {
        CustomTab customTab = new CustomTab(DashBoardActivity.this);
        customTab.bindWith(imageResId[position], tabTitles[position]);
        return customTab;
    }

    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        Fragment fragment = (Fragment) super.instantiateItem(container, position);
        registeredFragments.put(position, fragment);
        return fragment;
    }

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        registeredFragments.remove(position);
        super.destroyItem(container, position, object);
    }

    public Fragment getRegisteredFragment(int position) {
        return registeredFragments.get(position);
    }
}

From my Activity i call it like

mSecondaryPagerAdapter = new SecondaryPagerAdapter(getSupportFragmentManager());
mSecondaryPager = (ViewPager) findViewById(R.id.dashboard_pager);
mSecondaryPager.setOffscreenPageLimit(4);
mSecondaryPager.setAdapter(mSecondaryPagerAdapter);
like image 430
shubendrak Avatar asked Dec 05 '22 18:12

shubendrak


1 Answers

When you call getActivity().runOnUiThread(new Runnable() {}), this enqueues that Runnable to run on the UI thread after everything else that is already enqueued on the UI thread, which may include calls to onDestroy(), which would set mListener to null. This means that it's possible for your app to be closing while a broadcast comes in. While this shouldn't be a problem in the normal case because you unregister your receiver before clearing mListener, it is possible that the Runnable has been enqueued after this, which is why the listener is null when it executes.

To avoid the NPE, you should check for mListener == null in the Runnable. However, this still means that the callback is scheduled after your Fragment is destroyed, and because of that, your Fragment instance is leaked. The best thing to do is to create a Handler and post the Runnable to it instead of calling runOnUiThread(). Then, in onDestroy(), call mHandler.removeCallbacksAndMessages(null), which essentially clears the queue, so that the Runnable won't be called at all.

like image 189
Jschools Avatar answered Dec 10 '22 12:12

Jschools