web-dev-qa-db-fra.com

support FragmentPagerAdapter contient une référence à d'anciens fragments

DERNIERES INFORMATIONS:

j'ai réduit mon problème à celui de fragmentManager en conservant des occurrences d'anciens fragments et en désynchronisant mon viewpager avec mon FragmentManager. Voir ce numéro ... http://code.google.com/p/Android/issues/detail?id=19211#makechanges . Je n'ai toujours aucune idée de la façon de résoudre ce problème. Aucune suggestion...

J'ai essayé de résoudre ce problème depuis longtemps et toute aide serait grandement appréciée. J'utilise un FragmentPagerAdapter qui accepte une liste de fragments comme ceci:

List<Fragment> fragments = new Vector<Fragment>();
fragments.add(Fragment.instantiate(this, Fragment1.class.getName())); 
...
new PagerAdapter(getSupportFragmentManager(), fragments);

La mise en œuvre est standard. J'utilise la bibliothèque de calcul ActionBarSherlock et v4 pour Fragments.

Mon problème est que, après avoir quitté l'application et ouvert plusieurs autres applications, puis repris, les fragments perdent leur référence à FragmentActivity (c'est-à-dire getActivity() == null). Je ne peux pas comprendre pourquoi cela se produit. J'ai essayé de définir manuellement setRetainInstance(true); mais cela n'aide pas. Je pensais que cela se produisait lorsque mon FragmentActivity avait été détruit. Toutefois, cela se produit toujours si j'ouvre l'application avant de recevoir le message du journal. Y a-t-il des idées?

@Override
protected void onDestroy(){
    Log.w(TAG, "DESTROYDESTROYDESTROYDESTROYDESTROYDESTROYDESTROY");
    super.onDestroy();
}

L'adaptateur:

public class PagerAdapter extends FragmentPagerAdapter {
    private List<Fragment> fragments;

    public PagerAdapter(FragmentManager fm, List<Fragment> fragments) {
        super(fm);

        this.fragments = fragments;

    }

    @Override
    public Fragment getItem(int position) {

        return this.fragments.get(position);

    }

    @Override
    public int getCount() {

        return this.fragments.size();

    }

}

Un de mes fragments dépouillé mais j'ai tout recommandé - c'est dépouillé et ça ne marche toujours pas ...

public class MyFragment extends Fragment implements MyFragmentInterface, OnScrollListener {
...

@Override
public void onCreate(Bundle savedInstanceState){
    super.onCreate(savedInstanceState);
    handler = new Handler();    
    setHasOptionsMenu(true);
}

@Override
public void onAttach(Activity activity) {
    super.onAttach(activity);
    Log.w(TAG,"ATTACHATTACHATTACHATTACHATTACH");
    context = activity;
    if(context== null){
        Log.e("IS NULL", "NULLNULLNULLNULLNULLNULLNULLNULLNULLNULLNULL");
    }else{
        Log.d("IS NOT NULL", "NOTNOTNOTNOTNOTNOTNOTNOT");
    }

}

@Override
public void onActivityCreated(Bundle savedState) {
    super.onActivityCreated(savedState);
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    View v = inflater.inflate(R.layout.my_fragment,container, false);

    return v;
}


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

private void callService(){
    // do not call another service is already running
    if(startLoad || !canSet) return;
    // set flag
    startLoad = true;
    canSet = false;
    // show the bottom spinner
    addFooter();
    Intent intent = new Intent(context, MyService.class);
    intent.putExtra(MyService.STATUS_RECEIVER, resultReceiver);
    context.startService(intent);
}

private ResultReceiver resultReceiver = new ResultReceiver(null) {
    @Override
    protected void onReceiveResult(int resultCode, final Bundle resultData) {
        boolean isSet = false;
        if(resultData!=null)
        if(resultData.containsKey(MyService.STATUS_FINISHED_GET)){
            if(resultData.getBoolean(MyService.STATUS_FINISHED_GET)){
                removeFooter();
                startLoad = false;
                isSet = true;
            }
        }

        switch(resultCode){
        case MyService.STATUS_FINISHED: 
            stopSpinning();
            break;
        case SyncService.STATUS_RUNNING:
            break;
        case SyncService.STATUS_ERROR:
            break;
        }
    }
};

public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
    menu.clear();
    inflater.inflate(R.menu.activity, menu);
}

