Reatividade no Flutter: ValueNotifier vs MobX
Índice
Ao desenvolver um aplicativo Flutter, o gerenciamento de estado e a reatividade são aspectos centrais que afetam diretamente a manutenção do código e a experiência do usuário. Embora existam soluções como MobX, Riverpod e Provider para atender a essas necessidades, muitas vezes é desejável manter o desenvolvimento dentro das ferramentas básicas do Flutter, garantindo reatividade sem a necessidade de pacotes externos. Neste artigo, exploraremos como o uso de ValueNotifier
e ValueListenableBuilder
pode fornecer uma abordagem nativa, simples e eficaz para reatividade granular no Flutter.
O Cenário Comum: MobX e Computed Values
O MobX é uma biblioteca popular no ecossistema Flutter, amplamente utilizada para fornecer reatividade automática e controle sobre valores derivados por meio dos @observable
e @computed
. Com @computed
, você pode derivar valores que são recalculados automaticamente sempre que suas dependências mudam. Vamos ver um exemplo de como isso funciona:
import 'package:mobx/mobx.dart'; part 'store.g.dart'; class Cart = _Cart with _$Cart; abstract class _Cart with Store { @observable double price = 0.0; @observable int quantity = 0; @computed double get total => price * quantity; } void main() { final cart = Cart(); autorun((_) { print('Total updated: \${cart.total}'); }); cart.price = 5.0; // Saída: Total updated: 5.0 cart.quantity = 3; // Saída: Total updated: 15.0 }
Neste exemplo, @computed
garante que o valor total do carrinho seja recalculado automaticamente quando price
ou quantity
mudam, fornecendo uma experiência reativa com mínimo esforço do desenvolvedor.
Substituindo o MobX com ValueNotifier e Listenable.merge
Apesar da simplicidade que o MobX oferece, ele traz um overhead em termos de dependências, pré-processamento (build_runner
) e uma estrutura de código que não é tão direta. Com ValueNotifier
, é possível obter a mesma reatividade, mantendo o código dentro das ferramentas nativas do Flutter.
Vamos ver um exemplo similar usando ValueNotifier
:
import 'package:flutter/widgets.dart'; class Cart { ValueNotifier<double> price = ValueNotifier<double>(0.0); ValueNotifier<int> quantity = ValueNotifier<int>(0); ValueNotifier<double> total = ValueNotifier<double>(0.0); late final Listenable mergedListenable; Cart() { // Inicializa 'mergedListenable' combinando os ValueNotifiers. mergedListenable = Listenable.merge([price, quantity]); mergedListenable.addListener(_updateTotal); } void _updateTotal() { total.value = price.value * quantity.value; } } void main() { final cart = Cart(); cart.total.addListener(() { print('Total updated: \${cart.total.value}'); }); // Atualizando valores cart.price.value = 5.0; // Saída: Total updated: 5.0 cart.quantity.value = 3; // Saída: Total updated: 15.0 }
Análise da Abordagem com ValueNotifier
Nesta abordagem, o ValueNotifier
é utilizado para manter a reatividade dos valores price
, quantity
e total
. Vamos entender algumas vantagens dessa abordagem:
- Reatividade Granular e Controle Explícito:
- Usando
ValueNotifier
, temos controle direto sobre cada propriedade que deve ser observada. A reatividade é feita de maneira granular, apenas onde realmente importa. Isso significa que é possível ter uma notificação precisa, sem atualizações desnecessárias.
- Usando
- Uso do
Listenable.merge
:- O
Listenable.merge
nos permite combinarprice
equantity
em um únicoListenable
, tornando a estrutura mais organizada e evitando a necessidade de adicionar listeners individualmente a cadaValueNotifier
. Assim, conseguimos manter ototal
sempre atualizado, sem a necessidade de uma biblioteca externa.
- O
- Evita Pré-Processamento:
- Diferente do MobX, não precisamos usar o
build_runner
para gerar código automaticamente. Isso simplifica o fluxo de desenvolvimento, deixando-o mais direto e sem dependências adicionais, o que é particularmente útil para quem quer manter o projeto mais leve.
- Diferente do MobX, não precisamos usar o
Integrando com a Interface do Usuário usando ValueListenableBuilder
Um dos benefícios de ValueNotifier
é que ele funciona perfeitamente com widgets nativos do Flutter, como o ValueListenableBuilder
. Podemos usar este widget para atualizar a interface automaticamente quando o valor de total
mudar, garantindo uma UI reativa.
Veja um exemplo:
import 'package:flutter/material.dart'; class Cart { ValueNotifier<double> price = ValueNotifier<double>(15.25); ValueNotifier<int> quantity = ValueNotifier<int>(0); ValueNotifier<double> total = ValueNotifier<double>(0); late final Listenable mergedListenable; Cart() { mergedListenable = Listenable.merge([price, quantity]); mergedListenable.addListener(_updateTotal); } void _updateTotal() { total.value = price.value * quantity.value; } } void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { final cart = Cart(); MyApp({super.key}); @override Widget build(BuildContext context) { const font24 = TextStyle(fontSize: 24); const font20 = TextStyle(fontSize: 20); return MaterialApp( home: Scaffold( appBar: AppBar( title: const Text('Cart Total Example'), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Row( mainAxisAlignment: MainAxisAlignment.center, children: [ IconButton.filledTonal( onPressed: () => cart.quantity.value++, icon: const Icon(Icons.add), ), Padding( padding: const EdgeInsets.symmetric(horizontal: 20), child: ValueListenableBuilder( valueListenable: cart.quantity, builder: (context, value, _) => Text( 'Qt: $value', style: font20, ), ), ), IconButton.filledTonal( onPressed: () => cart.quantity.value--, icon: const Icon(Icons.remove), ), ], ), const SizedBox(height: 20), Row( mainAxisAlignment: MainAxisAlignment.center, children: [ IconButton.filledTonal( onPressed: () => cart.price.value += 10, icon: const Icon(Icons.add), ), Padding( padding: const EdgeInsets.symmetric(horizontal: 20), child: ValueListenableBuilder( valueListenable: cart.price, builder: (context, value, _) => Text( 'Price: ${value.toStringAsFixed(2)}', style: font20, ), ), ), IconButton.filledTonal( onPressed: () => cart.price.value -= 10, icon: const Icon(Icons.remove), ), ], ), const SizedBox(height: 20), ValueListenableBuilder( valueListenable: cart.total, builder: (context, value, _) => Text( 'Total: ${value.toStringAsFixed(2)}', style: font24, ), ), ], ), ), ), ); } }
Conclusão
A abordagem com ValueNotifier
e ValueListenableBuilder
oferece uma alternativa extremamente eficiente para quem deseja evitar dependências externas e manter o código simples e direto. Embora exija algumas linhas adicionais para configurar a reatividade, essa solução proporciona controle granular e elimina o overhead de soluções como MobX e build_runner
.
Manter o código dentro das ferramentas básicas do Flutter traz simplicidade, desempenho e uma curva de aprendizado menor, sem sacrificar a reatividade necessária para uma boa experiência de usuário. Essa abordagem é ideal para projetos que valorizam a manutenção, leveza e o uso de ferramentas nativas. Com ValueNotifier
e ValueListenableBuilder
, é possível controlar a reatividade de forma explícita e evitar notificações desnecessárias, mantendo o código eficiente e simplificado. Evitando dependências externas e pré-processamentos, também reduz-se o risco de problemas em futuras atualizações de pacotes. Essa solução é especialmente interessante para projetos menores ou médios, onde a simplicidade e o controle são fundamentais.
Em contrapartida, o MobX é vantajoso em projetos maiores e complexos, onde múltiplas dependências entre estados exigem sincronização constante. O uso de @computed
facilita o gerenciamento desses estados, permitindo o recalculo automático de valores derivados. Além disso, a automação de código com build_runner
poupa tempo em projetos grandes ao evitar configurações manuais de Listeners
. O MobX mantém um fluxo de dados consistente e facilita a escalabilidade, tornando-o ideal para aplicações que requerem reatividade avançada em múltiplas partes da interface.
Em resumo, para quem busca uma solução que equilibre simplicidade e controle, o ValueNotifier
com Listenable.merge
é uma excelente escolha para uma reatividade robusta e flexível no Flutter.
Deixe uma resposta