Bad state: Migrate To flutter_bloc v8.0.1 - flutter

I am trying to fix an issue related to Flutter Bloc. I am editing someone else code to make it work with the latest flutter_bloc version but I am unable to do so. Can someone do a rewrite for my code so I can run it? I saw many answers but I am unable to understand how to fix my own code.
This is the complete code for all_categories_bloc.dart
class AllCategoriesBloc extends Bloc<AllCategoriesEvent, AllCategoriesState> {
AllCategoriesBloc({
this.apiRepository,
}) : super(AllCategoriesInitial()) {
on<GetAllCategories>(_onGetAllCategories);
}
final ApiRepository apiRepository;
Future<void> _onGetAllCategories(
GetAllCategories event,
Emitter<AllCategoriesState> emit,
) async {
try {
emit(const AllCategoriesLoading());
final categoriesModel = await apiRepository.fetchCategoriesList();
emit(AllCategoriesLoaded(categoriesModel));
if (categoriesModel.error != null) {
emit(AllCategoriesError(categoriesModel.error));
}
} catch (e) {
emit(
const AllCategoriesError(
"Failed to fetch all categories data. Is your device online ?",
),
);
}
}
}
Code for all_categories_event.dart
abstract class AllCategoriesEvent extends Equatable {
AllCategoriesEvent();
}
class GetAllCategories extends AllCategoriesEvent {
#override
List<Object> get props => null;
}
Code for all_categories_state.dart
abstract class AllCategoriesState extends Equatable {
const AllCategoriesState();
}
class AllCategoriesInitial extends AllCategoriesState {
AllCategoriesInitial();
#override
List<Object> get props => [];
}
class AllCategoriesLoading extends AllCategoriesState {
const AllCategoriesLoading();
#override
List<Object> get props => null;
}
class AllCategoriesLoaded extends AllCategoriesState {
final CategoriesModel categoriesModel;
const AllCategoriesLoaded(this.categoriesModel);
#override
List<Object> get props => [categoriesModel];
}
class AllCategoriesError extends AllCategoriesState {
final String message;
const AllCategoriesError(this.message);
#override
List<Object> get props => [message];
}
It throws an error "Bad state: add(GetAllCategories) was called without a registered event handler.
Make sure to register a handler via on((event, emit) {...})"
I have this add(GetAllCategories) in my home. dart file but the solution is to edit this code which I am unable to do so. Can someone do a rewrite for the latest bloc? I would be thankful.

Let's get through the migration guide step by step:
package:bloc v5.0.0: initialState has been removed. For more information check out #1304.
You should simply remove the AllCategoriesState get initialState => AllCategoriesInitial(); portion from your BLoC.
package:bloc v7.2.0 Introduce new on<Event> API. For more information, read the full proposal.
As a part of this migration, the mapEventToState method was removed, each event is registered in the constructor separately with the on<Event> API.
First of all, register your events in the constructor:
AllCategoriesBloc() : super(AllCategoriesInitial()) {
on<GetAllCategories>(_onGetAllCategories);
}
Then, create the _onGetAllCategories method:
Future<void> _onGetAllCategories(
GetAllCategories event,
Emitter<AllCategoriesState> emit,
) async {
try {
emit(const AllCategoriesLoading());
final categoriesModel = await _apiRepository.fetchCategoriesList();
emit(AllCategoriesLoaded(categoriesModel));
if (categoriesModel.error != null) {
emit(AllCategoriesError(categoriesModel.error));
}
} catch (e) {
emit(
const AllCategoriesError(
"Failed to fetch all categories data. Is your device online ?",
),
);
}
}
Notice, that instead of using generators and yielding the next state, you should use the Emitter<AllCategoriesState> emitter.
Here is the final result of the migrated AllCategoriesBloc:
class AllCategoriesBloc extends Bloc<AllCategoriesEvent, AllCategoriesState> {
AllCategoriesBloc() : super(AllCategoriesInitial()) {
on<GetAllCategories>(_onGetAllCategories);
}
final ApiRepository _apiRepository = ApiRepository();
Future<void> _onGetAllCategories(
GetAllCategories event,
Emitter<AllCategoriesState> emit,
) async {
try {
emit(const AllCategoriesLoading());
final categoriesModel = await _apiRepository.fetchCategoriesList();
emit(AllCategoriesLoaded(categoriesModel));
if (categoriesModel.error != null) {
emit(AllCategoriesError(categoriesModel.error));
}
} catch (e) {
emit(
const AllCategoriesError(
"Failed to fetch all categories data. Is your device online ?",
),
);
}
}
}
Bonus tip
Instead of creating an instance of ApiRepository inside the BLoC directly, you can use the constructor injection:
class AllCategoriesBloc extends Bloc<AllCategoriesEvent, AllCategoriesState> {
AllCategoriesBloc({
required this.apiRepository,
}) : super(AllCategoriesInitial()) {
on<GetAllCategories>(_onGetAllCategories);
}
final ApiRepository apiRepository;
...
}
Now, when creating BLoC, pass the instance of the repository to the constructor, like AllCategoriesBloc(apiRepository: ApiRepository()). This way you will be able to properly unit test your BLoC by mocking dependencies (in this case, ApiRepository).

