Riverpod — Gerenciamento Compilado de Dependências para Flutter

Autor: IT Sectr Publicado: 2026-02-19 Tempo de leitura: 7 min

Riverpod — um gerenciador de estado e dependências compilado para Flutter, criado por Remi Rousselet em 2021 como sucessor do Provider. Riverpod resolve problemas fundamentais do Provider: falta de verificação em tempo de compilação, dependência do BuildContext e complexidade com ProviderNotFoundException. De acordo com o pub.dev, o pacote tem mais de 5 mil curtidas e está ativamente substituindo o Provider em novos projetos.

Pontos Principais

  • ProviderRef — um objeto para acessar outros providers dentro de um provider
  • AsyncValue — um invólucro para dados assíncronos com estados loading/error/data
  • Notifier — uma classe para estado mutável com métodos de mutação
  • ProviderScope — o widget raiz que gerencia todos os providers
  • Code Generation — anotações @riverpod para geração automática de providers

O que é Riverpod?

Riverpod é uma biblioteca de gerenciamento de estado e injeção de dependências para Flutter que compila descrições de providers em código Dart seguro. Ao contrário do Provider, os providers do Riverpod não estão vinculados ao BuildContext: eles são criados globalmente ou no ProviderScope e são acessíveis de qualquer lugar. O compilador verifica tipos, dependências e a integridade do grafo de providers em tempo de compilação, eliminando erros em tempo de execução como ProviderNotFoundException.

Riverpod usa o modelo override para testes: cada provider pode ser sobrescrito via ProviderScope.overrideWith sem necessidade de criar subclasses ou mockar interfaces. Isso torna os testes isolados: cada teste obtém sua própria cópia do grafo de dependências que é totalmente controlada.

De acordo com a Flutter Community Survey 2025, Riverpod ocupa o terceiro lugar em popularidade depois de Provider e BLoC. No entanto, Riverpod é o pacote que mais cresce: +120% de instalações em 2024. Principais motivos: segurança em tempo de compilação, sem ProviderNotFoundException, suporte assíncrono integrado via AsyncValue.

Tipos de Providers

Riverpod fornece 8 tipos de providers, cada um para um cenário específico: Provider (constante/serviço), StateProvider (estado primitivo), StateNotifierProvider (lógica complexa com StateNotifier), ChangeNotifierProvider (para migração do Provider), FutureProvider (dados assíncronos, única vez), StreamProvider (fluxo reativo), NotifierProvider (nova API, Flutter 3.10+) e AsyncNotifierProvider (Notifier assíncrono).

Dart
final counterProvider = StateNotifierProvider<CounterNotifier, int>((ref) {
  return CounterNotifier();
});

class CounterNotifier extends StateNotifier<int> {
  CounterNotifier() : super(0);

  void increment() => state++;
  void decrement() => state--;
}

class CounterScreen extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final count = ref.watch(counterProvider);
    return Text('$count');
  }
}

ProviderRef — um objeto passado para cada provider para acessar outros providers. ref.watch — assinatura de mudanças, ref.read — leitura única, ref.invalidate — reinicialização de cache. ProviderRef substitui o BuildContext do Provider: qualquer provider pode ler outros providers sem acesso à árvore de widgets. Isso permite construir um grafo de dependências fora da camada de UI.

ProviderScope — o widget raiz, obrigatório para o Riverpod funcionar. ProviderScope armazena todos os providers, gerencia seu ciclo de vida e armazena valores em cache. Sem o ProviderScope o aplicativo falhará com ProviderNotFoundException. ProviderScope pode ser aninhado — um escopo aninhado sobrescreve os providers pai, o que é usado para testes e isolamento de funcionalidades.

AsyncValue e Trabalho com Assincronia

