Страницы

Поиск по вопросам

Показаны сообщения с ярлыком fragment-manager. Показать все сообщения
Показаны сообщения с ярлыком fragment-manager. Показать все сообщения

понедельник, 24 февраля 2020 г.

java.lang.NullPointerException: Attempt to write to field 'int android.support.v4.app.Fragment.mNextAnim' on a null object reference [дубликат]

#java #android #android_fragment #fragment_transaction #fragment_manager


        
             
                
                    
                        
                            На этот вопрос уже даны ответы здесь:
                            
                        
                    
                
                        
                            Что такое Null Pointer Exception и как его исправить?
                                
                                    (4 ответа)
                                
                        
                                Закрыт 3 года назад.
            
                    
На Nexus 7 c Android 5.0(не 4.4) происходит крах с Exception из заголовка. После
непродолжительных поисков был найден следующий ответ. Меня он не устраивает, так как
в силу обстоятельств не могу отказаться от android.support.v4.app.  

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

String tag = FRAGMENT_STATE;
FragmentManager fragmentManager = getSupportFragmentManager();
...
stateFragment = new Fragment();
...
if (stateFragment != null) {
...
android.support.v4.app.FragmentTransaction transaction = fragmentManager.beginTransaction();//.support.v4
transaction.replace(R.id.content_frame, stateFragment, tag);
...          
transaction.commitAllowingStateLoss();  


Еще есть следующий отрывок:

  //Типо подключаю фрагменты
  if(onerFragment == null) Logi.d("OneFragment is Null");
  //if (oneFragment == null || twoeFragment == null) {
  Logi.d("блок подключения фрагментов");
  fragmentTransaction = fragmentManager.beginTransaction();
  Logi.d("oneGragment constructer before");
  oneFragment = new OneGragment();
  fragmentTransaction.add(R.id.one_fragment, oneFragment);
  Logi.d("TwoFragment constructer before");
  twoFragment = new TwoFragment();
  fragmentTransaction.add(R.id.two_fragment, twoFragment);

  fragmentTransaction.commitAllowingStateLoss();  


Логи:


  FATAL EXCEPTION: main
   Process: название пакета, PID: 7755
   java.lang.NullPointerException: Attempt to write to field 'int android.support.v4.app.Fragment.mNextAnim'
on a null object reference
      at android.support.v4.app.BackStackRecord.run(BackStackRecord.java:709)
      at android.support.v4.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:1501)
      at android.support.v4.app.FragmentManagerImpl$1.run(FragmentManager.java:458)
      at android.os.Handler.handleCallback(Handler.java:739)
      at android.os.Handler.dispatchMessage(Handler.java:95)
      at android.os.Looper.loop(Looper.java:135)
      at android.app.ActivityThread.main(ActivityThread.java:5221)
      at java.lang.reflect.Method.invoke(Native Method)
      at java.lang.reflect.Method.invoke(Method.java:372)
      at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:899)
      at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:694)  


P.S Устройства у меня нет, логи взяты из GoogleTestLab, тестирование на эмуляторе
тоже не смогу. есть ли у кого-нибудь идеи что делать с этим "эксепшеном"?
    


Ответы

Ответ 1



Вот здесь сказано: Это происходит из-за того, что Вы вызываете методы remove(), add() и т.п. со значением null. Поразумевается, что replace() - это remove() и за ним add().

воскресенье, 15 декабря 2019 г.

Изменить fragment 2 из fragment 1

#java #android #android_fragment #fragment_manager


Условия: Имеется приложение с Activity и тремя fragment'ами, которые можно листать
смахивая влево - вправо как страницы (Этот эффект осуществляется благодаря ViewPager).

На экране всегда виден один из трёх фрагментов. При этом, при перелистывании на любой
из фрагментов, следующий за ним (если он существует) автоматически подгружается, предыдущий
остаётся в памяти, остальные - выгружаются из памяти.

То есть работает так:


Когда виден фрагмент 1: 1 (загружен/видно) - 2 (подгружен)       - 3 (выгружен)
Когда виден фрагмент 2: 1 (подгружен)       - 2 (загружен/видно) - 3 (подгружен)
Когда виден фрагмент 3: 1 (выгружен)       - 2 (подгружен)       - 3 (загружен/видно)




Код создания фрагментов (в Activity):

