Introdução ao Estado Atômico no Flutter com RxNotifier

Jacob A. Moura
Flutterando
Published in
7 min readFeb 26, 2023

--

Olá devs.

Quando o Flutter foi concebido, não se imaginava que a forma declarativa de criar apps traria consigo tanta confusão para a comunidade, principalmente por dar dá ao desenvolvedor o controle sobre quando e como os widgets deveriam ser atualizados. O impressionante é que esse comportamento é literalmente uma feature importada de seu principal concorrente, o React.

As soluções para alterar o estado de um Widget são simples. Por exemplo, o StatefulWidget contém um método que solicita a reconstrução do Widget (o setState), e isso por muito tempo foi o suficiente, até surgir uma dúvida matadora: “Como atualizar Widgets que não são filhos do meu StatefulWidget?”. É aqui onde o pesadelo começa.

A resposta inicial do Google para distribuição do estado pela árvore de Widget foi o InheritedWidget e seus derivados (InheritedNotifier, InheritedModel e afins) trazendo uma proposta muito semelhante ao Context API do React.
Essa solução consiste em adicionar um InheritedWidget no começo da árvore e chamó-lo nos Widgets filhos através do BuildContext. Todos os Widgets filhos adicionam o InheritedWidget como dependência interna, sendo reconstruídos ao receberem um "sinal" do Widget pai.

Essa solução não agradou à comunidade que preferiu distribuir os estados usando um sistema de injeção de dependências externo como o Provider, GetIt ou Modular ao ter que trabalhar com InheritedWidget e sua implementação confusa. A solução ContextAPI do Flutter só funciona com o Provider e mesmo assim não é muito competente como no React. Isso tudo nos mostra que a verdadeira dificuldade no gerenciamento de estado do Flutter é a distribuição do estado. Mas ainda existem outros agravantes.

BLoC e Controllers

Todo desenvolvedor Flutter já deve ter escutado que a melhor proposta para gerenciamento de estados em um grande projeto seria o BLoC, e isso é tão incorreto quanto dizer que só se deve usar Redux em projetos grandes no React.

O objetivo do Google em apresentar o BLoC em 2019 era ter um padrão mais entreprise e com limites arquiteturais mais rígidos para trabalhos com equipes de desenvolvedores numerosas. E até hoje é adotado em projetos que necessitem de um pattern com uma boa escalabilidade.

O BLoC tem seu mérito, mas é apenas um padrão facilmente superado por outras propostas como o ChangeNotifier/ValueNotifier e MobX.
Os limites arquiteturais do BLoC só fariam sentido se trabalhasse com um estado global da aplicação e seus eventos fossem despachados como String ou qualquer outro tipo primitivo para a aplicação inteira, e se o BLoC fosse assim, seria uma espécie de Redux.
Porém, poucos desenvolvedores perceberam que o BLoC fere um dos princípios do SOLID, o Single Responsability.
O BLoC armazena e propaga o estado além "reduzir" a regra de negócio a outro estado. Temos aqui dois motivos para mudar, dois contextos diferentes, duas responsabilidades distintas.
Isso parece inofensivo no primeiro momento, mas o que mostrarei a seguir provará que cometemos esse erro por tempo demais.

Não irei fazer "meia culpa". Assim como você, também acreditei que o BLoC era perfeito arquiteturalmente, até que meus olhos foram abertos pela comunidade do React Brasil, que mostrou que só o Flutter tem problemas com gerenciamento de estado porque simplesmente todas as abordagens fazem muito mais do que apenas gerenciar o estado! Isso é insano!

O que deveria ser gerenciamento de estado então?
A resposta é muito simples: É a forma que armazenamos e propagamos o estado em nossa aplicação.
Você literalmente pode gerenciar o estado com apenas uma variável.

A distribuição do estado faz parte do gerenciamento do estado?
Não! São duas coisas distinta.

Tudo isso que falamos do BLoC também se aplica a ideia de Controllers com ChangeNotifier ou qualquer outra coisa. Ex:

class CounterController extends ChangeNotifier {
var counter = 0; // State

// Business Logic exec
void increment(){
counter++;
notifyListeners();
}

}

Em qualquer abordagem, sempre somos levados a cometer esse "pequeno" vacilo.

O verdadeiro vilão

