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.


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.


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
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.