I was trying to make a Covid Tracking application using flutter, and I came across this function getCountrySummary( ),
import 'package:covid_tracker/models/country_summary.dart';
import 'package:covid_tracker/models/global_summary.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
class CovidService {
Future<GlobalSummaryModel> getGlobalSummary() async {
final data =
await http.get(Uri.parse('https://api.covid19api.com/summary'));
if (data.statusCode != 200) {
throw Exception();
}
GlobalSummaryModel summary =
GlobalSummaryModel.fromJson(json.decode(data.body));
return summary;
}
Future<List<CountrySummaryModel>> getCountrySummary(String slug) async {
String url = "https://api.covid19api.com/total/dayone/country/$slug";
final data = await http.get(Uri.parse(url));
if (data.statusCode != 200) {
throw Exception();
}
List<CountrySummaryModel> summaryList = (json.decode(data.body) as List)
.map((item) => CountrySummaryModel.fromJson(item))
.toList();
return summaryList;
}
}
So I know what the function getCountrySummary() is trying to do, but I don't understand what statement
List<CountrySummaryModel> summaryList = (json.decode(data.body) as List).map((item) => CountrySummaryModel.fromJson(item)).toList();
is trying to do, and CountrySummaryModel is an object.
class CountrySummaryModel {
final String country;
final int confirmed;
final int death;
final int recovered;
final int active;
final DateTime date;
CountrySummaryModel(this.country, this.active, this.confirmed, this.date,
this.death, this.recovered);
factory CountrySummaryModel.fromJson(Map<String, dynamic> json) {
return CountrySummaryModel(
json["country"],
json["active"],
json["confirmed"],
DateTime.parse(json["date"]),
json["death"],
json["recovered"],
);
}
}
When you call Map on a list, it means you want to reach each item in it, in your case you call map on your list to parse each item in it and at then call toList() to make a list of this items.
If I understand your code correctly:
First, you convert data to List.
Then, use CountrySummaryModel.fromJson() and .toList() to convert it to List<CountrySummaryModel>.
Related
I have a Todo-List app with Task class.
I want to serialize a Task List with jsonEncode and persist them onto a file in Docs dir.
after that, I want to be able to re-serialize the same list and convert them into my native List datatype (from List<String, dynamic> that I get from jsonDecode). Whats the best way to do it?
Currently, I tried:
void reSerializeTaskList() async {
final directory = await getApplicationDocumentsDirectory();
File f = File('${directory.path}/new.txt');
String fileContent = await f.readAsString();
List<dynamic> jsonList = jsonDecode(fileContent).cast<Task>(); // does not work
print("JSONSTRING: ${jsonList.runtimeType}");
print("$jsonList");
}
I/flutter (29177): JSONSTRING: CastList<dynamic, Task>
E/flutter (29177): [ERROR:flutter/lib/ui/ui_dart_state.cc(177)] Unhandled Exception: type '_InternalLinkedHashMap<String, dynamic>' is not a subtype of type 'Task' in type cast
my workaround is to iterate through all array elements and build a Task type out of the values with "fromJson" method inside my Task class:
void reSerializeTaskList() async {
final directory = await getApplicationDocumentsDirectory();
File f = File('${directory.path}/new.txt');
String fileContent = await f.readAsString();
List<dynamic> jsonList = jsonDecode(fileContent);
List<Task> taskList = [];
for (var t in jsonList) {
print("T: $t and ${t.runtimeType}");
Task task = new Task();
taskList.add(task.fromJson(t));
}
print("JSONSTRING: ${jsonList.runtimeType}");
print("$jsonList");
print("$taskList");
print("$taskList.runtimeType");
}
my Task class:
import 'dart:io';
class Task {
String name;
bool isDone;
Task({this.name, this.isDone = false});
void toggleDone() {
isDone = !isDone;
}
#override
String toString() {
// TODO: implement toString
return "${this.name} is done: $isDone";
}
Map<String, dynamic> toJson() {
return {
"name": this.name,
"isDone": this.isDone,
};
}
Task fromJson(Map<String, dynamic> json) {
this.name = json['name'];
this.isDone = json['isDone'];
return this;
}
}
But is there maybe another (better) approach? This looks quite patchy to me...
Just to give you a little example, this is how I do it
final jsonResponse = json.decode(jsonString);
final List<Customer> customers = jsonResponse.map<Customer>((jR) => Customer.fromJson(jR)).toList();
and fromJson in Customer class looks like this
factory Customer.fromJson(Map<String, dynamic> json) => Customer(
id: json["id"] == null ? null : json["id"],
changeDate: json["changeDate"] == null ? null : DateTime.parse(json["changeDate"]),
name: json["name"] == null ? null : json["name"],
);
I have an app receive nested data from server in the page i print the data's and it is printed successfully :
class page :
final DateTime mDate;
final List<Games> games;
class DatedMatchs {
DatedMatchs(
this.mDate,
this.games,
);
}
class Games {
Games(
this.id,this.sId,this.wId,this.leagueName,this.homeTeam,this.awayTeam,this.homeGoals,this.awayGoals,this.mHour,this.homeEx,this.awayEx,
);
final String id;
final String sId;
final String wId;
final String leagueName;
final String homeTeam;
final String awayTeam;
final String homeGoals;
final String awayGoals;
final String mHour;
final String homeEx;
final String awayEx;
}
page i want to show data:
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:http/http.dart' as http;
import 'package:intl/intl.dart';
import '../models/dated_matchs.dart';
class Home extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
#override
Widget build(BuildContext context) {
List matchs = [];
Future<List> getmatchs() async {
var url =
'xxx/api/controller/matchs/dated_matchs.php?s_id=1';
var response = await http.get(url);
var data = jsonDecode(response.body);
print(data);
}
return FutureBuilder(
future: getmatchs(),
builder: (ctx, snapshot) {
return Container();
});
}
}
Now i don't know how to add received data to a list then show it on list-view
I used this way inside future function but there is something wrong :
Future<List> getmatchs() async {
var url =
'xxx/api/controller/matchs/dated_matchs.php?s_id=1';
var response = await http.get(url);
var data = jsonDecode(response.body);
for (var x in data) {
for (var y in x['games']) {
cont1.add(TextEditingController());
cont2.add(TextEditingController());
Games newmatch = Games(
y['id'],
y['s_id'],
y['w_id'],
y['league_name'],
y['home_team'],
y['away_team'],
y['home_goals'],
y['away_goals'],
y['m_hour'],
y['home_ex'],
y['away_ex']);
matchs.add(newmatch);
}
DatedMatchs newdated = DatedMatchs(x['m_date'], x['matchs']);
datedmatchs.add(newdated);
}
return datedmatchs;
}
no thing print
Some of your data is coming back as a Map, rather than a List. You'll need to debug and see which data is a Map, then you can print it from the Map.
Also, I wouldn't call an api in your UI. It's best to use a state management library, such as Bloc, Provider, or RxDart.
I solved it and below the future method which get me list of data correctly :
List<Games> games = []; // I added type of List
List<DatedMatchs> datedmatchs = []; // I added type of List
Future<List> getmatchs() async {
var url =
'xxx/api/controller/matchs/dated_matchs.php?s_id=1';
var response = await http.get(url);
var data = await jsonDecode(response.body);
for (var x in data) {
for (var y in x['games']) {
cont1.add(TextEditingController());
cont2.add(TextEditingController());
Games newmatch = Games(
y['id'],
y['s_id'],
y['w_id'],
y['league_name'],
y['home_team'],
y['away_team'],
y['home_goals'],
y['away_goals'],
y['m_hour'],
y['home_ex'],
x['away_ex']);
games.add(newmatch);
}
DatedMatchs newdated = DatedMatchs(x['m_date'], games); // add name of list
datedmatchs.add(newdated);
}
return datedmatchs;
}
I have this class :
class Weeks {
final int index;
final int udigree;
final int d_id;
final String activity_a;
final String activity_k;
final String title_a;
final String title_k;
Weeks(this.index, this.udigree, this.d_id, this.activity_a, this.activity_k,
this.title_a, this.title_k);
}
I used future function to get data from server :
import 'package:flutter/material.dart';
import 'package:jiyanUquraan/classes/weekly.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
class Weekly extends StatefulWidget {
#override
_WeeklyState createState() => _WeeklyState();
}
class _WeeklyState extends State<Weekly> {
#override
Widget build(BuildContext context) {
var widthView = MediaQuery.of(context).size.width;
var heightView = MediaQuery.of(context).size.height;
List weekly = [];
Map rData = ModalRoute.of(context).settings.arguments;
var cm_id = rData['current_m_id'];
var u_id = rData['u_id'];
var d_id = rData['d_id'];
var w_id = rData['w_id'];
// Futher Function for Get Data
Future<List> getWeeks() async {
print(cm_id);
print(u_id);
print(w_id);
var url =
'http://10.0.2.2/jiyan/test/api/digrees/weekly_report.php?m_id=$cm_id&u_id=$u_id&w_id=$w_id';
var response = await http.get(url);
var data = jsonDecode(response.body);
print(data);
print(data.length);
for (var x in data) {
Weeks _weeklyReport = Weeks(x['index'], x['udigree'], x['activity_a'],
x['activity_k'], x['title_a'], x['title_k'], x['d_id']);
weekly.add(_weeklyReport);
}
return weekly;
}
// End of Get Data
// Create Future Builder
return FutureBuilder(
future: getWeeks(),
builder: (context, snapshot) {
if (snapshot.data == null) {
return Center(
child: Text('Loading'),
);
} else {
return Center(child: Text('data'));
}
});
// End of Futur Builder
}
}
As I print the data and its length, the length is correct but the data is not complete as shown:
As it doesn't get the data correctly the snapshot.data gets null, How can I fix this?
Does the error occur because the data is Kurdish?
the Mistake was in the class .
the variables d_id and udigree must be String
class Weeks {
final int index;
final String udigree;
final Stringd_id;
final String activity_a;
final String activity_k;
final String title_a;
final String title_k;
Weeks(this.index, this.udigree, this.d_id, this.activity_a, this.activity_k,
this.title_a, this.title_k);
}
Hi im trying to get values from JSOn but when I map a value that is a INT then flutter show that error and no return data :
flutter: type 'String' is not a subtype of type 'int'
This is my model :
import 'package:http/http.dart' as http;
import 'dart:async';
import 'dart:convert';
class Kits {
final String country;
final String franchise;
final String type;
String brand;
String procedure;
String setdesc;
int traymaterialid;
String traydesc;
int referencia;
String descart;
int cantidad;
int homologado;
int bajopresu;
DateTime insdate;
Kits(this.country,this.franchise,this.type, this.brand, this.procedure,this.traydesc, this.traymaterialid);
factory Kits.fromJson(Map<String, dynamic> json) {
return Kits(json['COUNTRY'], json['FRANCHISE'], json['TYPE'], json['BRAND'], json['PROCEDURE'], json['TRAYDESCRIPTION'], json['TRAYMATERIALID'] /* <-- INT*/ );
}
}
class KitsData {
static const url = 'http://app-smith.com/en/kits_API.php?email=miquel#winfor.es&macid=888&passwd=Wcz95f4UGkax5G';
final JsonDecoder _decoder = new JsonDecoder();
Future<List<Kits>> fetch(){
return http.get(url).then((http.Response response){
final String jsonBody = response.body;
//final statusCode = response.statusCode;
/*if (statusCode < 200 || statusCode >= 300 || jsonBody == null){
return List();
}*/
final kitsContainer = _decoder.convert(jsonBody);
final List kitsItems = kitsContainer['kits'];
return kitsItems.map( (kitsRaw) => new Kits.fromJson(kitsRaw) ).toList();
});
}
}
class FetchDataException implements Exception {
String _message;
FetchDataException(this._message);
String toString() {
return "Exception: $_message";
}
}
I don't understand why it's happen because fromJson is only getting the value that I pass the key then in my list I convert to String the value .
Fragment of JSON:
{"kits": [{"COUNTRY":"FR","FRANCHISE":"KNEE","TYPE":"LEGION","BRAND":"REVISION","PROCEDURE":"LEGION REVISION","SETDESCRIPTION":"LEGION REVISION - INSTRUMENTS","TRAYMATERIALID":"551820141LRC","TRAYDESCRIPTION":"INSTR LEGION REVISION","REFERENCIA":"71431722","DESCRIPCIONARTICULO":"LGN SCW LWDG TRL S3 5D X 10P","CANTIDAD":"1","HOMOLOGADO":"","BAJOPRESUPUESTO":"","INS_DATE":"2018-08-23 18:57:04"}
All numeric values are inside quotes, so they are not transferred as int but as String. The error message is correct. If you want them as String either ensure the values are not quoted, if you don't control that use int.parse() to convert them from String to int
I'm wondering how can I parse a nested json to a class with generic types. My intention is to wrap responses from the backend (like loginRespose that contains a token) with a code and a message
I have
class BaseResponse<T>{
int code;
String message;
T responseObject;
BaseResponse.fromJson(Map<String, dynamic> parsedJson)
: code = parsedJson['Code'],
message = parsedJson['Message'],
responseObject = T.fromJson(parsedJson['ResponseObject']); //This is what I'd like to do
}
Obviously the last line throws an error because T doesn't has a named constructor "fromJson".
I tried adding some restrictions to the Type but I didn't find any solutions. Do you have any idea on how to pull this off?
You can't do such thing, at least not in flutter. As dart:mirror is disabled and there's no interface for classes constructors.
You'll have to take a different route.
I'll recommend using POO instead. You would here give up on deserializing responseObject from your BaseResponse. And then have subclass of BaseResponse handles this deserialization
Typically you'd have one subclass per type:
class IntResponse extends BaseResponse<int> {
IntResponse.fromJson(Map<String, dynamic> json) : super._fromJson(json) {
this.responseObject = int.parse(json["Hello"]);
}
}
You can then hide this mess away by adding a custom factory constructor on BaseResponse to make it more convenient to use.
class BaseResponse<T> {
int code;
String message;
T responseObject;
BaseResponse._fromJson(Map<String, dynamic> parsedJson)
: code = parsedJson['Code'],
message = parsedJson['Message'];
factory BaseResponse.fromJson(Map<String, dynamic> json) {
if (T == int) {
return IntResponse.fromJson(json) as BaseResponse<T>;
}
throw UnimplementedError();
}
}
Then either instantiate the wanted type directly, or use the factory constructor :
final BaseResponse foo = BaseResponse.fromJson<int>({"Hello": "42", "Code": 42, "Message": "World"});
You can achieve this with the built_value package (you'll also need built_value_generator and build_runner). Your class will look something like this:
part 'base_response.g.dart';
abstract class BaseResponse<T> implements Built<BaseResponse<T>, BaseResponseBuilder<T>> {
int code;
String message;
T responseObject;
factory BaseResponse([updates(BaseResponseBuilder<T> b)]) = _$BaseResponse<T>;
static Serializer<BaseResponse> get serializer => _$baseResponseSerializer;
}
You will have to run flutter packages pub run build_runner build to make the generated file. Then you use it like this:
BaseResponse baseResponse = serializers.deserialize(
json.decode(response.body),
specifiedType: const FullType(BaseResponse, const [FullType(ConcreteTypeGoesHere)])
);
There's just one more bit of boilerplate you have to take care of. You need another file called serializers.dart. You need to manually add all the classes you want to deserialize here, and also an addBuilderFactory function for each class that takes a type parameter - and for each concrete type you want to use.
part 'serializers.g.dart';
#SerializersFor(const [
BaseResponse,
ConcreteTypeGoesHere,
])
final Serializers serializers = (_$serializers.toBuilder()
..addBuilderFactory(
FullType(BaseResponse, const [const FullType(ConcreteTypeGoesHere)]),
() => new BaseResponseBuilder<ConcreteTypeGoesHere>()
)
..addPlugin(StandardJsonPlugin()))
.build();
Then re-run flutter packages pub run build_runner build
Makes me wish for Gson... :S
Here is my approach:
class Wrapper<T, K> {
bool? isSuccess;
T? data;
Wrapper({
this.isSuccess,
this.data,
});
factory Wrapper.fromJson(Map<String, dynamic> json) => _$WrapperFromJson(json);
Map<String, dynamic> toJson() => _$WrapperToJson(this);
}
Wrapper<T, K> _$WrapperFromJson<T, K>(Map<String, dynamic> json) {
return Wrapper<T, K>(
isSuccess: json['isSuccess'] as bool?,
data: json['data'] == null ? null : Generic.fromJson<T, K>(json['data']),
);
}
class Generic {
/// If T is a List, K is the subtype of the list.
static T fromJson<T, K>(dynamic json) {
if (json is Iterable) {
return _fromJsonList<K>(json) as T;
} else if (T == LoginDetails) {
return LoginDetails.fromJson(json) as T;
} else if (T == UserDetails) {
return UserDetails.fromJson(json) as T;
} else if (T == Message) {
return Message.fromJson(json) as T;
} else if (T == bool || T == String || T == int || T == double) { // primitives
return json;
} else {
throw Exception("Unknown class");
}
}
static List<K> _fromJsonList<K>(List<dynamic> jsonList) {
return jsonList?.map<K>((dynamic json) => fromJson<K, void>(json))?.toList();
}
}
In order to add support for a new data model, simply add it to Generic.fromJson:
else if (T == NewDataModel) {
return NewDataModel.fromJson(json) as T;
}
This works with either generic objects:
Wrapper<Message, void>.fromJson(someJson)
Or lists of generic objects:
Wrapper<List<Message>, Message>.fromJson(someJson)