@Override
public void onPause(){
    super.onPause();
}

public void onScroll(AbsListView arg0, int firstVisible, int visibleCount, int totalCount) {
    boolean loadMore = /* maybe add a padding */
        firstVisible + visibleCount >= totalCount;

    boolean away = firstVisible+ visibleCount <= totalCount - visibleCount;

    if(away){
        // startLoad can now be set again
        canSet = true;
    }

    if(loadMore) 

}

public void onScrollStateChanged(AbsListView arg0, int state) {
    switch(state){
    case OnScrollListener.SCROLL_STATE_FLING: 
        adapter.setLoad(false); 
        lastState = OnScrollListener.SCROLL_STATE_FLING;
        break;
    case OnScrollListener.SCROLL_STATE_IDLE: 
        adapter.setLoad(true);
        if(lastState == SCROLL_STATE_FLING){
            // load the images on screen
        }

        lastState = OnScrollListener.SCROLL_STATE_IDLE;
        break;
    case OnScrollListener.SCROLL_STATE_TOUCH_SCROLL:
        adapter.setLoad(true);
        if(lastState == SCROLL_STATE_FLING){
            // load the images on screen
        }

        lastState = OnScrollListener.SCROLL_STATE_TOUCH_SCROLL;
        break;
    }
}

@Override
public void onDetach(){
    super.onDetach();
    if(this.adapter!=null)
        this.adapter.clearContext();

    Log.w(TAG, "DETACHEDDETACHEDDETACHEDDETACHEDDETACHEDDETACHED");
}

public void update(final int id, String name) {
    if(name!=null){
        getActivity().getSupportActionBar().setTitle(name);
    }

}

}

La méthode update est appelée lorsqu'un utilisateur interagit avec un fragment différent et que getActivity renvoie la valeur null. Voici la méthode que l'autre fragment appelle ...

((MyFragment) pagerAdapter.getItem(1)).update(id, name);

Je crois que lorsque l'application est détruite, puis créée à nouveau au lieu de simplement démarrer l'application jusqu'au fragment par défaut, l'application démarre, puis viewpager navigue vers la dernière page connue. Cela semble étrange, l'application ne devrait-elle pas simplement se charger sur le fragment par défaut? 

102
Maurycy

Vous rencontrez un problème car vous instanciez et gardiez des références à vos fragments en dehors de PagerAdapter.getItem, et essayez d'utiliser ces références indépendamment du ViewPager. Comme le dit Seraph, vous avez la garantie qu'un fragment a été instancié/ajouté dans un ViewPager à un moment donné - ceci devrait être considéré comme un détail d'implémentation. Un ViewPager charge paresseusement ses pages; Par défaut, il charge uniquement la page en cours, ainsi que celle de gauche et de droite.

Si vous placez votre application en arrière-plan, les fragments ajoutés au gestionnaire de fragments sont automatiquement enregistrés. Même si votre application est supprimée, ces informations sont restaurées lorsque vous relancez votre application.

Considérons maintenant que vous avez visionné quelques pages, les fragments A, B et C. Vous savez que ceux-ci ont été ajoutés au gestionnaire de fragments. Comme vous utilisez FragmentPagerAdapter et non pas FragmentStatePagerAdapter, ces fragments seront quand même ajoutés (mais potentiellement détachés) lorsque vous passez à d'autres pages.

Considérez que vous avez alors votre candidature en arrière-plan, puis elle est tuée. À votre retour, Android se rappellera que vous aviez auparavant les fragments A, B et C dans le gestionnaire de fragments. Il les recrée ensuite pour vous, puis les ajoute. Cependant, ceux qui sont ajoutés au gestionnaire de fragments maintenant ne sont PAS ceux que vous avez dans votre liste de fragments dans votre activité.

