Como gerenciar estados com Flutter Provider

Como gerenciar estados com Flutter Provider
Ricarth Lima
Ricarth Lima

Compartilhe

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á?

Imersão dev Back-end: mergulhe em programação hoje, com a Alura e o Google Gemini. Domine o desenvolvimento back-end e crie o seu primeiro projeto com Node.js na prática. O evento é 100% gratuito e com certificado de participação. O período de inscrição vai de 18 de novembro de 2024 a 22 de novembro de 2024. Inscreva-se já!

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”.

Na imagem, há um comparativo do fluxo da atualização de estados sem e com o Provider. Na esquerda, sem o provider, percebemos que a informação é mandada de pai para filho entre os componentes, de cima para baixo, na seguinte ordem: Container, Componente Filho e Componente Neto. Na direita, percebemos que ela vem diretamente de um lugar chamado “Store”, ou seja, a informação é enviada pelo Provider, de uma vez só, para o Container, Componente Filho e Componente Neto.

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.

Na imagem, há um print do link do provider na lista de pesquisa do pub.dev.

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:

Na imagem, há um print da informação dada na aba “Installing” sobre a linha que deve ser adicionada no pubspec.yaml para instalação do provider em um projeto Flutter. É possível ler a seguinte frase: “This will add a line like this to your package’s pubspec.yaml (and run an implicit ‘flutter pub get’), ou seja, “Isto vai adicionar uma linha como essa em seu pacote pubspec.yaml (e executar um implícito ´flutter pub get´). Abaixo, há as linhas “dependencies: provider: ^6.0.0.

Por fim, adicione essa linha no nosso arquivo pubspec.yaml e rode, no terminal, o comando flutter pub get.

Na imagem, há um print do Visual Studio Code aberto no arquivo pubspec.yaml que mostra que a linha recomendada no site pub.dev foi adicionada dentro das dependecies.

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 ChangeNotifierProvierrecebe 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.

Na imagem, há um print da saída visual do projeto Flutter, notamos que há apenas um “Study Flutter” estático escrito na tela.

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!

Na imagem, há um print da saída visual (tela) do projeto Flutter. Notamos, agora, que a informação escrita mudou para “Drink Coffee”.

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:

Bons estudos e nós nos vemos novamente no maravilhoso mundo do Flutter!

Ricarth Lima
Ricarth Lima

Acredito que educação e computação podem mudar o mundo para melhor, em especial, juntas. Por isso além de fazer parte do Grupo Alura, sou professor, desenvolvedor de jogos educativos e criador de conteúdo! Amo Flutter e Unity!

Veja outros artigos sobre Mobile