1

I have a list contacts that stores Contact which is a type acquired from contact_services package

List<Contact> contacts = [];

and I store the List using SharedPrefrences by first jsonEncode it and then save it

void saveContacts() async{
    SharedPreferences prefs = await SharedPreferences.getInstance();
    await prefs.setString('list', jsonEncode(contacts));
}

but when I try to load the list it returns an exception type 'List<dynamic>' is not a subtype of type 'List<Contact>'

void loadList() async{
    SharedPreferences prefs = await SharedPreferences.getInstance();
    contacts = await jsonDecode(prefs.getString('list'));

}

Updated code to highlight changes :

this is the entire saveContacts function :

  void saveContacts() async{
    SharedPreferences prefs = await SharedPreferences.getInstance();
    var json = jsonEncode(contacts, toEncodable: (e) => e.toMap());
    await prefs.setString('list', jsonEncode(json));
  }

but I am receiving error : The method 'toMap' isn't defined for the type 'Object'.

contacts is just a List that stores Contact type

List<Contact> contact;

originally contact was stored in a separate folder (global) in order to be easily accessible, but this doesn't affect the outcome of the jsonEncode

Anan Saadi
  • 328
  • 4
  • 21
  • Does this answer your question? [How to Deserialize a list of objects from json in flutter](https://stackoverflow.com/questions/51053954/how-to-deserialize-a-list-of-objects-from-json-in-flutter) – ישו אוהב אותך Jul 15 '21 at 17:30
  • no since the problem isn't accessing information in the josn string but rather how to turn it from a String back to Contact type. -@ישו אוהב אותך – Anan Saadi Jul 18 '21 at 18:09

2 Answers2

2

Update:

I originally thought the problem could be solved by casting but diving into json.dart, I found the following comments:

  /// If value contains objects that are not directly encodable to a JSON
  /// string (a value that is not a number, boolean, string, null, list or a map
  /// with string keys), the [toEncodable] function is used to convert it to an
  /// object that must be directly encodable.
  ///
  /// If [toEncodable] is omitted, it defaults to a function that returns the
  /// result of calling `.toJson()` on the unencodable object.
  /// Directly serializable values are [num], [String], [bool], and [Null], as
  /// well as some [List] and [Map] values. For [List], the elements must all be
  /// serializable. For [Map], the keys must be [String] and the values must be
  /// serializable.
  ///
  /// If a value of any other type is attempted to be serialized, the
  /// `toEncodable` function provided in the constructor is called with the value
  /// as argument. The result, which must be a directly serializable value, is
  /// serialized instead of the original value.

TLDR; If the value parameter contains an object, we also have to provide toEncodable if the class doesn't have toJson implementation. And since in your case you can't provide toJson implementation for the class, the only way to properly encode the object is by providing toEncodable. I've implemented an example for your understanding:

import 'dart:convert';

class Class {
  String name;
  Class(this.name);

  @override
  String toString() => name;
}

void main() {
  final list = <Class>[
    Class('a'),
    Class('b'),
    Class('c'),
  ];

  final encoded = json.encode(list, toEncodable: (c) {
    if (c is Class) {
      return {'name': c.name};
    }
  });
  print(encoded);
  
  // Save and retreive `encoded`
  
  print((json.decode(encoded) as List).map((map) {
    if (map.containsKey('name')) {
      return Class(map['name']);
    }
  }));
}

addendum: Instead of providing a custom implementation of toEncodable and creating a Contact from json, try out the toMap and fromMap already defined.


Original answer:

You can convert the type of list using the cast method on a List.

So first convert the decoded data using as to a List and then apply cast on it to get the desired type.

contacts = await (jsonDecode(prefs.getString('list')) as List).cast<Contact>();
happy-san
  • 810
  • 1
  • 7
  • 27
  • I am getting an exception `type 'String' is not a subtype of type 'Contact' in type cast` -@happy_san – Anan Saadi Jul 18 '21 at 17:56
  • could you provide me the code that you updated? – happy-san Jul 18 '21 at 19:50
  • I copied the code you provided exactly, I believe the problem is that once the json string is decoded it comes out as a string of rows instead of a list and thus the cast method doesn't know how to convert it into `Contact` type - @happy_san – Anan Saadi Jul 18 '21 at 20:29
  • @AnanSaadi did you try my solution? – happy-san Jul 22 '21 at 09:00
  • Hey, I was busy so I didn't check the code but I have tried it and I get an error : `error: The method 'toMap' isn't defined for the type 'Object'.` @happy_san – Anan Saadi Jul 25 '21 at 18:33
  • You really should post the latest code as an edit to your question. Otherwise nobody can help you with the limited information. – happy-san Jul 26 '21 at 11:49
  • Sorry for not replying again, any way I just changed you code and added `toMap` , like this: `jsonEncode(global.cont, toEncodable: (e) => e.toMap());` -@happy_san – Anan Saadi Aug 02 '21 at 15:49
  • Please edit your question and paste the relevant updated code. I cannot tell from this what `global.cont` is or what its type is. Therefore I won't be able to help you by looking at the snippets. – happy-san Aug 02 '21 at 19:33
  • I have updated my code with the entire function @happy_san – Anan Saadi Aug 02 '21 at 22:13
  • Update the `toEncodable` function like it is in my answer. Instead of `return {'name': c.name};` do `return c.toMap();` – happy-san Aug 03 '21 at 17:02
  • the saving finally worked but I am having issues loading it back with `fromMap` , I have tried this code `contacts = (jsonDecode(prefs.getString('list'))as List ).map((e) {return e.fromMap();});` but receiving an exception : `'String' is not a subtype of type 'List' in type cast` @happy_san – Anan Saadi Aug 05 '21 at 16:15
  • Update `return e.fromMap();` to `return Contact.fromMap(e);` – happy-san Aug 05 '21 at 16:39
2

You need to cast to Map<String, dynamic> then convert the json to your Contact. Here a working example to convert the list of Contact to json text and read it again as List of contact. (go to parseContacts function on code below).

import 'dart:convert';

class Contact {
  final String name;
  final String phone;

  Contact(this.name, this.phone);

  Contact.fromJson(Map<String, dynamic> json)
      : name = json['name'],
        phone = json['phone'];

  Map<String, dynamic> toJson() => {
        'name': name,
        'phone': phone,
      };
}

void main() {
  List<Contact> contacts = [];
  for (int i = 0; i < 10; i++) {
    contacts.add(Contact("name$i", i.toString()));
  }
  
  var jsonText = jsonEncode(contacts);
  print(jsonText);

  var contactsFromJson = parseContacts(jsonText);
  for (var item in contactsFromJson) {
    print(item.name);
  }
}

List<Contact> parseContacts(String jsonText) {
  final parsed = jsonDecode(jsonText).cast<Map<String, dynamic>>();

  return parsed.map<Contact>((json) => Contact.fromJson(json)).toList();
}

For more details, you can read the example at https://flutter.dev/docs/cookbook/networking/background-parsing#complete-example

ישו אוהב אותך
  • 28,609
  • 11
  • 78
  • 96
  • Hey, `Contact` is a type acquired from the plugin `contacts_service`, and thus it's not a josn file and you can't use `fromJson` to extract data out of it. by the way `Contact` just consists of a string that is a phone number like this: `+123456789`, but I have to convert it to `Contact` in order to use it in the rest of the plugin. -@ישו אוהב אותך – Anan Saadi Jul 18 '21 at 20:59
  • @AnanSaadi: Did you meant this https://pub.dev/packages/contacts_service package? You can try the following code to encode: `var json = jsonEncode(contacts, toEncodable: (e) => e.toMap());` then in `parseContacts` change `Contact.fromJson(json)).toList();` to `Contact.fromMap(json)).toList();` you need to use `Contact` object from the library. See the related code: https://github.com/lukasgit/flutter_contacts/blob/master/lib/contacts_service.dart#L224 – ישו אוהב אותך Jul 18 '21 at 21:57
  • I am getting error: `error: The method 'toMap' isn't defined for the type 'Object'.` - @ישו אוהב אותך – Anan Saadi Jul 19 '21 at 19:11