Случайный NPE при доступе к фрагменту

Иногда я получаю NullPointerException при вводе фрагмента. Это происходит, когда приложение было в фоновом режиме в течение длительного времени, а затем я открываю его и прокручиваю на этот фрагмент.

public class SummaryFragment extends Fragment implements FragmentLifecycle {

    private static final String TAG = "DTAG";
    private DateFormat dateFormatName;
    private Preference prefs;
    private List<String> monthList;
    private TextView totalTimeFullTv;
    private TextView totalTimeNetTv;
    private TextView averageTimeTv;
    private TextView overUnderTv;
    private TextView minTimeTv;
    private TextView maxTimeTv;
    private TextView vacationsTv;
    private TextView sickTv;
    private TextView headlineTv;
    private TextView overUnderTvH;
    private OnFragmentInteractionListener mListener;

    public SummaryFragment() {
        // Required empty public constructor
    }


    public static SummaryFragment newInstance(String param1, String param2) {
        SummaryFragment fragment = new SummaryFragment();
        return fragment;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {

        View RootView = inflater.inflate(R.layout.fragment_summary, container, false);

        dateFormatName = new SimpleDateFormat(getResources().getString(R.string.month_text));
        monthList = Arrays.asList(new DateFormatSymbols().getMonths());
        prefs = new Preference(GeneralAdapter.getContext());


        totalTimeFullTv = RootView.findViewById(R.id.textView_sum_ttf);
        totalTimeNetTv = RootView.findViewById(R.id.textView_sum_ttn);
        averageTimeTv = RootView.findViewById(R.id.textView_sum_av);
        overUnderTv = RootView.findViewById(R.id.textView_sum_ou);
        overUnderTvH = RootView.findViewById(R.id.textView_sum_ou_h);


        minTimeTv = RootView.findViewById(R.id.textView_sum_min);
        maxTimeTv = RootView.findViewById(R.id.textView_sum_max);
        vacationsTv = RootView.findViewById(R.id.textView_sum_vac);
        sickTv = RootView.findViewById(R.id.textView_sum_sick);
        headlineTv= RootView.findViewById(R.id.textView_sum_headline);

        return RootView;
    }

    private void refreshData() {

        if (prefs == null)
        {
            prefs = new Preference(GeneralAdapter.getContext());
        }

        String month = prefs.getString(Preference.CURRENT_MONTH);

        MonthData monthData = Calculators.CalculateLocalData(MainActivity.db.getAllDays(month));

        totalTimeFullTv.setText(monthData.getTotalTimeFull()); //Crash here
        totalTimeNetTv.setText(monthData.getTotalTimeNet());
        averageTimeTv.setText(monthData.getAverageTime());
        overUnderTv.setText(monthData.getOverUnder());
        if (monthData.getOverUnderFloat()<0)
        {
            overUnderTvH.setText(R.string.sum_over_time_neg);
            overUnderTv.setTextColor(ContextCompat.getColor(GeneralAdapter.getContext(),R.color.negative_color));
        }
        else
        {
            overUnderTvH.setText(R.string.sum_over_time_pos);
            overUnderTv.setTextColor(ContextCompat.getColor(GeneralAdapter.getContext(),R.color.positive_color));
        }

        minTimeTv.setText(monthData.getMinTime());
        maxTimeTv.setText(monthData.getMaxTime());
        vacationsTv.setText(""+monthData.getVacations());
        sickTv.setText(""+monthData.getSick());
        headlineTv.setText(month);
    }

    public void onButtonPressed(Uri uri) {
        if (mListener != null) {
            mListener.onFragmentInteraction(uri);
        }
    }

    @Override
    public void onAttachFragment(Fragment childFragment) {
        super.onAttachFragment(childFragment);

    }

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

    @Override
    public void onPauseFragment() {

    }

    @Override
    public void onResumeFragment()
    {
        refreshData();
    }


    public interface OnFragmentInteractionListener {
        // TODO: Update argument type and name
        void onFragmentInteraction(Uri uri);
    }
}

MainActivity viewPager:

viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
            int currentPosition = 0;

            @Override
            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
            }

            @Override
            public void onPageSelected(int position) {

                FragmentLifecycle fragmentToHide = (FragmentLifecycle) adapter.getItem(currentPosition);
                fragmentToHide.onPauseFragment();

                FragmentLifecycle fragmentToShow = (FragmentLifecycle) adapter.getItem(position);
                fragmentToShow.onResumeFragment(); //Crash start

                currentPosition = position;
            }

            @Override
            public void onPageScrollStateChanged(int state) {

            }
        });

