Search code examples
flutterdartchopper

Flutter chopper swagger_dart_code_generator API repeating request endlessly without response


I am trying to set up a client to my nestJs server. I generate a swagger documentation with nestJS/swagger and from there I use the swagger_dart_code_generator to generate the API code for my client. The Server works fine, I tested this with Postman. All request do as they should.

The problem is my client. When I call any generated API function it goes to the server as planned, the server builds its response and sends it back. BUT THEN my client function does not get the response and instead the request is sent to the server again in an endless loop. My original function never gets a response and just waits forever. I tested the behavior in windows, web and android. I never get an error, or an exception. Is there a configuration I am missing? What else could trigger this behavior?

Edit deleted since unrelated.

Here is the code I wrote:

//build.yaml

targets:
  $default:
    sources:
      - "lib/**"
      - "lib/swagger/**"
    builders:
      chopper_generator:
        options:
          header: "//Generated code"
      swagger_dart_code_generator:
        options:
          input_folder: "lib/swagger/"
          output_folder: "lib/swagger/models/"
          ignore_headers: true
          separate_models: true
//services.dart

import 'dart:async';
import 'package:get_it/get_it.dart';
import 'package:my_client/core/services/my_authenticator.dart';
import 'package:my_client/swagger/models/client_index.dart';


final getIt = GetIt.instance;

Future<void> setupServices() async {
  getIt.registerSingleton<MyApi>(MyApi.create(
    baseUrl: Uri.parse("http://localhost:3000"),
    authenticator: getIt<MyAuthenticator>(),
  ));
}
//my_authenticator.dart

import 'dart:async';
import 'dart:io';
import 'package:chopper/chopper.dart';

class MyAuthenticator extends Authenticator{

  String token = "";

  @override
  FutureOr<Request?> authenticate(
      Request request,
      Response response, [
        Request? originalRequest,
      ]) async {
    if (response.statusCode == HttpStatus.unauthorized) {
      final String? newToken = await refreshToken();

      if (newToken != null) {
        return request.copyWith(headers: {
          ...request.headers,
          HttpHeaders.authorizationHeader: newToken,

        });
      }
    }
    return request;
  }

  Future<String?> refreshToken() async {
    return token;
  }

}
//user_provoider.dart

import 'package:my_client/core/extensions/date_time_extension.dart';
import 'package:my_client/core/models/user_week.dart';
import 'package:my_client/swagger/models/my_api.swagger.dart';

class UserWeekProvider {
  final MyApi _api;

  UserWeekProvider(this._api);

  Future<UserWeek?> getWorkWeek(DateTime? date) async {
    final date = (date?? DateTime.now()).toIso8601String();

    final response = await _api.userWeekGet(
        date: date
    );
    if (response.body == null) return null;
    return UserWeek.fromResponse(response.body!);
  }
}

Solution

  • I found it. It is the Authenticator. The old one always returned a request, and so chopper tried again in an endless loop. Now I give back a null in any case other than an unauthorized. So it does not try again. The actually authenticate process is not implemented. But it works for now.

    This change works now:

    class MyAuthenticator extends Authenticator {
      String token = "";
    
      @override
      FutureOr<Request?> authenticate(
        Request request,
        Response response, [
        Request? originalRequest,
      ]) async {
        if (response.statusCode == HttpStatus.unauthorized) {
          String? newToken = await refreshToken();
    
          final Map<String, String> updatedHeaders =
              Map<String, String>.of(request.headers);
    
          if (newToken != null) {
            newToken = 'Bearer $newToken';
            updatedHeaders.update('Authorization', (String _) => newToken!,
                ifAbsent: () => newToken!);
            return request.copyWith(headers: updatedHeaders);
          }
        }
        return null;
      }
    
      Future<String?> refreshToken() async {
        return token;
      }
    }