public class MainActivity extends FragmentActivity
{
    public final int FRAGMENT_ONE = 0; //идентификатор первого фрагмента
    public final int FRAGMENT_TWO = 1; //идентификатор второго фрагмента
    public final int FRAGMENT_THREE = 2; //идентификатор третьего фрагмента
    /** список фрагментов для отображения. */
    private final List _fragments = new ArrayList();


                               *** Остальной код ***


    @Override
    public void onResume()
    {
        super.onResume();

        //Создаем фрагменты.
        _fragments.add(FRAGMENT_ONE, new FirstFragment());
        _fragments.add(FRAGMENT_TWO, new SecondFragment());
        _fragments.add(FRAGMENT_THREE, new ThirdFragment());

        // Настройка фрагментов, определяющих количество фрагментов, экраны и название.
        FragmentPagerAdapter MainAdapter = new FragmentPagerAdapter(getSupportFragmentManager()) {

            @Override
            public int getCount() {return 3;} //Получаем количество фрагментов

            @Override
            public Fragment getItem(final int position) {return _fragments.get(position);}

            @Override
            public CharSequence getPageTitle(final int position) {

                switch (position) {
                    case FRAGMENT_ONE:
                        return "Title One";
                    case FRAGMENT_TWO:
                        return "Title Two";
                    case FRAGMENT_THREE:
                        return "Title three";
                    default:
                        return null;
                }
            }
        };

        ViewPager pager = (ViewPager) findViewById(R.id.pager); //Инициализируем
ViewPager
        pager.setAdapter(MainAdapter); //Устанавливаем Adapter для Viewpager


        int startPage = 1

        pager.setCurrentItem(startPage); //На каком фрагменте будет открываться 
    }

}




Задача: На каждом фрагменте есть картинка и кнопка. При нажатии на кнопку видимого
фрагмента, на остальных фрагментах должна поменяться картинка.


  С фрагментами которые выгружены - проблем нет. Перед тем как загрузится,
  они проверяют нужно ли менять картинку и, если нужно,
  подгружаются уже с изменённой картинкой:

         //Проверяем нужно ли менять картинку
        int ActivePage = strorageFileExemp.getInt(APP_STORAGE_LAST_PAGE, 8);
        if (ActivePage == 1 || ActivePage == 2)
        {
            ImageView MyImageView = view.findViewById(R.id.my_imageview_id);
            MyImageView.setImageResource(R.drawable.my_image);
        }



Проблема: Проблема имеется лишь с фрагментами, которые подгружены, но ещё не видны.
Не могу понять как поменять у них картинку, ведь они подгрузились, но всё ещё как бы null.


  Допустим, возьмём случай, когда виден фрагмент 1:
  
  1(загружен/видно) - 2(подгружен) - 3 (выгружен). 
  
  Я нажимаю кнопку на фрагменте 1. Фрагмент 3 изменит картинку, когда будет загружаться,
а вот в уже подгруженном, но ещё не видном фрагменте 2 не понимаю как поменять картинку.


Yellastro предложил рабочее, но не самое лучшее решение. Оно достаточно сложное и
создаёт свои неудобства. Поэтому вопрос всё ещё открыт и новые предложения активно
рассматриваются.
    


Ответы

Ответ 1



