Reputation: 25
I'm trying to built an app for my college degree with flutter that has a login screen where you insert the username and password and pass to the main screen. I use retrofit for the REST API. When I press the login button with the credentials I get the error: Exception: DioError [DioErrorType.other]: type 'String' is not a subtype of type 'Map<String, dynamic>?' in type cast.
I'm very new in Flutter can you help me? Here is my code:
Api_Service
@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);
}
//Login Service
@POST('...')
@FormUrlEncoded()
Future<LoginResponse> login(@Body() Map<String, dynamic> body);
Api_Response
@JsonSerializable()
class LoginResponse {
//show login response data
@JsonKey(name: 'Status')
final int statusCode;
@JsonKey(name: 'Message')
final String message;
@JsonKey(name: 'Content')
final UserEntity userEntity;
LoginResponse(this.statusCode, this.message, this.userEntity);
factory LoginResponse.fromJson(Map<String, dynamic> json) =>
_$LoginResponseFromJson(json);
Map<String, dynamic> toJson() => _$LoginResponseToJson(this);
}
User_entity
import 'package:json_annotation/json_annotation.dart';
part 'user_entity.g.dart';
//done this file
@JsonSerializable()
class UserEntity {
@JsonKey(name: 'id')
final String id;
@JsonKey(name: 'username')
final String username;
@JsonKey(name: 'role')
final String role;
UserEntity(this.id, this.username, this.role);
factory UserEntity.fromJson(Map<String, dynamic> json) =>
_$UserEntityFromJson(json);
Map<String, dynamic> toJson() => _$UserEntityToJson(this);
}
User
class User {
String? id;
String? username;
String? role;
String? token;
String? renewalToken;
User({this.id, this.username, this.role, this.token, this.renewalToken});
factory User.fromJson(Map<String, dynamic> responseData) {
return User(
id: responseData['id'],
username: responseData['username'],
role: responseData['role'],
token: responseData['token'],
renewalToken: responseData['token'],
);
}
User_provider
class UserProvider extends ChangeNotifier {
User _user = User();
User get user => _user;
void setUser(User? user) {
_user = user!;
notifyListeners();
}
}
Auth_provider
enum Status { NotLoggedIn, LoggedIn, Authenticating, LoggedOut }
class AuthProvider extends ChangeNotifier {
Status _loggedInStatus = Status.NotLoggedIn;
Status get loggedInStatus => _loggedInStatus;
set loggedInStatus(Status value) {
_loggedInStatus = value;
}
static Future<FutureOr> onValue(Response response) async {
var result;
final Map<String, dynamic> responseData = json.decode(response.body);
print(responseData);
if (response.statusCode == 200) {
// now we will create a user model
User authUser = User.fromJson(responseData);
// now we will create shared preferences and save data
UserPreferences().saveUser(authUser);
result = {
'status': true,
'message': 'Successfully registered',
'data': authUser
};
} else {
result = {
'status': false,
'message': 'Successfully registered',
'data': responseData
};
}
return result;
}
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: '');
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': ''};
}
return result;
}
onError(error) {
print('the error is ${error.detail}');
return {'status': false, 'message': 'Unsuccessful Request', 'data': error};
}
}
Main
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
Future<User> getUserData() => UserPreferences().getUser();
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => AuthProvider()),
ChangeNotifierProvider(create: (_) => UserProvider())
],
child: MaterialApp(
theme: ThemeData(
backgroundColor: Color(0Xfff7f7f5),
fontFamily: 'Cera',
appBarTheme: AppBarTheme(
backgroundColor: Colors.white,
),
),
debugShowCheckedModeBanner: false,
// home: LoginScreen(),
home: FutureBuilder<User>(
future: getUserData(),
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
case ConnectionState.waiting:
return CircularProgressIndicator();
default:
if (snapshot.hasError)
return Text('Error: ${snapshot.error}');
else if (snapshot.data!.token == null) {
return LoginScreen();
} else
Provider.of<UserProvider>(context).setUser(snapshot.data);
return TeamsScreen();
}
}),
routes: {
'/auth': (context) => LoginScreen(),
'/teams': (context) => TeamsScreen(),
},
));
}
}
shared_preferences
class UserPreferences {
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);
return saveUser(user);
}
Future<User> getUser() async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
String id = prefs.getString("id") ?? '';
String username = prefs.getString("username") ?? '';
String role = prefs.getString("role") ?? '';
String token = prefs.getString("token") ?? '';
String renewalToken = prefs.getString("renewalToken") ?? '';
return User(
id: id,
username: username,
role: role,
token: token,
renewalToken: renewalToken);
}
void removeUser() async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.remove('id');
prefs.remove('username');
prefs.remove('role');
prefs.remove('token');
}
Future<String?> getToken() async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
String? token = prefs.getString("token");
return token;
}
}
Upvotes: 0
Views: 11537
Reputation: 7880
That's because the server is returning a json string with a different encoding than application/json
. Check your response headers and you should probably see that the Content-Type of your response is not application/json
but something else. I had the error when getting a GitLab snippet that was not returned as actual json but as a string.
You have two solutions here :
Ask your server for application/json
Content-Type, and if he's a nice server he will comply to your request and return proper json. You can do that by setting HTTP headers either in Dio global options or for a specific request.
In your case it seems you already have tried this option but the server didn't want to comply to your request, so you should probably try option 2.
Add a Dio interceptor that will do the conversion from a String
to a Map<dynamic, String>
before it's actually treated by Retrofit generated code :
final dio = Dio()
..interceptors.add(
InterceptorsWrapper(
onResponse: (response, handler) {
if (response.requestOptions.method == HttpMethod.GET) {
response.data = jsonDecode(response.data as String);
}
return handler.next(response);
},
),
);
Upvotes: 4