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 aLazyColumnmantenha um controle mais preciso de quais elementos foram atualizados, é importante informar uma forma de identificar cada elemento unicamente:- Aqui usamos um
idcriado de forma randômica para cadaTask:val id: UUID = UUID.randomUUID();
- Aqui usamos um
- 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.ktPontos importantes:
-
Problema com o
LazyColumne estados:- Se elemento sai da tela, ele perde o estado:
- Solução simples: utilizar
rememberSaveableao invés deremember:rememberSaveableconsegue sobreviver a pequenas alterações na tela, mas não pode armazenar dados complexos como listas;
-
MutableLists observáveis:- Utilizar
MutableStateListOfoutoMutableStateList()
- Utilizar
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
onCheckedChangeeonRemoveClicksã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:
ViewModelssobrevivem a mais eventos do quecomposables:- Seguem o ciclo de vida de seu antecessor, por exemplo, da
Activity; - Inclusive a trocas de telas, se assim for necessário;
- Seguem o ciclo de vida de seu antecessor, por exemplo, da
É uma boa prática manter as Lógicas de UI e de Domínio separadas: mover para uma ViewModel;

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 umViewModelexistente 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;