FragmentPagerAdapter n'essaiera pas d'appeler getPosition s'il existe déjà un fragment ajouté pour cette position de page particulière. En fait, étant donné que le fragment recréé par Android ne sera jamais supprimé, vous ne pouvez plus le remplacer par un appel à getPosition. Il est également assez difficile d’obtenir une référence car il a été ajouté avec une balise qui vous est inconnue. C'est par conception; vous êtes découragé de manipuler les fragments gérés par le pageur de vues. Vous devez effectuer toutes vos actions dans un fragment, communiquer avec l'activité et demander de basculer vers une page particulière, si nécessaire.

Revenons maintenant à votre problème d’activité manquante. Appeler pagerAdapter.getItem(1)).update(id, name) une fois que tout cela est arrivé vous renvoie le fragment de votre liste, qui n'a pas encore été ajouté au gestionnaire de fragments , et il n'aura donc pas de référence d'activité. Je suggérerais que votre méthode de mise à jour modifie une structure de données partagée (éventuellement gérée par l'activité), puis, lorsque vous passez à une page particulière, elle peut se dessiner en fonction de ces données mises à jour.

118
antonyt

J'ai trouvé une solution simple qui a fonctionné pour moi.

Faites en sorte que votre adaptateur de fragment étend FragmentStatePagerAdapter au lieu de FragmentPagerAdapter et substitue la méthode onSave pour renvoyer null

@Override
public Parcelable saveState()
{
    return null;
}

Cela empêche Android de recréer des fragments


Un jour plus tard, j'ai trouvé une autre solution, meilleure.

Appelez setRetainInstance(true) pour tous vos fragments et sauvegardez-les quelque part. Je l'ai fait dans la variable statique de mon activité, car elle est déclarée en tant que singleTask et les fragments peuvent rester les mêmes tout le temps.

De cette façon, Android ne recrée pas de fragments mais utilise les mêmes instances.

103
mc.dev

J'ai résolu ce problème en accédant à mes fragments directement via FragmentManager au lieu de passer par FragmentPagerAdapter. Je dois d'abord comprendre la balise du fragment automatique généré par FragmentPagerAdapter ...

private String getFragmentTag(int pos){
    return "Android:switcher:"+R.id.viewpager+":"+pos;
}

Ensuite, j'obtiens simplement une référence à ce fragment et fais ce dont j'ai besoin comme ...

Fragment f = this.getSupportFragmentManager().findFragmentByTag(getFragmentTag(1));
((MyFragmentInterface) f).update(id, name);
viewPager.setCurrentItem(1, true);

À l'intérieur de mes fragments, j'ai défini la setRetainInstance(false); afin de pouvoir ajouter manuellement des valeurs à l'ensemble savedInstanceState. 

@Override
public void onSaveInstanceState(Bundle outState) {
    if(this.my !=null)
        outState.putInt("myId", this.my.getId());

    super.onSaveInstanceState(outState);
}

puis dans OnCreate, je récupère cette clé et restaure l'état du fragment si nécessaire. Une solution facile qui était difficile (du moins pour moi) à comprendre. 

27
Maurycy

Solution globale testée de travail.

getSupportFragmentManager() conserve parfois la référence null et View pager ne crée pas de nouveau puisqu'il trouve la référence au même fragment. Donc, pour surmonter cette utilisation, getChildFragmentManager() résout le problème de manière simple.

Ne fais pas ça:  

new PagerAdapter(getSupportFragmentManager(), fragments);

Faites ceci:

new PagerAdapter(getChildFragmentManager() , fragments);

21
Vinayak

N'essayez pas d'interagir entre les fragments dans ViewPager. Vous ne pouvez pas garantir qu'un autre fragment est attaché ou même existe. Au lieu de changer le titre de la barre d’action de fragment, vous pouvez le faire à partir de votre activité. Utilisez un modèle d'interface standard pour cela:

public interface UpdateCallback
{
    void update(String name);
}

public class MyActivity extends FragmentActivity implements UpdateCallback
{
    @Override
    public void update(String name)
    {
        getSupportActionBar().setTitle(name);
    }

}

public class MyFragment extends Fragment
{
    private UpdateCallback callback;

    @Override
    public void onAttach(SupportActivity activity)
    {
        super.onAttach(activity);
        callback = (UpdateCallback) activity;
    }

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

    public void updateActionbar(String name)
    {
        if(callback != null)
            callback.update(name);
    }
}
7
Seraph

