flutterretrofitdiojson-annotation

flutter dio is returning DioErrorType.unknown


I'm using clean architecture on a project and using dio as http package and I've added pretty DioLogger as a dio inceptor to log requests and response.

the pretty dio logger is logging a reponse of 200 ok but I'm getting instead of a correct dio response I'm getting dioErrorType.unknown and I'm unable to reach the actual response, what could be the cause of this error?

I've made a test project with only the faulty code, here's the dio factory class:

import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart';
import 'package:pretty_dio_logger/pretty_dio_logger.dart';

class DioFactory {
  Dio getDio() {
    Dio dio = Dio();

    Map<String, String> headers = {
      'content-type': 'application/json',
      'accept': 'application/json',
    };
    dio.options = BaseOptions(
      baseUrl: 'https://1rkj9.wiremockapi.cloud/',
      headers: headers,
      sendTimeout: const Duration(milliseconds: 60 * 1000),
      receiveTimeout: const Duration(milliseconds: 60 * 1000),
    );

    if (!kReleaseMode) {
      dio.interceptors.add(
        PrettyDioLogger(
          requestHeader: true,
          requestBody: true,
          responseHeader: true,
        ),
      );
    }
    return dio;
  }
}

I've used retrofit generator to generate the request, here is my app client class:

import 'package:dio/dio.dart';
import 'package:retrofit/retrofit.dart';

import '../responses/responses.dart';

part 'app_api.g.dart';

@RestApi(baseUrl: 'https://1rkj9.wiremockapi.cloud/')
abstract class AppServiceClient {
  factory AppServiceClient({required Dio dio, String? baseURL}) =>
      _AppServiceClient(dio);

  @POST('/customer/forgotPassword')
  Future<ForgotPasswordResponse> forgotPassword(@Field('email') String email);
}

and here's the generated app:

// GENERATED CODE - DO NOT MODIFY BY HAND

part of 'app_api.dart';

// **************************************************************************
// RetrofitGenerator
// **************************************************************************

// ignore_for_file: unnecessary_brace_in_string_interps,no_leading_underscores_for_local_identifiers

class _AppServiceClient implements AppServiceClient {
  _AppServiceClient(
    this._dio, {
    this.baseUrl,
  }) {
    baseUrl ??= 'https://1rkj9.wiremockapi.cloud/';
  }

  final Dio _dio;

  String? baseUrl;

  @override
  Future<ForgotPasswordResponse> forgotPassword(String email) async {
    const _extra = <String, dynamic>{};
    final queryParameters = <String, dynamic>{};
    final _headers = <String, dynamic>{};
    final _data = {'email': email};
    final _result = await _dio.fetch<Map<String, dynamic>>(
        _setStreamType<ForgotPasswordResponse>(Options(
      method: 'POST',
      headers: _headers,
      extra: _extra,
    )
            .compose(
              _dio.options,
              '/customer/forgotPassword',
              queryParameters: queryParameters,
              data: _data,
            )
            .copyWith(baseUrl: baseUrl ?? _dio.options.baseUrl)));
    final value = ForgotPasswordResponse.fromJson(_result.data!);
    return value;
  }

  RequestOptions _setStreamType<T>(RequestOptions requestOptions) {
    if (T != dynamic &&
        !(requestOptions.responseType == ResponseType.bytes ||
            requestOptions.responseType == ResponseType.stream)) {
      if (T == String) {
        requestOptions.responseType = ResponseType.plain;
      } else {
        requestOptions.responseType = ResponseType.json;
      }
    }
    return requestOptions;
  }
}

I've used json_annotation package to generate the repsonses, here's the response file:

import 'package:json_annotation/json_annotation.dart';

part 'responses.g.dart';

@JsonSerializable()
class BaseResponse {
  @JsonKey(name: "status")
  int? status;

  @JsonKey(name: "message")
  String? message;
}

@JsonSerializable()
class ForgotPasswordResponse extends BaseResponse {
  @JsonKey(name: "support")
  String? support;

  ForgotPasswordResponse({required this.support});

  factory ForgotPasswordResponse.fromJson(Map<String, dynamic> json) =>
      _$ForgotPasswordResponseFromJson(json);

  Map<String, dynamic> toJson() => _$ForgotPasswordResponseToJson(this);
}

and here's the generated response file:

// GENERATED CODE - DO NOT MODIFY BY HAND

part of 'responses.dart';

// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************

BaseResponse _$BaseResponseFromJson(Map<String, dynamic> json) => BaseResponse()
  ..status = json['status'] as int?
  ..message = json['message'] as String?;

Map<String, dynamic> _$BaseResponseToJson(BaseResponse instance) =>
    <String, dynamic>{
      'status': instance.status,
      'message': instance.message,
    };

ForgotPasswordResponse _$ForgotPasswordResponseFromJson(
        Map<String, dynamic> json) =>
    ForgotPasswordResponse(
      support: json['support'] as String?,
    )
      ..status = json['status'] as int?
      ..message = json['message'] as String?;

Map<String, dynamic> _$ForgotPasswordResponseToJson(
        ForgotPasswordResponse instance) =>
    <String, dynamic>{
      'status': instance.status,
      'message': instance.message,
      'support': instance.support,
    };

and finally this is the main file:

import 'dart:developer';

import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:testing_app/network/app_api.dart';

import 'network/dio_factory.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  final AppServiceClient appServiceClient =
      AppServiceClient(dio: DioFactory().getDio());

  MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
          appBar: AppBar(
            title: Text('test app'),
          ),
          body: Center(
            child: TextButton(
              onPressed: () async {
                try {
                  final response =
                      await appServiceClient.forgotPassword('abc@gmail.com');
                  log('response code: ${response.status}');
                } catch (error) {
                  if (error is DioError) {
                    log('error type: ${error.type}');
                  }
                }
              },
              child: Text('start dio post request'),
            ),
          )),
    );
  }
}


Solution

  • The problem was that I didn't add "Content-Type":"application/json" in the header of the response in the mockAPI that I'm using