You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter基于Retrofit实现登录功能时出现类型转换错误求助:type 'String' is not a subtype of type 'Map<String, dynamic>?' in type cast

解决Flutter Retrofit登录时的类型转换错误

你遇到的DioError [DioErrorType.other]: type 'String' is not a subtype of type 'Map<String, dynamic>?' in type cast错误,核心原因是Retrofit期望解析JSON格式的响应,但实际拿到了字符串类型的响应,或者请求格式不正确导致后端返回了非JSON内容。下面一步步帮你排查和修复:

1. 修复ApiService中的请求格式冲突

你在login方法里同时用了@FormUrlEncoded()和@Body(),这两个注解是冲突的:

  • @FormUrlEncoded()对应表单格式请求(Content-Type: application/x-www-form-urlencoded),应该搭配@FieldMap或@Field传参;
  • @Body()对应JSON格式请求(Content-Type: application/json),不需要@FormUrlEncoded()。

结合你配置的contentType: 'application/json',应该移除@FormUrlEncoded()注解:

@RestApi(baseUrl: '...')
abstract class ApiService {
  factory ApiService(Dio dio, {required String baseUrl}) {
    dio.options = BaseOptions(
        receiveTimeout: 3000,
        connectTimeout: 3000,
        contentType: 'application/json',
        headers: <String, String>{
          'Authorization': 'Basic Y29hY2g6Y29hY2g=',
          'Accept': 'application/json',
        },
        followRedirects: false,
        validateStatus: (status) {
          return status! < 400;
        });
    return _ApiService(dio, baseUrl: baseUrl);
  }

  // 移除@FormUrlEncoded(),保留@Body()传递JSON参数
  @POST('...')
  Future<LoginResponse> login(@Body() Map<String, dynamic> body);
}

如果后端要求表单格式,就保留@FormUrlEncoded(),把@Body()改成@FieldMap():

@POST('...')
@FormUrlEncoded()
Future<LoginResponse> login(@FieldMap() Map<String, dynamic> body);

2. 处理后端可能返回的非JSON响应

如果后端在验证失败时返回纯字符串(比如"用户名或密码错误")而非JSON对象,Retrofit尝试把字符串转成LoginResponse就会触发类型转换错误。你可以调整Dio配置允许所有状态码,然后手动处理错误:

先修改ApiService的validateStatus:

validateStatus: (status) {
  // 允许所有状态码,后续自己判断处理
  return true;
}

然后在AuthProvider的login方法中捕获错误并区分响应类型:

Future<Map<String, dynamic>> login(String username, String password) async {
  var result;
  Map<String, dynamic> loginData = {
    'Username': username,
    'Password': password,
  };
  _loggedInStatus = Status.Authenticating;
  notifyListeners();
  ApiService apiService = ApiService(dio.Dio(), baseUrl: '');
  
  try {
    final response = await apiService.login(loginData);
    print('${response.toJson()}');
    if (response.statusCode == 200) {
      User authUser = User(
        id: response.userEntity.id,
        username: response.userEntity.username,
        role: response.userEntity.role,
      );
      UserPreferences().saveUser(authUser);
      _loggedInStatus = Status.LoggedIn;
      notifyListeners();
      result = {'status': true, 'message': 'Successful', 'user': authUser};
    } else {
      _loggedInStatus = Status.NotLoggedIn;
      notifyListeners();
      result = {'status': false, 'message': response.message};
    }
  } on DioError catch (e) {
    _loggedInStatus = Status.NotLoggedIn;
    notifyListeners();
    // 检查响应体是否为字符串类型
    if (e.response?.data is String) {
      result = {'status': false, 'message': e.response?.data};
    } else {
      result = {'status': false, 'message': '请求失败,请稍后重试'};
    }
    print('登录错误详情: ${e.message}');
  } catch (e) {
    _loggedInStatus = Status.NotLoggedIn;
    notifyListeners();
    result = {'status': false, 'message': '未知错误: $e'};
  }
  return result;
}

3. 修复UserPreferences中的递归死循环

你的saveUser方法里有递归调用,会导致无限循环,赶紧修改:

Future<bool> saveUser(User user) async {
  final SharedPreferences prefs = await SharedPreferences.getInstance();
  prefs.setString('id', user.id as String);
  prefs.setString('username', user.username as String);
  prefs.setString('role', user.role as String);
  prefs.setString('token', user.token as String);
  // 移除递归调用,返回true表示保存成功
  return true;
}

4. 验证后端JSON结构与LoginResponse匹配

确保后端返回的JSON字段和你的LoginResponse完全对应:

  • Status必须是数字类型;
  • Content字段必须是包含id、username、role的对象;
    如果字段名、类型不匹配,也会导致解析失败触发类型转换错误。

内容的提问来源于stack exchange,提问作者kate_3dev

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.27 16:57:39