Vous pouvez supprimer les fragments lorsque vous détruisez le viewpager, dans mon cas, je les ai supprimés sur onDestroyView() de mon fragment:

@Override
public void onDestroyView() {

    if (getChildFragmentManager().getFragments() != null) {
        for (Fragment fragment : getChildFragmentManager().getFragments()) {
            getChildFragmentManager().beginTransaction().remove(fragment).commitAllowingStateLoss();
        }
    }

    super.onDestroyView();
}
4
Raoni Novellino

Après quelques heures à chercher un problème similaire, je pense avoir une autre solution. Celui-ci au moins cela a fonctionné pour moi et je n'ai qu'à changer quelques lignes.

C'est le problème que j'ai eu, j'ai une activité avec un pager de vue qui utilise un FragmentStatePagerAdapter avec deux Fragments. Tout fonctionne correctement jusqu'à ce que je force l'activité à être détruite (options de développeur) ou que je fasse pivoter l'écran. Je garde une référence aux deux fragments après leur création dans la méthode getItem.

À ce stade, l'activité sera créée à nouveau et tout fonctionnera correctement, mais j'ai perdu la référence à mes parfums, car getItem ne s'appelle plus. 

Voici comment j'ai résolu ce problème à l'intérieur de FragmentStatePagerAdapter:

    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        Object aux = super.instantiateItem(container, position);

        //Update the references to the Fragments we have on the view pager
        if(position==0){
            fragTabOne = (FragOffersList)aux;
        }
        else{
            fragTabTwo = (FragOffersList) aux;
        }

        return aux;
    }

Vous ne recevrez plus d'appel sur getItem si l'adaptateur a déjà une référence interne, et vous ne devriez pas changer cela. Au lieu de cela, vous pouvez obtenir le fragment utilisé en consultant cette autre méthode, instanciateItem (), qui sera appelée pour chacun de vos fragments.

J'espère que ça aide n'importe qui.

4
Lancelot

Ma solution: je règle presque chaque vue sur static. Maintenant, mon application interagit parfaitement. Pouvoir appeler les méthodes statiques de partout n'est peut-être pas un bon style, mais pourquoi jouer avec du code qui ne fonctionne pas? J'ai lu beaucoup de questions et leurs réponses ici sur SO et aucune solution n'a apporté le succès (pour moi). 

Je sais que cela peut laisser échapper de la mémoire et gaspiller de l'argent, et que mon code ne sera pas adapté à d'autres projets, mais je ne me sens pas effrayé à ce sujet. J'ai testé l'application sur différents appareils et dans différentes conditions. La plate-forme semble pouvoir gérer cela. L'interface utilisateur est actualisée toutes les secondes et même sur un périphérique S2 ICS (4.0.3), l'application est capable de gérer des milliers de marqueurs géographiques.

0
Martin Pfeffer

Étant donné que FragmentManager se chargera de restaurer vos fragments dès que la méthode onResume () sera appelée, le fragment appelle l’activité et s’ajoute une liste. Dans mon cas, je stocke tout cela dans mon implémentation de PagerAdapter. Chaque fragment connaît sa position car il est ajouté aux arguments de fragment lors de la création. Maintenant, chaque fois que je dois manipuler un fragment à un index spécifique, tout ce que j'ai à faire est d'utiliser la liste de mon adaptateur.

Vous trouverez ci-dessous un exemple d'adaptateur pour un ViewPager personnalisé qui agrandit le fragment au fur et à mesure de sa mise au point et le réduit à la baisse. Outre les classes Adapter et Fragment que j'ai ici, tout ce dont vous avez besoin est que l'activité parent puisse faire référence à la variable adaptateur et que vous êtes défini.

Adaptateur

