Why getContext() in fragment sometimes returns null?

Why getContext() sometimes returns null? I pass context to LastNewsRVAdapter.java as an argument. But LayoutInflater.from(context) sometimes crashes. I'm getting a few crash reports on play console. Below is crash report.

java.lang.NullPointerException
com.example.adapters.LastNewsRVAdapter.<init>

java.lang.NullPointerException: 
at android.view.LayoutInflater.from (LayoutInflater.java:211)
at com.example.adapters.LastNewsRVAdapter.<init> (LastNewsRVAdapter.java)
at com.example.fragments.MainFragment$2.onFailure (MainFragment.java)
or                     .onResponse (MainFragment.java)
at retrofit2.ExecutorCallAdapterFactory$ExecutorCallbackCall$1$1.run 
(ExecutorCallAdapterFactory.java)
at android.os.Handler.handleCallback (Handler.java:808)
at android.os.Handler.dispatchMessage (Handler.java:103)
at android.os.Looper.loop (Looper.java:193)
at android.app.ActivityThread.main (ActivityThread.java:5299)
at java.lang.reflect.Method.invokeNative (Method.java)
at java.lang.reflect.Method.invoke (Method.java:515)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run 
(ZygoteInit.java:825)
at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:641)
at dalvik.system.NativeStart.main (NativeStart.java)

This is LastNewsRVAdapter.java constructor.

public LastNewsRVAdapter(Context context, List<LatestNewsData> 
    latestNewsDataList, FirstPageSideBanner sideBanner) {
    this.context = context;
    this.latestNewsDataList = latestNewsDataList;
    inflater = LayoutInflater.from(context);
    this.sideBanner = sideBanner;
}

This is the code onCreateView inside Fragment

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {
    // Inflate the layout for this fragment

    final View view = inflater.inflate(R.layout.fragment_main_sonku_kabar, container, false);
    tvSonkuKabar = view.findViewById(R.id.textview_sonku_kabar_main);
    tvNegizgiKabar = view.findViewById(R.id.textview_negizgi_kabar_main);
    refresh(view);

    final SwipeRefreshLayout swipeRefreshLayout = (SwipeRefreshLayout) view.findViewById(R.id.mainRefreshSonkuKabar);
    swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
        @Override
        public void onRefresh() {
            refresh(view);
            swipeRefreshLayout.setRefreshing(false);
        }
    });
    setHasOptionsMenu(true);
    return view;
}

This is refresh method inside Fragment

private void refresh(final View view) {
    sideBanner = new FirstPageSideBanner();

    final RecyclerView rvLatestNews = (RecyclerView) view.findViewById(R.id.recViewLastNews);
    rvLatestNews.setLayoutManager(new LinearLayoutManager(getContext(), LinearLayoutManager.VERTICAL, false));
    rvLatestNews.setNestedScrollingEnabled(false);
    App.getApiService().getLatestNews().enqueue(new Callback<LatestNews>() {
        @Override
        public void onResponse(Call<LatestNews> call, Response<LatestNews> response) {
            if (response.isSuccessful() && response.body().isSuccessfull()){
                adapter = new LastNewsRVAdapter(getContext(), response.body().getData(), sideBanner);
                rvLatestNews.setAdapter(adapter);
                tvSonkuKabar.setVisibility(View.VISIBLE);
            }
        }

        @Override
        public void onFailure(Call<LatestNews> call, Throwable t) {

        }
    });

Answers


First of all, as you can see on this link, the method onCreateView() inside the fragment's lifecycle comes after onAttach(), so you should have already a context at that point. You may wonder, why does getContext() return null then? the problem lies on where you are creating your adapter:

App.getApiService().getLatestNews().enqueue(new Callback<LatestNews>() {
        @Override
        public void onResponse(Call<LatestNews> call, Response<LatestNews> response) {
            if (response.isSuccessful() && response.body().isSuccessfull()){
                adapter = new LastNewsRVAdapter(getContext(), response.body().getData(), sideBanner);
                rvLatestNews.setAdapter(adapter);
                tvSonkuKabar.setVisibility(View.VISIBLE);
            }
        }

        @Override
        public void onFailure(Call<LatestNews> call, Throwable t) {

        }
    });

Though you are specifying a callback in onCreateView(), that does not mean the code inside that callback will run at that point. It will run and create the adapter after the network call is done. With that in mind, you callback may run after that point in the lifecycle of the fragment. What I mean is that the user can enter to that screen (fragment) and go to other fragment or return to the previous one before the network request finishes (and the callback runs). If that happens, then getContext() could return null if the user leaves the fragment (onDetach() may have been called).

Besides, you can have memory leaks also, in case the activity is destroyed before your network request finishes. So you have two issues there.

My solutions to solve those issues are:

  1. in order to avoid the null pointer exception and the memory leak, you should cancel the network request when the onDestroyView() inside the fragment is being called (retrofit returns an object that can cancel the request: link).

  2. Another option that will prevent the null pointer exception is to move the creation of the adapter LastNewsRVAdapter outside the callback and keep a reference to it in the fragment. Then, use that reference inside the callback to update the content of the adapter: link


As accepted answer, you should look into the way you are using and caching context. Somehow you are leaking context that is causing Null Pointer Exception.


Below Answer is as per first revision of question.

Use onAttach() to get Context.

// Declare Context variable at class level in Fragment
private Context mContext;

// Initialise it from onAttach()
@Override
public void onAttach(Context context) {
    super.onAttach(context);
    mContext = context;
}

This context will be available in onCreateView, so You should use it.


From Fragment Documentation

Caution: If you need a Context object within your Fragment, you can call getContext(). However, be careful to call getContext() only when the fragment is attached to an activity. When the fragment is not yet attached, or was detached during the end of its lifecycle, getContext() will return null.


So getContext() is not called in onCreateView(). It's called inside the onResponse() callback which can be invoked anytime. If it's invoked when your fragment is not attached to activity, getContext() will return null.

The ideal solution here is to Cancel the request when the activity/fragment is not active (like user pressed back button, or minimized the app).

Another solution is to simply ignore whatever is in onResponse() when your fragment is not attached to your activity, like this: https://stackoverflow.com/a/10941410/4747587


Need Your Help

React Navigation pass props in TabNavigator

javascript android react-native react-navigation

I have props what are loaded from the server on the initial screen. I want to pass them to the rest of the tab screens. However, I have not found any examples online. I know of the screenProps, but...

serialize and deserialize enum with Gson

java serialization enums deserialization gson

How can i serialize and deserialize a simple enum like this with gson 2.2.4 ?