Como escrever aplicativos Flutter mais rápido com Riverpod Lint e Riverpod Snippets
A cada novo lançamento, o Riverpod e o ecossistema ao seu redor ficam cada vez melhores:
- os pacotes principais nos fornecem APIs poderosas para cache reativo e vinculação de dados.
- o pacote Riverpod Generator simplifica a curva de aprendizado e traz melhorias significativas de usabilidade.
- a extensão Riverpod Snippets nos ajuda a criar fornecedores e consumidores com facilidade.
E o novo pacote Riverpod Lint adiciona muitos lints úteis e opções de refatoração que facilitam a escrita de aplicativos Flutter.
Mas espere…
É melhor que isso.
O desenvolvimento moderno com Riverpod é uma alegria, pois você pode escrever recursos complexos como pesquisa e paginação com pouco código (e deixar as ferramentas guiá-lo).
Conteudo
Adicionando todos os pacotes necessários
Como usaremos Riverpod Generator e Riverpod Lint, precisamos adicionar alguns pacotes ao nosso arquivo pubspec.yaml
:
dependencies: # o principal pacote riverpod para aplicativos Flutter flutter_riverpod: # o pacote de anotações contendo @riverpod riverpod_annotation: dev_dependencies: # uma ferramenta para executar geradores de código build_runner: # o gerador de código riverpod_generator: # riverpod_lint torna mais fácil trabalhar com Riverpod riverpod_lint: # importe custom_lint também, pois riverpod_lint depende disso custom_lint:
E também precisamos habilitar o plugin custom_lint
dentro de analyze_options.yaml
analyzer: plugins: - custom_lint
Em seguida, precisamos iniciar o build_runner
no modo watch
:
flutter pub run build_runner watch -d
O sinalizador -d é opcional e é igual a –delete-conflicting-outputs. Como o nome indica, garante a substituição de quaisquer saídas conflitantes de compilações anteriores (que normalmente é o que queremos).
Isso observará todos os arquivos Dart em nosso projeto e atualizará automaticamente o código gerado quando fizermos alterações.
E agora que a configuração foi concluída, vamos dar uma olhada em alguns códigos. 👇
Exemplo de refatoração com Riverpod Generator e Riverpod Lint
Suponha que temos uma classe AuthRepository
que usamos como wrapper para a classe FirebaseAuth
:
// firebase_auth_repository.dart import 'package:firebase_auth/firebase_auth.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; class AuthRepository { AuthRepository(this._auth); final FirebaseAuth _auth; Stream<User?> authStateChanges() => _auth.authStateChanges(); }
E suponha que também tenhamos estes provedores:
// Torne a instância [FirebaseAuth] acessível como provedor final firebaseAuthProvider = Provider<FirebaseAuth>((ref) { return FirebaseAuth.instance; }); // Torne a instância [AuthRepository] acessível como provedor final authRepositoryProvider = Provider<AuthRepository>((ref) { return AuthRepository(ref.watch(firebaseAuthProvider)); }); // Um [StreamProvider] para o fluxo [authStateChanges] final authStateChangesProvider = StreamProvider.autoDispose<User?>((ref) { return ref.watch(authRepositoryProvider).authStateChanges(); });
Como podemos converter os provedores acima da nova sintaxe do Riverpod Generator?
Adicionando um arquivo de peça
Como vimos no meu artigo sobre o Riverpod Generator, o primeiro passo é adicionar um arquivo de peça.
E usando a extensão Flutter Riverpod Snippets, podemos apenas digitar alguns caracteres:
E a extensão completa isso automaticamente com o nome de arquivo correto:
part 'firebase_auth_repository.g.dart';
Convertendo um provedor simples
A seguir, vamos ver como converter este provedor:
final firebaseAuthProvider = Provider<FirebaseAuth>((ref) { return FirebaseAuth.instance; });
Mais uma vez, podemos começar digitando riverpod
e obter uma lista de opções
Ao decidir qual opção escolher, podemos nos perguntar:
- este provedor retornará um objeto, um
Future
, umStream
ou umNotifier
? - deveria ele se dispor quando não fosse mais ouvido ou deveria permanecer vivo?
Como FirebaseAuth
é um singleton
que permanece ativo durante todo o ciclo de vida do aplicativo, podemos escolher a opção riverpodKeepAlive
e terminar com isto:
@Riverpod(keepAlive: true) (Ref ref) { return; }
O próximo passo é preencher os espaços em branco adicionando:
- um tipo de retorno
- o nome da função
- quaisquer argumentos adicionais (nenhum neste caso)
- o órgão fornecedor
Aqui está o que terminamos:
@Riverpod(keepAlive: true) FirebaseAuth firebaseAuth(Ref ref) { // Provedores sem estado devem receber uma referência correspondente ao nome do provedor como seu primeiro parâmetro posicional.dart(stateless_ref) return FirebaseAuth.instance; }
Este código está quase correto. Mas Riverpod Lint nos lembra que devemos usar o tipo correto, pois o gerador cria um tipo Ref
específico para cada provedor.
Na verdade, há uma relação estrita entre o nome da função (firebaseAuth
) e o tipo Ref
gerado e o nome do provedor:
firebaseAuth()
→FirebaseAuthRef
efirebaseAuthProvider
Então, vamos usar o Quick Fix mais uma vez:
E pronto! O aviso do linter desaparece:
@Riverpod(keepAlive: true) FirebaseAuth firebaseAuth(FirebaseAuthRef ref) { return FirebaseAuth.instance; }
E enquanto o build_runner
ainda estiver rodando no modo watch, um firebaseAuthProvider
será gerado (dentro do arquivo de peça) e estará pronto para uso em nosso código.
Refatorando os provedores restantes
Em seguida, precisamos refatorar também os dois provedores restantes:
// Torna a instância [AuthRepository] acessível como um provedor final authRepositoryProvider = Provider<AuthRepository>((ref) { return AuthRepository(ref.watch(firebaseAuthProvider)); }); // Um [StreamProvider] para o fluxo [authStateChanges] final authStateChangesProvider = StreamProvider<User?>((ref) { return ref.watch(authRepositoryProvider).authStateChanges(); });
E com a ajuda de Riverpod Snippets
e Riverpod Lint
, isso é feito facilmente:
@Riverpod(keepAlive: true) AuthRepository authRepository(AuthRepositoryRef ref) { return AuthRepository(ref.watch(firebaseAuthProvider)); } @riverpod Stream<User?> authStateChanges(AuthStateChangesRef ref) { return ref.watch(authRepositoryProvider).authStateChanges(); }
Observe como optei por usar
keepAlive
parafirebaseAuthProvider
eauthRepositoryProvider
, mas não paraauthStateChangesProvider
. Isso faz sentido, pois os dois primeiros provedores contêm dependências de longa duração, enquanto o terceiro pode ou não precisar ser sempre ouvido.
Exemplo: Gerando um AsyncNotifier
Além de criar provedores para objetos, futuros e fluxos, também queremos gerar provedores para classes como Notifier
e AsyncNotifier
.
Por exemplo, aqui está uma subclasse AsyncNotifier
que tive em meu projeto:
class EditJobScreenController extends AutoDisposeAsyncNotifier<void> { @override FutureOr<void> build() { // omitido } // alguns métodos }
Eu poderia ter convertido isso manualmente.
Mas Riverpod Snippets nos ajuda novamente com opções úteis de riverpodAsyncClass
e riverpodClass
:
Ao escolher a opção acima, obtemos este código:
E então, podemos apenas preencher os espaços em branco:
@riverpod class EditJobScreenController extends _$EditJobScreenController { @override FutureOr<void> build() { // omitted } }
O que mais o Riverpod Lint pode fazer?
Os exemplos acima mostram como converter provedores ou notificadores existentes para a nova sintaxe.
Mas você pode fazer muito mais com Riverpod Lint, incluindo:
- Converter de
StatelessWidget
paraConsumerWidget
ouConsumerStatefulWidget
- Converter entre variantes funcionais e de classe
Mais uma vez, verifique a documentação oficial para obter a lista completa de opções.
Conclusão
Nos primeiros dias do Riverpod, era difícil escolher o provedor correto e acertar a sintaxe (especialmente ao lidar com provedores complexos com argumentos).
Mas como vimos, o Riverpod Generator e o Riverpod Lint facilitam muito a nossa vida.
E hoje em dia, converter qualquer provedor para a nova sintaxe @riverpod
é apenas um caso de:
- adicionando diretivas
part
usando a extensão Riverpod Snippets - escolhendo o provider certo (novamente com Riverpod Snippets)
- preenchendo os espaços em branco (tipo de retorno, nome da função e argumentos)
- escolhendo o tipo de
Ref
correto (Riverpod Lint torna isso mais fácil)
E feito isso, podemos salvar o arquivo e build_runner
cuida do resto.
Depois de usá-lo e vê-lo evoluir por mais de dois anos, sinto que o Riverpod está resolvendo todos os problemas certos, da maneira certa.