public class GrowPagerAdapter extends FragmentPagerAdapter implements OnPageChangeListener, OnScrollChangedListener {

public final String TAG = this.getClass().getSimpleName();

private final int COUNT = 4;

public static final float BASE_SIZE = 0.8f;
public static final float BASE_ALPHA = 0.8f;

private int mCurrentPage = 0;
private boolean mScrollingLeft;

private List<SummaryTabletFragment> mFragments;

public int getCurrentPage() {
    return mCurrentPage;
}

public void addFragment(SummaryTabletFragment fragment) {
    mFragments.add(fragment.getPosition(), fragment);
}

public GrowPagerAdapter(FragmentManager fm) {
    super(fm);

    mFragments = new ArrayList<SummaryTabletFragment>();
}

@Override
public int getCount() {
    return COUNT;
}

@Override
public Fragment getItem(int position) {
    return SummaryTabletFragment.newInstance(position);
}

@Override
public void onPageScrollStateChanged(int state) {}

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

    adjustSize(position, positionOffset);
}

@Override
public void onPageSelected(int position) {
    mCurrentPage = position;
}

/**
 * Used to adjust the size of each view in the viewpager as the user
 * scrolls.  This provides the effect of children scaling down as they
 * are moved out and back to full size as they come into focus.
 * 
 * @param position
 * @param percent
 */
private void adjustSize(int position, float percent) {

    position += (mScrollingLeft ? 1 : 0);
    int secondary = position + (mScrollingLeft ? -1 : 1);
    int tertiary = position + (mScrollingLeft ? 1 : -1);

    float scaleUp = mScrollingLeft ? percent : 1.0f - percent;
    float scaleDown = mScrollingLeft ? 1.0f - percent : percent;

    float percentOut = scaleUp > BASE_ALPHA ? BASE_ALPHA : scaleUp;
    float percentIn = scaleDown > BASE_ALPHA ? BASE_ALPHA : scaleDown;

    if (scaleUp < BASE_SIZE)
        scaleUp = BASE_SIZE;

    if (scaleDown < BASE_SIZE)
        scaleDown = BASE_SIZE;

    // Adjust the fragments that are, or will be, on screen
    SummaryTabletFragment current = (position < mFragments.size()) ? mFragments.get(position) : null;
    SummaryTabletFragment next = (secondary < mFragments.size() && secondary > -1) ? mFragments.get(secondary) : null;
    SummaryTabletFragment afterNext = (tertiary < mFragments.size() && tertiary > -1) ? mFragments.get(tertiary) : null;

    if (current != null && next != null) {

        // Apply the adjustments to each fragment
        current.transitionFragment(percentIn, scaleUp);
        next.transitionFragment(percentOut, scaleDown);

        if (afterNext != null) {
            afterNext.transitionFragment(BASE_ALPHA, BASE_SIZE);
        }
    }
}

@Override
public void onScrollChanged(int l, int t, int oldl, int oldt) {

    // Keep track of which direction we are scrolling
    mScrollingLeft = (oldl - l) < 0;
}
}

Fragment

public class SummaryTabletFragment extends BaseTabletFragment {

public final String TAG = this.getClass().getSimpleName();

private final float SCALE_SIZE = 0.8f;

private RelativeLayout mBackground, mCover;
private TextView mTitle;
private VerticalTextView mLeft, mRight;

private String mTitleText;
private Integer mColor;

private boolean mInit = false;
private Float mScale, mPercent;

private GrowPagerAdapter mAdapter;
private int mCurrentPosition = 0;

public String getTitleText() {
    return mTitleText;
}

public void setTitleText(String titleText) {
    this.mTitleText = titleText;
}

public static SummaryTabletFragment newInstance(int position) {

    SummaryTabletFragment fragment = new SummaryTabletFragment();
    fragment.setRetainInstance(true);

    Bundle args = new Bundle();
    args.putInt("position", position);
    fragment.setArguments(args);

    return fragment;
}

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

    mRoot = inflater.inflate(R.layout.tablet_dummy_view, null);

    setupViews();
    configureView();

    return mRoot;
}

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

    if (savedInstanceState != null) {
        mColor = savedInstanceState.getInt("color", Color.BLACK);
    }

    configureView();
}

@Override
public void onSaveInstanceState(Bundle outState)  {

    outState.putInt("color", mColor);

    super.onSaveInstanceState(outState);
}

@Override
public int getPosition() {
    return getArguments().getInt("position", -1);
}

