3

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>'

Anan Saadi
  • 328
  • 4
  • 21
  • Please share your `Contact` model. Probably you have another model object in your contact. – ישו אוהב אותך Aug 26 '21 at 15:50
  • `Contact` is actually an object from `contacts_services` plugin and thus you can find the full model [here](https://github.com/lukasgit/flutter_contacts/blob/master/lib/contacts_service.dart) and it contains `toMap` and `fromMap` methods but I couldn't get them to work, `toJosn` and `fromJson` are additions made by me and they also didn't work so the problem appears to be in the `loadContacts` part @ישו אוהב אותך – Anan Saadi Aug 27 '21 at 10:35
  • It would be easier if you use `sqflite` or `Hive` rather than `Shared Preferences` because it used to save small variables not a `List` and you will get an error if you try to save large variables. – Mohammed Alfateh Aug 28 '21 at 22:12
  • Where do you stuck now, on ` final List decoded = json.decode(saved);` or next line of code.? – Arul Aug 31 '21 at 15:06
  • The next line, converting the String back to a list of `Contact` @Arul Mani – Anan Saadi Sep 02 '21 at 20:22

3 Answers3

1

this probably will solve your problem:

var contacts = decoded.map((c) => Contact.fromJson(Map<String, dynamic>.from(c)));
  • the problem above was when trying to encode, although it's my fault for not clarifying, anyway I solved it by using ```toEncodeable``` in ```josnDecode``` but now I am getting ```Unhandled Exception: type 'MappedListIterable' is not a subtype of type 'List'``` when decoding although I am using the code you provided above @Emre Akçadağ – Anan Saadi Aug 15 '21 at 16:55
  • this is my method to use jsonlist to modelList `static List? fromJsonList(List? data) => data?.map((m) => m == null ? null : User.fromJson(Map.from(m))).toList();` hope it helps you. – Emre Akçadağ Aug 16 '21 at 18:08
  • Please explain about how this code works and is helpful rather than just posting the code. – Rahul Vashishtha Aug 28 '21 at 11:00
1

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"},<…>
Assaf
  • 668
  • 5
  • 5
  • thanks for your answer, I wasn't able to make this work by my own and gave up, I'll check this solution later – Anan Saadi Jan 14 '22 at 13:15
  • You might also want to do a pull request since the serializing method included in the package doesn't work – Anan Saadi Jan 14 '22 at 13:17
  • Happy to help. I hope that it works for you. Someone opened an issue asking for serialization to be included in the package and it didn't get any traction https://github.com/lukasgit/flutter_contacts/issues/162. I'll post a link to this thread and see if they're interested in the contribution. – Assaf Jan 19 '22 at 21:01
0

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

Lam Thanh Nhan
  • 486
  • 4
  • 5
  • you're code is returning `Converting object to an encodable object failed: Instance of 'MappedListIterable`, I have also tried `toEncodable` to encode the list, like this: `final String List = jsonEncode(contacts, toEncodable: (c)=> c.toJson() );` but it would return when loading `Unhandled Exception: type 'MappedListIterable' is not a subtype of type 'List'`.@Things of N – Anan Saadi Aug 28 '21 at 10:34
  • @AnanSaadi I just updated dartpad example. Please give it a try and let me know the issue. – Lam Thanh Nhan Aug 28 '21 at 14:16
  • I have copied your code exactly yet still receiving the same error `Converting object to an encodable object failed: Instance of 'MappedListIterable'`, I think the issue is that class `Contact` includes a list of `Iterable` and type called `Uint8List`, can you take a look at the [full model](https://github.com/lukasgit/flutter_contacts/blob/master/lib/contacts_service.dart#L181) and see if it needs something else in order to encode it ? @Things of N – Anan Saadi Aug 29 '21 at 11:11
  • @AnanSaadi I have updated the code that including `SubContact List` in Contact class. I think you have to refactor your code from using Iterable to List (which is extended from Iterable with a lot of benefit features). The code [here](https://dartpad.dev/?id=256a3af84f356bc6915fb9e1e8a8f7a4&null_safety=true) – Lam Thanh Nhan Aug 29 '21 at 12:31
  • Can you please explain what `SubContact` does and what should I do to `emails` and `phones` in order to get them working ? @Things of N – Anan Saadi Aug 29 '21 at 19:10
  • The `SubContact` is similar to `Item` or `PostalAddress` in your script. I have seen a pull request that already refactored the code from using Iterable to List [here](https://github.com/lukasgit/flutter_contacts/pull/251). The way using `List` or `List` is the same with `List`. Please check it and let me know if it's usable or not. You can use `iterable.toList()` to convert any `Iterable` object to `List` object. @AnanSaadi – Lam Thanh Nhan Aug 31 '21 at 04:35
  • I have updated the way to add the modified package to the current project. – Lam Thanh Nhan Aug 31 '21 at 05:07
  • Hey, I didn't get the time to check your solution but I believe it's the correct answer so I have awarded you the bounty and will update you later on wither it has worked or not @Things of N – Anan Saadi Sep 02 '21 at 20:32