I have a flutter application, which uses some server APIs.
When there is a successful response from the server, it would return json back:
{"success": true, "message": "success", "data": {"id": "21EE"} }
However, when there is failure, it would return:
{"success": false, "message": "failure"}
For more strict-typed use of flutter, I try to model the response.
Here is my try:
class ServerResponse {
final bool success;
final String message;
ServerResponse({
required this.success,
required this.message,
});
}
class _AuthAutenticationData {
final String id;
_AuthAutenticationData({
required this.id,
});
}
class AutoAuthenticateResponse extends ServerResponse {
final _AuthAutenticationData? data;
AutoAuthenticateResponse({
required success,
required message,
this.data,
}) : super(success: success, message: message);
}
Now, I have a function which calls a specific API:
Future<void> autoAuth() async {
final url = Uri.parse('${this._baseURL.toString()}/auto-auth');
try {
final response = await http.post(url, headers: {
'Authorization': 'SXXX',
});
print(response.body);
final AutoAuthenticateResponse responseBody = json.decode(response.body);
if (responseBody.success) {
return setUser(new User(id: responseBody.data!.id));
}
setUser(null);
} catch (error) {
print(error);
setUser(null);
}
}
Some code is irrelevant, but bottom line is that I receive the following error in the line: final AutoAuthenticateResponse responseBody = json.decode(response.body);:
I/flutter (14139): type '_InternalLinkedHashMap<String, dynamic>' is not a subtype of type 'AutoAuthenticateResponse'
I guess my solution is bad. Any advice how to fix it?
Well, you can use nullsafety feature for this. Since it's only if when the failure the data is not being returned.
{"success": true, "message": "success", "data": {"id": "21EE"} }
you can use this :
https://meruya-techhnology.github.io/json-to-dart/
Then the result will be a 2 class
class YourClass {
final bool success;
final String message;
final Data? data;
YourClass({this.success, this.message, this.data});
factory YourClass.fromJson(Map<String, dynamic> json) => YourClass(
success : json['success'],
message : json['message'],
data : json['data'] != null ? new Data.fromJson(json['data']) : null;
);
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['success'] = this.success;
data['message'] = this.message;
if (this.data != null) {
data['data'] = this.data.toJson();
}
return data;
}
}
And
class Data {
String id;
Data({this.id});
Data.fromJson(Map<String, dynamic> json) {
id = json['id'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['id'] = this.id;
return data;
}
}
Then you can use it like this :
Future<void> autoAuth() async {
final url = Uri.parse('${this._baseURL.toString()}/auto-auth');
try {
final response = await http.post(url, headers: {
'Authorization': 'SXXX',
});
debugPrint(response.body);
final responseBody = YourClass.fromJson(response.body)
if (responseBody.success) {
return setUser(new User(id: responseBody.data!.id));
}
setUser(null);
} catch (error) {
print(error);
setUser(null);
}
}
another advice is use debugPrint instead of print, here you can read more : https://medium.com/flutter-community/debugprint-and-the-power-of-hiding-and-customizing-your-logs-in-dart-86881df05929
Use this link to generate models for your response
Related
I still like to call myself a novice when it comes to parsing a JSON response into a model class for usage. I have a certain JSON response which I have converted into a Model using one of those websites that does all the hard work. However, I repeatedly keep getting errors saying type 'LoginModel' is not a subtype of type 'Map<String, dynamic>' when I make the API Call. Here is the response followed by the code:
The JSON response:
{
"data": {
"access_token": "*********",
"role": 0,
"id": 000,
"lat": "0.0",
"lng": "0.0",
"radius": 200,
"department": "IIIII",
"approval": 1
}
}
This here is the Model class:
class LoginModel {
Data? data;
LoginModel({this.data});
LoginModel.fromJson(Map<String, dynamic> json) {
data = json['data'] != null ? Data.fromJson(json['data']) : null;
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
if (this.data != null) {
data['data'] = this.data!.toJson();
}
return data;
}
}
class Data {
String? accessToken;
int? role;
int? id;
String? lat;
String? lng;
int? radius;
String? department;
int? approval;
Data(
{this.accessToken,
this.role,
this.id,
this.lat,
this.lng,
this.radius,
this.department,
this.approval});
Data.fromJson(Map<String, dynamic> json) {
accessToken = json['access_token'];
role = json['role'];
id = json['id'];
lat = json['lat'];
lng = json['lng'];
radius = json['radius'];
department = json['department'];
approval = json['approval'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['access_token'] = this.accessToken;
data['role'] = this.role;
data['id'] = this.id;
data['lat'] = this.lat;
data['lng'] = this.lng;
data['radius'] = this.radius;
data['department'] = this.department;
data['approval'] = this.approval;
return data;
}
}
The class where I get the error:
class LoginController with ChangeNotifier {
NetworkServices networkServices = NetworkServices();
Map<String, dynamic> _loginResponse = {}; //I would like to store the parsed response into this variable for local use
Map<String, dynamic> get loginResponse {
return _loginResponse;
}
Future<void> login(dynamic data, BuildContext context) async {
networkServices
.postLoginRequest(ApiUrl.loginUrl, data, context)
.then((value) {
_loginResponse =
LoginModel.fromJson(value['data']) as Map<String, dynamic>; //I'm pretty sure this is what I've done wrong
print('SUCCESS: $_loginResponse');
}).onError((error, stackTrace) {
// Loader(false);
print('ERRROR: $error'); //This is where the error gets printed after it comes from the NetworkServices class where the API Call is made
});
notifyListeners();
}
}
Network Class where the API gets called:
class NetworkServices {
Future<dynamic> postLoginRequest(
String url, dynamic data, BuildContext context) async {
var jsonResponse;
try {
final response = await http.post(Uri.parse(url),
body: json.encode(data),
headers: {
'Content-Type': 'application/json'
}).timeout(const Duration(seconds: 30));
jsonResponse = returnResponse(response);
print('FROM NETWORK: $jsonResponse');
} on SocketException {
Flushbar(
leftBarIndicatorColor: Colors.red,
icon: const Icon(Icons.warning, color: Colors.white),
message: 'Ooops!!! Something went wrong',
).show(context);
}
return jsonResponse;
}
dynamic returnResponse(http.Response response) {
if (response.statusCode >= 200 && response.statusCode <= 300) {
dynamic jsonResponse = json.decode(response.body);
return jsonResponse;
} else if (response.statusCode >= 400 || response.statusCode == 404) {
return 'An Error Occured';
} else if (response.statusCode >= 404) {
return 'Invalid Request';
} else {
return 'Error Occured While Communicating with Servers!!! Please try again later';
}
}
}
Problems:
You are casting the LoginModel object returned from LoginModel.fromJson(...) as a Map<String, dynamic> instead of casting value['data'] as Map<String, dynamic>.
You are also assigning the LoginModel.fromJson(...) to a variable of type Map<String, dynamic>, _loginResponse.
Solution:
Change this line of code:
_loginResponse = LoginModel.fromJson(value['data']) as Map<String, dynamic>;
to this:
_loginResponse = value['data'] as Map<String, dynamic>;
Update:
In other to store the login information in the LoginModel, you need to make the following to the LoginController class:
Make the _loginResponse variable a LoginModel instead of a Map<String, dynamic>
Make the loginResponse getter return a LoginModel object instead of a Map<String, dynamic>
Cast value['data'] to a Map<String, dynamic> and pass it into LoginModel.fromJson(...)
Your updated LoginController class will be:
class LoginController with ChangeNotifier {
NetworkServices networkServices = NetworkServices();
LoginModel _loginResponse = LoginModel();
LoginModel get loginResponse {
return _loginResponse;
}
Future<void> login(dynamic data, BuildContext context) async {
networkServices
.postLoginRequest(ApiUrl.loginUrl, data, context)
.then((value) {
_loginResponse =
LoginModel.fromJson(value['data'] as Map<String, dynamic>);
print('SUCCESS: $_loginResponse');
}).onError((error, stackTrace) {
// Loader(false);
print('ERRROR: $error');
});
notifyListeners();
}
}
I am uploading images to a storageAPI using POST method with content-type of multipart/form-data. The api returns an object response that looks as below:
{
"id": "6d50c066-cf65-4748-8b9a-183c3526f49b",
"name": "hotel_6.jpg",
"fileKey": "lv/im/5d9feb8e-2ea8-439d-a550-1e937081e085-hotel_6.jpg",
"fileExtension": ".jpg",
"mimeType": "image/jpeg",
"catalogueUrl": {
"mainUrl": "https://xy.abc.com/lv/im/5d9feb8e-2ea8-439d-a550-1e937081e085-hotel_6.jpg",
"thumbnailUrls": []
},
"createdAt": "2021-11-25T06:40:40.0869466+00:00"
}
How can I extract the variable "mainUrl" from the response so that I can assign its value to the _pictureController? Here is what I have done:
uploadFile() async {
var accessToken = await sharedPref.read(key);
var postUrl = '$baseUrl/catalogue?thumbnail=${param.thumbnailTrueFalse}';
Map < String, String > headers = {
"Authorization": "Bearer $accessToken",
};
// multipart request object
var request = http.MultipartRequest("POST", Uri.parse(postUrl));
request.headers.addAll(headers);
// add selected file with request
request.files.add(http.MultipartFile("file", imageStream, imageSize,
filename: imageName));
// Send request
var response = await request.send();
// Read response
var result = await response.stream.bytesToString();
print('readResponse: $result');
if (response.statusCode == 200) {
var data = StorageResponse.fromJson(jsonDecode(result));
print('data: $data');
setState(() {
_pictureController.text = data.catalogueUrl!.mainUrl!;
});
return data;
} else {
throw Exception('Failed to upload photo.');
}
}
The "StorageResponse" Class is as follows:
#JsonSerializable()
class StorageResponse {
var id;
var name;
var fileKey;
var fileExtension;
var mimeType;
Catalogue ? catalogueUrl;
var createdAt;
StorageResponse({
this.id,
this.name,
this.fileKey,
this.fileExtension,
this.mimeType,
this.catalogueUrl,
this.createdAt,
});
factory StorageResponse.fromJson(Map < String, dynamic > json) =>
_$StorageResponseFromJson(json);
Map < String, dynamic > toJson() => _$StorageResponseToJson(this);
#override
toString() {
String output =
'{id:${this.id},name:${this.name},fileKey: ${this.fileKey},fileExtension:${this.fileExtension},mimeType: ${this.mimeType}mimeType},catalogueUrl: ${this.catalogueUrl},,createdAt: ${this.createdAt}}';
return output;
}
}
You can use the following structure to convert a Json file to a class, and vice versa.
The following structure works properly.
import 'dart:convert';
class StorageResponse {
final String id;
final String name;
final String fileKey;
final String fileExtension;
final String mimeType;
Catalogue catalogueUrl;
final DateTime createdAt;
StorageResponse(
this.id,
this.name,
this.fileKey,
this.fileExtension,
this.mimeType,
this.catalogueUrl,
this.createdAt,
);
factory StorageResponse.fromMap(Map<String, dynamic> json) {
return StorageResponse(
json['id'],
json['name'],
json['fileKey'],
json['fileExtension'],
json['mimeType'],
Catalogue.fromMap(json['Catalogue']),
DateTime.parse(json['createdAt']));
}
Map<String, dynamic> toJson() => {
'id': id,
'name': name,
'fileKey': fileKey,
'fileExtension': fileExtension,
'mimeType': mimeType,
'Catalogue': catalogueUrl.toJson(),
'createdAt': createdAt
};
#override
toString() {
String output =
'{id:${this.id},name:${this.name},fileKey: ${this.fileKey},fileExtension:${this.fileExtension},mimeType: ${this.mimeType}mimeType},catalogueUrl: ${this.catalogueUrl},,createdAt: ${this.createdAt}}';
return output;
}
}
class Catalogue {
final String mainUrl;
final List<String> thumbnailUrls;
Catalogue(this.mainUrl, this.thumbnailUrls);
factory Catalogue.fromMap(Map<String, dynamic> json) {
return Catalogue(json['mainUrl'], jsonDecode(json['thumbnailUrls']));
}
Map<String, dynamic> toJson() =>
{'mainUrl': mainUrl, 'thumbnailUrls': jsonEncode(thumbnailUrls)};
}
for use
if (response.statusCode == 200) {
var data = StorageResponse.fromMap(jsonDecode(result));
print('data: $data');
setState(() {
_pictureController.text = data.catalogueUrl!.mainUrl!;
});
return data;
} else {
throw Exception('Failed to upload photo.');
}
so i have this http req payload, and i want to push it into an array, can someone help me?
The payload
{
"status":200,
"length":3,
"results":[
{
"_id":"60cd70b3fb9fe400117e8c6b",
"title":"Welcome to xxx",
"body":"Welcome to xx! We’re excited that everyone’s here and hope your ready for an epic weekend."
},
{
"_id":"60cd70b3fb9fe400117e8c6c",
"title":"Lunch Info",
"body":"Lunch is from our generous sponsors Lorem Ipsum! It will be served in the left atrium under the palm trees."
},
{
"_id":"60cd70b3fb9fe400117e8c6d",
"title":"Leash Dogs",
"body":"A friendly reminder that dogs must be leashed at all times, no matter how cute <3"
}
]
}
My Provider Code [UPDATED]
//So I've tried to debug on my own, and number 1 and number 2 is printed, while number 3 is not. I suspect its because of the way I handle extractedData.
class Announcements {
int? status;
int? length;
List<Results>? results;
Announcements(
{required this.status, required this.length, required this.results});
Announcements.fromJson(Map<String, dynamic> json) {
status = json['status'];
length = json['length'];
if (json['results'] != null) {
results = [];
json['results'].forEach((v) {
results!.add(new Results.fromJson(v));
});
}
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['status'] = this.status;
data['length'] = this.length;
if (this.results != null) {
data['results'] = this.results!.map((v) => v.toJson()).toList();
}
return data;
}
}
// so i've used your online converter json
class Results {
String? sId;
String? title;
String? body;
Results({required this.sId, required this.title, required this.body});
Results.fromJson(Map<String, dynamic> json) {
sId = json['_id'];
title = json['title'];
body = json['body'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['_id'] = this.sId;
data['title'] = this.title;
data['body'] = this.body;
return data;
}
}
class AnnouncementProvider with ChangeNotifier {
AnnouncementProvider(String? token, items);
List _items = [];
List get items {
return [..._items];
}
// List<Announcements> parseAnnouncement(String responseBody) {
// }
Future<List<Announcements>> fetchAnnouncements(String authToken) async {
//var url = Uri.https('api-staging.xxx.us.org', '/1.0/announcements');
final response = await http.get(
Uri.parse('https://api-staging.xxx.us.org/1.0/announcements'),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Authorization': 'Bearer $authToken',
},
);
print(response.body);
final t = Announcements.fromJson(response.body as Map<String, dynamic>);
print(t.results);
return t.results;
}
}
What I need to know is, how do I return the list correctly, since the print(t.results) is actually not printed for some reason, so now it only shows "An error has occured" in my interface.
Thanks for helping!
Consider making a Dart Model object for the same, I would highly recommend you to do so because this is guaranteed serialization and type safe
For your case I used an imaginary name FoodItems for the type of data you received from your api endpoint
class FoodItems {
int status;
int length;
List<Results> results;
FoodItems({this.status, this.length, this.results});
FoodItems.fromJson(Map<String, dynamic> json) {
status = json['status'];
length = json['length'];
if (json['results'] != null) {
results = new List<Results>();
json['results'].forEach((v) {
results.add(new Results.fromJson(v));
});
}
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['status'] = this.status;
data['length'] = this.length;
if (this.results != null) {
data['results'] = this.results.map((v) => v.toJson()).toList();
}
return data;
}
}
class Results {
String sId;
String title;
String body;
Results({this.sId, this.title, this.body});
Results.fromJson(Map<String, dynamic> json) {
sId = json['_id'];
title = json['title'];
body = json['body'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['_id'] = this.sId;
data['title'] = this.title;
data['body'] = this.body;
return data;
}
}
Now you can easily cast your response.body to the FoodItems class using fromJson method and then get the desired list of Results and then iterate over it
In my honest opinion, it makes it much simpler this way
Note: I would highly recommend reading the following
This is a nice article from the Flutter Developers themselves
Android Studio Plugin to do the serialization for you
Online converter
I have this data format
message": [
{
"id": 15989,
"title": "xxx",
"body": "xxx",
"type": "abc",
"data_hash": "{\"id\":\"3098\",\"number\":1}",
}, .....]
If I write like this
print(message['data']['type']);
I can get abc, but if I write print(message['data']['data_hash']);, I get invalid arguments error. Why?
I want to get the number in data_hash.
This is the full code
_firebaseMessaging.configure(
onMessage: (Map<String, dynamic> message) async {
print("===== onMessage ====");
try {
print(message['data']['data_hash']);
} catch (e) {
print(e.toString());
}
});
data_hash row is a json. So you need to decode that row for use.
final data_hash_map = jsonDecode(message['data']['data_hash']);
print(data_hash_map); // { "id": 3098, "number": 1 }
print(data_hash_map["number"]); // for number
Decode your json as below
Map<String, dynamic> jsonData = jsonDecode(message)
I recommend to create a class to predefine the object as followed:
class Message {
int id;
String title;
String body;
String type;
DataHash dataHash;
message({this.id, this.title, this.body, this.type, this.dataHash});
Message.fromJson(Map<String, dynamic> json) {
id = json['id'];
title = json['title'];
body = json['body'];
type = json['type'];
dataHash = json['data_hash'] != null
? new DataHash.fromJson(json['data_hash'])
: null;
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['id'] = this.id;
data['title'] = this.title;
data['body'] = this.body;
data['type'] = this.type;
if (this.dataHash != null) {
data['data_hash'] = this.dataHash.toJson();
}
return data;
}
}
class DataHash {
String id;
String number;
DataHash({this.id, this.number});
DataHash.fromJson(Map<String, dynamic> json) {
id = json['id'];
number = json['number'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['id'] = this.id;
data['number'] = this.number;
return data;
}
}
You can call Message.fromJson(data) to decode.
Messsage message = Message.fromJson(data);
print(message.dataHash.number);
I hope this will work correctly
class _HomeState extends State<Mytimeoff> {
List<Map> list = [];
Map leaveRoot ={};
void getList() async {
var data = await http
.get('https:your api link');
leaveRoot = Map.from(json.decode(data.body));
setState(() {
for (Map js in leaveRoot['leavetype']) {
list.add(js);
}
});
print(jsonData);
}
#override
void initState() {
super.initState();
getList();
}
#override
Widget build(BuildContext context) {
return Scaffold();
}
}
Getting errors after trying to convert nested JSON data using dart's factory concept.
Here I have two classes to handle the json data, but still getting this error:
Exception has occurred.
_TypeError (type 'FormatException' is not a subtype of type 'Map')
Here is the code:
class BodyResponse {
final Map<String, dynamic> message;
BodyResponse({
this.message
});
factory BodyResponse.fromJson(Map<String, dynamic> json) {
return BodyResponse(message: json['message']);
}
}
class ErrorResponse {
final BodyResponse body;
final int status;
final String contentType;
final bool success;
ErrorResponse({
this.body, this.status, this.contentType, this.success
});
factory ErrorResponse.fromJson(Map<String, dynamic> json) {
return ErrorResponse(
body: BodyResponse.fromJson(json['body']),
status: json['status'],
contentType: json['content_type'],
success: json['success']
);
}
}
ErrorResponse errors = ErrorResponse.fromJson("""
{
"body": {
"message": "Some one has already taken this username(fooBar), please try again with a new username."
},
"status": 500,
"content_type": "application\/json",
"success": false
}
""");
print(errors);
What could go wrong here?
Modified most of your code here. Hope that this is what you tried to achieve.
import 'dart:convert';
class BodyResponse {
final String message;
BodyResponse({this.message});
BodyResponse.fromJson(Map<String, dynamic> json):
message = json['message'];
factory BodyResponse.fromString(String encodedJson) {
return BodyResponse.fromJson(json.decode(encodedJson));
}
Map<String, dynamic> toJson() => {
"message": message,
};
String toString() => json.encode(this.toJson());
}
class ErrorResponse {
final BodyResponse body;
final int status;
final String contentType;
final bool success;
ErrorResponse({this.body, this.status, this.contentType, this.success});
ErrorResponse.fromJson(Map<String, dynamic> json):
body = BodyResponse.fromJson(json['body']),
status = json['status'],
contentType = json['content_type'],
success = json['success'];
factory ErrorResponse.fromString(String encodedJson) {
return ErrorResponse.fromJson(json.decode(encodedJson));
}
Map<String, dynamic> toJson() => {
"body": body.toJson(),
"status": status,
"contentType": contentType,
"success": success,
};
String toString() => json.encode(this.toJson());
}
void main() {
ErrorResponse errors = ErrorResponse.fromString("""
{
"body": {
"message": "Some one has already taken this username(fooBar), please try again with a new username."
},
"status": 500,
"content_type": "application\/json",
"success": false
}
""");
print(errors);
}
Let me know if this helped.