0

I have created an http service for handling requests and made a generic response. It works when passing the Generic as a primitive, but when passing a List (even dynamic), it throws an error during assigning. Skipping typing and casting later on doesn't work either. I am stumped.

class Room {
  final String name;

  const Room({required this.name});
}

  List<Room> ownRoomsList = [];
  List<dynamic> savedRooms = [];

  Future<void> updateRooms() async {
    HttpService httpService = new HttpService();
    await httpService.init();
    final response = await httpService.request<List<Room>>(
        url: "puff/rooms", method: Method.POST);
    if (response != null) {
      setState(() {
        ownRoomsList = response.data; // Error here.
      });
    }
  }

class APIResponse<T> {
  final bool success;
  final T data;
  final String message;

  const APIResponse(
      {required this.success, required this.data, required this.message});

  factory APIResponse.fromJson(Map<String, dynamic> json) {
    return APIResponse(
      success: json['success'],
      data: json['data'],
      message: json['message'],
    );
  }
}

class HttpService<T> {
  Dio? _dio;

  var logger = Logger(
    printer: PrettyPrinter(),
  );

  Future<HttpService> init() async {
    SharedPreferences pref = await SharedPreferences.getInstance();
    final userId = pref.getInt('userId');
    final headers = {
      "Content-Type": "application/json",
      "userId": userId.toString()
    };
    _dio = Dio(BaseOptions(baseUrl: BASE_URL, headers: headers));
    initInterceptors();
    return this;
  }

  void initInterceptors() {
    _dio!.interceptors.add(
      InterceptorsWrapper(
        onRequest: (requestOptions, handler) {
          logger.i(
              "REQUEST[${requestOptions.method}] => PATH: ${requestOptions.path}"
              "=> REQUEST VALUES: ${requestOptions.queryParameters}"
              "=> POST VALUES: ${requestOptions.data}"
              "=> HEADERS: ${requestOptions.headers}");
          return handler.next(requestOptions);
        },
        onResponse: (response, handler) {
          logger
              .i("RESPONSE[${response.statusCode}] => DATA: ${response.data}");
          return handler.next(response);
        },
        onError: (err, handler) {
          logger.i("Error[${err.response?.statusCode}]");
          return handler.next(err);
        },
      ),
    );
  }

  Future<APIResponse<T>?> request<T>(
      {required String url,
      required Method method,
      Map<String, dynamic>? params}) async {
    Response response;

    try {
      if (method == Method.POST) {
        response = await _dio!.post(url, data: params);
      } else if (method == Method.DELETE) {
        response = await _dio!.delete(url);
      } else if (method == Method.PATCH) {
        response = await _dio!.patch(url);
      } else {
        response = await _dio!.get(url, queryParameters: params);
      }

      if (response.statusCode == 200) {
        return APIResponse.fromJson(jsonDecode(response.data));
      } else if (response.statusCode == 401) {
        //throw Exception("Unauthorized");
      } else if (response.statusCode == 500) {
        //throw Exception("Server Error");
      } else {
        //throw Exception("Something went wrong");
      }
    } on SocketException catch (e) {
      logger.e(e);
      //throw Exception("No Internet Connection");
    } on FormatException catch (e) {
      logger.e(e);
      //throw Exception("Bad response format");
    } on DioError catch (e) {
      logger.e(e);
      //throw Exception(e);
    } catch (e) {
      logger.e(e);
      //throw Exception("Something went wrong");
    }

    return null;
  }
}

Error:

type 'List<dynamic>' is not a subtype of type 'List<Room>'
Alexey
  • 3,607
  • 8
  • 34
  • 54
  • You don't say how you tried to cast the `List`, but if you want to cast a `List` to a `List`, you probably should be using `List.from` or `List.cast`, not `as`. (See https://stackoverflow.com/a/67223011/, https://stackoverflow.com/a/71785805/, or https://stackoverflow.com/a/70963189/ for details.) – jamesdlin Aug 24 '22 at 19:02
  • @jamesdlin My question is why this does not work? I pass the type of the response. – Alexey Aug 24 '22 at 19:07
  • When `request` calls `ApiResponse.fromJson`, it does not pass the parameter type, so it's calling `ApiResponse.fromJson`. – jamesdlin Aug 24 '22 at 19:16
  • @jamesdlin is there a way to pass it to a constructor? Or what would be a way to fix this on the http service level? – Alexey Aug 24 '22 at 19:25
  • `request` should be calling `ApiResponse.fromJson` instead. – jamesdlin Aug 24 '22 at 19:31
  • @jamesdlin changed post to `var a = jsonDecode(response.data); logger.e(a); return APIResponse.fromJson(a);` --- and the API to `class APIResponse { final bool success; final T data; final String message; const APIResponse( {required this.success, required this.data, required this.message}); factory APIResponse.fromJson(Map json) { return APIResponse( success: json['success'], data: json['data'] as T, message: json['message'], ); } }` The error is during jsonDecode – Alexey Aug 24 '22 at 19:42
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/247531/discussion-between-jamesdlin-and-alexey). – jamesdlin Aug 24 '22 at 20:25

1 Answers1

0

As it happens, Dart does not support dynamic typing of non-primitives, so the casting inside never worked. First, need to add a fromJson to the type, change httpService.request<List<Room>> to httpService.request<List<dynamic>> and then process the answer from ownRoomsList = response.data; to ownRoomsList = response.data.map((e) => Room.fromJson(e)).toList();

class Room {
  final String name;

  const Room({required this.name});

  factory Room.fromJson(Map<String, dynamic> json) {
    return Room(name: json['name']);
  }
}
Alexey
  • 3,607
  • 8
  • 34
  • 54