Почему getContext() в фрагменте иногда возвращает null?

Почему getContext() иногда возвращает null? Я LastNewsRVAdapter.java контекст LastNewsRVAdapter.java в качестве аргумента. Но LayoutInflater.from(context) иногда сбой. Я получаю несколько отчетов о сбоях на игровой консоли. Ниже приведен краткий отчет.

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)

Это конструктор LastNewsRVAdapter.java.

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

Это код onCreateView внутри фрагмента

@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;
}

Это метод refresh внутри фрагмента

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

        }
    });

Ответ 1

Прежде всего, как вы можете видеть по этой ссылке, метод onCreateView() внутри жизненного цикла фрагмента приходит после onAttach(), поэтому у вас должен быть уже контекст в этой точке. Вы можете задаться вопросом, почему getContext() возвращает null? проблема заключается в том, где вы создаете свой адаптер:

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

        }
    });

Хотя вы указываете обратный вызов в onCreateView(), это не означает, что код внутри этого обратного вызова будет выполняться в этой точке. Он будет запускаться и создавать адаптер после завершения сетевого вызова. Имея это в виду, вы можете выполнить обратный вызов после этой точки жизненного цикла фрагмента. Я имею в виду, что пользователь может войти на этот экран (фрагмент) и перейти к другому фрагменту или вернуться к предыдущему, прежде чем сетевой запрос завершится (и будет выполняться обратный вызов). Если это произойдет, getContext() может вернуть значение null, если пользователь покидает фрагмент (может быть вызван onDetach()).

Кроме того, вы также можете иметь утечки памяти, если активность будет уничтожена до завершения вашего сетевого запроса. Таким образом, у вас есть два вопроса.

Мои решения для решения этих проблем:

  1. чтобы избежать исключения нулевого указателя и утечки памяти, вы должны отменить сетевой запрос при вызове onDestroyView() внутри фрагмента (retrofit возвращает объект, который может отменить запрос: ссылка).

  2. Другой вариант, который предотвратит исключение нулевого указателя, - это переместить создание адаптера LastNewsRVAdapter за пределы обратного вызова и сохранить ссылку на него в фрагменте. Затем используйте эту ссылку внутри обратного вызова, чтобы обновить содержимое адаптера: link

Ответ 2

Как принятый ответ, вы должны изучить то, как вы используете и кешируете контекст. Как-то вы просачиваете контекст, вызывающий исключение Null Pointer.


Ниже приведен первый вопрос.

Используйте onAttach() для получения контекста.

// 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;
}

Этот контекст будет доступен в onCreateView, поэтому вы должны его использовать.


Из фрагментарной документации

Внимание: если вам нужен объект Context внутри вашего Fragment, вы можете вызвать getContext(). Однако будьте осторожны, чтобы вызвать getContext() только тогда, когда фрагмент привязан к активности. Когда фрагмент еще не прикреплен или был отсоединен в конце его жизненного цикла, getContext() вернет значение null.

Ответ 3

Поэтому getContext() не onCreateView() в onCreateView(). Он вызван внутри обратного вызова onResponse() который может быть вызван в любое время. Если он вызывается, когда ваш фрагмент не связан с активностью, getContext() возвращает null.

Идеальное решение здесь - Отменить запрос, когда активность/фрагмент не активна (например, кнопка нажата на кнопку или минимизировала приложение).

Другим решением является просто игнорировать все, что находится в onResponse() когда ваш фрагмент не привязан к вашей активности, например: fooobar.com/info/24342/...