ViewPager2 / Problema de pestañas con el estado de ViewModel

9

Estoy siguiendo el patrón MVVM, lo que significa que tengo un ViewModel para cada Fragmento.

Añadí dos lengüetas mediante el uso de ViewPager2.

Mi adaptador se ve así:

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

Las pestañas están funcionando. Sin embargo, noté que el ViewModel de mi MergedItemsFragment se comporta de manera extraña. Antes de agregar pestañas, navegué al Fragmento de esta manera:

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

Cuando dejé ese fragmento con NavHostFragment.findNavController(this).popBackStack()y luego volví a ese fragmento, obtendría un nuevo ViewModel vacío. Esto fue intencionado.

Con el nuevo enfoque con el que estoy navegando return new MergedItemsFragment(). Cuando dejo ese fragmento y más tarde regreso, recibo un ViewModel que contiene los datos antiguos . Eso es un problema porque los datos antiguos ya no son relevantes porque el usuario seleccionó datos diferentes en otro fragmento.


Actualización n. ° 1

Me di cuenta de que en realidad guarda todos los Fragmentos antiguos en la memoria porque las mismas declaraciones de impresión se llaman varias veces. Las veces que se llama aumenta con la cantidad de veces que salgo y regreso a esa pantalla. Entonces, si me voy y regreso 10 veces y giro mi dispositivo, en realidad ejecutará una línea 10 veces. ¿Alguna idea de cómo implementar Tabs / ViewPagers con componentes de navegación de una manera que funcione con ViewModels?


Actualización n. ° 2

Configuré mis ViewModels así:

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

Obtengo los mismos resultados con:

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

Ato el ViewModel en el Fragmento mismo. Por lo tanto, thises el Fragmento.

usuario123456789
fuente
¿Puede mostrar cómo está configurando sus ViewModels? Además, ¿hay alguna razón por la que no pueda crear un nuevo ViewModel cuando obtenga nuevos datos?
BlackHatSamurai
He actualizado mi pregunta. ¿No es el punto de vista de un modelo que se encarga de eso mismo? Lo creo una vez y persiste por un fragmento. ¿Cómo exactamente lo volvería a crear en caso de que tuviera datos nuevos y por qué no necesitaba hacerlo previamente?
user123456789
No era necesario hacerlo antes porque el fragmento fue destruido. Ahora está utilizando un ViewPager y almacena el fragmento en la memoria. Sugeriría simplemente borrar los datos cuando sea necesario. Debe administrar los datos en la VM, en lugar de la VM misma.
BlackHatSamurai
El problema que tengo es que las máquinas virtuales viejas todavía están sirviendo LiveData viejo y alimentando los datos viejos de los otros componentes. Por lo tanto, borrar los datos no funcionará porque las máquinas virtuales antiguas siguen interfiriendo. Ejemplo: borro una lista en el ViewModel actual. Sin embargo, la pantalla todavía obtiene la lista anterior. Cuando depuro el ViewModel y verifico la longitud de la Lista, dice 0, ya que se ha borrado. La única explicación lógica son otros ViewModels que sirven datos antiguos.
user123456789
¿Está utilizando la misma máquina virtual para cada uno de los fragmentos? ¿O cada fragmento tiene su propia VM?
BlackHatSamurai

Respuestas:

3

Según su comentario, está utilizando Fragment y dentro de ese Fragment está su visor. Entonces, al crear su Adaptador para ViewPager, debe pasar childFragmentManager en lugar de getActivity ()

A continuación se muestra un adaptador de muestra para su viewPager que puede usar

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

y mientras creas tu adaptador llámalo como

   val adapter = NewViewPagerAdapter(
        childFragmentManager,
        FragmentPagerAdapter.POSITION_UNCHANGED
    )

como si viera la documentación de FragmentStatePagerAdapter , indica que debe pasar (FragmentManager, int) dentro del constructor de su adaptador

Espero que esto resuelva tu problema, ya que un día me enfrenté al mismo problema.

Feliz codificación

Rakshit Nawani
fuente
1
Gracias. Como ya dijo ianhanniballake, pasar el fragmento en sí es suficiente, solo asegúrese de tener un constructor adecuado. Entonces ambas respuestas son correctas.
user123456789