I have a list of favorite music, which I retrieve from music when the app is opened for the first time, the app gets a favorite music list from favorite. I want to save this list to shared
preferences.List<Music> favoriteMusic = new List<Music>();
where music class is:
class Music {
final int id;
final String name, size, rating, duration, img;
bool favorite;
Music({
this.id,
this.rating,
this.size,
this.duration,
this.name,
this.img,
this.favorite,
});
factory Music.fromJson(Map<String, dynamic> jsonData){
return Music(
id: jsonData['id'],
rating: jsonData['rating'],
size: jsonData['size'],
duration: jsonData['duration'],
name: jsonData['name'],
img: jsonData['img'],
favorite: false,
);
}
}
How can I save favorite music list?
You should do these steps
to save the object:
convert your object to map with toMap() method
encode your map to string with encode(...) method
save the string to shared preferences
for restoring your object:
decode shared preference string to a map with decode(...) method
use fromJson() method to get your object
UPDATE FULL SAMPLE
import 'dart:convert';
void main() async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
// Encode and store data in SharedPreferences
final String encodedData = Music.encode([
Music(id: 1, ...),
Music(id: 2, ...),
Music(id: 3, ...),
]);
await prefs.setString('musics_key', encodedData);
// Fetch and decode data
final String musicsString = await prefs.getString('musics_key');
final List<Music> musics = Music.decode(musicsString);
}
class Music {
final int id;
final String name, size, rating, duration, img;
bool favorite;
Music({
this.id,
this.rating,
this.size,
this.duration,
this.name,
this.img,
this.favorite,
});
factory Music.fromJson(Map<String, dynamic> jsonData) {
return Music(
id: jsonData['id'],
rating: jsonData['rating'],
size: jsonData['size'],
duration: jsonData['duration'],
name: jsonData['name'],
img: jsonData['img'],
favorite: false,
);
}
static Map<String, dynamic> toMap(Music music) => {
'id': music.id,
'rating': music.rating,
'size': music.size,
'duration': music.duration,
'name': music.name,
'img': music.img,
'favorite': music.favorite,
};
static String encode(List<Music> musics) => json.encode(
musics
.map<Map<String, dynamic>>((music) => Music.toMap(music))
.toList(),
);
static List<Music> decode(String musics) =>
(json.decode(musics) as List<dynamic>)
.map<Music>((item) => Music.fromJson(item))
.toList();
}
Flutter's shared_preferences plugin has a method: setStringList(String key, List<String> value), so you can just write serializer for your objects.
Convert it to a string, you can store it
import 'dart:convert';
...
var s = json.encode(myList);
// or var s = jsonEncode(myList);
json.decode() //convert a string to List when you load it
For noob folks like me who want to understand a bit more about the magic our dear friend Hamed did in his answer, or want to adapt his solution to more complex classes with lists/other classes, check out these two links:
https://bezkoder.com/dart-flutter-parse-json-string-array-to-object-list/
https://bezkoder.com/dart-flutter-convert-object-to-json-string/
jsonEncode() and jsonDecode() are the same as json.encode() and json.decode()
simply use stringlist in shared preferences
basic syntax:
// read
final myStringList = prefs.getStringList('my_string_list_key') ?? [];
// write
prefs.setStringList('my_string_list_key', ['a', 'b', 'c']);
Firstly convert the object to a map. Then convert the map to a JSON string using jsonEncode and at the end save the JSON string to shared preferences
Sample example:
// import 'dart:convert';
Person person = Person('Mary', 30);
Map<String, dynamic> map = {
'name': person.name,
'age': person.age
};
String rawJson = jsonEncode(map);
prefs.setString('my_string_key', rawJson);
retrieve data
final rawJson = prefs.getString('my_string_key') ?? '';
Map<String, dynamic> map = jsonDecode(rawJson);
final person = Person(map['name'], map['age']);
Related
I use complex objects to manage data in my app. For example I have an object defined by the "Defi class" (meaning Challenge in French).
Here is the Defi class :
class Defi {
final int modeApprentissage;
final DateTime date;
final double score;
final int seconds;
final List mots;
final List erreurs;
final List listes;
final int langue;
final int type;
final int typeMots1;
final int typeMots2;
const Defi(
{required this.modeApprentissage,
required this.date,
required this.type,
required this.langue,
required this.typeMots1,
required this.typeMots2,
required this.score,
required this.seconds,
required this.listes,
required this.mots,
required this.erreurs});
}
I have a LIST of Defi that I would like to save on FIREBASE FIRESTORE.
My question : Is it absolutely necessary to transform my list of Defi into a map to save it on Firestore ? Or is there another way ?
Here is how I do it :
List _defisMap = [];
for (Defi def in _defis) {
_defisMap.add({
'modeApprentissage': def.modeApprentissage,
'type': def.type,
'langue': def.langue,
'typeMots1': def.typeMots1,
'typeMots2': def.typeMots2,
'date': def.date,
'score': def.score,
'seconds': def.seconds,
'listes': def.listes,
'mots': def.mots,
'erreurs': def.erreurs,
});
}
if (await _connectivity.checkConnectivity() != ConnectivityResult.none) {
await FirebaseFirestore.instance
.collection('familyAccounts')
.doc(_accountEmail)
.update({
'defis': _defisMap,
});
I read in some article that in classes such as Defi, I could add a "factory" constructor ? Does this have anything to do with what I'd like to do ?
I created another class :
class Classes {
final String code;
final String nom;
const Classes({
required this.code,
required this.nom,
});
Map<String, dynamic> toJson() => {
'code': code,
'nom': nom,
};
factory Classes.fromMap(Map data) => Classes(
code: data['code'] ?? '',
nom: data['nom'] ?? '',
);
}
I save a list of Classes on Firestore. No problem.
But to retrieve this list : I must go from the list of maps that is on Firestore to a list of "Classes". And I just can't get the syntax right !
Here is my code :
final DocumentSnapshot<Map<String, dynamic>> docInfo =
await FirebaseFirestore.instance
.collection('familyAccounts')
.doc(_accountEmail)
.get();
_typeCompte =
docInfo['typeCompte'] == 'prof' ? TypeCompte.prof : TypeCompte.eleve;
_userId = docInfo['userId'];
_linked = docInfo['linked'];
_name = docInfo['name'];
_avatar = docInfo['avatar'];
_classe = docInfo['classe'];
_classeCode = docInfo['classeCode'];
_country = docInfo['country'];
_region = docInfo['region'];
docInfo['langue'] == 'french'
? _selectedIoLanguage = Language.french
: _selectedIoLanguage = Language.english;
_teacherCode = docInfo['teacherCode'];
_indexList = docInfo['indexList'];
_nbrList = docInfo['nbrList'];
_dateCreationCompte = docInfo['dateCreation'].toDate();
_defiTemp = docInfo['defis'].toList();
if (_typeCompte == TypeCompte.prof) {
_etablissement = docInfo['etablissement'];
_mesClasses = docInfo['mesClasses'];
(_mesClasses should be a list of "Classes").
I sense it should be some kind of xxx.map() etc.... but I don't master this syntax.
You need to create toJson method to set as a map of your list. If you have a list of Defi class. You can send it to map.
class Defi {
final int modeApprentissage;
final DateTime date;
final double score;
final int seconds;
final List mots;
final List erreurs;
final List listes;
final int langue;
final int type;
final int typeMots1;
final int typeMots2;
const Defi(
{required this.modeApprentissage,
required this.date,
required this.type,
required this.langue,
required this.typeMots1,
required this.typeMots2,
required this.score,
required this.seconds,
required this.listes,
required this.mots,
required this.erreurs});
Map<String, dynamic> toJson() => {
'modeApprentissage': modeApprentissage,
'type': type,
'langue': langue,
'typeMots1': typeMots1,
'typeMots2': typeMots2,
'date': date,
'score': score,
'seconds': seconds,
'listes': listes,
'mots': mots,
'erreurs': erreurs,
};
}
Your list name is should be List<Defi> _defis then map it with toJson. var jsonMap = _defisMap.map((e) => e.toJson()).toList();
var jsonMap = _defis.map((e) => e.toJson()).toList();
if (await _connectivity.checkConnectivity() != ConnectivityResult.none) {
await FirebaseFirestore.instance
.collection('familyAccounts')
.doc(_accountEmail)
.update({
'defis': jsonMap,
});
You can also Map it from api call with fromJson method. Here is the way. Add this to your Defi class.
factory Defi.fromJson(Map<String, dynamic> json) {
return Defi(
modeApprentissage: json['modeApprentissage'],
type: json['type'],
langue: json['langue'],
typeMots1: json['typeMots1'],
typeMots2: json['typeMots2'],
date: json['date'],
score: json['score'],
seconds: json['seconds'],
listes: json['listes'],
mots: json['mots'],
erreurs: json['erreurs'],);
}
And when you call api you need to call that function.
final Map<String, dynamic> jsonResult = json.decode(response.body);
//if it is a list returning from api. You can change variables what you got from api.
(jsonResult as List<dynamic>)
.map((data) => Defi.fromJson(data))
.toList();
// if it is not list
Defi.fromJson(jsonResult);
Yes, it's absolutely necessary to transform into a map. Firestore is a document store, the definition of a document is a json object, which is represented by a map in Dart. BUT, you don't have to do it "manually". Most of us use packages to do the mapping for us.
Freezed or JsonSerializable and then we simply call .toJson and pass that to the function. In addition to that Firestore now supports the .withConverter function so you don't have to do any conversion if you know what type the collection is storing.
So if you know the type of a collection is Defi you can do
final defyCollection = FirebaseFirestore.instance.collection('defis').withConverter<Defi>(
fromFirestore: (snapshot, _) {
final data = snapshot.data()!;
data['id'] = snapshot.id;
return Defi.fromJson(data);
},
toFirestore: (object, _) => object.toJson(),
)
This way you can simply use the defyCollection and the data property or function will be typed to your type.
I have an object that contains a json array , which am trying to store in shared preferences but i don't know how to do so .
This is my model :
import 'dart:convert';
import 'package:deepnrise/models/settings/perimeter.dart';
import 'package:deepnrise/models/user/user_perims.dart';
UserWithPerim user(String str) => UserWithPerim.fromJson(json.decode(str));
class UserWithPerim {
// ignore: non_constant_identifier_names
UserWithPerim({
required this.identifier,
required this.firstName,
required this.lastName,
required this.email,
required this.role,
required this.perimeters,
});
String identifier;
String firstName;
String lastName;
String email;
String role;
List<UserPerimeter> perimeters;
factory UserWithPerim.fromJson(Map<String, dynamic> json) {
return UserWithPerim(
identifier: json['identifier'] ?? "",
firstName: json['firstName'] ?? "",
lastName: json['lastName'] ?? "",
email: json['email'] ?? "",
role: json['role'] ?? "",
perimeters: (json['perimeters'] as List)
.map((p) => UserPerimeter.fromJson(p))
.toList(),
);
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['identifier'] = identifier;
data['firstName'] = firstName;
data['lastName'] = lastName;
data['role'] = role;
data['email'] = email;
data['perimeters'] = perimeters;
return data;
}
}
This the perimeters model :
import 'dart:convert';
Userperimeters(String str) => UserPerimeter.fromJson(json.decode(str));
String UserPerimToJson(UserPerimeter data) => json.encode(data.tojson());
class UserPerimeter {
// ignore: non_constant_identifier_names
UserPerimeter(
{required this.id, required this.label, required this.perimeterId});
// ignore: non_constant_identifier_names
int id;
String label;
int perimeterId;
factory UserPerimeter.fromJson(Map<String, dynamic> json) {
return UserPerimeter(
id: json['id'] ?? "",
label: json['label'] ?? "",
perimeterId: json["perimeterId"] ?? "");
}
Map<String, dynamic> tojson() => {
"id": id,
"label": label,
"perimeterId": perimeterId,
};
}
For now I've two models of my user object , one that contains the perils list and one that doesn't because whenever I try to store my user in shared prefs I get this exception thrown :
Unhandled Exception: type 'UserPerimeter' is not a subtype of type 'Map<String, dynamic>'
This is how am saving and reading my user:
saveUser(value) async {
final prefs = await SharedPreferences.getInstance();
String user = jsonEncode(User.fromJson(value));
prefs.setString(Preferences.USER_KEY, user);
}
Future<User?> getUser() async {
final prefs = await SharedPreferences.getInstance();
if (prefs.containsKey(Preferences.USER_KEY)) {
Map<String, dynamic> userMap =
jsonDecode(prefs.getString(Preferences.USER_KEY) ?? "");
User user = User.fromJson(userMap);
return user;
}
}
Is there a way with which I can store the whole user model with the perils object list without making two models of the user object ? thank you so much in advance.
The work around here to convert your whole json response to string.
save that string into sharedprefs, then you can call it back and decode it using:
var response = json.decode(prefs.getString("response");
So, the full idea:
prefs.setString("response",json.encode(response.body));
using that String as json format again:
MyModel model = MyModel.fromJson(json.decode(prefs.getString("response")));
I hope you find what you need from this idea.
Convert the list of perimeters to list of Json like this:
if (this.perimeters != null) {
data['perimeters'] = this.perimeters!.map((v) => v.toJson()).toList();
}
class Restaurant {
Restaurant({
required this.name,
required this.description,
required this.address,
required this.imageUrl,
});
Restaurant.fromJson(Map<String, Object?> json)
: this(
name: json['name']! as String,
description: json['description']! as String,
address: json['address']! as String,
imageUrl: json['imageUrl'] as String,
);
final String name;
final String description;
final String address;
final String imageUrl;
Map<String, Object?> toJson() {
return {
'name': name,
'description': description,
'address': address,
'imageUrl': imageUrl,
};
}
}
I get the list of restaurants from Firebase and when the user clicks Favorite Icon, I want to save that restaurant locally and retrieve the list of favorite restaurants and show it on another page. I know I can do it on Firebase directly but for now, I want it saved locally.
I am looking forward to hearing from all of you. Thank you.
Like this
List<Restaurant> restaturants = [Restuarant(...),Restuarant(...)];
List<String> encodedRestaturants = restaturants.map((res)=>json.encode(res.toJson())).toList();
//to write
prefs.setStringList("restaturants",encodedRestaturants);
//to read
List<String> decodedRestaturantsString = prefs.getStringList("restaturants");
List<Restaurant> decodedRestaturants = decodedRestaturantsString.map((res)=>Restaturant.fromJson(json.decode(res))).toList();
Instead using SharedPreferences setStringList, you can use setString.
Here excerpt using setStringList:
// sample restaurants.
List<Restaurant> favoriteRestos = [resto1, resto2, resto3];
List<String> favorites = [];
// Generate json for each restaurant
for(var resto in favoriteRestos) {
var json = jsonEncode(resto);
favorites.add(json);
}
// Obtain shared preferences.
final prefs = await SharedPreferences.getInstance();
// saving
await prefs.setStringList('favorites', favorites);
// Reading part
List<String> jsonRestos = prefs.getStringList('favorites')??[];
List<Restaurant> resFavorites = [];
for(var jsonResto in jsonRestos) {
var map = jsonDecode(jsonResto);
var resto = Restaurant.fromJson(map);
resFavorites.add(resto);
}
For setString:
List<Restaurant> favoriteRestos = [resto1, resto2, resto3];
var json = jsonEncode(favoriteRestos);
// Obtain shared preferences.
final prefs = await SharedPreferences.getInstance();
// saving
await prefs.setString('favorites', json);
// reading
var resJson = prefs.getString('favorites')??'';
var parsedJson = jsonDecode(resJson);
List<Restaurant> items = List<Restaurant>.from(parsedJson.map((i) => Restaurant.fromJson(i)));
Note:
You need to update your Restaurant class. See https://docs.flutter.dev/development/data-and-backend/json#serializing-json-inside-model-classes
I have a working json parsing from my commerce API. all are working fine except for storing the add cart product to the shared preferences. How can I achieve this? I got an error of type
'ProductsModel' is not a subtype of type 'Map<String, dynamic>';
Here's my ProductsModel
class ProductsList{
final List<ProductsModel> products;
ProductsList({this.products});
factory ProductsList.fromJSON(List<dynamic> parsedJson){
List <ProductsModel> productsList = new List<ProductsModel>();
productsList = parsedJson.map((i) => ProductsModel.fromJSON(i)).toList();
return new ProductsList(
products: productsList
);
}
}
class ProductsModel {
final int id;
final String name;
final String catalog_visibility;
final String description;
final String short_description;
final String price;
final String regular_price;
final String sale_price;
final String date_created;
final List<CategoriesModel> categories;
final List<ImagesModel> images;
ProductsModel(
{this.id,
this.name,
this.catalog_visibility,
this.description,
this.short_description,
this.price,
this.regular_price,
this.sale_price,
this.date_created,
this.categories,
this.images
});
factory ProductsModel.fromJSON(Map<String, dynamic> parsedJson) {
var categoriesList = parsedJson['categories'] as List;
var imagesList = parsedJson['images'] as List;
List<ImagesModel> dataImages = imagesList.map((i) => ImagesModel.fromJSON(i)).toList();
List<CategoriesModel> dataCategories =
categoriesList.map((i) => CategoriesModel.fromJSON(i)).toList();
return ProductsModel(
id: parsedJson['id'],
name: parsedJson['name'],
catalog_visibility: parsedJson['catalog_visibility'],
description: parsedJson['description'],
short_description: parsedJson['short_description'],
regular_price: parsedJson['regular_price'],
sale_price: parsedJson['sale_price'],
date_created: parsedJson['date_created'],
categories: dataCategories,
images: dataImages
);
}
}
class CategoriesModel {
final int id;
final String name;
CategoriesModel({this.id, this.name});
factory CategoriesModel.fromJSON(Map<String, dynamic> parsedJson) {
return CategoriesModel(id: parsedJson['id'], name: parsedJson['name']);
}
}
class ImagesModel{
final int id;
final String src;
final String name;
ImagesModel({this.id,this.src,this.name});
factory ImagesModel.fromJSON(Map<String,dynamic> parsedJson){
return ImagesModel(
id: parsedJson['id'],
src: parsedJson['src'],
name: parsedJson['name']
);
}
}
and I am testing to stored the ProductsModel only by using this function only
Here's my function
storedCart(products){
SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.setString('cart', products);
}
STEP 1 : Add a toMap() method in your class
Map<String,dynamic> toMap() {
var map = new Map<String, dynamic>();
map["id"] = id;
map["name"] = name;
map["description"] = description;
// Add all other fields
return map;
}
STEP 2 : While storing this in SharedPreferences call the toMap() method on the object
This will return a Map<String,dynamic> representation of your current object.
Map<String,dynamic> productsMap = products.toMap();
STEP 3 : Convert the object to String by using json.encode() and store it !
storedCart(productsMap){
SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.setString('cart', json.encode(productsMap));
}
Note :
Dont forget to import dart:convert
While retrieving the object don't forget to use json.decode()
If you are having trouble understanding why we used json.encode(), try printing the object before and after using the function, you'll notice that when we convert our object to JSON it becomes a big String, Therefore it is possible for us to store it in SharedPreferences using the "putString()" method.
This is my Code to store CartItems.
I want to store it in Shared Preferences as cartItems since on refresh, the data are being lost.
Also I want to have function in the CartItem to check if the Product is in the cart or not so that I can make it checked/unchecked.
class CartItem {
final String id;
final String prodID;
final String title;
final String img;
final double quantity;
final double price;
final double availQuantity;
CartItem({
#required this.id,
#required this.prodID,
#required this.title,
#required this.quantity,
#required this.price,
#required this.img,
this.availQuantity,
});
}
class Cart with ChangeNotifier {
Map<String, CartItem> _items = {};
Map<String, CartItem> get items {
return {..._items};
}
int get itemCount {
return _items.length;
}
void addItem(String productId, double price, String title, String img,
double availQuantity) {
if (_items.containsKey(productId)) {
_items.update(
productId,
(existingCartItem) => CartItem(
id: existingCartItem.id,
title: existingCartItem.title,
img: existingCartItem.img,
price: existingCartItem.price,
quantity: existingCartItem.quantity + 1,
availQuantity: existingCartItem.availQuantity,
),
);
} else {
_items.putIfAbsent(
productId,
() => CartItem(
id: productId,
title: title,
img: img,
price: price,
quantity: 1,
availQuantity: availQuantity,
),
);
}
notifyListeners();
}
}
I tried many ways to convert to string and call setString(),
convert to List too. but couldn't accomplish.
Help needed.
You can store it if you convert the Map to a String as follows:
import 'dart:convert';
...
String mapToStr = json.encode(map);
When you want to get the string out of shared preferences and use it as a Map again, decode it from String to Map again as follows:
Map<String, Object> strToMap = json.decode(mapToStr);
it's possible to see your function that save in sharedPreference,
Do you have try to convert tour Cart in Map<String, dynamic> and save. I think you try to save your directly your Cart class, but it's not allowed, you can only save int, string, List, Map ...
You can check from this code, if the product is in cart or not:
bool checkProductAddedToCart(productId) {
if (_items.containsKey(productId)) {
return true;
} else {
return false;
}
}