06 - Gerenciamento de Estado e ViewModels

Código exemplo: Jogo da Velha

https://github.com/tads-ufpr-alexkutzke/ds151-example-tictactoe

Desenvolvimento da aplicação MyTasks

A seguir está o passo a passo do desenvolvimento da aplicação MyTasks

https://github.com/tads-ufpr-alexkutzke/ds151-aula-06-mytasks/

1. Criação do componente TaskItem

O componente TaskItem exibe uma Tarefa.

Ele foi planejado para ser do tipo stateless, ou seja, seu estado será manipulado pelo seu antecessor.

Commit: TaskItem Layout - Diffs 17b16e81

TaskItem.kt

2. Criação do componente TaskList

O componente TaskItem exibe uma lista de tarefas, ou seja, uma lista de TaskItem.

Pontos importantes:

  • Parâmetro key: para que a LazyColumn mantenha um controle mais preciso de quais elementos foram atualizados, é importante informar uma forma de identificar cada elemento unicamente:
    • Aqui usamos um id criado de forma randômica para cada Task: val id: UUID = UUID.randomUUID();
  • Também foi projetada para ser stateless;

Commit: TaskList - Diffs 1b615fd4

TaskList.kt

3. Criação dos componentes principal - MyTasks

O componente MyTasks é responsável por abrigar outros dois componentes:

  • NewTaskControl: caixa de texto e botão para a criação de uma nova tarefa;
  • TaskList: lista de tarefas;

Commit: NewTaskControl and MyTasks - Diffs a39ac305

MyTasks.kt
Componente NewTaskControl

O componente NewTaskControl faz uso de um TextField, mais precisamente, de um OutlinedTextField.

Campos de texto precisam de, pelo menos, dois parâmetros:

  • value: texto atual apresentado na caixa de texto;
  • onValueChange: evento de alteração do texto. É um callback com um argumento, o novo valor do texto;
NewTaskControl.kt

4. Inclusão dos primeiros estados

Nesse commit o componente MyTasks recebe dois estados novos:

var newTaskText by remember { mutableStateOf("") }
var tasks = remember { mutableStateListOf<Task>() }

newTaskText é o texto armazenado no TextField.

tasks é a lista de tarefas atual.

Commit: First states - Diffs bb906d1c

MyTasks.kt

Pontos importantes:

  • Problema com o LazyColumn e estados:

    • Se elemento sai da tela, ele perde o estado:
    • Solução simples: utilizar rememberSaveable ao invés de remember:
      • rememberSaveable consegue sobreviver a pequenas alterações na tela, mas não pode armazenar dados complexos como listas;
  • MutableLists observáveis:

    • Utilizar MutableStateListOf ou toMutableStateList()
TaskList.kt

5. Implementação completa de estados

Para adicionar todo o funcionamento da tela, tornamos o valor checked observável na classe Task.

Assim, o componente TaskList consegue recompor sempre que uma tarefa tem seu estado de checked alterado.

Pontos importantes:

  • Como os eventos onCheckedChange e onRemoveClick são implementados.

Commit: Events and states working - Diffs 07479b85

MyTasks.kt

TaskList.kt

5. Implementação completa de estados

Apenas ordena as tarefas para apresentar as não realizas antes.

Commit: Sort tasks - Diffs 26f4dc53

MyTasks.kt

important

Na forma como está, qualquer alteração na tela, como rotacionar o celular, faz toda a lista de tarefas ser perdida.


6. Utilizando ViewModel para armazenar estados

Commit: Implements MyTasksViewModel - Diffs c3d0f24b

Existem dois tipos de lógica:

  • Logica de UI ou Comportamento: como exibir alterações no estado;
  • Lógica de Negócios ou Domínio: o que fazer quando o estado é alterado;

A Lógica de Domínio é, geralmente, armazenada em outras camadas da aplicação (por exemplo, camada de dados, como veremos a seguir).

ViewModels permitem que composables e outros componentes de UI acessem a lógica de domínio armazenadas em uma camada de dados da aplicação:

  • ViewModels sobrevivem a mais eventos do que composables:
    • Seguem o ciclo de vida de seu antecessor, por exemplo, da Activity;
    • Inclusive a trocas de telas, se assim for necessário;

É uma boa prática manter as Lógicas de UI e de Domínio separadas: mover para uma ViewModel;

UI = UI Elements + UI State

Unidirectional data flow

The UI update loop on unidirectional data flow

Nesse commit, criamos uma nova ViewModel, chamada MyTasksViewModel:

MyTasksViewModel.kt

Para que o projeto compile corretamente, é necessário adicionar uma nova dependência ao arquivo app/build.gradle.kts: j

implementation("androidx.lifecycle:lifecycle-viewmodel-compose:{latest_version}")

// ou (depende da versão do android studio)

implementation(libs.androidx.lifecycle.viewmodel.compose)
build.gradle.kts
Utilizando o ViewModel criado

Podemos acessar o MyTasksViewModel em qualquer composable, chamando viewModel().:

  • viewModel() retorna um ViewModel existente ou cria um novo no escopo atual;

No componente MyTasks adicionamos um parâmetro que chamado myTasksViewModel que recebe como valor default o resultado de viewModel();

MyTasks.kt

myTasksViewModel poderá ser permitirá ao composable acessar e manipular os dados desse ViewModel;

O ViewModel criado será mantido enquanto o seu escopo estiver vivo. Nesse caso, enquanto a Activity que possui MyTasks estiver viva;