В вашем случае, когда три разных класса делают по сути одно и тоже (провоцируют смену картинок на остальных фрагментах и меняют свою картинку по какому то внешнем условию), лучше будет сделать просто для них суперкласс, примерно такой: public abstract class PictureChangingFragment extend Fragment { private MainActivity mActivity; //Надо запросить в конструкторе Активность, которая будет иметь ссылки на все фрагменты public PictureChangingFragment(MainActivity fActivity) { super(); mActivity = fActivity; } public void onChangePicture(ImageView fImageView) { //ваш код смены картинки у конкретного фрагмента } //Этот метод должен вызываться когда вы нажимаете на текущую картинку, или //когда еще вы захотите поменять картинки на соседних фрагментах public void changePictureOnOtherFragments() { //Теперь мы можем тупо через главную активность обратиться // ко всем остальным фрагментам и поменять на ни картинки for ( PictureChangingFragment qFragment :mActivity.mFragments) if(qFragment!=this) { ImageView fImageView = (ImageView) qFragment.FindviewById(R.id.*imageView*) Fragment.onChangePicture(fImageView); } } Затем нужно наследовать ваши классы фрагментов от этого абстрактного класса. И поменять формат вашего списка фрагментов в главной активности. Во первых он должен быть публичный, что бы сами фрагменты могли им пользоваться. А во вторых он будет типизированн соглассно нашему новому классу: class MainActivity... { ... Public List mFragments = new ArrayList<>(); ... И вот. Теперь нам не надо проверять при загрузке фрагментов ничего, смена картинок будет происходить в тот же момент, когда для этого было выполнено условие (нажатие картинки то есть. Если вы в функции нажатия конечно добавите соответствующую функцию) Upd. Прошу прощения за несколько мелких ошибок в моем коде, это они вызвали у вас часть вопросов. Сейчас все исправил. 1) В вашей активности место создания фрагментов теперь должно выглядеть так _fragments.add(FRAGMENT_ONE, new FirstFragment(this)); _fragments.add(FRAGMENT_TWO, new SecondFragment(this)); _fragments.add(FRAGMENT_THREE, new ThirdFragment(this)); Что бы ваши фрагменты могли принимать такой параметр, см. пункт 4 2) Вью в этом случае может быть фрагментом, как раз их мы и перебираем в цикле функции changePictureOnOtherFragment(). В коде выше я исправил все что бы работало. Вообще, когда вы переопределяете событие нажатия по вью, вы работаете с методом onClick(View view). И вот эта view, которую метод получает в параметрах, и есть ваша ImageView, если вы конечно назначили этот метод на прослушивание нажатий по картинке. То есть вам нужно сейчас в этом методе onClick(View view) дописать строчки ImageView imgView = (ImageView)view;//преобразование абстрактной вью к формату вью-картинки onChangePicture(imgView);//этот метод в моем классе я только переписал выше. 3) Это была моя ошибка, там просто буквы перепутались 4) Да, правильно. Обозначение классов ваших фрагментов теперь должно выглядеть так public class *FragmentName* extends PictureChangingFragment. И ещё им нужен новый формат конструктора: public *fragmentName*(MainActivity mActivity) { super(mActivity); ... }

Ответ 2



Просто перенеси ту конструкцию которая меняет картинки при загрузки из функции onCreate в onResume. Тогда каждый фрагмент будет менять картинку как раз перед своим отображением. Upd Раз не получается использовать по назначению onResome(), тогда делай все фрагменты экземплярами интерфейса PicturesChangeListener с одним методом onPictureChange(), и вызывай этот метод через for(PicturesChangeListener qFragment : твой список фрагментов) qFragment.onPictureChange();

вторник, 10 декабря 2019 г.

Перезапуск фрагмента из RecyclerView

#java #android #fragment_back_stack #fragment_manager #fragment_transaction


Разрабатываю приложение несколько месяцев, оно постепенно обрастает новыми добавлениями.
Вкратце - это интернет магазин, оформлен в виде одной активити с NavigationDrawer,
все страницы реализованы на фрагментах, в некоторых случаях в дело вступает backstack,
например в случае каталога, у которого есть 4 уровня вложенности - основные категории,
подкатегории, список продуктов, страница продукта. первые два оформлены в виде ListView,
третий в виде RecyclerView с GridLayoutManager. 

Метод onBackPressed в активити определен следующим образом 

@Override
public void onBackPressed() {

    ProductPageFragment ppf = (ProductPageFragment)  getFragmentManager().findFragmentByTag(Flags.Stack.PRODUCT_PAGE_FRAGMENT);
    Log.d("ppf", ppf + "");
    SubcatalogFragment sf = (SubcatalogFragment) getFragmentManager().findFragmentByTag(Flags.Stack.SUBCATALOG_FRAGMENT);
    Log.d("sf", sf + "");
    ProductGridFragment pf  = (ProductGridFragment) getFragmentManager().findFragmentByTag(Flags.Stack.PRODUCT_GRID_FRAGMENT);
    Log.d("pf", pf + "");
    CartMainFragment cmf = (CartMainFragment) getFragmentManager().findFragmentByTag(Flags.Stack.CART_FRAGMENT);
    Log.d("cmf", cmf + "");
    PurchaseFragment purf = (PurchaseFragment) getFragmentManager().findFragmentByTag(Flags.Stack.PURCHASE_FRAGMENT);
    Log.d("purf", purf + "");

    boolean sfVisible = sf != null && sf.isVisible();
    boolean ppfVisible = ppf != null && ppf.isVisible();
    boolean pfVisible = pf != null && pf.isVisible();
    boolean cmfVisible = cmf != null && cmf.isVisible();
    boolean purfVisible = purf != null && purf.isVisible();


    //If the fragment exists and has some back-stack entry
    if ((sfVisible || pfVisible || ppfVisible || cmfVisible || purfVisible) && getFragmentManager().getBackStackEntryCount()
> 0) {
        bpCount = 0;
        getFragmentManager().popBackStack();
    }

    //Else, nothing in the direct fragment back stack
    else {
        // Let super handle the back press
        bpCount++;
        switch (bpCount) {
            case 1:
                Toast.makeText(
                        getApplicationContext(),
                        "Для выхода нажмите \"Назад\" ещё раз",
                        Toast.LENGTH_SHORT
                ).show();
                break;
            case 2:
                super.onBackPressed();
                break;
        }
    }
}


bpCount, в данном случае, – счетчик нажатий на back, чтобы приложение не закрывалось
по первому нажатию.

На главной странице потребовалось разместить популярные продукты, использовал для
этого RecyclerView с GridLayoutManager и тем же viewholder, что и в списке продуктов.
По нажатию на итем должна открываться страница продукта. Запускаем приложение - видим
список продуктов, открываем страницу первого попавшегося продукта - отрисовывается
нормально. Нажимаем клавишу back, видим главную, открываем другой продукт (или этот
же, не имеет значения) и фрагмент не отрисовывается. если помещать его в контейнер
методом replace - то видим серый экран (стандартный фон) на месте контейнера, если
add, то RecyclerView отскролливается на выбранный продукт, при этом в обоих случаях
onCreateView фрагмета выполняется. 

Код, вызывающий фрагмент выглядит так:

private void showProductPage() {
    ProductPageFragment ppf = new ProductPageFragment();

    Bundle bundle = new Bundle();
    bundle.putParcelable(PRODUCT_TAG, product);

    ppf.setArguments(bundle);

    ((Activity) this.itemView.getContext()).getFragmentManager().beginTransaction()
            .replace(R.id.container, ppf, Flags.Stack.PRODUCT_PAGE_FRAGMENT)
            .addToBackStack(Flags.Stack.CATALOG_BACKSTACK)
            .commit();
}


this - это viewholder, itemView - view холдера, контекст является инстансом активити.

Мне бы хотелось понять, почему такое может происходить, ведь по первому клику фрагмент
отрисовывается нормально. И если кто нибудь подробно может мне объяснить разницу между
основными методами FragmentTransaction, то я тоже был бы очень признателен. Спасибо.

PS. Да, забыл, к чему я, собственно, расписывал как устроен каталог - во всем каталоге
нажатие на back отрабатывает нормально, а вот со страницей продукта происходит такая
же ерунда, как и при открытии его с главной. При первом запуске страница продукта отрисовывается,
а потом - нет.

PPS. Удалил лишние проверки из popBackStack, на всякий случай, ситуация не изменилась.
Вроде всё выполняется в одном потоке, но объявлял syncronized как метод, так и блок,
результат тот же. Мне не понятно, почему метод onCreateView выполняется, как и остальные
методы фрагмента, в  методе onResume вывожу isVisible и оно true, но при этом вьющка
не рисуется. Или может она рисуется где то ещё? Смотрим реализацию isVisible в базовом
классе фрагмента:

    final public boolean isVisible() {
        return isAdded() && !isHidden() && mView != null
                && mView.getWindowToken() != null && mView.getVisibility() == View.VISIBLE;
    }


Значит нам надо выяснить, как реализованы и что значат все эти условия. Ищем isAdded

    final public boolean isAdded() {
        return mActivity != null && mAdded; // Задается FragmentManager при добавлении
фрагмента
    }


isHidden:

    final public boolean isHidden() {
        return mHidden; // значение задается функциями showFragment и hideFragment
вызванные из экземляра FragmentManager
    }


mView это view фрагмента, который создаётся в onCreateView, 

view.getWindowToken выглядит так

    public IBinder getWindowToken() {
        return mAttachInfo != null ? mAttachInfo.mWindowToken : null;


// возвращет null если view не присоединено к контейнеру
        }
ну и View.VISIBLE означает, что view фрагмента отрисовывается. 

Следовательно ОС считает, что фрагмент у меня нарисован и существует. 
    


Ответы

Ответ 1



Не увидел в вашем коде используете ли вы рекомендованный паттерн OnFragmentInteraction - судя по обилию всяких условий в onBackPressed видимо нет, а зря. Смысл паттерна, в том, что корневой Activity должен реализовывать интерфейс отрабатывающий изменения в подчиненных фрагментах. Грубо говоря, если вы выбрали некий элемент списка, то корневой Activity должен быть в курсе этого, чтобы при возврате обратно отработать эти изменения. По-моему это плохая идея ловить onBackPressed - гораздо удобнее работать с onBackStackChanged примерно так: @Override public void onBackStackChanged() { Log.i(TAG, "Back stack changed!"); Log.i(TAG, "Stack size=" + getSupportFragmentManager().getBackStackEntryCount()); if(getSupportFragmentManager().getBackStackEntryCount()==0) {//we're on main //перерисовываем здесь } shouldDisplayHomeUp(); } Для перерисовки надо или дать знать фрагменту, что контент изменился - типа notifyDatasetChanged или просто перерисовать Cursor который лежит под фрагментом (если лежит). Можно попробовать отцепить и заново прицепить фрагмент: getSupportFragmentManager() .beginTransaction() .detach(contentFragment) .attach(contentFragment) .commit(); Говорят работает - честно скажу не пробовал.

Ответ 2



Попробуйте в адаптер передать ссылку на фрагмент, коий содержит ваш RecyclerView и вместо FragmentManager-а активити использовать FragmentManager фрагмента, который можно получить методом getChildFragmentManager() класса Fragment. Т.е. вместо ((Activity) this.itemView.getContext()).getFragmentManager() сделать instanceOfYoursFragmentThatHoldesRecycleViewWhichYouPassToAdaptersClass.getChildFragmentManager()

вторник, 7 мая 2019 г.

java.lang.NullPointerException: Attempt to write to field 'int android.support.v4.app.Fragment.mNextAnim' on a null object reference [дубликат]

На данный вопрос уже ответили: Что такое Null Pointer Exception и как его исправить? 4 ответа На Nexus 7 c Android 5.0(не 4.4) происходит крах с Exception из заголовка. После непродолжительных поисков был найден следующий ответ. Меня он не устраивает, так как в силу обстоятельств не могу отказаться от android.support.v4.app.
Возможность операции над фрагментом, равным null, я тоже рассматривал, но такого быть не должно. Отрывок кода под подозрением:
String tag = FRAGMENT_STATE; FragmentManager fragmentManager = getSupportFragmentManager(); ... stateFragment = new Fragment(); ... if (stateFragment != null) { ... android.support.v4.app.FragmentTransaction transaction = fragmentManager.beginTransaction();//.support.v4 transaction.replace(R.id.content_frame, stateFragment, tag); ... transaction.commitAllowingStateLoss();
Еще есть следующий отрывок:
//Типо подключаю фрагменты if(onerFragment == null) Logi.d("OneFragment is Null"); //if (oneFragment == null || twoeFragment == null) { Logi.d("блок подключения фрагментов"); fragmentTransaction = fragmentManager.beginTransaction(); Logi.d("oneGragment constructer before"); oneFragment = new OneGragment(); fragmentTransaction.add(R.id.one_fragment, oneFragment); Logi.d("TwoFragment constructer before"); twoFragment = new TwoFragment(); fragmentTransaction.add(R.id.two_fragment, twoFragment);
fragmentTransaction.commitAllowingStateLoss();
Логи:
FATAL EXCEPTION: main Process: название пакета, PID: 7755 java.lang.NullPointerException: Attempt to write to field 'int android.support.v4.app.Fragment.mNextAnim' on a null object reference at android.support.v4.app.BackStackRecord.run(BackStackRecord.java:709) at android.support.v4.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:1501) at android.support.v4.app.FragmentManagerImpl$1.run(FragmentManager.java:458) at android.os.Handler.handleCallback(Handler.java:739) at android.os.Handler.dispatchMessage(Handler.java:95) at android.os.Looper.loop(Looper.java:135) at android.app.ActivityThread.main(ActivityThread.java:5221) at java.lang.reflect.Method.invoke(Native Method) at java.lang.reflect.Method.invoke(Method.java:372) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:899) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:694)
P.S Устройства у меня нет, логи взяты из GoogleTestLab, тестирование на эмуляторе тоже не смогу. есть ли у кого-нибудь идеи что делать с этим "эксепшеном"?


Ответ

Вот здесь сказано: Это происходит из-за того, что Вы вызываете методы remove(), add() и т.п. со значением null
Поразумевается, что replace() - это remove() и за ним add()