im trying to store my List data in a disk storage using shared preferences, however idk how to convert the json back to DateTime format when trying to fetch data again from memory when i rebuild/restart the app.
here's my code:
String id;
String title;
double amount;
DateTime date;
Transaction({
#required this.id,
#required this.title,
#required this.amount,
#required this.date,
});
Transaction.fromMap(Map map)
: this.id = map['id'],
this.title = map['title'],
this.date = map['date'],
this.amount = map['amount'];
Map toMap() {
return {
'id': this.id,
'title': this.title,
'date': this.date.toIso8601String(),
'amount': this.amount,
};
}
}
heres where im using sharedPreferences
#override
void initState() {
initSharedPreferences();
super.initState();
}
initSharedPreferences() async {
sharedPreferences = await SharedPreferences.getInstance();
loadData();
}
void saveData() {
setState(() {
List<String> spList =
transactions.map((item) => json.encode(item.toMap())).toList();
var list = sharedPreferences.setStringList('list', spList);
print(list);
});
}
void loadData() {
List<String> spList = sharedPreferences.getStringList('list');
transactions =
spList.map((item) => Transaction.fromMap(json.decode(item))).toList();
setState(() {});
print(transactions);
}
In Transaction.fromMap constructor
replace
this.date = map['date'],
with
this.date = DateTime.parse(map['date']),
Related
I'm trying to deserialize a json file and create instances from it but whatever way I use, I end up stucked because of the dynamic type :
type '_Map<String, dynamic>' is not a subtype of type 'Map<String, int>'
Here's my model :
class Race {
final String name;
final Map<String, int> abilitiesUpdater;
const Race({
required this.name,
required this.abilitiesUpdater
});
static fromJson(json) => Race(name: json['name'], abilitiesUpdater: json['abilitiesUpdater']);
}
Here's how I'm trying to deserialize the json file :
class RacesApi {
static Future<List<Race>> getRacesLocally(BuildContext context) async {
final assetBundle = DefaultAssetBundle.of(context);
final String fileContent = await assetBundle.loadString('Assets/COC_Monstres/Races.json');
List<dynamic> parsedListJson = jsonDecode(fileContent);
List<Race> racesList = List<Race>.from(parsedListJson.map<Race>((dynamic i) => Race.fromJson(i)));
return racesList;
}
}
Here's my json file :
[
{
"name": "Vampire",
"abilitiesUpdater": {
"DEX": 2,
"CHA": 2
}
},
{
"name": "Créature du lagon",
"abilitiesUpdater": {
"FOR": 2,
"CON": 2
}
},
...
]
How can I properly cast this json object to fit into my class ?
This works:
class Race {
final String name;
// changed to dynamic
final Map<String, dynamic> abilitiesUpdater;
const Race({required this.name, required this.abilitiesUpdater});
static fromJson(json) =>
Race(name: json['name'], abilitiesUpdater: json['abilitiesUpdater']);
}
Maybe after get the object you can parse that dynamic into int if you need it.
Change your Model Class to this:
class Race {
Race({
required this.name,
required this.abilitiesUpdater,
});
late final String name;
late final AbilitiesUpdater abilitiesUpdater;
Race.fromJson(Map<String, dynamic> json){
name = json['name'];
abilitiesUpdater = AbilitiesUpdater.fromJson(json['abilitiesUpdater']);
}
Map<String, dynamic> toJson() {
final _data = <String, dynamic>{};
_data['name'] = name;
_data['abilitiesUpdater'] = abilitiesUpdater.toJson();
return _data;
}
}
class AbilitiesUpdater {
AbilitiesUpdater({
required this.FOR,
required this.CON,
});
late final int FOR;
late final int CON;
AbilitiesUpdater.fromJson(Map<String, dynamic> json){
FOR = json['FOR'];
CON = json['CON'];
}
Map<String, dynamic> toJson() {
final _data = <String, dynamic>{};
_data['FOR'] = FOR;
_data['CON'] = CON;
return _data;
}
}
you can cast the json['abilitiesUpdater'] as Map<String, int> because internally flutter will set it default as Map<String, dynamic>
Code
class Race {
final String name;
final Map<String, int> abilitiesUpdater;
const Race({
required this.name,
required this.abilitiesUpdater
});
static fromJson(json) => Race(name: json['name'], abilitiesUpdater: json['abilitiesUpdater']) as Map<String,int>;
}
it is working fine with me i tried it here is the link to the code https://dartpad.dev/?id=550918b56987552eb3d631ce8cb9e063.
If you still getting error you can try this
class Race {
final String name;
final Map<String, int> abilitiesUpdater;
Race({
required this.name,
required this.abilitiesUpdater
});
static fromJson(json) => Race(name: json['name'], abilitiesUpdater: (json['abilitiesUpdater']as Map<String,int>)) ;
}
or you can try this
class Race {
final String name;
final Map<String, int> abilitiesUpdater;
const Race({required this.name, required this.abilitiesUpdater});
static fromJson(json) => Race(
name: json['name'],
abilitiesUpdater: json['abilitiesUpdater']
.map((key, value) => MapEntry<String, int>(key, value as int)),
);
}
Edit : To have something a little bit more handy and scalable, I created an extension, and it works fine eventhough I have to cast twice the object...
My model :
// import my extension
class Race {
Race({
required this.name,
required this.abilitiesUpdater,
});
late final String name;
late final Map<String, int> abilitiesUpdater;
// late final AbilitiesUpdater abilitiesUpdater;
Race.fromJson(Map<String, dynamic> json){
name = json['name'];
abilitiesUpdater = (json['abilitiesUpdater'] as Map<String, dynamic>).parseToStringInt();
}
}
My extension :
extension Casting on Map<String, dynamic> {
Map<String, int> parseToStringInt() {
final Map<String, int> map = {};
forEach((key, value) {
int? testInt = int.tryParse(value.toString());
if (testInt != null) {
map[key] = testInt;
} else {
debugPrint("$value can't be parsed to int");
}
});
return map;
}
}
Once again, any help on cleaning this is appreciated !
Original answer :
Thanks to Sanket Patel's answer, I ended up with a few changes that made my code works. However I'm pretty clueless on why I can't directly cast a
Map<String, dynamic>
object into a
Map<String, int>
one.
Any info on this would be appreciated :)
Here's how I changed my model class in the end :
class Race {
Race({
required this.name,
required this.abilitiesUpdater,
});
late final String name;
late final AbilitiesUpdater abilitiesUpdater;
Race.fromJson(Map<String, dynamic> json){
name = json['name'];
abilitiesUpdater = AbilitiesUpdater.fromJson(json['abilitiesUpdater']);
}
}
class AbilitiesUpdater {
final Map<String, int> abilitiesUpdater = {};
AbilitiesUpdater.fromJson(Map<String, dynamic> json){
json.forEach((key, value) {
abilitiesUpdater[key] = int.parse(value.toString());
});
}
}
Here is my controller code
class PackageController extends GetxController {
PackageController({Key? key});
List<dynamic> _packageList = [];
List _items = [];
List<dynamic> get packageList => _packageList;
void getPackageList() {
_packageList = [];
_packageList.addAll(PackageModel.fromJson(_items).packages);
}
Future<void> readJson() async {
final String response = await rootBundle.loadString('local/packages.json');
final data = await json.decode(response);
_items = data["packages"];
update();
}
//end
}
but am faced with this issue
Here is my Model that controls the fromJson data in the package controller of the project.
class PackageModel {
PackageModel({
required this.packages,
});
late final List<Packages> packages;
PackageModel.fromJson(Map<String, dynamic> json) {
packages =
List.from(json['packages']).map((e) => Packages.fromJson(e)).toList();
}
}
class Packages {
Packages({
required this.id,
required this.name,
required this.description,
required this.price,
required this.img,
});
late final int id;
late final String name;
late final String description;
late final String price;
late final String img;
Packages.fromJson(Map<String, dynamic> json) {
id = json['id'];
name = json['name'];
description = json['description'];
price = json['price'];
img = json['img'];
}
}
Here is the json data
{
"packages": [
{
"id": 1,
"name": "VIP 1",
"description": "",
"price": "10",
"img": ""
}
]
}
I am trying how to retrieve Json Data stored locally in flutter but I ran into a problem, I need help
I have decoded my response.body i.e var jsonData = jsonDecode(response.body); and its working fine
But when i convert it into object and saved into local storage using sharedpref like this
if (response.statusCode == 200) {
jsonData['categoryList'].forEach((data) => {
categoryList.add(new ExpertCategory(
id: jsonData['_id'],
color: jsonData['color'],
type: jsonData['category_name'],
icon: ":)"))
});
print(categoryList) ;
localStorage.setCategoryData(categoryList.toString());
It stored in it And whenever i try to decode this its not working i.e
localStorage.getCategoryData().then((data) => {
userMap = jsonDecode(data),
});
class LocalStorage {
Future setCategoryData(data) async {
final prefs = await SharedPreferences.getInstance();
prefs.setString('category', data);
}
Future getCategoryData() async {
final prefs = await SharedPreferences.getInstance();
final category = prefs.getString('category');
return category;
}
}
import 'package:flutter/foundation.dart';
class ExpertCategory {
final String id;
final String type;
final String icon;
final String color;
const ExpertCategory( {
#required this.id,
#required this.type,
#required this.icon,
#required this.color,
});
}
its not the same as before,its showing error and after fixing some 1st element of string '['
is showing. please help with this thanks in advance.
Change your ExpertCategory model to this:
import 'package:flutter/material.dart';
class ExpertCategory {
String id;
String type;
String icon;
String color;
ExpertCategory(
{#required this.id,
#required this.type,
#required this.icon,
#required this.color});
ExpertCategory.fromJson(Map<String, dynamic> json) {
id = json['id'];
type = json['type'];
icon = json['icon'];
color = json['color'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['id'] = this.id;
data['type'] = this.type;
data['icon'] = this.icon;
data['color'] = this.color;
return data;
}
}
For your LocalStorage class, there are two approaches to setting your data into SharedPreferences. One using setString, the other being setStringList since you are storing the list of categories.
Checkout both approaches below.
APPROACH 1
class LocalStorage {
Future setCategoryData(List<ExpertCategory> data) async {
final prefs = await SharedPreferences.getInstance();
prefs.setString(
'category', jsonEncode(data.map((e) => e.toJson()).toList()));
}
Future<List<ExpertCategory>> getCategoryData() async {
final prefs = await SharedPreferences.getInstance();
final category = prefs.getString('category');
return List<ExpertCategory>.from(
List<Map<String, dynamic>>.from(jsonDecode(category))
.map((e) => ExpertCategory.fromJson(e))
.toList());
}
}
APPROACH 2
class LocalStorage {
Future setCategoryData(List<ExpertCategory> data) async {
final prefs = await SharedPreferences.getInstance();
prefs.setStringList('category',
List<String>.from(data.map((e) => jsonEncode(e.toJson())).toList()));
}
Future<List<ExpertCategory>> getCategoryData() async {
final prefs = await SharedPreferences.getInstance();
final category = prefs.getStringList('category');
return List<ExpertCategory>.from(
category.map((e) => ExpertCategory.fromJson(jsonDecode(e))).toList());
}
}
And finally you set your data into the SharedPreferences using
localStorage.setCategoryData(categoryList);
I'm having a hard time parsing JSON in the background.
I'm using the FoodData API.
Here is an example response for one dish:
{
"fdcId": 167782,
"description": "Abiyuch, raw",
"dataType": "SR Legacy",
"publicationDate": "2019-04-01",
"ndbNumber": "9427",
"foodNutrients": [
{
"number": "318",
"name": "Vitamin A, IU",
"amount": 100,
"unitName": "IU",
"derivationCode": "A",
"derivationDescription": "Analytical"
},
{
"number": "268",
"name": "Energy",
"amount": 290,
"unitName": "kJ",
"derivationCode": "NC",
"derivationDescription": "Calculated"
},
]
I used the JSON to DART convertor and got this output-
class FoodGen {
int fdcId;
String description;
String dataType;
String publicationDate;
String ndbNumber;
List<FoodNutrients> foodNutrients;
FoodGen(
{this.fdcId,
this.description,
this.dataType,
this.publicationDate,
this.ndbNumber,
this.foodNutrients});
FoodGen.fromJson(Map<String, dynamic> json) {
fdcId = json['fdcId'];
description = json['description'];
dataType = json['dataType'];
publicationDate = json['publicationDate'];
ndbNumber = json['ndbNumber'];
if (json['foodNutrients'] != null) {
foodNutrients = new List<FoodNutrients>();
json['foodNutrients'].forEach((v) {
foodNutrients.add(new FoodNutrients.fromJson(v));
});
}
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['fdcId'] = this.fdcId;
data['description'] = this.description;
data['dataType'] = this.dataType;
data['publicationDate'] = this.publicationDate;
data['ndbNumber'] = this.ndbNumber;
if (this.foodNutrients != null) {
data['foodNutrients'] =
this.foodNutrients.map((v) => v.toJson()).toList();
}
return data;
}
}
class FoodNutrients {
String number;
String name;
double amount;
String unitName;
String derivationCode;
String derivationDescription;
FoodNutrients(
{this.number,
this.name,
this.amount,
this.unitName,
this.derivationCode,
this.derivationDescription});
FoodNutrients.fromJson(Map<String, dynamic> json) {
number = json['number'];
name = json['name'];
amount = json['amount'];
unitName = json['unitName'];
derivationCode = json['derivationCode'];
derivationDescription = json['derivationDescription'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['number'] = this.number;
data['name'] = this.name;
data['amount'] = this.amount;
data['unitName'] = this.unitName;
data['derivationCode'] = this.derivationCode;
data['derivationDescription'] = this.derivationDescription;
return data;
}
}
Here is my entire code-
import 'dart:async';
import 'dart:convert';
import 'package:fit_app/fitness_app_theme.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
Future<List<FoodGen>> fetchPhotos(http.Client client) async {
final response =
await client.get('https://api.nal.usda.gov/fdc/v1/foods/list?dataType=Foundation,SR%20Legacy&pageSize=25&api_key=h8GO51P1H4e0dfvWOmVsu75dafKwNqJk41kf0HMD');
// Use the compute function to run parsePhotos in a separate isolate.
return compute(parsePhotos, response.body);
}
// A function that converts a response body into a List<Photo>.
List<FoodGen> parsePhotos(String responseBody) {
final parsed = jsonDecode(responseBody).cast<Map<String, dynamic>>();
return parsed.map<FoodGen>((json) => FoodGen.fromJson(json)).toList();
}
// To parse this JSON data, do
//
// final welcome = welcomeFromJson(jsonString);
class FoodGen {
int fdcId;
String description;
String dataType;
String publicationDate;
String ndbNumber;
List<FoodNutrients> foodNutrients;
FoodGen(
{this.fdcId,
this.description,
this.dataType,
this.publicationDate,
this.ndbNumber,
this.foodNutrients});
FoodGen.fromJson(Map<String, dynamic> json) {
fdcId = json['fdcId'];
description = json['description'];
dataType = json['dataType'];
publicationDate = json['publicationDate'];
ndbNumber = json['ndbNumber'];
if (json['foodNutrients'] != null) {
foodNutrients = new List<FoodNutrients>();
json['foodNutrients'].forEach((v) {
foodNutrients.add(new FoodNutrients.fromJson(v));
});
}
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['fdcId'] = this.fdcId;
data['description'] = this.description;
data['dataType'] = this.dataType;
data['publicationDate'] = this.publicationDate;
data['ndbNumber'] = this.ndbNumber;
if (this.foodNutrients != null) {
data['foodNutrients'] =
this.foodNutrients.map((v) => v.toJson()).toList();
}
return data;
}
}
class FoodNutrients {
String number;
String name;
double amount;
String unitName;
String derivationCode;
String derivationDescription;
FoodNutrients(
{this.number,
this.name,
this.amount,
this.unitName,
this.derivationCode,
this.derivationDescription});
FoodNutrients.fromJson(Map<String, dynamic> json) {
number = json['number'];
name = json['name'];
amount = json['amount'];
unitName = json['unitName'];
derivationCode = json['derivationCode'];
derivationDescription = json['derivationDescription'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['number'] = this.number;
data['name'] = this.name;
data['amount'] = this.amount;
data['unitName'] = this.unitName;
data['derivationCode'] = this.derivationCode;
data['derivationDescription'] = this.derivationDescription;
return data;
}
}
class FoodPage extends StatefulWidget {
FoodPage({Key key}) : super(key: key);
#override
_FoodPageState createState() => _FoodPageState();
}
class _FoodPageState extends State<FoodPage> {
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: FitnessAppTheme.darkBackground,
body: FutureBuilder<List<FoodGen>>(
future: fetchPhotos(http.Client()),
builder: (context, snapshot) {
if (snapshot.hasError) print(snapshot.error);
return snapshot.hasData
? PhotosList(photos: snapshot.data)
: Center(child: CircularProgressIndicator());
},
),
);
}
}
class PhotosList extends StatelessWidget {
final List<FoodGen> photos;
PhotosList({Key key, this.photos}) : super(key: key);
#override
Widget build(BuildContext context) {
return GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
),
itemCount: photos.length,
itemBuilder: (context, index) {
Text(photos[index].description.toString());
},
);
}
}
BUT when running this using flutter run, I get an error in my console: Flutter - Exception: type 'int' is not a subtype of type 'double'
I would really appreciate your help!
Thank you guys so much!
you need to replace
double amount;
with
num amount;
It seems your amount is double. But sometimes it comes as int from the server. So, you need to convert these int values to double, too. Try this:
FoodNutrients.fromJson(Map<String, dynamic> json) {
number = json['number'];
name = json['name'];
amount = json['amount'].toDouble(); // Here we converted!
unitName = json['unitName'];
derivationCode = json['derivationCode'];
derivationDescription = json['derivationDescription'];
}
Try changing double amount; to int amount;.
Looks like you are receiving an Integer here
amount = json['amount'];
and then u try to assign this integer to amount which expects a dobule
I had the same problem, you may try with this parsing:
double parseDouble(Map<String, dynamic> json, String key, {double defaultValue = 0.0}) {
if (json == null) return defaultValue;
if (!json.containsKey(key)) return defaultValue;
var rawValue = json[key];
if (rawValue == null) return defaultValue;
if (rawValue is double) return rawValue;
if (rawValue is int) {
return rawValue.toDouble();
}
if (rawValue is String) {
return double.tryParse(rawValue) ?? defaultValue;
}
return defaultValue;
}
Replace
double amount;
with
dynamic amount;
Always use dynamic where you are not sure if the value is in which format. It can be int or double.
There are two classes. A Product class and a Characteristic class.
class Product {
int id;
String price;
String description;
List<Characteristic> characteristics;
Product({
#required this.id,
#required this.price,
#required this.description,
this.characteristics,
});
factory Product.fromJson(Map<String, dynamic> json) {
var characteristics = new List<Characteristic>();
json['characteristics'].forEach((v) {
characteristics.add(Characteristic.fromJson(v));
});
return Product(
id: json['id'],
price: json['price'].toString(),
description: json['description'],
characteristics: characteristics,
);
}
Map<String, dynamic> toJson() {
return {
"id": this.id,
"price": this.price,
"description": this.description,
"characteristics": this.characteristics,
};
}
}
class Characteristic {
int id;
String name;
int amount;
Characteristic({
this.id,
this.name,
this.amount,
});
factory Characteristic.fromJson(Map<String, dynamic> json) {
return Characteristic(
id: json['id'],
name: json['name'],
amount: json['amount'],
);
}
Map<String, dynamic> toJson() {
return {
"id": this.id,
"name": this.name,
"amount": this.amount,
}
}
}
I get the products from a server.
List<Product> products = fetchProducts();
Then I save the products in local storage.
void saveProductsToSharedPrefs(List<Product> products) {
List<String> productIds = [];
products.forEach((product) {
Preference.saveValue(key: product.id, value: jsonEncode(product));
productIds.add(product.id);
});
Preference.saveValue(
key: "ProductIds", value: json.encode(productIds));
}
In the following function, I have to retrieve the data form local storage and reconstruct the product object.
Future<List<Product>> getProductsFromSharedPrefs() async {
String productIdsString =
await Preference.getValue(key: "ProductIds");
List<dynamic> productIdsList = json.decode(productIdsString);
List<Product> products = [];
productIdsList.forEach((productId) async {
String productString =
await Preference.getValue(key: productId.toString());
final decodedProductString = jsonDecode(productString);
...
????
});
return products;
}
I am stuck here and do not know what to do.
What makes it complex is that the Product class has a characteristics property which is of type List<Characteristic> and I don't know how to decode it..