Problem serializing a list of objects in flutter and saving it using shared preferences

Viewed 922

I have been trying to save a list of type Contact (which is a class from contacts_service) package by serializing it using toJosn and fromJson and then saving it as String in Shared Preferences

to & fromJosn :

 Contact.fromJson(Map<String, dynamic> json):
        identifier = json['identifier'],
        displayName = json['displayName'],
        givenName = json['givenName'],
        middleName = json['middleName'],
        prefix = json['prefix'],
        suffix = json['suffix'],
        familyName = json['familyName'],
        company = json['company'],
        avatar = json['avatar'],
        androidAccountType = json['androidAccountType'],
        jobTitle = json['jobTitle'],
        androidAccountTypeRaw = json['androidAccountTypeRaw'],
        androidAccountName = json['androidAccountName'],
        emails = json['emails'], phones = json['phones'],
        postalAddresses = json['postalAddresses'],
        birthday = json['birthday'];
  Map<String, dynamic> toJson() => {
    'identifier':identifier,
    'displayName':displayName,
    'givenName':givenName,
    'middleName':middleName,
    'prefix':prefix,
    'suffix':suffix,
    'familyName':familyName,
    'company':company,
    'avatar':avatar,
    'jobTitle':jobTitle,
    'androidAccountTypeRaw':androidAccountTypeRaw,
    'androidAccountName':androidAccountName,
    'emails':emails,
    'phones':phones,
    'postalAddresses':postalAddresses,
    'birthday':birthday,
    'androidAccountType':androidAccountType};

saving & loading:

  void saveContacts() async{
    SharedPreferences prefs = await SharedPreferences.getInstance();
    final String List = json.encode(contacts.map((contact) => contact.toJson()));
    await prefs.setString('contactList', List);
  }
  void loadConatcs() async{
    SharedPreferences prefs = await SharedPreferences.getInstance();
    final String saved = prefs.getString('contactList');
    final List<dynamic> decoded = json.decode(saved);
    contacts = decoded.map((contact) => Contact.fromJson(contact));
  }

but I am gettign an error Converting object to an encodable object failed: Instance of 'MappedListIterable<dynamic, Item>'

also note that this is my first time trying to serialize and object so might have missed some things up in to & fromJosn

edit:

I also tried using toEncodable

final String List = jsonEncode(contacts, toEncodable: (c)=> c.toJson());        

but it would return an exception : type 'MappedListIterable<dynamic, Contact>' is not a subtype of type 'List<Contact>'

3 Answers

this probably will solve your problem:

var contacts = decoded.map((c) => Contact.fromJson(Map<String, dynamic>.from(c)));

It took me a while to parse through these answers to get to a block of working code for serializing the Contacts returned from contacts_service.

I used json_serializable to generate the toJson methods, added them to the existing classes using extension, and used the json.encode @Silris posted.

Here's the working block of code:

import 'package:contacts_service/contacts_service.dart';
import 'dart:convert';

// Copied generated toJson methods
extension ContactToJson on Contact {
  Map<String, dynamic> toJson() => <String, dynamic>{
        'identifier': identifier,
        'displayName': displayName,
        'givenName': givenName,
        'middleName': middleName,
        'prefix': prefix,
        'suffix': suffix,
        'familyName': familyName,
        'company': company,
        'jobTitle': jobTitle,
        'androidAccountTypeRaw': androidAccountTypeRaw,
        'androidAccountName': androidAccountName,
        'AndroidAccountType': androidAccountType,
        'emails': emails?.map((item) => item.toJson()).toList(),
        'phones': phones?.map((item) => item.toJson()).toList(),
        'postalAddresses':
            postalAddresses?.map((address) => address.toJson()).toList(),
        'birthday': birthday?.toIso8601String(),
        'avatar': avatar,
      };
}

extension ItemToJson on Item {
  Map<String, dynamic> toJson() => {
        'value': value,
        'label': label,
      };
}

extension PostalAddressToJson on PostalAddress {
  Map<String, dynamic> toJson() => <String, dynamic>{
        'label': label,
        'street': street,
        'city': city,
        'postcode': postcode,
        'region': region,
        'country': country,
      };
}

serializeContacts(List<Contact> contacts) => json.encode(contacts.map((contact) => contact.toJson()).toList());

Using this with the supporting permissions and services in a button looks like:

onPressed: () async {
  // Either the permission was already granted before or the user just granted it.
  if (await Permission.contacts.request().isGranted) {
    List<Contact> contacts = await ContactsService.getContacts(
        withThumbnails: false, photoHighResolution: false);
    // Imported from above file
    print(serializeContacts(contacts));
  }
},

Pressing this button in the iphone simulator will print:

[{"identifier":"F57C8277-585D-4327-88A6-B5689FF69DFE","displayName":"Anna Haro","givenName":"Anna","middleName":"","prefix":"","suffix":"","familyName":"Haro","company":"","jobTitle":"","androidAccountTypeRaw":null,"androidAccountName":null,"AndroidAccountType":null,"emails":[{"value":"anna-haro@mac.com","label":"home"}],"phones":[{"value":"555-522-8243","label":"home"}],"postalAddresses":[{"label":"home","street":"1001  Leavenworth Street","city":"Sausalito","postcode":"94965","region":"CA","country":"USA"}],"birthday":"1985-08-28T00:00:00.000","avatar":null},{"identifier":"AB211C5F-9EC9-429F-9466-B9382FF61035","displayName":"Daniel Higgins Jr.","givenName":"Daniel","middleName":"","prefix":"","suffix":"Jr.","familyName":"Higgins","company":"","jobTitle":"","androidAccountTypeRaw":null,"androidAccountName":null,"AndroidAccountType":null,"emails":[{"value":"d-higgins@mac.com","label":"home"}],"phones":[{"value":"555-478-7672","label":"home"},{"value":"(408) 555-5270","label":"mobile"},<…>

UPDATE: You can use the refactored package from using Iterable to List (Still waiting for approval when I write this update) here, you still need to change your code as I have posted below to make it work. It will remove the conflict between JSON type (which uses [] in its format) and Iterable type (uses ()) because List type is also using [] in its format.

This is how to add another version of a package to your project:

Change this line in pubspec.yaml from:

dependencies:
  contacts_service: any

to:

dependencies:
  contacts_service:
    git: https://github.com/Abion47/flutter_contacts

YOU STILL NEED TO DO THIS TO MAKE IT WORK:

You just need to change this line from:

final String list = json.encode(contacts.map((contact) => contact.toJson()));

to

final String list = json.encode(contacts.map((contact) => contact.toJson()).toList());

If you print the first list you will get something like below which is not a JSON type:

({name: a, number: 123}, {name: b, number: 123})

and the second will look like this:

[{name: a, number: 123}, {name: b, number: 123}]

The .toList() will change the runtimeType from MappedListIterable<Contact, dynamic> to List<dynamic>


Example: dartpad

Related