Журнал

                                            E/AndroidRuntime: FATAL EXCEPTION: main
                                               Process: michlind.com.workcalendar, PID: 25038
                                               java.lang.NullPointerException: Attempt to invoke virtual method 'void android.widget.TextView.setText(java.lang.CharSequence)' on a null object reference
                                                   at michlind.com.workcalendar.mainfragments.SummaryFragment.refreshData(SummaryFragment.java:99)
                                                   at michlind.com.workcalendar.mainfragments.SummaryFragment.onResumeFragment(SummaryFragment.java:147)
                                                   at michlind.com.workcalendar.activities.MainActivity.onPageSelected(MainActivity.java:84)
                                                   at android.support.v4.view.ViewPager.dispatchOnPageSelected(ViewPager.java:1941)
                                                   at android.support.v4.view.ViewPager.scrollToItem(ViewPager.java:680)
                                                   at android.support.v4.view.ViewPager.setCurrentItemInternal(ViewPager.java:664)
                                                   at android.support.v4.view.ViewPager.onTouchEvent(ViewPager.java:2257)
                                                   at android.view.View.dispatchTouchEvent(View.java:11776)
                                                   at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2962)
                                                   at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2643)
                                                   at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2968)
                                                   at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2657)
                                                   at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2968)
                                                   at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2657)
                                                   at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2968)
                                                   at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2657)
                                                   at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2968)
                                                   at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2657)
                                                   at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2968)
                                                   at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2657)
                                                   at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2968)
                                                   at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2657)
                                                   at com.android.internal.policy.DecorView.superDispatchTouchEvent(DecorView.java:448)
                                                   at com.android.internal.policy.PhoneWindow.superDispatchTouchEvent(PhoneWindow.java:1829)
                                                   at android.app.Activity.dispatchTouchEvent(Activity.java:3307)
                                                   at android.support.v7.view.WindowCallbackWrapper.dispatchTouchEvent(WindowCallbackWrapper.java:68)
                                                   at com.android.internal.policy.DecorView.dispatchTouchEvent(DecorView.java:410)
                                                   at android.view.View.dispatchPointerEvent(View.java:12015)
                                                   at android.view.ViewRootImpl$ViewPostImeInputStage.processPointerEvent(ViewRootImpl.java:4795)
                                                   at android.view.ViewRootImpl$ViewPostImeInputStage.onProcess(ViewRootImpl.java:4609)
                                                   at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4147)
                                                   at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:4200)
                                                   at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:4166)
                                                   at android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:4293)
                                                   at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:4174)
                                                   at android.view.ViewRootImpl$AsyncInputStage.apply(ViewRootImpl.java:4350)
                                                   at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4147)
                                                   at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:4200)
                                                   at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:4166)
                                                   at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:4174)
                                                   at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4147)
                                                   at android.view.ViewRootImpl.deliverInputEvent(ViewRootImpl.java:6661)
                                                   at android.view.ViewRootImpl.doProcessInputEvents(ViewRootImpl.java:6635)
                                                   at android.view.ViewRootImpl.enqueueInputEvent(ViewRootImpl.java:6596)
                                                   at android.view.ViewRootImpl$WindowInputEventReceiver.onInputEvent(ViewRootImpl.java:6764)
                                                   at android.view.InputEventReceiver.dispatchInputEvent(InputEventReceiver.java:186)
                                                   at android.os.MessageQueue.nativePollOnce(Native Method)
                                                   at android.os.MessageQueue.next(MessageQueue.java:325)
                                                   at android.os.Looper.loop(Looper.java:142)
                                                   at android.app.ActivityThread.main(ActivityThread.java:6494)

UPDATE:

В конечном итоге я использовал:

@Override
public void onPageSelected(int position) {

    Fragment fragment = adapter.getFragment(position);
    if (fragment != null) {
        fragment.onResume();
    }
}

В моем MainActivity и используем onResume() для каждого фрагмента. И это решение для адаптера: http://thedeveloperworldisyours.com/android/update-fragment-viewpager/

Ответ 1

Проблема заключается в том, что вы пытаетесь получить доступ к представлениям слишком рано: иерархия просмотров еще не создана на этом этапе.

Если вы разместите событие, которое будет иметь место в следующем кадре, вы гарантированно, что иерархия представлений уже будет настроена:

@Override
public void onResumeFragment() {
    new Handler().post(new Runnable() {
        @Override
        public void run() {
            refreshData();
        }
    });
}

Ответ 2

У меня возникла такая же проблема, когда я реализовал пользовательские жизненные циклы для ViewPager. Я думаю, вы используете FragmentStatePagerAdapter для заполнения фрагментов с помощью ViewPager. Как известно, FragmentStatePagerAdapter уничтожает все фрагменты, когда они теряют фокус. Нам нужно предоставить тот же объект для каждой страницы, используя singleton pattern.

В вашем коде создание фрагмента может быть как показано ниже для одноэлементного паттерна.

private SummaryFragment mInstance;

private SummaryFragment() {
        // Required empty public constructor
    }