AsyncValue — uma classe selada do Riverpod para representar estado assíncrono. AsyncValue tem três variantes: AsyncData (dados bem-sucedidos), AsyncError (erro), AsyncLoading (carregando). Em vez de alternar manualmente entre loading/error/data, cada FutureProvider ou StreamProvider retorna automaticamente AsyncValue, e o widget lida com todos os três estados via ref.watch.

Dart
final userProvider = FutureProvider((ref) async {
  final api = ref.watch(apiProvider);
  return await api.fetchUser();
});

class UserScreen extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final userAsync = ref.watch(userProvider);
    return userAsync.when(
      data: (user) => UserWidget(user),
      error: (e, _) => ErrorWidget(e.toString()),
      loading: () => CircularProgressIndicator(),
    );
  }
}

AsyncValue.when — um método para pattern-matching de todos os três estados. O compilador verifica se todos os três casos são tratados — se você esquecer loading ou error, o código não compilará. AsyncValue.whenData — apenas para data (se loading/error não forem necessários). AsyncValue.guard — um invólucro sobre try-catch para converter exceções em AsyncError. keepAlive — uma flag que impede que o cache do provider seja destruído ao sair do escopo.

Geração de Código e @riverpod

Geração de código — um recurso chave do Riverpod 2.0+. A anotação @riverpod em uma função gera automaticamente um provider com o tipo correto, suporte a refatoração e autocompletar. A geração de código usa riverpod_generator e build_runner. O desenvolvedor escreve uma função pura, e todo o resto — tipos, classes, construtores factory — é gerado automaticamente.

Dart
@riverpod
String helloWorld(HelloWorldRef ref) {
  return 'Hello World';
}

// Gerado: final helloWorldProvider = Provider((ref) => 'Hello World');

@riverpod
class Counter extends _$Counter {
  int build() => 0;
  void increment() => state++;
}

Notifier — a nova API para estado mutável com geração de código. Notifier é uma classe com um método build() e métodos de mutação de estado. Ao contrário do StateNotifier, o Notifier não requer uma classe de estado separada e fornece acesso direto a state via getter/setter. Riverpod gera automaticamente um NotifierProvider para cada classe Notifier anotada com @riverpod.

build_runner: a geração de código é executada com o comando dart run build_runner build. Os arquivos gerados têm o sufixo .g.dart e são importados no código fonte. Quando as anotações ou tipos de providers mudam, a geração de código precisa ser reexecutada. Riverpod 2.x recomenda geração de código para todos os projetos novos — a criação manual de providers está se tornando obsoleta.

Riverpod vs Provider

Principais diferenças entre Riverpod e Provider: independência do BuildContext, segurança em tempo de compilação, suporte assíncrono integrado, cache automático e testes via override. Provider requer BuildContext para acessar estado (context.watch, context.read), Riverpod usa WidgetRef e providers declarados globalmente.

CaracterísticaProviderRiverpod
Dependência de BuildContextSimNão
Verificação em compilaçãoNãoSim (via @riverpod)
ProviderNotFoundExceptionRuntimeImpossível
AssincroniaManualAsyncValue (integrado)
TestesInvólucro no ProviderProviderScope.overrideWith
CacheNãoAutomático + keepAlive

Migração do Provider: Riverpod suporta ChangeNotifierProvider.adaptive para usar ChangeNotifier existente sem reescrever. Migração gradual: primeiro novos recursos são escritos com Riverpod, depois instâncias antigas do Provider são substituídas por providers do Riverpod via um adaptador. Ambos os pacotes podem coexistir em um projeto, permitindo migrar sem congelar o desenvolvimento.

Testando Riverpod

Testar Riverpod é baseado em ProviderScope.overrideWith. Cada provider é sobrescrito dentro de um ProviderScope de teste sem mocks ou contêineres DI. ProviderContainer — um ambiente isolado para testes sem Flutter (Dart puro), permitindo testar providers sem renderização de widgets.

Dart
import 'package:flutter_test/flutter_test.dart';
import 'package:riverpod/riverpod.dart';

