Home > Enterprise >  Issue parsing data from API using http
Issue parsing data from API using http

Time:11-02

I am trying to fetch data from a third-party API(https://randomuser.me/api/?results=50) and have hit the following error:

E/flutter (18902): [ERROR:flutter/lib/ui/ui_dart_state.cc(199)] Unhandled Exception: type 'String' is not a subtype of type 'int' of 'index'
E/flutter (18902): #0      ProfileProvider.fetchData.<anonymous closure>.<anonymous closure> (package:profile_app/provider/data.dart:54:32)
E/flutter (18902): #1      _LinkedHashMapMixin.putIfAbsent (dart:collection-patch/compact_hash.dart:311:23)
E/flutter (18902): #2      ProfileProvider.fetchData.<anonymous closure> (package:profile_app/provider/data.dart:52:13)
E/flutter (18902): #3      _LinkedHashMapMixin.forEach (dart:collection-patch/compact_hash.dart:397:8)
E/flutter (18902): #4      ProfileProvider.fetchData (package:profile_app/provider/data.dart:51:19)
E/flutter (18902): <asynchronous suspension>
E/flutter (18902): 

The lines where the errors occur are commented on my code below:

import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
import 'dart:core';

class Profile {
  final String userName;
  final String name;
  final String emailId;
  final int timePeriod;
  final int age;
  final String nationality;
  final String number;
  final int streetNumber;
  final String streetName;
  final String city;
  final String country;
  final int postCode;
  final String picture;

  Profile({
    required this.userName,
    required this.name,
    required this.emailId,
    required this.timePeriod,
    required this.age,
    required this.nationality,
    required this.number,
    required this.streetNumber,
    required this.streetName,
    required this.city,
    required this.country,
    required this.postCode,
    required this.picture
  });
}

class ProfileProvider with ChangeNotifier {
  Map<String, Profile> _data = {};

  Map<String, Profile> get data {
    return {..._data};
  }

  Future<void> fetchData() async {
    final url = Uri.parse('https://randomuser.me/api/?results=50');
    final response = await http.get(url);
    final extractedData = json.decode(response.body);
    print(extractedData);
    Map<String, Profile> map = {};
    extractedData.forEach((key, value) =>           //51:19
        map.putIfAbsent(key, () =>                  //52:13
            Profile(
                userName: value['results'].login.username,  //54:32
                name: value['results'].name.first,
                emailId: value['results'].email,
                timePeriod: value['results'].registered.age,
                age: value['results'].dob.age,
                nationality: value['results'].nat,
                number: value['results'].cell,
                streetNumber: value['results'].location.street.number,
                streetName: value['results'].location.street.name,
                city: value['results'].location.city,
                country: value['results'].location.country,
                postCode: value['results'].location.postcode,
                picture: value['results'].picture.large
            )
        )
    );
    _data = map;
    notifyListeners();
  }
}

Initially, I thought it was owing to me not correctly defining the data types of the following values: postCode and timePeriod which were both defined as integers in the JSON response. But correcting those didn't help either. The API call seems to work correctly as it doesn't hesitate to send the response which is as below:

    {
"results": [
  {
    "gender": "male",
    "name": {
      "title": "Mr",
      "first": "André",
      "last": "Brunner"
  },
  "location": {
    "street": {
    "number": 7771,
    "name": "Marktplatz"
  },
  "city": "Hatzfeld (Eder)",
  "state": "Bayern",
  "country": "Germany",
  "postcode": 39781,
  "coordinates": {
    "latitude": "-44.0823",
    "longitude": "-161.4976"
  },
  "timezone": {
    "offset": " 8:00",
    "description": "Beijing, Perth, Singapore, Hong Kong"
  }
},
  "email": "[email protected]",
  "login": {
    "uuid": "01dca515-c167-4eeb-a217-60e5178010c8",
    "username": "bigladybug476",
    "password": "buck",
    "salt": "WhBB53R6",
    "md5": "f0594b35b2b2baf0c434546e64f0fa05",
    "sha1": "db3586cac4b75d2d0c31ad5b6af6061b07aa1b60",
    "sha256": "a7225266f739056b352ce001bd76746c60f6e1f0c9955fc983bee3c9f949d8e9"
},
  "dob": {
    "date": "1946-11-28T06:55:28.967Z",
    "age": 75
},
  "registered": {
    "date": "2018-05-26T04:04:45.356Z",
    "age": 3
},
  "phone": "0145-0007517",
  "cell": "0170-7712395",
  "id": {
    "name": "",
    "value": null
},
  "picture": {
    "large": "https://randomuser.me/api/portraits/men/31.jpg",
    "medium": "https://randomuser.me/api/portraits/med/men/31.jpg",
    "thumbnail": "https://randomuser.me/api/portraits/thumb/men/31.jpg"
},
"nat": "DE"
}
}

CodePudding user response:

It is a bit hard to explain what the problem is, because the code is a bit confusing.

First; you have this line:

final extractedData = json.decode(response.body);

So let's remember that extractedData is a Map<String, dynamic> which contains the json response.

So then:

extractedData.forEach((key, value) => ...

That is the interesting part, for each key in your JSON response, you are running some code with the key and the value. But here is where it gets confusing because the response that you posted is definitely wrong, it is not valid JSON, so I can't tell you exactly how this code will run, but I can tell you one thing:

The first time that code runs, the key will be equal to 'results' and value will be a list with a bunch of objects

That is because 'results' is the first key in the JSON response.

So when you do this:

userName: value['results'].login.username,  

value is a list, but you are trying to access a key as if it was a map, so you get an error.

I'm not sure what the solution is because I don't know what you expected this code to do.

There is also another error in your code

You can't access a map's values through the dot operator, you need to use [], so even if you properly read value['results'] calling .login will throw an error

so instead of value['results'].login.username it should be value['results']['login']['username']

CodePudding user response:

I suggest you use the package json_serializable to help you create the method to convert from JSON and to convert to JSON for your model classes.

In your case, you would have the method:

factory Profile.fromJson(Map<String, dynamic> json) => Profile(
      username: json['username'] as String? ?? '',
      name: json['name'] as String? ?? '',
      emailId: json['emailId'] as String? ?? '',
      ...
    );

Then in your fetchData() method you can do this:

final jsonMap = jsonDecode(response.body) as Map<String, dynamic>;
final profile = Profile.fromJson(jsonMap);

Also you can use this link: JSON to Dart Converter to help you generate the Dart class from your JSON response string.

  • Related