public static SummaryFragment newInstance(String param1, String param2) {
        if(mInstance == null)
        mInstance = new SummaryFragment();
        return mInstance;
    }

Это решило мою проблему. Если это не сработает для вас? Можете ли вы поделиться своим классом PagerAdapter.

Ответ 3

onResumeFragment() вызывается перед созданием всех представлений этого фрагмента. Сначала попробуйте воссоздать newInstance, а затем вызовите onResumeFragment интерфейса FragmentLifeCycle в своей деятельности.

Ответ 4

ViewPager сохраняет несколько элементов с обеих сторон (например, фрагменты возобновлены), однако FragmentPagerAdapter использует Fragment.setUserVisibleHint для указания, какой элемент является текущим. Используйте это вместо.

Здесь, что делать, чтобы использовать видимый пользовательский намек:

  • Удалите OnPageChangeListener.
  • Поверните интерфейс FragmentLifecycle.
  • Установите свой фрагмент так:

(в Котлине, но вы получите суть)

override fun setUserVisibleHint(isVisibleToUser: Boolean) {
    super.setUserVisibleHint(isVisibleToUser)

    if (isVisibleToUser && isResumed) {
        // Do stuff.
    }
}

override fun onResume() {
    super.onResume()

    if (userVisibleHint) {
        // Do the same stuff.
    }
}

Дополнительная информация

FragmentPagerAdapter.getItem - это метод factory. Он всегда должен был вернуть вам новый экземпляр фрагмента. Если вы попытались кэшировать их, удалите кеш (1) и не используйте getItem самостоятельно (2).

  • Код, который иногда сбой, а иногда не является b **** для отладки. Это может быть вызвано повторным использованием фрагментов, когда вы не должны.
  • Новый экземпляр фрагмента не подключен, не имеет причин создавать представления и будет собираться мусор, как только вы покинете onPageSelected.

Ответ 5

Вы неправильно используете OnPageChangeListener. Это не безопасный способ контролировать события жизненного цикла просмотра. Вам нужно использовать PagerAdapter вместе с ViewPager и переопределить обратные вызовы instantiateItem/destroyItem.

Смотрите этот пример: http://android-er.blogspot.com/2014/04/example-of-viewpager-with-custom.html

PagerAdapter - это ViewPager, что ListAdapter для ListView, вам нужны оба, чтобы ваша система работала правильно.

Ответ 6

Используйте onViewCreated() метод обратного вызова фрагмента, чтобы обновить данные таким образом, чтобы вы были уверены, что все ваши представления выложены идеально.

@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
   refreshData();

}

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

Ответ 7

ПРОБЛЕМА

Жизненный цикл a Fragment является независимым. Вы не можете быть уверены, что когда регистрируется onPageSelected(), этот фрагмент уже выложен. Это асинхронное событие. Поэтому вы не можете полагаться на этот обратный вызов.

Но, с другой стороны, вы не можете также полагаться только на onResume(), так как в ViewPager предварительно загружаются страницы, смежные с текущей видимой страницей.

Решение

В принципе вам понадобится refreshData(), когда фрагмент будет видимым для пользователя и будет активно работать. Определение onResume() говорит то же самое:

Вызывается, когда фрагмент отображается пользователю и активно работает. (...)

Так просто наберите refreshData() в onResume() вашего фрагмента и не волнуйтесь, если вы заметили, что этот вызов вызван, в то время как ViewPager на самом деле не показывал эту страницу.

Ответ 8

Как и большинство людей, вы должны убедиться, что ваш фрагмент активен и видим для пользователя. У меня была аналогичная проблема. Я использовал onHiddenChanged, чтобы решить, когда перезагружать данные.

@Override
public void onHiddenChanged(boolean hidden) {
    super.onHiddenChanged(hidden);
    if (!hidden) {
        refreshData();
    }
}

Ответ 9

Вы должны раздуть свой макет в onCreateView, но не должны инициализировать другие представления, используя findViewById в onCreateView.

вот код из FragmentManager

  // This calls onCreateView()
f.mView = f.performCreateView(f.getLayoutInflater(f.mSavedFragmentState), null, f.mSavedFragmentState);

// Null check avoids possible NPEs in onViewCreated
// It also safe to call getView() during or after onViewCreated()
if (f.mView != null) {
    f.mView.setSaveFromParentEnabled(false);
    if (f.mHidden) f.mView.setVisibility(View.GONE);
    f.onViewCreated(f.mView, f.mSavedFragmentState);
}

Лучше выполнять любое задание subviews для полей в onViewCreated. Это связано с тем, что фреймворк выполняет автоматическую проверку нуля, чтобы убедиться, что иерархия представления фрагментов была создана и завышена (если используется файл макета XML) должным образом.

после создания представления, а затем инициализируйте свои представления.

Ответ 10

Добавьте эту проверку в метод refreshData():

if (isAdded() && getActivity() != null)