Flutter access BloC without Context

816 views Asked by At

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 implemented 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;
  }
}
1

There are 1 answers

0
Markus Ridziauskas On

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