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.mergenos permite combinarpriceequantityem um únicoListenable, tornando a estrutura mais organizada e evitando a necessidade de adicionar listeners individualmente a cadaValueNotifier. Assim, conseguimos manter ototalsempre atualizado, sem a necessidade de uma biblioteca externa.
- O
- Evita Pré-Processamento:
- Diferente do MobX, não precisamos usar o
build_runnerpara 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