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 aLazyColumn
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 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.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 deremember
:rememberSaveable
consegue sobreviver a pequenas alterações na tela, mas não pode armazenar dados complexos como listas;
-
MutableList
s observáveis:- Utilizar
MutableStateListOf
outoMutableStateList()
- 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
onCheckedChange
eonRemoveClick
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 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 umViewModel
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;