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

Viewed 75

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?
          }
      }
...

}
1 Answers

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