kate_3dev
kate_3dev

Reputation: 25

Error in Flutter: type 'string' is not a subtype of type 'map<string, dynamic>?' in type cast

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

Answers (1)

Pom12
Pom12

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 :

  1. 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.

  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

Related Questions