Related

Error on generated file for AsyncNotifierProvider `.family` Riverpod providers

I wonder if I am overlooking something. When ever I try to generate the following via riverpod_annotation I'm getting the error below where it cannot find Family class. I'm pretty sure I'm doing something wrong, but I'm not sure what.
I've deleted and rebuilt the file multiple times and I'm not sure what I can change to make it work.
Here's the gist with both the controller and the generated controller logic
https://gist.github.com/Morzaram/7d75bcfed06ea7cce88a8b11c4fad223
import 'package:front_end/utils/pocketbase_provider.dart';
import 'package:pocketbase/pocketbase.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'mangage_topic_voices_controller.g.dart';
#riverpod
class ManageTopicVoicesController extends _$ManageTopicVoicesController {
List<String> _selectedVoices = [];
bool mounted = true;
get selectedVoices => _selectedVoices;
#override
FutureOr<void> build({required List<String> ids}) {
ref.onDispose(() {
mounted = false;
});
if (mounted) {
_selectedVoices = ids;
}
}
void addVoice(String id) {
_selectedVoices = [..._selectedVoices, id];
}
void removeVoice(String id) {
_selectedVoices = _selectedVoices.where((e) => e != id).toList();
}
Future<RecordModel> updateTopic({topicId, selectedVoices}) async {
final res = await pb
.collection('topics')
.update(topicId, body: {"voices": selectedVoices});
return res;
}
}
The error I'm getting is Classes can only extend other classes. Try specifying a different superclass, or removing the extends clause. and it's occuring on the first line of Family<AsyncValue<void>>
class ManageTopicVoicesControllerFamily extends Family<AsyncValue<void>> {
ManageTopicVoicesControllerFamily();
ManageTopicVoicesControllerProvider call({
required List<String> ids,
}) {
return ManageTopicVoicesControllerProvider(
ids: ids,
);
}
#override
AutoDisposeAsyncNotifierProviderImpl<ManageTopicVoicesController, void>
getProviderOverride(
covariant ManageTopicVoicesControllerProvider provider,
) {
return call(
ids: provider.ids,
);
}
#override
List<ProviderOrFamily>? get allTransitiveDependencies => null;
#override
List<ProviderOrFamily>? get dependencies => null;
#override
String? get name => r'manageTopicVoicesControllerProvider';
}
I know that the error is saying that the Family class doesn't exist, but I'm not sure if the error is due to me or not.
Can I not use family with this currently? I would love any help that I can get.
I'm new to dart, so apologies, and thank you in advance!
Here's the gist with both files

why posting data to firebase using flutter bloc is not emitting?

