The Android Developer Challenge is back! Submit your idea before December 2.

Visão geral do ViewModel

Visão geral do ViewModel   Parte do Android Jetpack.

A classe ViewModel foi projetada para armazenar e gerenciar dados relacionados à IU considerando o ciclo de vida. A classe ViewModel permite que os dados sobrevivam às mudanças de configuração, como a rotação da tela.

O framework do Android gerencia os ciclos de vida dos controladores de IU, como atividades e fragmentos. O framework pode decidir destruir ou recriar um controlador de IU em resposta a determinadas ações do usuário ou eventos do dispositivo que estão completamente fora do seu controle.

Se o sistema destruir ou recriar um controlador de IU, todos os dados temporários relacionados à IU armazenados nele serão perdidos. Por exemplo, seu app pode incluir uma lista de usuários em uma das atividades dele. Quando a atividade for recriada para uma mudança de configuração, a nova atividade precisará buscar novamente a lista de usuários. Para dados simples, a atividade pode usar o método onSaveInstanceState() e restaurar os próprios dados a partir do pacote em onCreate(), mas essa abordagem é adequada apenas para pequenos volumes de dados que podem ser serializados e depois desserializados, não para volumes potencialmente grandes de dados. como uma lista de usuários ou bitmaps.

Outro problema é que os controladores de IU geralmente precisam fazer chamadas assíncronas que podem levar algum tempo para retornar. O controlador de IU precisa gerenciar essas chamadas e garantir que o sistema as limpe depois de ser destruído, para evitar possíveis vazamentos de memória. Esse gerenciamento requer muita manutenção e, caso o objeto seja recriado para uma mudança de configuração, representa um desperdício de recursos, porque o objeto pode precisar emitir novamente as chamadas já feitas.

Os controladores de IU, como atividades e fragmentos, têm como objetivo principal exibir dados da IU, reagir às ações do usuário ou lidar com a comunicação do sistema operacional, como solicitações de permissão. A exigência de que os controladores de IU também sejam responsáveis pelo carregamento de dados de um banco de dados ou de uma rede acaba tornando a classe pesada. Atribuir responsabilidades excessivas aos controladores de IU pode fazer com que uma única classe precise lidar sozinha com todo o trabalho de um app, em vez de delegar o trabalho para outras classes. Atribuir responsabilidades excessivas aos controladores de IU dessa maneira também dificulta muito os testes.

É mais fácil e eficiente separar a propriedade de dados de visualização da lógica do controlador de IU.

Implementar um ViewModel

Os componentes de arquitetura fornecem a classe auxiliar ViewModel para o controlador de IU responsável por preparar dados para a IU. Objetos ViewModel são retidos automaticamente durante as mudanças de configuração, de modo que os dados retidos estejam imediatamente disponíveis para a próxima atividade ou instância de fragmento. Por exemplo, se você precisar exibir uma lista de usuários no app, atribua a responsabilidade de adquirir e manter a lista de usuários a um ViewModel, em vez de uma atividade ou fragmento, conforme ilustrado pelo código de amostra a seguir:

Kotlin

    class MyViewModel : ViewModel() {
        private val users: MutableLiveData<List<User>> by lazy {
            MutableLiveData().also {
                loadUsers()
            }
        }

        fun getUsers(): LiveData<List<User>> {
            return users
        }

        private fun loadUsers() {
            // Do an asynchronous operation to fetch users.
        }
    }
    

Java

    public class MyViewModel extends ViewModel {
        private MutableLiveData<List<User>> users;
        public LiveData<List<User>> getUsers() {
            if (users == null) {
                users = new MutableLiveData<List<User>>();
                loadUsers();
            }
            return users;
        }

        private void loadUsers() {
            // Do an asynchronous operation to fetch users.
        }
    }
    

Depois, você pode acessar a lista de uma atividade da seguinte maneira:

Kotlin

    class MyActivity : AppCompatActivity() {

        override fun onCreate(savedInstanceState: Bundle?) {
            // Create a ViewModel the first time the system calls an activity's onCreate() method.
            // Re-created activities receive the same MyViewModel instance created by the first activity.

            val model = ViewModelProviders.of(this)[MyViewModel::class.java]
            model.getUsers().observe(this, Observer<List<User>>{ users ->
                // update UI
            })
        }
    }
    

Java

    public class MyActivity extends AppCompatActivity {
        public void onCreate(Bundle savedInstanceState) {
            // Create a ViewModel the first time the system calls an activity's onCreate() method.
            // Re-created activities receive the same MyViewModel instance created by the first activity.

            MyViewModel model = ViewModelProviders.of(this).get(MyViewModel.class);
            model.getUsers().observe(this, users -> {
                // update UI
            });
        }
    }
    

Se a atividade for recriada, ela receberá a mesma instância MyViewModel que foi criada pela primeira atividade. Quando a atividade do proprietário estiver concluída, o framework chamará o método onCleared() do objeto ViewModel para que ele possa limpar os recursos.

Objetos ViewModel são projetados para sobreviver aos instanciamentos específicos de visualizações ou LifecycleOwners. Esse design também significa que é possível escrever testes para cobrir um ViewModel mais facilmente, porque ele não conhece os objetos de visualização e objetos Lifecycle. Objetos ViewModel podem conter LifecycleObservers, como objetos LiveData. No entanto, os objetos ViewModel nunca podem observar mudanças nos observáveis com reconhecimento de ciclo de vida, como os objetos LiveData. Se o ViewModel precisar do contexto de Application, por exemplo, para localizar um serviço do sistema, ele poderá estender a classe AndroidViewModel e ter um construtor que receba o Application, já que a classe Application estende o Context.

O ciclo de vida de um ViewModel

Objetos ViewModel têm como escopo o Lifecycle transferido para o ViewModelProvider ao receber o ViewModel. O ViewModel permanece na memória até o Lifecycle referente ao escopo dele desaparecer permanentemente: no caso de uma atividade, quando ela termina, e no caso de um fragmento, quando ele é desanexado.

A Figura 1 ilustra os vários estados do ciclo de vida de uma atividade à medida que ela é submetida a uma rotação e, em seguida, é concluída. A ilustração também mostra a vida útil do ViewModel ao lado do ciclo de vida da atividade associada. Este diagrama específico ilustra os estados de uma atividade. Os mesmos estados básicos se aplicam ao ciclo de vida de um fragmento.

Ilustra o ciclo de vida de um ViewModel como um estado de mudanças de atividade.

Geralmente, você solicita um ViewModel na primeira vez que o sistema chama o método onCreate() de um objeto de atividade. O sistema pode chamar onCreate() várias vezes durante a vida de uma atividade, como quando a tela de um dispositivo é rotacionada. O ViewModel existe a partir do momento em que um ViewModel é solicitado pela primeira vez até que a atividade seja finalizada e destruída.

Compartilhar dados entre fragmentos

É muito comum que dois ou mais fragmentos em uma atividade precisem se comunicar uns com os outros. Imagine um caso comum de fragmentos mestre/detalhes, em que há um fragmento em que o usuário seleciona um item de uma lista e outro que exibe o conteúdo do item selecionado. Esse caso nunca é trivial, porque ambos os fragmentos precisam definir certa descrição da interface, e a atividade do proprietário precisa unir os dois. Além disso, ambos os fragmentos precisam lidar com o cenário em que o outro fragmento ainda não foi criado ou não está visível.

Esse ponto problemático comum pode ser resolvido usando objetos ViewModel. Esses fragmentos podem compartilhar um ViewModel usando o escopo de atividade deles para lidar com essa comunicação, conforme ilustrado pela seguinte amostra de código:

Kotlin

    class SharedViewModel : ViewModel() {
        val selected = MutableLiveData<Item>()

        fun select(item: Item) {
            selected.value = item
        }
    }

    class MasterFragment : Fragment() {

        private lateinit var itemSelector: Selector

        private lateinit var model: SharedViewModel

        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            model = activity?.run {
                ViewModelProviders.of(this)[SharedViewModel::class.java]
            } ?: throw Exception("Invalid Activity")
            itemSelector.setOnClickListener { item ->
                // Update the UI
            }
        }
    }

    class DetailFragment : Fragment() {

        private lateinit var model: SharedViewModel

        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            model = activity?.run {
                ViewModelProviders.of(this)[SharedViewModel::class.java]
            } ?: throw Exception("Invalid Activity")
            model.selected.observe(this, Observer<Item> { item ->
                // Update the UI
            })
        }
    }
    

Java

    public class SharedViewModel extends ViewModel {
        private final MutableLiveData<Item> selected = new MutableLiveData<Item>();

        public void select(Item item) {
            selected.setValue(item);
        }

        public LiveData<Item> getSelected() {
            return selected;
        }
    }

    public class MasterFragment extends Fragment {
        private SharedViewModel model;
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
            itemSelector.setOnClickListener(item -> {
                model.select(item);
            });
        }
    }

    public class DetailFragment extends Fragment {
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            SharedViewModel model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
            model.getSelected().observe(this, { item ->
               // Update the UI.
            });
        }
    }
    

Os dois fragmentos recuperam a atividade que os contém. Dessa forma, quando os fragmentos receberem o ViewModelProvider, receberão a mesma instância SharedViewModel, que tem escopo para essa atividade.

Essa abordagem oferece os seguintes benefícios:

  • A atividade não precisa fazer nada nem saber nada sobre essa comunicação.
  • Os fragmentos não precisam conhecer uns aos outros além do contrato SharedViewModel. Se um dos fragmentos desaparecer, o outro continuará funcionando normalmente.
  • Cada fragmento tem o próprio ciclo de vida e não é afetado pelo ciclo de vida do outro. Se um fragmento substituir o outro, a IU continuará funcionando sem problemas.

Substituir carregadores pelo ViewModel

As classes de carregadores, como o CursorLoader, geralmente são usadas para manter os dados na IU de um app em sincronia com um banco de dados. Você pode usar o ViewModel, com algumas outras classes, para substituir o carregador. Usar um ViewModel separa seu controlador de IU da operação de carregamento de dados, o que significa que você terá menos referências fortes entre as classes.

Em uma abordagem comum do uso de carregadores, um app pode usar um CursorLoader para observar o conteúdo de um banco de dados. Quando um valor no banco de dados é modificado, o carregador aciona automaticamente uma atualização dos dados e atualiza a IU:

Figura 2. Carregar dados com carregadores.

ViewModel funciona com Room e LiveData para substituir o carregador. O ViewModel garante que os dados sobrevivam a uma mudança na configuração do dispositivo. A Room informa seu LiveData quando o banco de dados muda, e o LiveData, por sua vez, atualiza a IU com os dados revisados.

Figura 3. Carregar dados com o ViewModel.

Usar corrotinas com o ViewModel

O ViewModel inclui compatibilidade com corrotinas de Kotlin. Para mais informações, consulte Usar corrotinas do Kotlin com Componentes da arquitetura do Android.

Mais informações

À medida que seus dados se tornam mais complexos, você pode optar por ter uma classe separada apenas para carregar os dados. O objetivo do ViewModel é encapsular os dados de um controlador de IU para permitir que eles sobrevivam às mudanças de configuração. Para saber mais sobre como carregar, manter e gerenciar dados entre as mudanças de configuração, consulte Como salvar estados de IU.

O Guia para a arquitetura do app sugere a criação de uma classe de repositórios para lidar com essas funções.

Outros recursos

Para mais informações sobre a classe ViewModel, consulte os recursos a seguir.

Amostras (links em inglês)

Codelabs (links em inglês)

Blogs (links em inglês)

Vídeos (em inglês)