void main() {
  test('Counter increments correctly', () {
    final container = ProviderContainer();
    container.read(counterProvider.notifier).increment();
    expect(container.read(counterProvider), 1);
  });

  testWidgets('UI updates on increment', (tester) async {
    await tester.pumpWidget(
      ProviderScope(
        overrides: [counterProvider.overrideWithValue(5)],
        child: CounterScreen(),
      ),
    );
    expect(find.text('5'), findsOneWidget);
  });
}

ProviderContainer — sem Flutter. Use ProviderContainer para testes unitários de providers sem widgets. overrideWithValue — substituir um provider por um valor específico. overrideWith — substituir por uma fábrica de providers (para mockar serviços). autodispose — em testes verifique se o provider é destruído ao sair do escopo usando container.dispose().

Perguntas Frequentes

Como o Riverpod é diferente do BLoC?

Riverpod é uma biblioteca de gerenciamento de estado com providers globais, AsyncValue e geração de código. BLoC é um padrão arquitetural com Event → Stream → State. Riverpod é mais fácil de aprender e tem melhor DX através de anotações @riverpod. BLoC fornece isolamento estrito de lógica de negócios e rastreamento de Event via BlocObserver. A escolha depende do paradigma do projeto: Riverpod está mais próximo do Provider, BLoC — dos fluxos reativos.

O que é autodispose no Riverpod?

Autodispose é um mecanismo para destruir automaticamente um provider quando ninguém está inscrito nele. Por padrão, todos os providers do Riverpod fazem autodispose: quando um widget sai da árvore, o provider é removido da memória. keepAlive — uma flag que desativa o autodispose para providers que devem viver sempre (clientes de API, repositórios, configurações). Isso evita vazamentos de memória — providers não utilizados são destruídos automaticamente.

Como funciona o ref.invalidate?

ref.invalidate — um método que reinicia forçosamente o cache do provider. Após invalidate, o provider é recriado na próxima leitura: FutureProvider reexecuta a função assíncrona, StreamProvider reassina o fluxo. Use invalidate para forçar a atualização de dados (pull-to-refresh, troca de usuário). ref.refresh — uma combinação de invalidate + leitura: reinicia e imediatamente lê o novo valor em uma operação.

Riverpod pode ser usado sem geração de código?

Sim. Riverpod 1.x funciona apenas sem geração de código — providers são criados manualmente usando Provider(), StateNotifierProvider(), FutureProvider(), etc. Riverpod 2.x suporta ambas as abordagens. Sem geração de código há mais código repetitivo, mas nenhuma dependência de build_runner e dart run build_runner build. Para projetos pequenos (até 30 providers), a criação manual é justificada; para grandes, a geração de código é obrigatória.

O que são providers Family?

Family — um modificador de provider que aceita um parâmetro externo. Por exemplo, userProvider(123) — um provider que carrega um usuário com ID 123. Providers Family armazenam em cache o resultado para cada parâmetro único separadamente. Use Family para listas de itens onde cada item é carregado por ID. O modificador Family está disponível para todos os tipos de providers: Provider.family, FutureProvider.family, StreamProvider.family.

Resumo

  • Riverpod — um gerenciador de estado compilado, sucessor do Provider sem ProviderNotFoundException
  • ProviderRef — um substituto para BuildContext para acessar providers dentro de outros providers
  • AsyncValue — uma classe selada com estados loading/error/data para dados assíncronos
  • Geração de código @riverpod — inferência automática de tipos e fábricas de providers
  • ProviderScope.overrideWith — testes isolados sem mocks e contêineres DI
  • Family — providers parametrizados com cache individual
  • autodispose e keepAlive — gerenciamento automático do ciclo de vida dos providers

Vamos desenvolver um aplicativo móvel chave na mão

A IT Sectr cria aplicativos para iOS e Android para startups e empresas desde 2017. Nós vamos aconselhá-lo e propor a melhor solução.

Discutir o projeto

Leia também