Problem
I'm trying to receive data from a rest API in my flutter app, inside a future function but i keep getting the following error:
type 'CastList<dynamic, List>' is not a subtype of type 'List'.
The function I'm using to fetch the data is as follows:
static Future<List<Questionnaire>> fetchQuestionnaires() async {
try {
final response =
await http.get('http://10.0.2.2:4010/phone/questionnaires');
if (response.statusCode == 200) {
List<Questionnaire> resp =
json.decode(response.body).cast<List<Questionnaire>>();
List<Questionnaire> questionnaires = resp
.map<Questionnaire>((dynamic item) => Questionnaire.fromJson(item))
.toList();
log(
questionnaires.toString(),
);
return questionnaires;
} else {
throw Exception('Unable to fetch questionnaires');
}
} catch (error) {
log(error.toString());
}
}
I don't understand why this is. Why does using cast, cast to CastList<dynamic, Type> and not the original Type? What changes do I do to get my data?
The data model is given below.
Data Model
The Data i expect from my backend is like this. An array called questionnaires, containing multiples of a questionnaire, each containing an id and a displayQuestion. A displayQuestion in turn has the question text and the answers Array_.
For this, I have the following structure in my Json.
[
{
"questionId": 1,
"displayQuestions": [
{
"question": "how old are you",
"answers": [
"1",
"2",
"3",
"4"
]
}
]
}
]
This is my questionnaires.dart model
class Questionnaires {
List<Questionnaire> _questionnaires;
Questionnaires.fromJson(this._questionnaires);
}
This is questionnaire.dart model
class Questionnaire {
int questionnaireId;
List<DisplayQuestion> displayQuestions = [];
Questionnaire(this.questionnaireId, this.displayQuestions);
Questionnaire.fromJson(Map<String, dynamic> json)
: questionnaireId = json['questionnaireId'],
displayQuestions = json['displayQuestions'];
}
The code from display-question.dart model
class DisplayQuestion {
String _question;
List<String> _answers;
String get question => this._question;
List get answers => this._answers;
DisplayQuestion(this._question, [List<String> answers])
: this._answers = answers ?? [];
DisplayQuestion.fromJson(Map<String, dynamic> json)
: _question = json['question'],
_answers = json['answers'];
}
Why does using cast, cast to CastList<dynamic, Type> and not the original Type?
From the docs, myList.cast<MyType> returns a List<MyType>. In your case you're calling resp.cast<List<Questionnaire>>, so the return will be List<List<Questionnaire>>, which is not what you want.
If you're asking about CastList<dynamic, Type>, it's a subclass of List<Type>, see the source code. It's useful because CastList doesn't need to create a new list, it's just a wrapper around the original list where each element is cast with as Type before being returned.
What changes do I do to get my data?
The problem is you're calling resp.cast<Type> where resp is not a list that constains Type.
Here's a working sample based on the code you provided:
import 'dart:convert';
final sampleJson = """[
{
"questionId": 1,
"displayQuestions": [
{
"question": "how old are you",
"answers": [
"1",
"2",
"3",
"4"
]
}
]
}
]
""";
class DisplayQuestion {
String question;
List<String> answers;
DisplayQuestion.fromJson(Map<String, dynamic> json)
: question = json["question"],
answers = json["answers"].cast<String>();
String toString() => 'question: $question | answers: $answers';
}
class Questionnaire {
int questionnaireId;
List<DisplayQuestion> displayQuestions;
Questionnaire.fromJson(Map<String, dynamic> json)
: questionnaireId = json['questionnaireId'],
displayQuestions = (json['displayQuestions'] as List<dynamic>)
.map((questionJson) => DisplayQuestion.fromJson(questionJson))
.toList();
String toString() => '$displayQuestions';
}
List<Questionnaire> parseQuestionnaires() {
List<Questionnaire> questionnaires =
(json.decode(sampleJson) as List<dynamic>)
.map((questionnaireJson) => Questionnaire.fromJson(questionnaireJson))
.toList();
return questionnaires;
}
void main() {
print(parseQuestionnaires());
// => [[question: how old are you | answers: [1, 2, 3, 4]]]
}
Related
I am writing data to a Hive box in flutter using the following data from an API;
{
"deliveryAddresses": [
{
"deliveryAddressNo": "1130119",
"deliveryAddressName": "AIRCRAFT MOVEMENTS 2169 (EAA)",
"pointOfServices": [
{
"deliveryAddressNo": "1130119",
"pointOfServiceNo": "1",
"pointOfServiceName": "HT54505",
"pointOfServiceDescription": ""
},
{
"deliveryAddressNo": "1130119",
"pointOfServiceNo": "2",
"pointOfServiceName": "WASH BAY",
"pointOfServiceDescription": ""
}
]
},
{
"deliveryAddressNo": "1130147",
"deliveryAddressName": "TESCO - 6144 - HARROW APOLLO",
"pointOfServices": [
{
"deliveryAddressNo": "1130147",
"pointOfServiceNo": "1",
"pointOfServiceName": "ACTE711092",
"pointOfServiceDescription": ""
}
]
}
]
}
The data is showing in the Box as i expect however, the 2 pointOfServices for the first account show in the Box as NULL. The 2nd customers pointOfService gets written OK.
Any ideas? Is it because there's 2 sets of data on the first account?
Edited:
Showing my Model code for deliveryAddresses;
List<Order> orderListFromJson(String val) => List<Order>.from(json
.decode(val)['deliveryAddresses']
.map((val) => Order.orderInfofromJson(val)));
#HiveType(typeId: 0)
class Order extends HiveObject {
#HiveField(0)
String? deliveryAddressNo;
#HiveField(1)
String? deliveryAddressName;
#HiveField(2)
List<PointOfServices>? pointOfServices;
Order(
{this.deliveryAddressNo, this.deliveryAddressName, this.pointOfServices});
factory Order.orderInfofromJson(Map<String, dynamic> deliveryAddresses) =>
Order(
deliveryAddressNo: deliveryAddresses['deliveryAddressNo'],
deliveryAddressName: deliveryAddresses['deliveryAddressName'],
pointOfServices: List<PointOfServices>.from(
deliveryAddresses['pointOfServices']
.map((pos) => PointOfServices.fromJson(pos))));
}
Points of service model;
List<PointOfServices> posListFromJson(String val) =>
List<PointOfServices>.from(json
.decode(val)['pointOfServices']
.map((val) => PointOfServices.fromJson(val)));
#HiveType(typeId: 1)
class PointOfServices {
#HiveField(7)
String? deliveryAddressNo;
#HiveField(8)
String? pointOfServiceNo;
#HiveField(9)
String? pointOfServiceName;
#HiveField(10)
String? pointOfServiceDescription;
PointOfServices(
{this.deliveryAddressNo,
this.pointOfServiceNo,
this.pointOfServiceName,
this.pointOfServiceDescription});
factory PointOfServices.fromJson(Map<String, dynamic> pointOfServices) =>
PointOfServices(
deliveryAddressNo: pointOfServices['deliveryAddressNo'],
pointOfServiceNo: pointOfServices['pointOfServiceNo'],
pointOfServiceName: pointOfServices['pointOfServiceName'],
pointOfServiceDescription:
pointOfServices['pointOfServiceDescription']);
}
Code that builds the the data and adds to the box;
if (!Hive.isBoxOpen(orderInfoBox)) {
_orderInfo = await Hive.openBox<Order>('orderInfo_db');
_orderInfo.clear();
var result = await OrderNetwork().get();
var resultBody = await json.decode(json.encode(result.body));
List<Order> orderList = List<Order>.empty(growable: true);
orderList.addAll(orderListFromJson(resultBody));
_orderInfo.addAll(orderList);
Example Code (Not working):
void main() {
List json = [
{
"sku": "SKU0001",
"uids": [
{
"uid": "U001"
}
]
}
];
var result = json.map( (item) {
item['no_desc'] = true; // able to add the key and bool value
item['uids'] = item['uids'].map( (uid) {
uid['is_scanned'] = true; // failed to add, but able to accept string value only.
return uid;
}).toList();
return item;
}).toList();
print(result);
}
return error 'bool' is not a subtype of type 'String'
Expected Result
[
{
sku: SKU0001,
no_desc: true,
uids: [
{
uid: U001,
is_scanned: true // must be boolean
}
]
}
]
It work when i try in String value.
uid['is_scanned'] = 'true';
How can I add bool value into the nested list?
If I am writing in javascript way,
it should be able to add the key into nested array ( list ).
But why in dart lang, it prompt me error?
Can someone expert in dart lang willing to explain to me?
Your issue can be fixed by creating a new Map instance in the inner .map method with the Map.from constructor.
item['uids'] = item['uids'].map( (uid) {
uid['is_scanned'] = true; // failed to add, but able to accept string value only.
return uid;
}).toList();
should be changed to:
item['uids'] = item['uids'].map( (uid) {
Map<String, dynamic> toReturn = Map.from(uid);
toReturn['is_scanned'] = true;
return toReturn;
}).toList();
I believe this issue is due to dart implicitly declaring the inner uids maps as Map<String, String> so creating a new instance changes this and allows you to assign any value to the keys of the Map.
Full working sample:
void main() {
List json = [
{
"sku": "SKU0001",
"uids": [
{
"uid": "U001"
}
]
}
];
var result = json.map( (item) {
item['no_desc'] = true;
item['uids'] = item['uids'].map( (uid) {
Map<String, dynamic> toReturn = Map.from(uid);
toReturn['is_scanned'] = true;
return toReturn;
}).toList();
return item;
}).toList();
print(result);
}
I couldn't find a way to store the Type value in Map so that I could use it in is operator to check the validity of type using this map later on. Also, can is operator accept Type as a variable?
For eg, Below is hypothetical code solving the problem but it's invalid.
Map<String, Type> map = {
"sku": String,
"price": double,
"quantity": int,
};
dynamic value = 10;
if(value is map["quantity"]){
print("value is of type int and int is expected for quantity value");
}
You can do something like this:
class TypeCheck<T> {
const TypeCheck();
bool typeCheck(dynamic value) => value is T;
}
void main() {
Map<String, TypeCheck> map = {
"sku": TypeCheck<String>(),
"price": TypeCheck<double>(),
"quantity": TypeCheck<int>(),
};
dynamic value = 10;
if (map["quantity"]!.typeCheck(value)) {
print("value is of type int and int is expected for quantity value");
}
}
Im not sure I fully understand I understand what you are trying to do but why don't you try something like.
bool _validate(Map productDetails){
if (productDetails.containsKey("sold_individually") && productDetails["sold_individually"] is bool) {
//return true or false
}
else if (productDetails.containsKey("stock_quantity") && productDetails["stock_quantity"] is int){
//return true or false
}
else if (productDetails.containsKey("tax_class") && productDetails["tax_class"] is String && productDetails["tax_class"].isNotEmpty) {
//return true or false
} else {
//return true or false
}
}
As for the other part of your question you wont get an error but you will always return false. In contrast if you check if a variable is dynamic it will always return true.
I don't really understand your end goal. But from what you have, I don't think you are taking advantage of the strongly-typed nature of dart.
Assuming you are getting your map from an API, you could enforce
typing manually in your code as follows;
Map<String, Type> map = {
"sku": json['key'] as String,
"price": json['key'] as double,
"quantity": json['key'] as int,
};
And avoid using dynamic when declaring variables.
OR
In the case you have a user-defined type you what to compare, you can use the equatable package on a class for instance as follows;
class CustomMap extends Equatable {
String sky;
double price;
int quantity;
// here you put the fields of a class you want for two instances of a class to be equal.
#overide
List<Object> get props => [sky, price, quantity];
}
Update from your comment
You should have a custom class for the API objects for instance;
class Item extends Equatable {
String sku;
double price;
int quantity;
Item({this.sky, this.price, this.quantity});
// factory constructor
factory Item.fromMap(Map<String, dynmic> json) {
final sku = json['sku'] as String,
final price = (json['price'] as num) as double,
final quantity = json['quantity'] as num,
return Item(sku: sku, price: price, quantity: quantity);
}
// define equatable objects
#override
List<Object> get props => [sku, price, quantity];
}
Now you can use it as follows;
Future<Item> objectsFromService(Map<String, dynamic> json ) async {
http.Response response = http.get(url);
if(response.status == 200) {
final decodedJson = json.decode(response.body);
return Item.fromJson(decodedJson);
}else{
print('Error fetch data');
return null;
}
}
Hope it helps
My problem is that I have a list of the following class:
class ingreso_Egreso_Dummy{
int tipo;
String monto;
String moneda;
String descripcion;
}
then I want to insert the data into a Map which later will be converted into a json and which I am creating like this:
Map<String, dynamic> body;
body = {
"Cod_Prom": "01",
"CodCli": "003526",
"Status": _index_status.toString(),
"NOMBRE": controller_nombre.text,
"APELLIDOS": controller_apellidos.text,
"solicitud":[{
"Cod_Solicit": 1.toString(),
"Fecha": DateFormat("y-d-M").format(DateTime.now()),
"Status_Solicit": "E",}],
"prestamo":[{
"Monto_Solicit":controller_monto_solic.text,
"Plazo":controller_plazo.text,
"Cod_TipoPlazo":_index_tipoplazo.toString(),
"Nombre_Resp":controller_nombreresp.text,
"Telf_Resp":controller_telefonoresp.text,}],
"Ingresos": [{
//// here I want create a loop that returns a map for each value
//// of the list like this:
//// "Descripcion": Listaingresos[i].descripcion;
})
}]
};
Every help is very appreciated, thank you.
// camelCaseStyle is a standard for class names for Dart
class IngresoEgresoDummy {
int tipo;
String monto;
String moneda;
String descripcion;
Map<String, dynamic> toJson(){
return {
'tipo': tipo,
'monto': monto,
'monedo': moneda,
'descripcion': descripcion
};
}
}
and after that
List<IngresoEgresoDummy> listaingresos= List();
Map<String, dynamic> body = {
// all your params
"Ingresos": listaingresos.map((ingreso) => ingreso.toJson()).toList()
// all your params
};
Let's say I have a JSON array like this:
"videos": [
{
"id": 25182,
"game": 115653,
"name": "Trailer",
"video_id": "BdA22Lh6Rwk"
},
27749,
{
"id": 29188,
"game": 115653,
"name": "A New Team and New Rivals in Pokémon Sword and Pokémon Shield! ⚔️🛡️",
"video_id": "ZBiTpi8ecTE"
}
]
Normally if the item's JSON format in videos is like videos[0] or videos[2] then I was able to parse it to Video like this:
json['videos']?.cast<Map<String, dynamic>>()?.map<Video>((f) {
return Video.fromJson(f);
})?.toList();
My Video class:
class Video {
int id;
int game;
String name;
String videoId;
Video({this.id, this.game, this.name, this.videoId});
Video.fromJson(Map<String, dynamic> json) {
id = json['id'];
game = json['game'];
name = json['name'];
videoId = json['video_id'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['id'] = this.id;
data['game'] = this.game;
data['name'] = this.name;
data['video_id'] = this.videoId;
return data;
}
}
But if something with the different structure like videos[1] is within the array then I ended up with Exception. How can I parse videos[1] to Video with video[1] as Video's id?
You have to know the different formats and figure out which one each entry is.
You can do that by checking the type of the entry: Is it an integer or a map?
Example:
List<Video> videosFromJson(List<Object> videoJson) {
var result = <Video>[];
for (int i = 0; i < videoJson.length; i++) {
var entry = videoJson[i];
if (entry is Map<String, dynamic>) {
result.add(Video.fromJson(entry));
} else if (entry is int) {
result.add(Video()..id = entry);
} else {
throw FormatException("Not a recognized video format", entry, i);
}
}
return result;
}