Problema ViewPager2 / Tabs con stato ViewModel


9

Sto seguendo il modello MVVM - nel senso che ho un ViewModel per ogni frammento.

Ho aggiunto due schede utilizzando ViewPager2.

Il mio adattatore è simile al seguente:

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

Le schede funzionano. Tuttavia, ho notato che ViewModel del mio MergedItemsFragment si sta comportando in modo strano. Prima di aggiungere le schede sono passato al frammento in questo modo:

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

Quando lasciavo quel frammento NavHostFragment.findNavController(this).popBackStack()e poi tornavo a quel frammento, ottenevo un nuovo ViewModel vuoto. Questo era previsto.

Con il nuovo approccio con cui sto navigando return new MergedItemsFragment(). Quando lascio quel frammento e successivamente ritorno, ricevo un ViewModel che contiene i vecchi dati . Questo è un problema perché i vecchi dati non sono più rilevanti perché l'utente ha selezionato dati diversi in un altro frammento.


Aggiornamento n. 1

Mi sono reso conto che in realtà mantiene tutti i vecchi frammenti in memoria perché le stesse dichiarazioni di stampa vengono chiamate più volte. Il tempo che viene chiamato aumenta con il numero di volte che lascio e torno a quella schermata. Quindi, se lascio e ritorno 10 volte e ruoto il mio dispositivo, eseguirà effettivamente una riga 10 volte. Qualcuno indovina come implementare Tab / ViewPager con componenti di navigazione in un modo che funziona con ViewModels?


Aggiornamento n. 2

Ho impostato i miei ViewModels in questo modo:

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

Ottengo gli stessi risultati con:

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

Lego ViewModel nel frammento stesso. Pertanto, thisè il frammento.


Puoi mostrare come stai impostando i tuoi ViewModels? Inoltre, c'è qualche motivo per cui non puoi semplicemente creare un nuovo ViewModel quando ricevi nuovi dati?
BlackHatSamurai,

Ho aggiornato la mia domanda. Non è forse il punto di vista di un modello che se ne occupa da solo? Lo creo una volta e persiste per un frammento. Come lo ricreai esattamente se avessi nuovi dati e perché non avrei dovuto farlo prima?
user123456789,

Non era necessario farlo prima perché il frammento era stato distrutto. Ora stai usando un ViewPager e memorizza il frammento in memoria. Vorrei solo cancellare i dati quando è necessario. È necessario gestire i dati nella VM, piuttosto che nella VM stessa.
BlackHatSamurai,

Il problema che ho è che le vecchie macchine virtuali stanno ancora servendo il vecchio LiveData e alimentando i vecchi dati degli altri componenti. Quindi cancellare i dati non funzionerà perché le vecchie macchine virtuali continuano a interferire. Esempio: deseleziono un elenco nel ViewModel corrente. Tuttavia, lo schermo ottiene ancora il vecchio elenco. Quando eseguo il debug di ViewModel e controllo la lunghezza dell'elenco, viene visualizzato 0, poiché è stato cancellato. L'unica spiegazione logica sono altri ViewModel che servono dati vecchi.
user123456789,

Stai usando la stessa VM per ognuno dei frammenti? O ogni frammento ha la propria VM?
BlackHatSamurai,

Risposte:


3

Come da tuo commento, stai usando Frammento e dentro quel Frammento c'è il tuo viewpager. Pertanto, durante la creazione dell'adattatore per ViewPager è necessario passare childFragmentManager anziché getActivity ()

Di seguito è riportato un adattatore di esempio per viewPager che è possibile utilizzare

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]
    }
}

e mentre crei il tuo adattatore chiamalo come

   val adapter = NewViewPagerAdapter(
        childFragmentManager,
        FragmentPagerAdapter.POSITION_UNCHANGED
    )

come se vedessi la documentazione per FragmentStatePagerAdapter afferma che dovresti passare (FragmentManager, int) all'interno del costruttore dell'adattatore

Spero che questo risolva il tuo problema poiché un giorno stavo affrontando lo stesso problema.

Buona codifica.


1
Grazie. Come già detto da ianhanniballake, passare il frammento stesso è sufficiente, assicurati di avere un contrettore adatto. Quindi entrambe le risposte sono corrette.
user123456789
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.