Problema do ViewPager2 / Tabs com o estado do ViewModel

9

Estou seguindo o padrão MVVM - o que significa que tenho um ViewModel para cada fragmento.

Eu adicionei duas guias usando o ViewPager2.

Meu adaptador fica assim:

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

As guias estão funcionando. No entanto, notei que o ViewModel do meu MergedItemsFragment está se comportando de maneira estranha. Antes de adicionar guias, naveguei para o fragmento assim:

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

Quando deixei esse fragmento NavHostFragment.findNavController(this).popBackStack()e depois retornei a ele, recebi um novo ViewModel vazio. Isso foi planejado.

Com a nova abordagem, estou navegando return new MergedItemsFragment(). Quando deixo esse fragmento e, mais tarde, retorno, estou recebendo um ViewModel que contém os dados antigos . Esse é um problema porque os dados antigos não são mais relevantes porque o Usuário selecionou dados diferentes em outro fragmento.


Atualização # 1

Percebi que ele realmente mantém todos os fragmentos antigos na memória porque as mesmas instruções de impressão são chamadas várias vezes. O tempo que é chamado aumenta com a quantidade de vezes que saio e volto para a tela. Portanto, se eu sair e retornar 10 vezes e girar meu dispositivo, ele executará uma linha 10 vezes. Alguma idéia de como implementar Tabs / ViewPagers com componentes de navegação de uma maneira que funcione com o ViewModels?


Atualização # 2

Defino meus ViewModels assim:

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

Eu obtenho os mesmos resultados com:

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

Eu vinculo o ViewModel no próprio fragmento. Portanto, thisé o fragmento.

user123456789
fonte
Você pode mostrar como está configurando seus modelos de exibição? Além disso, existe algum motivo para você não poder simplesmente criar um novo ViewModel ao obter novos dados?
BlackHatSamurai 18/02
Eu atualizei minha pergunta. Não é o ponto de vista de um modelo que ele cuide disso? Eu o crio uma vez e persiste por um fragmento. Como exatamente eu o recriaria no caso de ter novos dados e por que não precisei fazer isso anteriormente?
user123456789 18/02
Você não precisava fazer isso antes porque o fragmento foi destruído. Agora você está usando um ViewPager e ele armazena o fragmento na memória. Eu sugeriria apenas limpar os dados quando você precisar. Você precisa gerenciar os dados na VM, em vez da própria VM.
BlackHatSamurai 18/02
O problema que tenho é que as VMs antigas ainda estão servindo o LiveData antigo e alimentando os dados antigos dos outros componentes. Portanto, limpar os dados não funcionará porque as VMs antigas continuam interferindo. Exemplo: eu limpo uma lista no ViewModel atual. No entanto, a tela ainda recebe a lista antiga. Quando depuro o ViewModel e verifico o comprimento da lista, diz 0 - desde que foi limpo. A única explicação lógica são outros ViewModels que servem dados antigos.
user123456789 18/02
Você está usando a mesma VM para cada um dos fragmentos? Ou cada fragmento tem sua própria VM?
BlackHatSamurai 18/02

Respostas:

3

De acordo com o seu comentário, você está usando o Fragment e, dentro desse Fragmento, está o seu viewpager. Portanto, ao criar seu adaptador para o ViewPager, você precisa passar childFragmentManager em vez de getActivity ()

Abaixo está um adaptador de amostra para o seu viewPager que você pode 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]
    }
}

e enquanto cria seu adaptador, chame-o como

   val adapter = NewViewPagerAdapter(
        childFragmentManager,
        FragmentPagerAdapter.POSITION_UNCHANGED
    )

como se você vir a documentação para FragmentStatePagerAdapter, ela afirma que deve passar (FragmentManager, int) dentro do construtor do adaptador

Espero que isso resolva seu problema, pois eu estava enfrentando o mesmo problema um dia.

Feliz codificação.

Rakshit Nawani
fonte
11
Obrigado. Como ianhanniballake já disse, passar o fragmento em si é suficiente, apenas verifique se você tem um contratante adequado. Então, ambas as respostas estão corretas.
User123456789 20/02