Problème de ViewPager2 / Tabs avec l'état de ViewModel

9

Je suis le modèle MVVM - ce qui signifie que j'ai un ViewModel pour chaque fragment.

J'ai ajouté deux onglets en utilisant ViewPager2.

Mon adaptateur ressemble à ceci:

@Override
public Fragment createFragment(int position) {
    switch (position) {
        case 0:
            return new MergedItemsFragment();
        case 1:     
            return new ValidatedMergedItemsFragment();
    }
    return new MergedItemsFragment();
}

Les onglets fonctionnent. Cependant, j'ai remarqué que le ViewModel de mon MergedItemsFragment se comporte bizarrement. Avant d'ajouter des onglets, j'ai navigué vers le fragment comme ceci:

NavHostFragment.findNavController(this).navigate(R.id.action_roomFragment_to_itemsFragment);

Lorsque je NavHostFragment.findNavController(this).popBackStack()quittais ce fragment avec et revenais plus tard sur ce fragment, j'obtenais un nouveau ViewModel vide. C'était prévu.

Avec la nouvelle approche avec laquelle je navigue return new MergedItemsFragment(). Lorsque je quitte ce fragment et que je reviens plus tard, j'obtiens un ViewModel qui contient les anciennes données . C'est un problème parce que les anciennes données ne sont plus pertinentes car l'utilisateur a sélectionné différentes données dans un autre fragment.


Mise à jour # 1

J'ai réalisé qu'il garde en fait tous les anciens fragments en mémoire car les mêmes instructions d'impression sont appelées plusieurs fois. Le nombre d'appels augmente avec le nombre de fois où je pars et reviens à cet écran. Donc, si je pars et reviens 10 fois et que je fais pivoter mon appareil, il exécutera une ligne 10 fois. Vous devinez comment implémenter Tabs / ViewPagers avec des composants de navigation d'une manière qui fonctionne avec ViewModels?


Mise à jour # 2

J'ai défini mes ViewModels comme ceci:

viewModel = new ViewModelProvider(this, providerFactory).get(MergedItemViewModel.class)

J'obtiens les mêmes résultats avec:

viewModel = ViewModelProviders.of(this).get(MergedItemViewModel.class);

Je lie le ViewModel dans le fragment lui-même. C'est donc thisle Fragment.

user123456789
la source
Pouvez-vous montrer comment vous définissez vos ViewModels? De plus, y a-t-il une raison pour laquelle vous ne pouvez pas simplement créer un nouveau ViewModel lorsque vous obtenez de nouvelles données?
BlackHatSamurai
J'ai mis à jour ma question. Le but d'un modèle de vue n'est-il pas de s'en occuper lui-même? Je le crée une fois et il persiste pour un fragment. Comment pourrais-je le recréer exactement si j'ai de nouvelles données et pourquoi n'ai-je pas dû le faire auparavant?
user123456789
Vous n'aviez pas besoin de le faire auparavant car le fragment a été détruit. Vous utilisez maintenant un ViewPager et il stocke le fragment en mémoire. Je suggérerais simplement d'effacer les données lorsque vous en avez besoin. Vous devez gérer les données dans la machine virtuelle, plutôt que la machine virtuelle elle-même.
BlackHatSamurai
Le problème que j'ai, c'est que les anciennes machines virtuelles servent en fait d'anciens LiveData et alimentent les autres composants en anciennes données. Ainsi, l'effacement des données ne fonctionnera pas parce que les anciennes machines virtuelles continuent d'interférer. Exemple: j'efface une liste dans le ViewModel actuel. Cependant, l'écran obtient toujours l'ancienne liste. Lorsque je débogue le ViewModel et vérifie la longueur de la liste, il indique 0 - car il a été effacé. La seule explication logique est que d'autres ViewModels servent d'anciennes données.
user123456789
Utilisez-vous la même machine virtuelle pour chacun des fragments? Ou chaque fragment a-t-il sa propre machine virtuelle?
BlackHatSamurai

Réponses:

3

Selon votre commentaire, vous utilisez Fragment et à l'intérieur de ce Fragment se trouve votre viewpager. Ainsi, lors de la création de votre adaptateur pour ViewPager, vous devez passer childFragmentManager au lieu de getActivity ()

Voici un exemple d'adaptateur pour votre viewPager que vous pouvez utiliser

class NewViewPagerAdapter(fm: FragmentManager, behavior: Int) : FragmentStatePagerAdapter(fm, behavior) {
    private val mFragmentList: MutableList<Fragment> = ArrayList()
    private val mFragmentTitleList: MutableList<String> = ArrayList()

    override fun getItem(position: Int): Fragment {
        return mFragmentList[position]
    }

    override fun getCount(): Int {
        return mFragmentList.size
    }

    fun addFragment(fragment: Fragment, title: String) {
        mFragmentList.add(fragment)
        mFragmentTitleList.add(title)
    }

    override fun getPageTitle(position: Int): CharSequence? {
        return mFragmentTitleList[position]
    }
}

et lors de la création de votre adaptateur, appelez-le comme

   val adapter = NewViewPagerAdapter(
        childFragmentManager,
        FragmentPagerAdapter.POSITION_UNCHANGED
    )

comme si vous voyez la documentation de FragmentStatePagerAdapter, il indique que vous devez passer (FragmentManager, int) à l' intérieur du constructeur de votre adaptateur

J'espère que cela résoudra votre problème car j'étais confronté au même problème un jour.

Codage heureux.

Rakshit Nawani
la source
1
Merci. Comme l'a déjà dit ianhanniballake, passer le fragment lui-même est suffisant, assurez-vous simplement que vous avez un constructeur approprié. Les deux réponses sont donc correctes.
user123456789