web-dev-qa-db-fra.com

Pourquoi ai-je une référence nulle sur ma RecyclerView

J'essaie d'utiliser le RecyclerView dans mes fragments. Il apparaît bien pour le premier onglet, mais lorsque je glisse vers le deuxième onglet, puis reviens au premier, j'obtiens l'erreur suivante:

Java.lang.NullPointerException: tentative d'appeler la méthode virtuelle 'void Android.support.v7.widget.RecyclerView $ LayoutManager.stopSmoothScroller ()' sur une référence d'objet null

Est-ce que quelqu'un sait pourquoi j'obtiendrais cette erreur? Il semble qu'il y ait un appel qui me manque avant que les fragments ne changent, mais je ne peux pas le comprendre.

Adaptateur de téléavertisseur pour fragments:

public class PagerAdapter extends FragmentPagerAdapter {

    private final String[] TITLES = {"Header 0", "Header 1" };

    public PagerAdapter(FragmentManager fm){
        super(fm);
    }

    @Override
    public CharSequence getPageTitle(int position){
        return TITLES[position];
    }

    @Override
    public int getCount() {
        return TITLES.length;
    }

    @Override
    public Fragment getItem(int position){
        switch(position){
            case 0:
                return new FragmentOne();
            case 1:
                return new FragmentTwo();
            default:
                return new FragmentOne();
        }
    }
}

FragmentOne et FragmentTwo utilisent une classe passe-partout que j'ai créée:

public class FragmentOne extends Base {
    ... code for displaying content in the RecyclerView. Just contains my custom Adapter for the RecyclerView ...
}

Base:

public abstract class Base extends Fragment {

    public View mView;
    public RecyclerView mDataView;
    public ProgressBar mProgressBar;
    public Context mContext;
    private RecyclerView.LayoutManager mLayoutManager;

    public Base() { }

    @Override
    public View onCreateView(LayoutInflater _i, ViewGroup _vg, Bundle savedInstanceBundle){
        mView = _i.inflate(R.layout.f_base, null);
        mDataView = (RecyclerView) mView.findViewById(R.id.data);
        mProgressBar = (ProgressBar)mView.findViewById(R.id.loading);
        mLayoutManager = new LinearLayoutManager(mContext);
        mDataView.setLayoutManager(mLayoutManager);
        mContext = this.getActivity();
        return mView;
    }

    @Override
    public void onStart() {
        super.onStart();
        init();
        doWork();
    }

    public abstract void init();

    public abstract void doWork();

}

MODIFIER

Trace de pile d'erreur:

11-02 12:49:44.171    3708-3708/com.nitrox.digitune E/AndroidRuntime﹕ FATAL EXCEPTION: main
    Process: com.nitrox.digitune, PID: 3708
    Java.lang.NullPointerException: Attempt to invoke virtual method 'void Android.support.v7.widget.RecyclerView$LayoutManager.stopSmoothScroller()' on a null object reference
            at Android.support.v7.widget.RecyclerView.stopScrollersInternal(RecyclerView.Java:1159)
            at Android.support.v7.widget.RecyclerView.stopScroll(RecyclerView.Java:1151)
            at Android.support.v7.widget.RecyclerView.onDetachedFromWindow(RecyclerView.Java:1356)
            at Android.view.View.dispatchDetachedFromWindow(View.Java:13441)
            at Android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.Java:2837)
            at Android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.Java:2834)
            at Android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.Java:2834)
            at Android.view.ViewGroup.removeViewInternal(ViewGroup.Java:4163)
            at Android.view.ViewGroup.removeViewInternal(ViewGroup.Java:4136)
            at Android.view.ViewGroup.removeView(ViewGroup.Java:4068)
            at Android.support.v4.view.ViewPager.removeView(ViewPager.Java:1326)
            at Android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.Java:1045)
            at Android.support.v4.app.FragmentManagerImpl.detachFragment(FragmentManager.Java:1280)
            at Android.support.v4.app.BackStackRecord.run(BackStackRecord.Java:724)
            at Android.support.v4.app.FragmentManagerImpl.execPendingActions(FragmentManager.Java:1489)
            at Android.support.v4.app.FragmentManagerImpl.executePendingTransactions(FragmentManager.Java:486)
            at Android.support.v4.app.FragmentPagerAdapter.finishUpdate(FragmentPagerAdapter.Java:141)
            at Android.support.v4.view.ViewPager.populate(ViewPager.Java:1073)
            at Android.support.v4.view.ViewPager.populate(ViewPager.Java:919)
            at Android.support.v4.view.ViewPager$3.run(ViewPager.Java:249)
            at Android.view.Choreographer$CallbackRecord.run(Choreographer.Java:767)
            at Android.view.Choreographer.doCallbacks(Choreographer.Java:580)
            at Android.view.Choreographer.doFrame(Choreographer.Java:549)
            at Android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.Java:753)
            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)
