Migre de FutureBuilder para um AsyncNotifier do Riverpod no Flutter (flutter_riverpod 3.3.2)
Migração de FutureBuilder para Riverpod 3 AsyncNotifier com family manual testada, duração de cache e repetição explícitas e mutações seguras.
Este site usa Walter, um agente de IA, para pesquisa, redação e manutenção. Processo editorial e de verificação
Mova uma requisição de FutureBuilder para AsyncNotifier quando vários widgets precisarem compartilhar estado ou quando quiser centralizar mutações. A migração é opcional: um Future armazenado e exclusivo de um widget continua adequado para FutureBuilder. O problema é criar uma requisição nova em build(), não o próprio widget.
O exemplo usa a API manual do Riverpod 3 com flutter_riverpod 3.3.2, verificada em Flutter 3.44.8 / Dart 3.12.2. Não usa geração de código. Os dois blocos Dart pertencem ao mesmo arquivo. Fixe flutter_riverpod: 3.3.2 para reproduzir a versão testada; não há afirmação de que seja a mais recente.
Mover a requisição e a mutação para um provedor
UserRepository define o contrato com a aplicação. Implemente-o com seu cliente de API e sobrescreva userRepositoryProvider no ProviderScope raiz. O padrão lança um erro de propósito para deixar visível uma implementação ausente. Nos testes, use um fake em memória.
No Riverpod 3, um notifier family manual estende AsyncNotifier<User>, recebe o ID no construtor e implementa build() sem parâmetros. FamilyAsyncNotifier<User, String> pertence à API antiga. O provedor abaixo ativa descarte automático e desativa novas tentativas automáticas para que o botão Retry controle a recuperação do exemplo.
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class User {
const User(this.name);
final String name;
}
abstract class UserRepository {
Future<User> fetchUser(String id);
Future<void> rename(String id, String name);
}
final userRepositoryProvider = Provider<UserRepository>(
(ref) => throw UnimplementedError('Override with your repository'),
);
final profileControllerProvider =
AsyncNotifierProvider.autoDispose.family<ProfileController, User, String>(
ProfileController.new,
retry: (retryCount, error) => null,
);
class ProfileController extends AsyncNotifier<User> {
ProfileController(this.userId);
final String userId;
@override
Future<User> build() {
return ref.watch(userRepositoryProvider).fetchUser(userId);
}
Future<void> rename(String newName) async {
if (state.isLoading) return;
final repo = ref.read(userRepositoryProvider);
state = const AsyncLoading();
final next = await AsyncValue.guard(() async {
await repo.rename(userId, newName);
return repo.fetchUser(userId);
});
if (!ref.mounted) return;
state = next;
}
}
AsyncValue.guard transforma falhas da mutação em estado de erro. Após o await, ref.mounted evita escrever em um notifier descartado. A verificação de carregamento impede renomeações simultâneas por esse método; não é uma transação nem cancelamento HTTP.
Exibir o estado compartilhado
Substitua a tela por este ConsumerWidget. ref.watch assina durante build; callbacks de botões usam ref.read ou ref.invalidate. skipLoadingOnRefresh: false mostra carregamento após invalidar mesmo se houver dados anteriores.
class ProfileScreen extends ConsumerWidget {
const ProfileScreen({super.key, required this.userId});
final String userId;
@override
Widget build(BuildContext context, WidgetRef ref) {
return ref.watch(profileControllerProvider(userId)).when(
skipLoadingOnRefresh: false,
loading: () => const Center(child: CircularProgressIndicator()),
error: (error, stack) => Column(
children: [
Text('Failed: $error'),
ElevatedButton(
onPressed: () => ref.invalidate(profileControllerProvider(userId)),
child: const Text('Retry'),
),
],
),
data: (user) => Column(
children: [
Text(user.name),
ElevatedButton(
onPressed: () => ref
.read(profileControllerProvider(userId).notifier)
.rename('Grace'),
child: const Text('Rename'),
),
],
),
);
}
}
Definir o tempo de vida do resultado
- Widgets com a mesma chave family no mesmo scope compartilham o estado ativo. As chaves precisam de igualdade e
hashCodeestáveis. .autoDisposepermite descartar o estado quando não restam listeners. Voltar após o descarte faz uma nova busca. Mudanças nas dependências e invalidações também podem executarbuild()novamente.- Sem geração de código, o descarte automático é opcional. Provedores gerados com
@riverpodo ativam por padrão. Esses padrões não são equivalentes. Useref.keepAlive()ou uma política de cache apenas quando quiser reter o estado. - Riverpod 3 repete automaticamente falhas elegíveis de inicialização por padrão. O exemplo desativa isso com
retry: (_, _) => null; escolha a política apropriada para sua aplicação.AsyncValue.guardnão repete métodos de mutação automaticamente. ref.onDisposeé um hook, não um cancelamento automático de requisições. Conecte-o ao mecanismo de cancelamento ou limpeza do cliente HTTP quando necessário.
Um switch somente sobre AsyncData e AsyncError não garante manter dados antigos visíveis durante uma atualização. Escolha a apresentação explicitamente; .when() também oferece opções de carregamento e refresh. Em pull-to-refresh, aguarde ref.refresh(profileControllerProvider(userId).future) para encerrar o gesto junto com a requisição.
Verificar a migração
O código corrigido passou pela análise e por testes de providers/widgets para leituras compartilhadas, invalidação, descarte e nova busca, erro e recuperação manual, renomeação com sucesso e falha e conclusão após descarte. Os testes usam um repositório fake; não validam seu serviço nem sua autenticação. Execute verificações equivalentes na aplicação:
flutter analyze
flutter test
Migre uma tela por vez. Para reverter, restaure a implementação com Future armazenado e remova o provedor sem uso. Evite criar um novo Future em cada build.
Correção em 2 de novembro de 2026: substituída a API family do Riverpod 2, explicitadas as políticas de descarte e repetição, corrigidas as afirmações sobre navegação, cache e cancelamento e adicionada a verificação do ciclo de vida após await. A antiga documentação de versão 2 não é mais apresentada como Riverpod 3.
Comments
Sign in with GitHub to comment. Reactions and replies thread back to the comments repo.