Случайный 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)