18
Andrew Butler

Fondamentalement, votre LayoutManager est éliminé avant que votre recycleur n'en ait fini avec lui.

Depuis la source Android source:

@Override
protected void onDetachedFromWindow() {
    super.onDetachedFromWindow();
    if (mItemAnimator != null) {
        mItemAnimator.endAnimations();
    }
    mFirstLayoutComplete = false;
    stopScroll();
    mIsAttached = false;
    if (mLayout != null) {
        mLayout.onDetachedFromWindow(this, mRecycler);
    }
    removeCallbacks(mItemAnimatorRunner);
}

Le problème est que stopScroll essaie d'appeler mLayout.stopSmoothScroller (); sans vérifier si mLayout est nul. J'ai lancé un correctif très hacky pour une application sur laquelle je travaille, mais ne devrait PAS être utilisé comme une solution à long terme, car il s'agit en fait d'un hack, mais j'avais un délai serré, j'ai donc dû attraper l'exception de pointeur nul . J'ai supprimé mon gestionnaire car il était spécifique à l'application.

Mon correctif consistait simplement à créer une vue personnalisée étendant RecyclerView:

import Android.content.Context;
import Android.support.v7.widget.RecyclerView;
import Android.util.AttributeSet;

public class HotFixRecyclerView extends RecyclerView
{
    public HotFixRecyclerView( Context context )
    {
        super( context );
    }

    public HotFixRecyclerView( Context context, AttributeSet attrs )
    {
        super( context, attrs );
    }

    public HotFixRecyclerView( Context context, AttributeSet attrs, int defStyle )
    {
        super( context, attrs, defStyle );
    }

    @Override
    public void stopScroll()
    {
        try
        {
            super.stopScroll();
        }
        catch( NullPointerException exception )
        {
            /**
             *  The mLayout has been disposed of before the 
             *  RecyclerView and this stops the application 
             *  from crashing.
             */
        }
    }
}

Ensuite, changez toutes les références à RecyclerView à ma propre vue. Si vous l'utilisez, veuillez le supprimer une fois Android a corrigé ce problème car c'est un peu un hack.

  • Si vous utilisez recylerview sur XML, n'oubliez pas de modifier le fichier XML en conséquence en com.your.package.HotFixRecyclerView au lieu de Android.support.v7.widget.RecyclerView
20
James Lockhart

Il semble que si vous avez un RecyclerView dans votre mise en page et que vous le chargez dans l'activité, il est indispensable de définir un LayoutManager pour cela, sinon il lèvera cette exception en essayant de le détruire. Je viens de rencontrer cette erreur et voici comment je l'ai résolue:

Mon activité a montré des éléments dans une paire de TextViews ou dans un RecyclerView en fonction de la quantité d'articles: s'il s'agissait d'une collection d'articles, j'ai utilisé le RecyclerView; s'il n'y avait qu'un seul élément, il était affiché sur les TextViews et j'ai défini la visibilité de RecyclerView sur GONE.

Ensuite, dans le premier cas, j'ai appelé myRecyclerView.setLayoutManager(myLayoutManager), mais dans l'autre cas je ne l'ai pas fait, car je n'allais pas l'utiliser. Dans les deux cas, l'activité contenant le RecyclerView était parfaitement affichée. Mais lors de sa fermeture, l'exception a été levée dans le deuxième cas parce que le RecyclerView, bien que non utilisé, existait et lors de la tentative d'élimination, il semble qu'il ne pouvait pas. Je viens donc d'affecter le LayoutManager dans les deux cas, même si je ne l'utilisais pas, et cela a résolu le problème.

Voici le code de mon activité:

ItemsAdapter adapter;
RecyclerView myRecyclerView = findViewById(R.id.my_recycler_view);
RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(this);

if (items.size() > 1){
    adapter = new ItemsAdapter(this, R.layout.item_layout, items);
    myRecyclerView.setLayoutManager(layoutManager);
    myRecyclerView.setAdapter(adapter);
} else {
    tv_field1.setText(items.get(0).getField1());
    tv_field2.setText(items,get(0).getField2());
    myRecyclerView.setVisibility(View.GONE);

    //This is what I had to add
    myRecyclerView.setLayoutManager(layoutManager);
}

Parce qu'il a déjà gonflé pref.xml. Vous devez supprimer ceci:

    @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {
    mRootView = inflater.inflate(R.layout.xxx, container, false);
    return mRootView;
}
2
Brasc elok

En lisant le bug signalé par Isaak, la meilleure solution semble être d'assigner le Layout Manager dès que la vue est gonflée.

Dans mon cas, je n'avais qu'à l'assigner sur onCreate pour résoudre le problème:

mLayoutManager = new LinearLayoutManager(this);
mRecyclerView.setLayoutManager(mLayoutManager);
1
Felizardo