Riverpod 3.0: O Guia Essencial para Iniciantes
1. Por que Riverpod? (O Problema do “setState”)
No Flutter, o setState é ótimo para um único widget, mas ele falha conforme seu aplicativo cresce. Ele cria:
-
Prop Drilling: Passar dados por 10 widgets apenas para alcançar aquele que realmente precisa deles.
-
UI Sobrecarregada: O código da sua tela vira uma bagunça de lógica e design misturados.
-
Perda de Lógica: Quando um widget é destruído, o estado dele é perdido.
O Riverpod funciona como uma “Fonte Única da Verdade” (Single Source of Truth). Ele mantém seus dados em uma “nuvem global” segura, na qual os widgets podem se “sintonizar”, independentemente de onde estejam na árvore.
2. O Ponto de Entrada: ProviderScope
Antes de poder usar o Riverpod, você deve envolver seu aplicativo em um ProviderScope. Esta é a “Sala de Máquinas” que armazena o estado de todos os seus providers.
void main() {
runApp(
// This makes Riverpod available everywhere in your app
const ProviderScope(
child: MyApp(),
),
);
}
3. A Magia das Anotações (@riverpod)
O Riverpod 3.0 se afasta das declarações manuais. Agora, utilizamos a Geração de Código (Code Generation).
-
O Benefício: Isso evita os erros mais comuns dos desenvolvedores (como esquecer de limpar — dispose — um provider).
-
A Regra: Você escreve a lógica e o Riverpod gera o “boilerplate” (código repetitivo) para você. É mais limpo, rápido e seguro.
4. A Caixa de Ferramentas “Ref”: Como falar com os Providers
Guia de Uso do Objeto ref
| Ferramenta (Tool) | Ação (Action) | Melhor Caso de Uso (Best Use Case) | Zona de Perigo (The Danger Zone) |
ref.watch |
Observa e Reconstrói (Rebuild) | Exibir dados na tela. | Nunca use dentro de um onPressed. |
ref.read |
Captura o valor uma única vez | Dentro de botões ou callbacks. | Nunca use dentro de um método build. |
ref.listen |
Dispare-e-esqueça (Fire-and-forget) | Exibir SnackBars ou Diálogos. | Não use para construir a interface (UI). |
ref.invalidate |
Reseta um provider | “Pull-to-refresh” ou Logouts. | Usar dentro do build causa loops infinitos |
Dentro do seu widget, você usa um ref (Referência) para interagir com os providers.
5. ConsumerWidget vs. ConsumerStatefulWidget
No Riverpod, não utilizamos o StatelessWidget padrão. Usamos seus “primos mais inteligentes”:
-
ConsumerWidget: Use este em 95% das suas telas. Ele te fornece um
WidgetRefdiretamente no métodobuild. -
ConsumerStatefulWidget: Use este apenas se precisar de métodos de ciclo de vida local, como
initState()oudispose()(comum ao usarAnimationControllers).
6. Galeria de Providers (Trechos de Código)
A. Provider Funcional (O Leitor)
Ideal para: Dados estáticos ou leitura única (apenas leitura).
// O Provedor: Apenas um valor constante
@riverpod
String shopName(Ref ref) => "Blue Mountain Coffee";
// Utilização no widget
Widget build(BuildContext context, WidgetRef ref) {
final name = ref.watch(shopNameProvider);
return Text(name); // Displays: Blue Mountain Coffee
}
**Quando usar: Para informações "fixas", como URLs de API, títulos de aplicações ou cores de temas
B. O Notifier Provider
Cenário: Ideal para quando o estado aumenta ou diminui (conforme o estado muda).
// O Provedor: Uma classe que gerencia um valor que muda
@riverpod
class Counter extends _$Counter {
@override
int build() => 2; // Comece com 2 pacotes
void add() => state++; // Increase the number
void remove() => state--; // Decrease the number
}
// Utilização no widget
Widget build(BuildContext context, WidgetRef ref) {
final count = ref.watch(counterProvider);
return Row(
children: [
IconButton(onPressed: () => ref.read(counterProvider.notifier).remove(), icon: Icon(Icons.remove)),
Text("$count Packets"),
IconButton(onPressed: () => ref.read(counterProvider.notifier).add(), icon: Icon(Icons.add)),
],
);
}
**Quando usar: Para qualquer coisa que o utilizador «alterne», «incremente» ou «edite» (como um carrinho de compras ou modo escuro).
C. O FutureProvider
Cenário: Basicamente para chamadas de API.
// O Provider: Gerencia dados que levam tempo para chegar
@riverpod
Future<List<String>> fetchMenu(FetchMenuRef ref) async {
await Future.delayed(Duration(seconds: 2)); // Simula a espera pela internet
return ["Espresso", "Latte", "Cappuccino"];
}
// Uso no Widget
Widget build(BuildContext context, WidgetRef ref) {
final menuAsync = ref.watch(fetchMenuProvider);
return menuAsync.when(
data: (items) => Text("Cardápio de hoje: ${items.join(', ')}"),
loading: () => CircularProgressIndicator(), // Mostra um carregando enquanto espera
error: (err, stack) => Text("Não foi possível carregar o cardápio"), // Mostra se a internet falhar
);
}
D. O StreamProvider
Cenário: Para fluxo contínuo de dados, como rastreamento em mapas e fluxos de dados (Data Flow).
// O Provider: Envia atualizações conforme elas acontecem
@riverpod
Stream<String> orderStatus(OrderStatusRef ref) async* {
yield "Preparando...";
await Future.delayed(Duration(seconds: 5));
yield "Servindo...";
await Future.delayed(Duration(seconds: 5));
yield "Pronto para retirar! ☕";
}
// Uso no Widget
Widget build(BuildContext context, WidgetRef ref) {
final statusAsync = ref.watch(orderStatusProvider);
return statusAsync.maybeWhen(
data: (msg) => Text(msg),
orElse: () => Text("Fazendo pedido..."),
);
}
Aqui estão as dependências necessárias
dependencies:
flutter:
sdk: flutter
# The core engine
flutter_riverpod: latest
# The annotation tool
riverpod_annotation: latest
dev_dependencies:
# The generator that writes the code for you
riverpod_generator: latest
# The standard runner for all Dart code generation
build_runner: latest
# Optional: Helpful for checking logic in Notifiers
custom_lint: latest
riverpod_lint: latest
O Pacote Mágico build_runner: Quando você utiliza @riverpod, você está escrevendo instruções de alto nível. No entanto, o Flutter não entende naturalmente o que @riverpod significa. É aqui que entra o Build Runner.
dart run build_runner build || dart run build_runner watch
Por que estou escrevendo este guia
Como desenvolvedor Flutter, notei algo frustrante: o Riverpod 3.0 avançou tão rápido que a internet não conseguiu acompanhar. Embora a nova abordagem baseada em Geradores seja a maneira mais poderosa de construir apps, a maioria dos tutoriais e artigos ainda está presa ao “jeito antigo”. Tenho visto estagiários e colegas desenvolvedores lutarem para encontrar um caminho claro na documentação da versão 3.0.
Eu quis preencher essa lacuna. Quis pegar os conceitos complexos que aprendi — como o Build Runner, AsyncValue e Notifiers — e simplificá-los em um manual que realmente faça sentido para quem está começando hoje. Isso não é apenas sobre código; é sobre fazer com que o gerenciamento de estado pareça um superpoder, em vez de um fardo.