Agora irei explodir sua mente: Perceba que gerenciamento de estado é a coisa mais simples que existe e que a nossa maior dificuldade é, na verdade, a distribuição do estado. E temos essa dificuldade porque não existem ferramentas nativas competentes para a distribuição do estado como temos no React (Context API e Hooks).

Pense em um cenário em que temos uma relação de um-pra-um entre BLoCs e Widget. Quando estamos nessa situação tudo parece tranquilo e fácil, até que esse mesmo Widget precise receber outros BLoCs. Isso significa criar "binds" entre BLoCs diretamente no Widget ou em um Facade. Veja essa representação:

O ProductBloc precisa da informação de outros BLoCs para produzir o estado referente ao ProductPage, então os 3 BLoCs precisam se encontrar em um Facade ou no próprio Widget e aguardarem os eventos um dos outros, gerando um "Lock In" no estado que fica aguardando a entrega dos eventos para poder agir.
Note que essa complexidade aumenta muito mais a cada dependência externa de outro BLoC. Mesmo assim, não é nada impossível produzir resultados positivos com essa abordagem, estou apenas escancarando mais uma vez: A maior dificuldade no gerenciamento de estado não é o gerenciamento em si, mas a distribuição do estado.

Trabalhar com BLoC não é difícil, trabalhar com muitos BLoCs simultaneamente que é complicado. Mas por que isso não é um problema no React? Porque comumente trabalham com uma distribuição e apenas um estado global para a aplicação inteira, e graças a isso não há necessidade de intercessão de múltiplas reatividades. Como nesse exemplo:

Eu sei que a ideia de um "Estado Único" deve assustar algumas pessoas, principalmente aquelas que trabalham em grande projetos, mas saiba que existem muitos apps produzidos dessa maneira.
Mas a própria comunidade do React admite que manter um "Estado Único" é um desafio, mesmo resolvendo os problemas da distribuição de estado.

Em resumo, identificamos que o verdadeiro desafio é a distribuição do estado e não o gerenciamento de estado em si e que a solução de um "Estado Único", apesar de solucionar o problema da distribuição, ainda continua sendo algo complexo, principalmente na modelagem desse estado e na modificação do mesmo, já que se trata de um objeto imutável.

Agora que sabemos qual o verdadeiro problema, fica mais fácil atacar essa complexidade.

A solução por trás dos átomos

A equipe do Facebook resolveu atacar a complexidade do Redux com uma proposta totalmente nova inspirada nos Observables do MobX chamada de Recoil.

A ideia do Recoil é dividir o estado em partes menores, cada uma contendo sua própria reatividade. Isso é exatamente o oposto do Redux, que tem a proposta de juntar todo o estado da aplicação em um lugar só(Store). Vamos chamar essas partes reativas do estado de micro-estados ou simplesmente de átomos.

A vantagem de dividir o estado em átomos é a facilidade ao agrupar as reatividades em um Widget de maneira declarativa. No caso do React, ao colocar um ou mais átomos em um Hook já é o suficiente fazer o link da reatividade e a reconstrução do Widget. Isso é poderoso demais no React:

Vamos chamar isso tudo de arquitetura de átomos ou Estado Atômico.

Como aplicar o Estado Atômico no Flutter?

Apesar das semelhanças, o Flutter e o React são frameworks diferentes, e mesmo se tentássemos replicar o Recoil no Flutter não seria bem-sucedido, pois a cabeça de um desenvolvedor Flutter funciona diferente. Inclusive existe um package chamado flutter_recoil que tenta isso, mas falha pelo simples fato do Dart ser uma linguagem fundamentalmente diferente do Javascript.

Mas existe um objeto que facilmente pode representar um Atom no Flutter, o ValueNotifier. Vamos trabalhar com o RxNotifier que é uma extensão do ValueNotifier para adiciona Programação Reativa Funcional Transparente nesse objeto. Isso fará a diferença na leitura dessa reatividade no Widget.

final counterState = RxNotifier<int>(0);

E aqui começa nossa nova jornada rumo ao Estado Atômico no Flutter.

Se isso te interessou, acompanhe a documentação desse padrão no repositório do RxNotifier.

Nos vemos na próxima.

Bíbliografia

Documentação Recoil
Documentação Redux
Documentação BLoC
Documentação ValueNotifier
Documentação RxNotifier

--

--