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

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

Related

Bad state: Migrate To flutter_bloc v8.0.1

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

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 (_) {}
}
}

How to use the BLoC library?

I'm trying to figure out the BLoC library, but it gives me headaches.
I'm trying to fetch hotel names from an API. I have a model and a service responsible for contacting the API and fetching the data. However, I don't know how to connect it to the BLoC library.
Once my app starts, I want BLoC to fetch the data from the API and then show it in the app.
Here's my code:
hotel_model.dart
class Hotels {
final List<Hotel> hotels;
Hotels({this.hotels});
factory Hotels.fromJson(Map<String, dynamic> json) {
return Hotels(
hotels: List<Hotel>.from(
json['hotels'].map(
(x) => Hotel.fromJson(x),
),
),
);
}
}
class Hotel {
final String hotelName;
Hotel({this.hotelName});
factory Hotel.fromJson(Map<String, dynamic> json) {
return Hotel(
hotelName: json['name'],
);
}
}
hotel_service.dart
import 'package:http/http.dart' as http;
abstract class DownloadService {
Future<http.Response> fetchHotels();
}
class HotelService extends DownloadService {
#override
Future<http.Response> fetchHotels() {
final Uri uri = Uri.https('services.lastminute.com', 'mobile/stubs/hotels');
return http.get(uri);
}
}
And here's what I did wit the BLoC lib.
hotel_event.dart
part of 'hotel_bloc.dart';
#immutable
abstract class HotelEvent {}
class OnAppStartEvent extends HotelEvent {}
hotel_bloc.dart
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:hotels/models/hotel/hotel_model.dart';
import 'package:hotels/services/hotel/hotel_service.dart';
import 'package:meta/meta.dart';
part 'hotel_event.dart';
part 'hotel_state.dart';
class HotelBloc extends Bloc<HotelEvent, HotelState> {
HotelBloc() : super(HotelFinal());
final HotelService hotelService = HotelService();
#override
Stream<HotelState> mapEventToState(
HotelEvent event,
) async* {
if (event is FetchEvent) {
final response = hotelService.fetchHotels();
yield
}
}
}
hotel_state.dart
part of 'hotel_bloc.dart';
#immutable
abstract class HotelState {
HotelState();
}
class HotelFinal extends HotelState {
final Hotel hotel;
HotelFinal(this.hotel);
Hotel getHotel() {
return hotel;
}
}
First of all add await to this line in your bloc
final response = await hotelService.fetchHotels();
return List<Hotel> from your fetchHotels function
you must have stateful class for your screen and in the initState
you can create your bloc object and call .add method on it
in your build method wrap your widget with BlocBuilder and on builder callback check your bloc state, if the state is HotelFinal return your ui with list of hotels in your state object.
It'll be useful to add another state for your HotelState for when your bloc is fetching the data, and even for when there's an error. e.g;
part of 'hotel_bloc.dart';
#immutable
abstract class HotelState {
HotelState();
}
class HotelFinal extends HotelState {
final Hotel hotel;
HotelFinal(this.hotel);
Hotel getHotel() {
return hotel;
}
}
class HotelLoading extends HotelState {
HotelLoading();
}
class HotelError extends HotelState {
final String error;
HotelError(this.error);
}
You would want to change your mapEventToState to something like this:
#override
Stream<HotelState> mapEventToState(
HotelEvent event,
) async* {
if (event is FetchEvent) {
yield HotelLoading();
try {
final response = await hotelService.fetchHotels();
// It seems like your service doesn't return an hotel directly, so you'll have to deal with this as it is not part of the question.
final hotel = getYourHotelHereWithTheResponse;
yield HotelFinal(hotel);
} catch (e) {
yield HotelError('something went wrong getting the hotel info');
}
}
}
Lastly, add a widget to your widget tree that adds FetchEvent to your bloc and add a BlocBuilder to react to the change of states. Note that this is very flexible and can be done in many ways, but it is out of the scope of your very broad question, I'm just showing you how to use the library at a minimal:
class MyStatefulWidget extends StatefulWidget {
#override
_MyStatefulWidgetState createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
HotelBloc hotelBloc;
#override
void initState() {
hotelBloc = HotelBloc..add(FetchEvent());
super.initState();
}
#override
void dispose() {
hotelBloc.close();
super.dispose();
}
#override
Widget build(BuildContext context) {
return BlocBuilder(builder: (context, state) {
if(state is HotelLoading) {
// return a widget to deal with loading
}
if(state is HotelFinal) {
// return a widget to deal with success
}
if(state is HotelError) {
// return a widget to deal with error
}
});
}
}

flutter_bloc share state for many blocs

let's say I want to check for internet connection every time I call Api, if there's no internet the call with throw exception like NoInternetException and then show a state screen to the user tells him to check their connection.
How can I achieve that without creating a new state for every bloc in flutter_bloc library?
You can do this in the bloc that manages your root pages like authentication_page and homepage.
Create a state for noConnectivity.
NoConnectivity extends AuthenticationState{
final String message;
const NoConnectivity({ this.message });
}
Now create an event for noConnectivity.
NoConnectivityEvent extends AuthenticationEvent{}
Finally, create a StreamSubscription in your AuthenticationBloc to continuously listen to connecitvityState change and if the state is connectivity.none we'll trigger the NoConnecitivity state.
class AuthenticationBloc
extends Bloc<AuthenticationEvent, AuthenticationState> {
StreamSubscription subscription;
#override
AuthenticationState get initialState => initialState();
#override
Stream<AuthenticationState> mapEventToState(
AuthenticationEvent event,
) async* {
// ... all other state map
else if(event is NoConnectivityEvent) {
yield* _mapNoConnectivityEventToState();
}
Stream<AuthenticationState> _mapNoConnectivityEventToState() async * {
subscription?.cancel();
//Edit to handle android internet connectivity.
subscription = Connectivity()
.onConnectivityChanged
.listen((ConnectivityResult result) {
if(Platform.isAndroid) {
try {
final lookupResult = InternetAddress.lookup('google.com');
if (lookupResult.isNotEmpty && lookupResult[0].rawAddress.isNotEmpty) {
print('connected');
}
} on SocketException catch (error) {
return add(NoConnectivityState(message: error.message ));
}
} else if(result == ConnectivityResult.none ) {
return add(NoConnectivityState(message: "Noconnection")) ;
}
print("Connected");
});
}
#override
Future<void> close() {
subscription?.cancel();
return super.close();
}
}
This subscription Stream will forever listen to a no connection and push the appropriate page you like to the state.
Required packages
rxdart
connectivity
Hope it helps!
you need base class bloc let's say his name "BaseBloc" and he shall inherit from the "Bloc" class, and implement "mapToStateEvent" method to process "noInternet" exception, and after that call method let's say his name "internalMapToStateEvent" you created, this method it's override method, and inherited all your bloc class from "BaseBloc" and you need same that for pages to draw one widget "noInternetWidget"