Como escrever aplicativos Flutter mais rápido com Riverpod Lint e Riverpod Snippets

Tempo de leitura: 5 minutes

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).

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, um Stream ou um Notifier?
  • 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 e firebaseAuthProvider

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 para firebaseAuthProvider e authRepositoryProvider, mas não para authStateChangesProvider. 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 para ConsumerWidget ou ConsumerStatefulWidget
  • 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.