Como gerenciar estados com Flutter Provider
Resumão
Esse artigo é para você que já tem alguma base em Flutter e quer dar o primeiro passo em projetos maiores e mais avançados.
Se você já programou um projeto em Flutter, pode ter notado que a transmissão de informações e estados pode ser tornar caótica quando o projeto escala, e é visando amenizar esse problema que vamos aprender o conceito de Gerenciamento de Estados usando o Provider.
Esse artigo está dividido nos seguintes passos:
- O que é um gerenciador de estados?
- Como instalar o Provider no projeto Flutter
- Primeiras configurações do Provider
- Consumindo uma informação com o Consumer
- Como atualizar o estado de uma informação no Provider?
- Concluindo
- Para saber mais
Aprenda como utilizar essa poderosa ferramenta em seus projetos profissionais e pessoais. Vamos lá?
O que é um gerenciador de estados?
Imagine a seguinte situação: Durante o uso de sua aplicação Flutter, uma informação (digamos, o título) de um objeto mudou. Pode ser que quem esteja usando a aplicação digitou um novo título e clicou em "salvar".
Agora seu objeto tem um novo título, porém existem vários componentes que mostram essa informação. Da forma que fazemos nativamente no Flutter, teríamos que repassar esse objeto modificado para todos os componentes seguindo uma árvore de componentes filhos e netos.
Percebemos, então, que essa forma de trabalhar com as informações do estado de um objeto não parece muito escalável. Para pequenos projetos, tudo bem. Mas, conforme o projeto cresce e uma mesma informação pode ser usada em diversos componentes, diversos widgets e diversas telas, organizar esse estado pode ser mais difícil.
É aí que entra o Gerenciador de Estados e o conceito de ”Single Source of Truth”, que em uma tradução literal significa “Fonte Única da Verdade”.
Um gerenciador de estados vai ser o responsável por observar um certo objeto e avisar para todos os componentes interessados se esse objeto mudou, para que eles se atualizem.
O Flutter tem um gerenciador de estados muito usado, simples e confiável, chamado “Provider”, e é ele que vamos aprender a usar neste artigo.
Como instalar o Provider no projeto Flutter
Como a maioria das dependências do Flutter, começamos nossa jornada visitando o site pub.dev, onde encontramos as dependências de Dart e Flutter. No campo de busca que aparece, escreva “provider” e pesquise.
Clique na dependência correta (ela é “Flutter Favorite”) e, na página dela, clique em “Installing”. Busque pela linha de recomendação para a instalação nas dependências do Flutter. Na data de publicação desse artigo, temos:
Por fim, adicione essa linha no nosso arquivo pubspec.yaml e rode, no terminal, o comando flutter pub get
.
Pronto, o Provider está pronto para uso!
Primeiras configurações do Provider
Para começar a usar nosso Gerenciador de Estados, vamos mostrar como fazer isso usando um “micro projeto” de exemplo para que você entenda melhor! Nosso exemplo será um aplicativo de de Lista de Tarefas, que ainda está no início do desenvolvimento. Assim, por enquanto, apenas temos os três seguintes arquivos de código:
lib/main.dart
import 'package:flutter/material.dart';
import 'models/task.dart';
import 'widgets/task_widget.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
appBar: AppBar(title: const Text("My to-do list")),
drawer: const Drawer(),
body: Center(
child: TaskWidget(Task("Study Flutter")),
),
),
);
}
}
Esse é o arquivo principal, e nele fazemos uma breve configuração do MaterialApp e criamos uma tela que contém apenas um TaskWidget no centro.
lib/widgets/task_widget.dart
import 'package:flutter/material.dart';
import 'package:flutter_provider/models/task.dart';
class TaskWidget extends StatelessWidget {
final Task task;
const TaskWidget(this.task);
@override
Widget build(BuildContext context) {
return Card(child: Text(task.title));
}
}
Esse é o nosso TaskWidget, um widget responsável por mostrar as informações de um objeto da classe Task de forma agradável na tela.
lib/models/task.dart
class Task{
String title;
Task(this.title);
}
Por fim, essa é nossa classe Task, um modelo pelo qual nós criaremos nossos objetos com as informações sobre as nossas atividades.
Com isso tudo estabelecido, a primeira coisa que temos que fazer é informar para o Provider quem é que vai usá-lo, e quais vão ser os estados que vamos gerenciar. Para isso, vamos fazer uma breve alteração na nossa linha “runApp”. Vamos checar o código abaixo:
lib/main.dart
void main() {
runApp(
ChangeNotifierProvider(
create: (context) => Task(""),
child: MyApp(),
),
);
}
Adicionamos esse ChangeNotifierProvider
, ele é o responsável por notificar para os widgets se houveram mudanças nas informações, e que portanto eles precisam ser atualizados.
O ChangeNotifierProvier
recebe dois argumentos: create e child. O create é basicamente o que vai mudar e, no nosso caso, o que muda é uma instância de Task. E o child é quem precisa ser avisado quando isso mudar, que no nosso caso é nossa aplicação inteira.
Depois de fazermos apenas essa alteração, você notará que haverá um erro de compilação, pois nossa classe “Task” não estende de “ChangeNotifier”, e isso é essencial. Então, vamos fazer essa alteração:
lib/models/task.dart
import 'package:flutter/material.dart';
class Task extends ChangeNotifier {
String title;
Task(this.title);
}
Só que, até então, nada mudou, já que a tela ainda mostra “Study Flutter”, e isso acontece pois a instância de Task passada em MyApp, que vai para TaskWidget, ainda é a mesma, pois estamos usando aquela maneira mais simples de transferir informação.
Finalizado esse passo, o que devemos fazer agora é receber essa informação do nosso Provider, e para isso vamos entender como usar o Consumer.
Consumindo uma informação com o Consumer
O “Consumer”, como o próprio nome diz, vai “consumir” a informação que está no nosso provider.
Para começar, vamos editar o arquivo task_widget para usar o “Consumer”. Vamos ao código:
lib/widgets/task_widget.dart
import 'package:flutter/material.dart';
import 'package:flutter_provider/models/task.dart';
import 'package:provider/provider.dart';
class TaskWidget extends StatelessWidget {
//final Task task;
//const TaskWidget(this.task);
@override
Widget build(BuildContext context) {
return Consumer<Task>(
builder: (context, storedValue, child) {
return Card(child: Text(storedValue.title));
},
);
}
}
Nosso Widget, ao invés de retornar diretamente nosso “Card”, agora vai retornar um Consumer<Task>
, sendo esse “Task” o tipo do objeto que queremos consumir. Ele receberá um builder que será uma função com um context
que é o contexto atual, um storedValue
que é justamente a informação que ele vai receber do provider, e um child
.
O retorno desse builder é justamente o que queremos mostrar na tela. Note que não usamos mais task.title
, que seria para pegar o título da tarefa que recebemos por parâmetro na classe TaskWidget. Na verdade esse parâmetro e o próprio construtor nem são mais úteis, e por isso estão comentados. Agora, pegamos essa informação diretamente do storedValue.title
.
Agora que removemos o construtor, haverá um erro no arquivo main.dart, pois lá ainda estávamos passando aquela Task(“Study Flutter”)
. Logo, podemos remover isso e pronto! Nosso TaskWidget já está consumindo a informação que está no nosso provider!
Show! Conseguimos ler uma informação armazenada no Provider, o próximo passo é alterarmos essa informação, correto? Então vamos lá!
Como atualizar o estado de uma informação no Provider?
Antes de qualquer coisa, em uma situação real, esse seria o momento de criar conexões com um banco de dados para adicionar e remover tarefas de uma lista. Mas, para fins didáticos, vamos abstrair essa mudança, criando o seguinte método dentro da classe Task:
lib/models/task.dart
void randomize() {
List<String> listTasks = [
"Study Flutter",
"Code Project",
"Take a break",
"Drink Coffee"
];
this.title = listTasks[Random().nextInt(listTasks.length)];
notifyListeners();
}
Muita atenção para a linha notifyListeners()
, porque é ela que vai avisar, para todos os “ouvidores” que configuramos no provider, quando uma mudança acontecer. Sem essa linha, você até consegue alterar o valor guardado, mas nada vai mudar na tela.
Com essa simples função, podemos aleatorizar uma tarefa dentre as da lista, o que é excelente para testarmos nossa funcionalidade com o Provider. Além disso, precisamos chamar essa função em algum lugar, e nada melhor do que usar um botão - melhor ainda se for um FloatingActionButton, então, para isso, vamos adicionar a seguinte linha no nosso Scaffold:
lib/main.dart
floatingActionButton: Consumer<Task>(
builder: (context, storedValue, child) {
return FloatingActionButton(
child: Icon(Icons.change_circle_outlined),
onPressed: () {
storedValue.randomize();
print(storedValue.title);
});
},
),
Da mesma forma que usamos o Consumer para ler o valor armazenado, também usamos o Consumer para pegar o valor armazenado e alterá-lo. Então, adicionamos o FloatingActionButton como retorno do builder e, quando ele for pressionado, o valor será aleatorizado.
Agora, basta clicar no nosso FloatingActionButton que novas tarefas aparecerão na tela!
E pronto! Agora que terminamos essa configuração, o Provider está preparado para notificar todos os widgets quando houverem alterações no nosso Task. E quais foram os benefícios de todo esse processo?
1) Agora a informação está sendo mandada diretamente para o widget que vai usá-la, sem precisar passar por uma árvore de widgets, o que facilita muito a legibilidade e a manutenção do código.
2) Você fez uma aplicação com alteração dinâmica sem precisar usar sequer um StatefulWidget, o que faz com que seus componentes apenas sejam recriados quando houver necessidade, portanto, tornando a aplicação muito mais eficiente!
Concluindo
O gerencimento de estados é uma técnica de Clean Code essencial para a saúde e escalabilidade de projetos Flutter grandes e complexos, pois o Provider é uma dependência que nos ajuda desenvolver esse gerenciamento com um baixo esforço. Neste artigo, nós aprendemos:
- O que é um Gerenciador de Estados;
- Como instalar o Provider em um projeto Flutter;
- Como fazer as primeiras configurações do Provider;
- Como acessar uma informação armazenada no Provider;
- Como alterar uma informação armazenada no Provider.
Para saber mais
Espero que tenha gostado da leitura e tenha sentido aquela ansiedade boa de colocar a mão na massa para implementar uma nova ferramenta!
Mas existe muito mais conteúdo sobre Flutter Provider! Assista o curso “Flutter: Gerenciamento de Estados com Provider” da Formação Flutter da Alura.
Além disso, recomendo as seguintes leituras complementares:
- (Em inglês) Flutter | Simple app state management
- Gerenciamento de Estado no Flutter, uso do Provider
- (Em inglês) Provider Readme
Bons estudos e nós nos vemos novamente no maravilhoso mundo do Flutter!