Why getContext() in fragment sometimes returns null?
Asked Answered
H

5

47

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) {

        }
    });
Heavyfooted answered 27/12, 2017 at 6:49 Comment(8)
did you try for getActivity() in onCreateView() ?Ragg
No. If I get more crashes then I'm gonna try.Heavyfooted
I would recommend to use getActivity. As we use this in Activity like that we use getActivity in Fragment's onCreateView().Ragg
use getActivity() (it will return context of activity) instead of getContext().Dody
getContext() in onCreateView() should never return null. Are you sure the problem is with that? or something else.Blush
post the code of your fragmentCaponize
@Shalauddin posted.Heavyfooted
@Blush edited my question. posted onCreateViewHeavyfooted
S
31

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, your callback may run after that point in the lifecycle of the fragment. What I mean is that the user can enter that screen (fragment) and go to another 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 suggestions 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

Stylopodium answered 27/12, 2017 at 7:46 Comment(1)
Sure, an asynchronous operation can access context after a fragment has already been detached. It his case we should check that it exists with isAdded() method. I caught exceptions many times when left a fragment after a request started.Stairwell
T
50

As the accepted answer says, you must 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 the first revision of the question.

Use onAttach() to get Context. Most of the cases, you don't need this solution so use this solution only if any other solution does not work. And you may create a chance to activity leak, so be cleaver while using it. You may require to make context null again when you leave from fragment

// 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.

Tauten answered 27/12, 2017 at 6:54 Comment(7)
should we have to override onAttach() ?? Or we should use getActivity ? I have little confusion. I always use getActivity to get reference. Please clear itRagg
getActivity() can also return null sometime (Read at developer.android.com/reference/android/support/v4/app/… ). So overriding onAttach() is best option to take context object.Tauten
@Pankaj Ok I'll try it and update my app and post the result.Heavyfooted
Seriously? Should you ever store the context like that? Isn't this because the view is gone before the callback happens?Northeasterly
This is a bad practice to retain context. It becomes null for a reason: this example teaches to retained and potentially access potentially invalid context (later detached). Where is the cleanup of it?Patricapatrice
@Patricapatrice It depends on how your concept is clear, and how you read the answer. My answer clearly says about looking for the reason on NPE. And if your concept is clear, then you know very well that you need to make it null on deattach. Anyway, I am updating the answer.Tauten
Note, as good as all this sounds, sometimes this is not feasible because we're not the ones requesting the context in our own code. For example: getString internally calls requireContext so whether or not we remembered a context from onAttach, we still need to make an if (getContext() != null) safety checkMazur
S
31

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, your callback may run after that point in the lifecycle of the fragment. What I mean is that the user can enter that screen (fragment) and go to another 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 suggestions 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

Stylopodium answered 27/12, 2017 at 7:46 Comment(1)
Sure, an asynchronous operation can access context after a fragment has already been detached. It his case we should check that it exists with isAdded() method. I caught exceptions many times when left a fragment after a request started.Stairwell
A
4

When you are sure fragment is attached to its host(onResume, onViewCreated, etc) use this instead of getContext() :

requireContext()

It will not be inspected by lint, but it will throw an Exception if context detached!

Then you should check nullity by if clouse (or some thing) or be sure that if the program reaches this line, it isn't null.

In retrofit calls or retrofit (and may others in the same way), it will return a disposable that must be cleared or disposed before onDestroy.

Adon answered 8/2, 2020 at 15:47 Comment(0)
B
3

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://mcmap.net/q/80547/-fragment-myfragment-not-attached-to-activity

Blush answered 27/12, 2017 at 13:14 Comment(0)
C
1

Write a common method that will ensure you will never get null Context.

public class BaseFragment extends Fragment {
    private Context contextNullSafe;

     @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
         /*View creation related to this fragment is finished here. So in case if contextNullSafe is null
         * then we can populate it here.In some discussion in - https://mcmap.net/q/126331/-getactivity-returns-null-in-fragment-function
         * and https://mcmap.net/q/128934/-why-getcontext-in-fragment-sometimes-returns-null,
         * there are some recommendations to call getContext() or getActivity() after onCreateView() and
         * onViewCreated() is called after the onCreateView() call every time */
        if (contextNullSafe == null) getContextNullSafety();
    }


   @Override
    public void onAttach(@NonNull Context context) {
        super.onAttach(context);
        contextNullSafe = context;
    }

    /**CALL THIS IF YOU NEED CONTEXT*/
    public Context getContextNullSafety() {
                if (getContext() != null) return getContext();
                if (getActivity() != null) return getActivity();
                if (contextNullSafe != null) return contextNullSafe;
                if (getView() != null && getView().getContext() != null) return getView().getContext();
                if (requireContext() != null) return requireContext();
                if (requireActivity() != null) return requireActivity();
                if (requireView() != null && requireView().getContext() != null)
                    return requireView().getContext();
                
                return null;
            
        }

    /**CALL THIS IF YOU NEED ACTIVITY*/
    public FragmentActivity getActivityNullSafety() {
        if (getContextNullSafety() != null && getContextNullSafety() instanceof FragmentActivity) {
            /*It is observed that if context it not null then it will be
             * the related host/container activity always*/
            return (FragmentActivity) getContextNullSafety();
        }
        return null;
    }
Countersignature answered 11/4, 2021 at 17:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.