0

I can't find any solutions to read contacts from Android and save them in Realm. Anyone done that before?

I know that I will have to use Contacts Provider, but this is all I know. AFAIK, Realm doesn't support Cursor so...what else?

edit:

realm.executeTransaction(new Realm.Transaction() {
        @Override
        public void execute(Realm realm) {
            Contact realmContact = new Contact();
            String filter = "" + ContactsContract.Contacts.HAS_PHONE_NUMBER + " > 0 and "
                    + ContactsContract.CommonDataKinds.Phone.TYPE +"="
                    + ContactsContract.CommonDataKinds.Phone.TYPE_MAIN;

            Cursor phones = getActivity()
                    .getContentResolver()
                    .query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, filter, null, null);

            while (phones.moveToNext()) {
                String id = phones.getString(phones.getColumnIndex(ContactsContract.CommonDataKinds.Phone._ID));
                String name = phones.getString(phones.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
                String phoneNumber = phones.getString(phones.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
                realmContact.setId(id);
                realmContact.setName(name);
                realmContact.setNumber(phoneNumber);
                realmContact.setIsBeingSaved(true);
                realm.insertOrUpdate(realmContact);
            }

            /** merge mechanism */
            realm.where(Contact.class)
                    .equalTo("isBeingSaved", false)
                    .findAll()
                    .deleteAllFromRealm(); // delete all non-saved data
            for(Contact contact : realm.where(Contact.class).findAll()) {
                realmContact.setIsBeingSaved(false); // reset all save state
            }

Contact.class

public class Contact extends RealmObject{

@PrimaryKey
private String id;

@Index
private String name;

@Index
private String number;

@Index
private boolean isBeingSaved;

public String getId() {
    return id;
}

public void setId(String id) {
    this.id = id;
}

public String getName() {
    return name;
}

public void setName(String name) {
    this.name = name;
}

public String getNumber() {
    return number;
}

public void setNumber(String number) {
    this.number = number;
}

public boolean getIsBeingSaved() {
    return isBeingSaved;
}

public void setIsBeingSaved(boolean beingSaved) {
    isBeingSaved = beingSaved;
}

}

jean d'arme
  • 4,033
  • 6
  • 35
  • 70
  • 1
    Reading contacts and storing them in a database are 2 completely separate operations. Start with reading and if that succeeds worry about storing them in realm – Tim Sep 06 '16 at 15:16
  • this should help http://stackoverflow.com/questions/37664661/loading-contacts-and-saving-to-realm-taking-a-very-long-time – FriendlyMikhail Sep 06 '16 at 15:17
  • When the returned value of `copyToRealmOrUpdate()` is ignored, then `insertOrUpdate()` is a more optimal (more memory-efficient) solution. – EpicPandaForce Sep 07 '16 at 11:31
  • 1
    The duplication issue is quite common, if you search for **duplication whatsapp stack overflow** in Google, you'll find other answers. I think the assumption that telephone numbers between people must be unique is a bit hardcore though and even my contacts would break it. – EpicPandaForce Sep 07 '16 at 13:16
  • 1
    @EpicPandaForce http://stackoverflow.com/questions/39371540/removing-contacts-from-realm-in-android :)) – jean d'arme Sep 07 '16 at 13:41

2 Answers2

4

Create RealmObject, read the data from content provider, save data to RealmObject, save data in Realm:

// background thread
Realm realm = null;
try {
    realm = Realm.getDefaultInstance();
    realm.executeTransaction(new Realm.Transaction() {
        @Override
        public void execute(Realm realm) {
            RealmContact realmContact = new RealmContact();
            Cursor phones = getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, null, null, null);
            while (phones.moveToNext()) {
               String name = phones.getString(phones.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
               String phoneNumber = phones.getString(phones.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
               realmContact.setName(name);
               realmContact.setPhoneNumber(phoneNumber);
               realm.insertOrUpdate(realmContact);
            }
        }
    });
} finally {
    if(realm != null) {
        realm.close();
    }
}

EDIT: okay, here's a trick to merging data and removing all data that's not in the list you've saving

public class RealmContract extends RealmObject {
    @PrimaryKey
    private long id;

    @Index
    private String name;

    @Index
    private String phoneNumber;

    @Index
    private boolean isBeingSaved;

    //getters, setters
}

Then merge:

// background thread
Realm realm = null;
try {
    realm = Realm.getDefaultInstance();
    realm.executeTransaction(new Realm.Transaction() {
        @Override
        public void execute(Realm realm) {
            RealmContact realmContact = new RealmContact();
            Cursor phones = getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, null, null, null);
            while (phones.moveToNext()) {
               String id = phones.getString(phones.getColumnIndex(ContactsContract.CommonDataKinds._ID));
               String name = phones.getString(phones.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
               String phoneNumber = phones.getString(phones.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
               realmContact.setId(id);
               realmContact.setName(name);
               realmContact.setPhoneNumber(phoneNumber);
               realmContact.setIsBeingSaved(true);
               realm.insertOrUpdate(realmContact);
            }
            realm.where(RealmContact.class)
                 .equalTo(RealmContactFields.IS_BEING_SAVED, false) // compile 'dk.ilios:realmfieldnameshelper:1.0.0'
                 .findAll()
                 .deleteAllFromRealm(); // delete all non-saved data
            for(RealmContact realmContact : realm.where(RealmContact.class).findAll()) { // realm 0.89.0+
                realmContact.setIsBeingSaved(false); // reset all save state
            }
        }
    });
} finally {
    if(realm != null) {
        realm.close();
    }
}

EDIT: Refer to OP's other question for reading contact data reliably (because there's something up with the Contact LOOKUP_ID and making sure the IDs are correct): Obtaining contacts from content provider without duplicates or invalid contacts, and save to Realm

EpicPandaForce
  • 79,669
  • 27
  • 256
  • 428
  • 1
    This is fantastic! Thank You! – jean d'arme Sep 06 '16 at 16:04
  • One more question: if contact gets deleted on phone it stays in database - how to update that so it will reflect changes? – jean d'arme Sep 06 '16 at 16:41
  • 1
    If you also get ContactsContract.Contacts._ID and use it as primary key, then you can keep track of the users in your app. I'll respond to this with code when I get to a PC on how you can easily add new elements and remove non-existent elements in your write transaction. – EpicPandaForce Sep 06 '16 at 16:50
  • Added merge logic – EpicPandaForce Sep 06 '16 at 17:30
  • This is great! Also, I found it works without try/catch surrounding - not sure about that, but no `Exceptions` appear :) – jean d'arme Sep 07 '16 at 08:17
  • 1
    I do the `try-finally` thing to ensure that the Realm is closed no matter what happens. It's extremely important to close Realm instances on background threads. – EpicPandaForce Sep 07 '16 at 08:31
  • One more question: some of the contacts appear double on the list (those contacts does not have more that one number assigned to then). How can I resolve that? – jean d'arme Sep 07 '16 at 08:45
  • That's... strange... Did you use the second solution in which you also obtain `_ID` from the content provider? Because then every contact you obtain ought to be unique and directly from the contact list. There must be some kind of difference between them. – EpicPandaForce Sep 07 '16 at 08:56
  • I updated my question with actual code. `id` in `Contact`model has `@PrimaryKey` annotation and rest got `@Index`. I found that contacts that are doubled have WhatsApp number as well and it can be found in Contacts as well so maybe ContentProvider interprets it as normal phone number? – jean d'arme Sep 07 '16 at 10:46
  • Apparently contact duplication by Whatsapp is not a "new" problem. Maybe [this works](http://stackoverflow.com/a/34436969/2413303)? (the filter of the content provider) – EpicPandaForce Sep 07 '16 at 11:31
  • Hmm...solution You pointed seems fine, but I suspect that merging mechanism might be broken: when I change name it changes in Realm as well. When I add second phone number to that contact then I have two contacts with different numbers (let it be like that for now), but when I delete one of these numbers Realm still keeps these two. So I think I might still have duplicates because merging is not working - I think I should add additional filters... – jean d'arme Sep 07 '16 at 12:36
  • The merging **does** work. I use this exact same mechanism in production. The problem is your getter setter name – EpicPandaForce Sep 07 '16 at 12:38
  • I updated my question with most recent version of code and added `Contact.class` - might be that I made an error and can't notice that. I noticed that instead of `.equalTo(RealmContactFields.IS_BEING_SAVED, false)` I put simply `"isBeingSaved"` String - could that be a cause? – jean d'arme Sep 07 '16 at 12:44
  • I think the issue might be that `isBeingSaved` should have a getter called `getIsBeingSaved()` and a setter called `setIsBeingSaved()`. The field name itself should work. Your setter is NOT called `setIsBeingSaved()` – EpicPandaForce Sep 07 '16 at 12:55
  • Ups, just updated that - thanks for pointing out. Still have duplicates - I think I must install Stetho to see what is going on in database. From code logic perspective it should work like a charm. I read somewhere about making unique combos - like there can be only one `Contact` named `A` with number `123`, but can't find it now :/ – jean d'arme Sep 07 '16 at 13:07
  • You've tried clearing data and filling it up from scratch, right? :P But yeah, `stetho-realm` might help. Unfortunately this duplication thing is currently beyond my scope at the moment. :| The merge should definitely work though, now that the setters/getters have the right name – EpicPandaForce Sep 07 '16 at 13:14
  • Yes, yes, I cleaned it up (I even have in configuration `deleteRealmIfMigrationNeeded()` ) :) but the problem is when I add new number to contact and then I delete it (just second number). Same with contact - when I add it Realm gets it, but when I delete it it's not removed. – jean d'arme Sep 07 '16 at 13:25
  • 1
    I consider _this_ question answered (because the comment tree is getting too long) and I guarantee that the merge works because I use this in production and stuff, but if you ask a new question regarding the deletion failing, I'll answer that one :p – EpicPandaForce Sep 07 '16 at 13:31
  • Okay, I'll keep that :D Thanks for helping out - I would buy You a beer if You'd be from somewhere near! – jean d'arme Sep 07 '16 at 13:33
  • Apparently this answer for obtaining the contacts themselves isn't perfect, for that refer to [this question](http://stackoverflow.com/questions/30609515/how-do-implicit-joined-columns-work-with-android-contacts-data) – EpicPandaForce Sep 08 '16 at 08:58
0

You can create a Model Class to store desired data from contact

then using Cursor get data and set data in model class.

Save the cursor data in list of model object.

Then insert all model object in single transaction as per object transaction has overhead and slow our app

Dharmaraj
  • 1,256
  • 13
  • 12