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
相关产品推荐
相关产品推荐

