BLoC x Riverpod: Comparando abordagens de gerenciamento de estado no Flutter
BLoC vs. Riverpod – Este artigo faz parte de uma série que explica por que Riverpod é uma opção melhor para gerenciamento de estado em Flutter do que BLoc. Neste artigo, veremos os princípios do Riverpod e do BLoc e demonstraremos como eles podem ser usados para corrigir problemas em aplicativos básicos. Este artigo é um excelente ponto de partida se você não estiver familiarizado com esses pacotes ou quiser comparar seus principais recursos e habilidades.
A principal diferença entre Riverpod e BLoC
Esta seção mostrará como esses dois pacotes funcionam em cenários simples. Nós vamos demonstrar isso para você.
- Como construir suas classes lógicas (em provedores Riverpod e BLoc/Cubit em BLoc)?
- Injeção de dependência.
- Como seus widgets no Flutter podem consumir seus estados?
Conteudo
Provedores e BLOC/Cubit
Considere o cenário em que desejamos criar uma página de lista de filmes para nosso aplicativo. Podemos usar um repositório intuitivo e uma classe de serviço para recuperar títulos de filmes e remover filmes.
A classe de serviço de filme exibida abaixo faz consultas HTTP para um endpoint da API usando o pacote Dio. Enquanto o método removeMovie
recebe um nome de filme como entrada e o remove da API, o método fetchMovieNames
retorna um Future com uma lista de nomes de filmes.
import 'package:dio/dio.dart'; class MovieService { final Dio _dio = Dio(); Future<list<string>> fetchMovieNames() async { try { Response response = await _dio.get('https://someapi.url'); return (response.data) as List<string>; } catch (error, stacktrace) { // ignorar: evitar_print print("Exception occured: $error stackTrace: $stacktrace"); adicione seu tratamento de erros rethrow; } } Future<void> removeMovie(String movieName) async { try { await _dio.delete('https://someapi.url/$movieName'); } catch (error, stacktrace) { // ignorar: evitar_print print("Exception occured: $error stackTrace: $stacktrace"); // adicione seu tratamento de erros rethrow; } } }
Cubit e BLoc
Esta é a classe BLoc que podemos utilizar para gerenciar o estado atual da página da lista de filmes. O MovieBloc define dois manipuladores de eventos: _onFetchMovie e _onRemoveMovie, e estende a classe BLoc do pacote bloc.
import 'package:flutter_bloc/flutter_bloc.dart'; class MovieBloc extends Bloc<movieevent, moviestate=""> { final MovieRepository movieRepository; MovieBloc({required this.movieRepository}) : super(MovieInitial()) { on<fetchmovie>(_onFetchMovie); on<removemovie>(_onRemoveMovie); } void _onFetchMovie(FetchMovie event, Emitter<moviestate> emit) async { emit(MovieLoading()); try { final movieList = await movieRepository.fetchMovieNames(); emit(MovieLoaded(movieList)); } catch (e) { emit(MovieError(e.toString())); } } void _onRemoveMovie(RemoveMovie event, Emitter<moviestate> emit) async { try { emit(MovieLoading()); await movieRepository.removeMovie(event.movieName); add(FetchMovie()); } catch (e) { emit(MovieError(e.toString())); } } }
Podemos usar Cubit em vez de BLoc em casos de uso menos complexos. Foi assim que implementamos a classe MovieCubit:
import 'package:flutter_bloc/flutter_bloc.dart'; class MovieCubit extends Cubit<moviestate> { final MovieRepository movieRepository; MovieCubit({required this.movieRepository}) : super(MovieInitial()); void onFetchMovie() async { emit(MovieLoading()); try { final movieList = await movieRepository.fetchMovieNames(); emit(MovieLoaded(movieList)); } catch (e) { emit(MovieError(e.toString())); } } void onRemoveMovie(String movieName) async { try { emit(MovieLoading()); await movieRepository.removeMovie(movieName); onFetchMovie(); } catch (e) { emit(MovieError(e.toString())); } } }
Provedores de Riverpod
Vejamos como usar o Riverpod
para desenvolver classes lógicas, agora que vimos como fazer isso com BLoC e Cubit. Dependendo de suas demandas exclusivas, Riverpod
oferece uma variedade de tipos de provedores. Nessa situação, StateNotifier
é uma escolha razoável porque faremos chamadas de API e retornaremos resultados.
Aqui está uma demonstração de como StateNotifier pode ser usado para construir um provedor Riverpod:
final movieNotifierProvider = StateNotifierProvider<movienotifier, moviestate="">( (ref) => MovieNotifier(ref.watch(movieRepositoryProvider)), ); class MovieNotifier extends StateNotifier<moviestate> { MovieNotifier( this.movieRepository, ) : super(MovieInitial()); final MovieRepository movieRepository; void fetchMovie() async { state = MovieLoading(); try { final movieList = await movieRepository.fetchMovieNames(); state = MovieLoaded(movieList); } catch (e) { state = MovieError(e.toString()); } } void removeMovie(String movieName) async { try { state = MovieLoading(); await movieRepository.removeMovie(movieName); fetchMovie(); } catch (e) { state = MovieError(e.toString()); } } }
No entanto, você também pode utilizar um FutureProvider para este caso de aplicação simples. O AsyncValue que este provedor fornecerá tem estados de carregamento, carregamento e erro. Isso implica que você não precisará implementar vários estados de UI.
final movieListFutureProvider = FutureProvider<list<string>>((ref) async { final movieRepository = ref.watch(movieRepositoryProvider); return movieRepository.fetchMovieNames(); });
Basta dar uma olhada no código; tem apenas quatro linhas. Este caso de uso é relativamente fácil de entender, mas muitos aplicativos o utilizam porque é útil.
Examine todos os provedores listados na documentação.
Injeção de dependência BloC
O uso dos widgets BlocProvider e MultiBlocProvider no BloC é sugerido para injetar dependências em suas classes BloC. Com a ajuda desses widgets, você pode adicionar uma instância BLoc/Cubit à árvore de widgets que qualquer widget na subárvore pode usar.
Para fornecer uma única instância de um BloC/Cubit, use o BlocProvider. Aqui está uma ilustração de como você pode aplicá-lo:
BlocProvider( create: (context) => MyBloc(), child: MyWidget(), )
Neste caso, estamos usando BlocProvider para dar à árvore de widgets acesso a uma instância do MyBloc. Qualquer descendente de MyWidget pode utilizar context.read() para obter a instância de MyBloc que o BlocProvider desenvolveu.
Por outro lado, MultiBlocProvider entrega muitas instâncias de Bloc/Cubit simultaneamente. Aqui está uma ilustração:
MultiBlocProvider( providers: [ BlocProvider(create: (context) => BlocA()), BlocProvider(create: (context) => BlocB()), ], child: MyWidget(), )
Para fornecer instâncias de Blocs/Cubits à árvore de widgets por meio de injeção de dependência, BlocProvider e MultiBlocProvider são utilizados. Eles possibilitam gerenciar as dependências do seu aplicativo de forma rápida e fácil.
Injeção de Dependência de Riverpod
Utilizando provedores no Riverpod, a injeção de dependência é possível. Classes chamadas provedores criam e mantêm objetos e podem ser usadas para injetar dependências em widgets ou outros provedores.
Riverpod tem diferentes tipos de provedores, incluindo Provider, FutureProvider, StreamProvider e ScopedProvider. Cada tipo de provedor tem um caso de uso específico e, ao fundi-los e compô-los, podem ser criadas dependências mais complicadas.
Aqui está um exemplo de como injetar uma dependência no Riverpod usando um provedor:
final movieRepositoryProvider = Provider((ref) => MovieRepository());
Da mesma forma, como uma instância do FutureProvider:
final movieListProvider = FutureProvider<list<string>>((ref) async { final movieRepository = ref.watch(movieRepositoryProvider); return movieRepository.fetchMovieNames(); });
Nesta ilustração, usamos um FutureProvider para recuperar uma lista de títulos de filmes usando o movieRepositoryProvider. O valor do provedor movieRepositoryProvider é recuperado e usado no FutureProvider usando o método ref.watch.
Construtores que os utilizam em widgets
Use o widget BlocBuilder no pacote BLoc para reconstruir seu widget com base nas alterações de estado do BLoc.
BlocBuilder é um widget oferecido pelo pacote Bloc que facilita a reconstrução rápida de uma árvore de widgets a partir do estado de um BLOC. Ele fica atento às mudanças no estado do Bloco e reconstrói a árvore de widgets sempre que elas ocorrem.
Aqui está uma ilustração de como o BlocBuilder é usado:
class MovieListBlocWidget extends StatelessWidget { const MovieListBlocWidget({super.key}); @override Widget build(BuildContext context) { return BlocBuilder<moviebloc, moviestate="">( bloc: MovieBloc(movieRepository: MovieRepository()), builder: (context, state) { if (state is MovieInitial) { return const Placeholder(); } else if (state is MovieLoading) { return const CircularProgressIndicator(); } else if (state is MovieLoaded) { return ListView.builder( itemCount: state.movieList.length, itemBuilder: (context, index) { return ListTile( title: Text(state.movieList[index]), ); }, ); } else if (state is MovieError) { return Text(state.message); } return Container(); }, ); } }
Se desejar, o côvado pode ser usado no lugar do BLoc; mude MovieBloc para MovieCubit.
Consumer Widgets do Riverpod
Você pode usar ConsumerWidget ou ConsumerStatefulWidget e a função de observação que o objeto ref oferece para consumir um provedor Riverpod em um widget.
Lembre-se de nosso movieNotifierProvider, não é? StateNotifierProvider era exatamente o que era.
final movieNotifierProvider = StateNotifierProvider<movienotifier, moviestate="">( (ref) => MovieNotifier(ref.watch(movieRepositoryProvider)), );
Podemos usar ref.watch(movieNotifierProvider) para obter o valor mais recente do provedor antes de usá-lo no método de construção do widget.
O estado do objeto movieList pode então ser verificado usando instruções condicionais e, com base em seu estado atual, vários widgets podem ser retornados.
class MovieListStateNotifierWidget extends ConsumerWidget { const MovieListStateNotifierWidget({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { final movieList = ref.watch(movieNotifierProvider); if (movieList is MovieInitial) { return const Placeholder(); } else if (movieList is MovieLoading) { return const CircularProgressIndicator(); } else if (movieList is MovieLoaded) { return ListView.builder( itemCount: movieList.movieList.length, itemBuilder: (context, index) { return ListTile( title: Text(movieList.movieList[index]), ); }, ); } else if (movieList is MovieError) { return Text(movieList.message); } return Container(); } }
O código para usar FutureProvider em seu código é o seguinte.
class MovieListFutureProviderWidget extends ConsumerWidget { const MovieListFutureProviderWidget({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { final movieList = ref.watch(movieListFutureProvider); return movieList.when( data: (data) { return ListView.builder( itemCount: data.length, itemBuilder: (context, index) { return ListTile( title: Text(data[index]), ); }, ); }, loading: () => const CircularProgressIndicator(), error: (error, stackTrace) => Text(error.toString()), ); } }
Conclusão
Neste artigo, comparamos os componentes fundamentais dos pacotes de gerenciamento de estado Flutter BLOC e Riverpod. Investigamos as necessidades básicas de aplicativos simples que ambos os pacotes podem satisfazer.
Além disso, a decisão entre o BLoc e o Riverpod depende, em última análise, dos requisitos específicos de cada projeto e das preferências da equipe de desenvolvimento. A dimensão e a complexidade do projecto, bem como o grau de experiência e familiaridade com os dois quadros, devem ser tidos em conta. Os desenvolvedores frequentemente usam ambas as estruturas e demonstraram eficácia no gerenciamento do estado dos aplicativos Flutter. Independentemente da decisão, é essencial compreender os princípios de gestão do estado em sua essência e utilizar as melhores práticas ao desenvolver, testar e manter software. Com isso, os desenvolvedores Flutter da renomada empresa de desenvolvimento de aplicativos Flutter podem garantir uma experiência de usuário agradável e desenvolverão seus aplicativos com desempenho, escalabilidade e manutenção.