@Override
public void setPosition(int position) {
    getArguments().putInt("position", position);
}

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

    mAdapter = mActivity.getPagerAdapter();
    mAdapter.addFragment(this);
    mCurrentPosition = mAdapter.getCurrentPage();

    if ((getPosition() == (mCurrentPosition + 1) || getPosition() == (mCurrentPosition - 1)) && !mInit) {
        mInit = true;
        transitionFragment(GrowPagerAdapter.BASE_ALPHA, GrowPagerAdapter.BASE_SIZE);
        return;
    }

    if (getPosition() == mCurrentPosition && !mInit) {
        mInit = true;
        transitionFragment(0.00f, 1.0f);
    }
}

private void setupViews() {

    mCover = (RelativeLayout) mRoot.findViewById(R.id.cover);
    mLeft = (VerticalTextView) mRoot.findViewById(R.id.title_left);
    mRight = (VerticalTextView) mRoot.findViewById(R.id.title_right);
    mBackground = (RelativeLayout) mRoot.findViewById(R.id.root);
    mTitle = (TextView) mRoot.findViewById(R.id.title);
}

private void configureView() {

    Fonts.applyPrimaryBoldFont(mLeft, 15);
    Fonts.applyPrimaryBoldFont(mRight, 15);

    float[] size = UiUtils.getScreenMeasurements(mActivity);
    int width = (int) (size[0] * SCALE_SIZE);
    int height = (int) (size[1] * SCALE_SIZE);

    RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(width, height);
    mBackground.setLayoutParams(params);

    if (mScale != null)
        transitionFragment(mPercent, mScale);

    setRandomBackground();

    setTitleText("Fragment " + getPosition());

    mTitle.setText(getTitleText().toUpperCase());
    mLeft.setText(getTitleText().toUpperCase());
    mRight.setText(getTitleText().toUpperCase());

    mLeft.setOnClickListener(new OnClickListener() {

        @Override
        public void onClick(View v) {

            mActivity.showNextPage();
        }
    });

    mRight.setOnClickListener(new OnClickListener() {

        @Override
        public void onClick(View v) {

            mActivity.showPrevPage();
        }
    });
}

private void setRandomBackground() {

    if (mColor == null) {
        Random r = new Random();
        mColor = Color.rgb(r.nextInt(255), r.nextInt(255), r.nextInt(255));
    }

    mBackground.setBackgroundColor(mColor);
}

public void transitionFragment(float percent, float scale) {

    this.mScale = scale;
    this.mPercent = percent;

    if (getView() != null && mCover != null) {

        getView().setScaleX(scale);
        getView().setScaleY(scale);

        mCover.setAlpha(percent);
        mCover.setVisibility((percent <= 0.05f) ? View.GONE : View.VISIBLE);
    }
}

@Override
public String getFragmentTitle() {
    return null;
}
}
0
saulpower

Juste pour que vous sachiez ...

Ajoutant à la litanie de malheurs de ces classes, il y a un bug plutôt intéressant qui mérite d'être partagé. 

J'utilise un ViewPager pour parcourir une arborescence d'éléments (sélectionnez un élément et le pager de vue s'anime en défilant vers la droite et la branche suivante apparaît, revenez en arrière et le ViewPager défile dans la direction opposée pour revenir au nœud précédent) .

Le problème se pose lorsque je presse et extrait des fragments à la fin de FragmentStatePagerAdapter. Il est suffisamment intelligent pour remarquer que les éléments changent et suffisamment pour créer et remplacer un fragment lorsque l'élément a été modifié. Mais pas assez intelligent pour ignorer l'état de fragment, ni assez pour couper les états de fragment sauvegardés en interne lorsque la taille de l'adaptateur change. Ainsi, lorsque vous insérez un élément et placez-en un nouveau à la fin, le fragment du nouvel élément obtient son état de sauvegarde pour l'ancien élément, ce qui a provoqué des ravages absolus dans mon code. Mes fragments contiennent des données nécessitant beaucoup de travail de récupération à partir d'Internet. Ne pas enregistrer l'état était donc vraiment une option.

Je n'ai pas de solution de rechange propre. J'ai utilisé quelque chose comme ça:

  public void onSaveInstanceState(Bundle outState) {
    IFragmentListener listener = (IFragmentListener)getActivity();
    if (listener!= null)
    {
        if (!listener.isStillInTheAdapter(this.getAdapterItem()))
        {
            return; // return empty state.
        }

    }
    super.onSaveInstanceState(outState);

    // normal saving of state for flips and 
    // paging out of the activity follows
    ....
  }

Une solution imparfaite, car la nouvelle instance de fragment reçoit toujours un paquet SavedState, mais au moins, elle ne contient pas de données obsolètes.

0
Robin Davies

J'ai résolu le problème en enregistrant les fragments dans SparceArray:

public abstract class SaveFragmentsPagerAdapter extends FragmentPagerAdapter {

    SparseArray<Fragment> fragments = new SparseArray<>();

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

    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        Fragment fragment = (Fragment) super.instantiateItem(container, position);
        fragments.append(position, fragment);
        return fragment;
    }

    @Nullable
    public Fragment getFragmentByPosition(int position){
        return fragments.get(position);
    }

}
0
xaxtix