I'm creating an app with firebase as a database. After sending data to firebase, app screen should pop out for that I had bloclistener on the screen but after sending the data to firestore database, nothing is happening, flow is stopped after coming to loaded state in bloc file why? check my code so that you will know. I can see my data in firebase but it is not popping out because flow is not coming to listener.
state:
class SampletestInitial extends SampletestState {
#override
List<Object> get props => [];
}
class SampletestLoaded extends SampletestState {
SampletestLoaded();
#override
List<Object> get props => [];
}
class SampletestError extends SampletestState {
final error;
SampletestError({required this.error});
#override
List<Object> get props => [error];
}
bloc:
class SampletestBloc extends Bloc<SampletestEvent, SampletestState> {
SampletestBloc() : super(SampletestInitial()) {
on<SampletestPostData>((event, emit) async {
emit(SampletestInitial());
try {
await Repo().sampleTesting(event.des);
emit(SampletestLoaded());
} catch (e) {
emit(SampletestError(error: e.toString()));
print(e);
}
});
}
}
Repo: ---- Firebase post data
Future<void> sampleTesting(String des) async {
final docTicket = FirebaseFirestore.instance.collection('sample').doc();
final json = {'Same': des};
await docTicket.set(json);
}
TicketScreen:
//After clicking the button ---
BlocProvider<SampletestBloc>.value(
value: BlocProvider.of<SampletestBloc>(context, listen: false)
..add(SampletestPostData(description.text)),
child: BlocListener<SampletestBloc, SampletestState>(
listener: (context, state) {
if (state is SampletestLoaded) {
Navigator.pop(context);
print("Popped out");
}
},
),
);
im not sure but i think that you have the same hash of:
AllData? data;
try to remove AllData? data; and create new data variable so you can be sure that you has a new hash code every time you call createTicket method;
final AllData data = await repo.createTicket(AllData(
Check your AllData class properties.
BLoC will not show a new state if it not unique.
You need to check whether all fields of the AllData class are specified in the props field.
And check your BlocProvider. For what you set listen: false ?
BlocProvider.of<SampletestBloc>(context, listen: false)

How to call logout in API service class when API throws 401 error

I am working with flutter bloc in my project which is working great.
I have an API service class which uses Dio for making API requests. I am using an Interceptors for handling error. What I am trying to achieve is whenever API throws 401 error I want to logout the user and show login page. I will also be invalidating the token.
So my question is,
If I want to logout the user as soon as 401 occur ie. from API service class do I call logout event which is defined in the Authentication Bloc. To do so I will have to have instance of Authentication Bloc in API Service class. Is this approach correct? or is there any other simpler ways to achieve this.
class APIService{
...
onError: (DioError error, handler) async {
if (error.response?.statusCode == 401){
//invalidate the token
//Do I call the logout event of authentication bloc here?
}
}
...
}
You can do it, but this is not correct way.
You should handle errors somewhere above in interactors or blocks. There are quite a few ways to implement this. For example, you can use base loading block, something like this:
abstract class LoadingBloc<Event, T> extends Bloc<Event, LoadingState<T>> {
LoadingBloc([LoadingState<T>? state, this.authBloc])
: super(state ?? const LoadingInitialState());
T? get result => state.as<LoadingCompleteState<T>>()?.result;
final AuthenticationBloc? authBloc;
#protected
Future<void> load(
AsyncValueGetter<T> block,
Emitter<LoadingState<T>> emit,
) async {
assert(state is! LoadingProgressState);
emit(const LoadingProgressState());
try {
final result = await block();
if (!isClosed) {
emit(LoadingCompleteState(result));
}
} catch (error, stackTrace) {
print(error);
print(stackTrace);
if (!isClosed) {
if (error is DioError && error.response?.statusCode == 401) {
authBloc.add(LogOutState(error));
} else {
emit(const LoadingErrorState(RawException()));
}
}
}
}
}
Example:
class FirstBloc extends LoadingBloc<FistBloctEvent, FistBloctState>{
FirstBloc(Authentication authBlock):super(authBlock);
}
class SecondBloc extends LoadingBloc<SecondBloctEvent, SecondBloctState>{
SecondBloc(Authentication authBlock):super(authBlock);
}
States for your blocs:
#immutable
abstract class LoadingState<T> extends Equatable {
const LoadingState();
#override
List<Object?> get props => const [];
}
class LoadingInitialState<T> extends LoadingState<T> {
const LoadingInitialState();
}
class LoadingCompleteState<T> extends LoadingState<T> {
final T result;
const LoadingCompleteState(this.result);
#override
List<Object?> get props => [result];
}
class LoadingErrorState<T> extends LoadingState<T> {
final Exception error;
const LoadingErrorState(this.error);
#override
List<Object?> get props => [error];
}
As an option also, if you are using interceptors you could use wrapper, where you will override methods onRequest(..), onError(..):
class RequestHandler extends QueuedInterceptorsWrapper {
final Dio _dio;
RequestHandler({
required Dio dio,
required this.networkBlockRepository,
}) : _dio = dio {
_dio.interceptors.add(this);
}
void close() {
_dio.interceptors.remove(this);
_citySubscription?.cancel();
}
#override
Future<void> onRequest(..) ...
#override
Future<void> onError(..) ...
}

Flutter Bloc/Cubit with multiple properties in one Cubit class

I'm trying to use Bloc package for my state management in my Flutter app. I have situation where I have two list within the same screen, and I am confused should I use two cubits for each list or I can make something like in code bellow where I used one cubit for both lists. I wanna know is this way of structuring code correct? In this image is the sketch of the screen.
abstract class CounterState extends Equatable {
const CounterState({this.valOne, this.valTwo, this.error});
final int? valOne;
final int? valTwo;
final String? error;
#override
List<Object?> get props => [valOne, valTwo, error];
}
class CounterInitial extends CounterState {}
class ValOneSuccess extends CounterState {
const ValOneSuccess(int? val, int? valTwo)
: super(valOne: val, valTwo: valTwo);
}
class ErrorState extends CounterState {
const ErrorState(int? val, int? valTwo, String error)
: super(valOne: val, valTwo: valTwo, error: error);
}
class CounterCubit extends Cubit<CounterState> {
CounterCubit() : super(CounterInitial());
void loadData() async {
int valOne;
int valTwo;
emit(CounterInitial());
try {
valOne = 2;
emit(ValOneSuccess(valOne, state.valTwo));
///If error is thrown
//throw Exception('ValueOne exception');
} catch (e) {
print(e);
emit(ErrorState(state.valOne, state.valTwo, e.toString()));
}
try {
valTwo = 1;
emit(ValOneSuccess(state.valOne, valTwo));
///If error is thrown
//throw Exception('ValueTwo exception');
} catch (e) {
state.error != null
? emit(ErrorState(
state.valOne, state.valTwo, 'Exception on both values'))
: emit(ErrorState(state.valOne, state.valTwo, e.toString()));
}
}
}

How to access Flutter BLoC state value in the new version?

I am new in this state management world. So I was trying to follow this tutorial (How to Save Products in a Wishlist using the BloC Pattern - EP10 - The eCommerce Series), but mapEventToState is deprecated so I am not sure what to do.
Here is my state:
part of 'wishlist_bloc.dart';
abstract class WishlistState extends Equatable {
const WishlistState();
#override
List<Object> get props => [];
}
class WishlistLoading extends WishlistState {}
class WishlistLoaded extends WishlistState {
final WishlistModel wishlist;
const WishlistLoaded({this.wishlist = const WishlistModel()});
#override
List<Object> get props => [wishlist];
}
class WishlistError extends WishlistState {}
Here is my event:
part of 'wishlist_bloc.dart';
abstract class WishlistEvent extends Equatable {
const WishlistEvent();
#override
List<Object> get props => [];
}
class StartWishlist extends WishlistEvent {}
class AddWishlistProduct extends WishlistEvent {
final ProductModel product;
const AddWishlistProduct(this.product);
#override
List<Object> get props => [product];
}
class RemoveWishlistProduct extends WishlistEvent {
final ProductModel product;
const RemoveWishlistProduct(this.product);
#override
List<Object> get props => [product];
}
Here is my bloc:
import '../models/product_model.dart';
import '../models/wishlist_model.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:equatable/equatable.dart';
part 'wishlist_event.dart';
part 'wishlist_state.dart';
class WishlistBloc extends Bloc<WishlistEvent, WishlistState> {
WishlistBloc() : super(WishlistLoading()) {
on<StartWishlist>(_mapStartWishlistToState);
on<AddWishlistProduct>(_mapAddWishlistToState);
on<RemoveWishlistProduct>(_mapRemoveWishlistToState);
}
void _mapStartWishlistToState(event, emit) async {
emit(WishlistLoading());
try {
await Future.delayed(Duration(seconds: 1));
emit(WishlistLoaded());
} catch (_) {}
}
// Error ...
void _mapAddWishlistToState(event, emit) async {
if (state is WishlistLoaded) {
try {
emit(WishlistLoaded(
wishlist: WishlistModel(
products: List.from(state.wishlist.products)
..add(event.product))));
} catch (_) {}
}
}
void _mapRemoveWishlistToState(event, emit) async {}
}
But I get this error instead: "The getter 'wishlist' isn't defined for the type 'WishlistState'. Try importing the library that defines 'wishlist', correcting the name to the name of an existing getter, or defining a getter or field name 'wishlist'".
How to access 'wishlist' in the new version of flutter_bloc? Thank you.
Try to define arguments types while difining the functions. So your updated code would look like this:
import '../models/product_model.dart';
import '../models/wishlist_model.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:equatable/equatable.dart';
part 'wishlist_event.dart';
part 'wishlist_state.dart';
class WishlistBloc extends Bloc<WishlistEvent, WishlistState> {
WishlistBloc() : super(WishlistLoading()) {
on<StartWishlist>(_mapStartWishlistToState);
on<AddWishlistProduct>(_mapAddWishlistToState);
on<RemoveWishlistProduct>(_mapRemoveWishlistToState);
}
void _mapStartWishlistToState(
// Added argument types below
StartWishlist event, Emitter<WishlistState> emit) async {
emit(WishlistLoading());
try {
await Future.delayed(Duration(seconds: 1));
emit(WishlistLoaded());
} catch (_) {}
}
void _mapAddWishlistToState(
// Added argument types below
AddWishlistProduct event, Emitter<WishlistState> emit) async {
if (state is WishlistLoaded) {
try {
emit(WishlistLoaded(
wishlist: WishlistModel(
products: List.from(state.wishlist.products)
..add(event.product))));
} catch (_) {}
}
}
void _mapRemoveWishlistToState(
// Added argument types below
RemoveWishlistProduct event, Emitter<WishlistState> emit) async {}
}
Type promotion is impossible for properties, since they can potentially return different values each time they are called. As such, it is impossible for the compiler to know that the state getter will return a WishlistLoaded instance, even after knowing that the same getter returned a WishlistLoaded four lines earlier.
One way around this is to assign the state to a local variable, which is eligible for type promotion.
void _mapAddWishlistToState(AddWishlistProduct event, Emitter<WishlistState> emit) async {
final state = this.state; // local variable
if (state is WishlistLoaded) {
try {
emit(WishlistLoaded(
wishlist: WishlistModel(
products: List.from(state.wishlist.products)
..add(event.product))));
} catch (_) {}
}
}
The linked video used a parameter, which is also eligible for type promotion. If it had used the state getter directly in _mapAddWishlistProductToState, it would have run into the same error.
You just need to cast your state as following
void _mapAddWishlistToState(event, emit) async {
if (state is WishlistLoaded) {
try {
emit(WishlistLoaded(
wishlist: WishlistModel(
products: List.from((state as WishlistLoaded).wishlist.products)
..add(event.product))));
} catch (_) {}
}
}