4

I have a problem with my BloC provider in one of my Flutter applications. I use Chopper as HTTP client and it creates a Chopper instance right at the beginning of the app which I then inject with get_it wherever I want to fetch data from my API. In the meantime I have implimented a ChopperAuthenticator which fetches a new token from the API when the old one expires (statuscode 401). But the problem is, if the refresh-token is expired too, the user should be logged out. For this I just check if the fetching of the refresh-token was successful, if this is not the case my Authentication BloC should be notified that the user is not logged in anymore.

But I can't call BlocProvider.of<AuthenticationBloc>(context).add(Logout()); because in the chopper instance the build context is not known. Is there another way to change the state of a BloC? Do you have any ideas how I can rebuild the whole thing to make it work?

FlutterSecureStorage storage = locator.getIt<FlutterSecureStorage>();
UserRepository userRepository = locator.getIt<UserRepository>();

Swagger getClient(String baseUrl) {
  return Swagger.create(
    baseUrl: baseUrl,
    authenticator: ChopperAuthenticator(),
    interceptors: [
      MobileDataInterceptor(),
      const HeadersInterceptor(
        {
          'content-type': 'application/json',
          'Accept': 'application/json',
        },
      ),
      HttpLoggingInterceptor(),
      (Response response) async {
        return response;
      },
      (Request request) async {
        final String? token = await storage.read(key: 'accessToken');
        if (token != null && !request.url.contains('refresh')) {
          final Map<String, String> map = {'authorization': 'Bearer $token'};
          request.headers.addAll(map);
        }
        return request;
      },
    ],
  );
}

class ChopperAuthenticator extends Authenticator {
  @override
  FutureOr<Request?> authenticate(
    Request request,
    Response<dynamic> response, [
    Request? originalRequest,
  ]) async {
    if (response.statusCode == 401) {
      final String? refreshToken = await storage.read(
        key: 'refreshToken',
      );
      final String? accessToken = await storage.read(
        key: 'accessToken',
      );
      final Response newResponse =
          await userRepository.apiClient.apiAuthenticateRefreshTokenPost(
        body: RefreshRequestDTO(
          accessToken: accessToken,
          refreshToken: refreshToken,
        ),
      );

      if (newResponse.isSuccessful) {
        await storage.write(
          key: 'accessToken',
          value: newResponse.body['accessToken'].toString(),
        );
        await storage.write(
          key: 'refreshToken',
          value: newResponse.body['refreshToken'].toString(),
        );
        String? newToken = newResponse.body['accessToken'].toString();
        final Map<String, String> updatedHeaders =
            Map<String, String>.of(request.headers);

        newToken = 'Bearer $newToken';
        updatedHeaders.update(
          'Authorization',
          (String _) => newToken!,
          ifAbsent: () => newToken!,
        );
        debugPrint('Token renewed');
        return request.copyWith(headers: updatedHeaders);
      } else {
        <Here should be the logic to log out the user>
      }
    }
    return null;
  }
}
morzan1001
  • 73
  • 4
  • Does this answer help you? A way to always know the current context...: https://stackoverflow.com/a/71999875/14335655 – Karolina Hagegård Sep 13 '22 at 18:39
  • Thanks for the link but i blieve that does not help me, because i have to pass the GlobalKey to the Api-Client Class. – morzan1001 Sep 13 '22 at 19:15
  • Ah, ok... It was just a quick thought, while I was reviewing, I didn't read the qn in depth. Anyway, hope you get a better answer! – Karolina Hagegård Sep 13 '22 at 20:35
  • Did you solve this? I have the exact same scenario. – Dylan Cross Nov 04 '22 at 23:37
  • Well, "solved" is a bit much, but i worked around the problem by instead of using the context in the static api client i create an exeption that i can then intercept and process in the controller that makes the request and knows the context. this works quite well. – morzan1001 Jan 09 '23 at 14:50

1 Answers1

-1

Quick and dirty solution:

Since you are using getIt you should add it to it when creating the bloc like:

BlocProvider<AuthBloc>(
          create: (context) {
            var bloc = AuthBloc();
            getIt.registerLazySingleton(() => bloc); //registering for later use
            return bloc;
          },
)

Then in the log-out section you could just call

getIt<AuthBloc>().add(LogOut());

Proper solution:

Create a repository that holds authentication. This repository should have a stream that your AuthBloc is subscribed to. Inject the repository into your chopper Authenticator and from there manipulate the stream of the repository.

Your AuthBloc should react to stream changes and therefore logout the user.

This approach would comply with the official docs https://bloclibrary.dev/#/architecture