J'ai rencontré le même problème, mais mon ViewPager se trouvait dans un TopFragment qui avait créé et défini un adaptateur à l'aide de setAdapter(new FragmentPagerAdapter(getChildFragmentManager())).

J'ai résolu ce problème en surchargeant onAttachFragment(Fragment childFragment) dans TopFragment comme ceci:

@Override
public void onAttachFragment(Fragment childFragment) {
    if (childFragment instanceof OnboardingDiamondsFragment) {
        mChildFragment = (ChildFragment) childFragment;
    }

    super.onAttachFragment(childFragment);
}

Comme on le sait déjà (voir les réponses ci-dessus), lorsque le childFragmentManager se recrée, il crée également les fragments qui se trouvaient dans le viewPager.
L’important est qu’après cela, il appelle onAttachFragment et nous avons maintenant une référence au nouveau fragment recréé! 

J'espère que cela aidera quiconque à obtenir ce vieux Q comme moi :)

0
Shirane85

Comme les gens ne lisent généralement pas les commentaires, voici une réponse qui reprend en grande partie ce que j’ai écrit ici :

la cause première du problème est le fait que Android le système n'appelle pas getItem pour obtenir les fragments réellement affichés, mais instantiateItem. Cette méthode tente d’abord de rechercher et de réutiliser une instance de fragment pour un onglet donné dans FragmentManager. Seulement si cette recherche échoue (ce qui ne se produit que la première fois lorsque FragmentManager est nouvellement créé), alors getItem est appelé. Pour des raisons évidentes, évitez de recréer des fragments (qui peuvent être lourds), par exemple chaque fois qu'un utilisateur fait pivoter son appareil.
Pour résoudre ce problème, au lieu de créer des fragments avec Fragment.instantiate dans votre activité, vous devez le faire avec pagerAdapter.instantiateItem et tous ces appels doivent être entourés par startUpdate/finishUpdate appels de méthode qui commencent./commit fragment transaction respectivement. getItem devrait être l'endroit où les fragments sont réellement créés en utilisant leurs constructeurs respectifs.

List<Fragment> fragments = new Vector<Fragment>();

@Override protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.myLayout);
        ViewPager viewPager = (ViewPager) findViewById(R.id.myViewPager);
        MyPagerAdapter adapter = new MyPagerAdapter(getSupportFragmentManager());
        viewPager.setAdapter(adapter);
        ((TabLayout) findViewById(R.id.tabs)).setupWithViewPager(viewPager);

        adapter.startUpdate(viewPager);
        fragments.add(adapter.instantiateItem(viewPager, 0));
        fragments.add(adapter.instantiateItem(viewPager, 1));
        // and so on if you have more tabs...
        adapter.finishUpdate(viewPager);
}

class MyPagerAdapter extends FragmentPagerAdapter {

        public MyPagerAdapter(FragmentManager manager) {super(manager);}

        @Override public int getCount() {return 2;}

        @Override public Fragment getItem(int position) {
            if (position == 0) return new Fragment0();
            if (position == 1) return new Fragment1();
            return null;  // or throw some exception
        }

        @Override public CharSequence getPageTitle(int position) {
            if (position == 0) return getString(R.string.tab0);
            if (position == 1) return getString(R.string.tab1);
            return null;  // or throw some exception
        }
}
0
morgwai