Content Observer onChange method called twice after 1 change in cursor
Asked Answered
B

5

17

I have an app in which I am hoping to send details in the android contact list to a remote server, so that the user can see his contacts online. To do this I want to notify the remote server of any changes made on the phone to the contacts list.

I have set up a ContentObserver on the 'ContactsContract.Contacts.CONTENT_URI' from a service that gets started when the phone is booted.

I have a number of quiestions, the first 2 are incidental, the third is my major concern.

1: Once I have set up a service that registers a ContentObserver on my Cursor, does that observer only exist within the service? I mean, if the service is killed, does the contentObserver continue to observe?

2: I suspect the answer is no, but I will ask anyway. Is there anyway of knowing which contact being updated is triggering the onchange method of my contentObserver? currently I have to compile the list of all teh contacts on the phone and send them off to my remote server, it would be so much easier to just send details of the contacts being updated.

3: This is my main question, when I make a change to my Contact List the onChange method is being fired twice in quick succession. 1 change, 2 calls. Is there anyway of managing this?

    public class ContactService extends Service {

    JSONArray contactList;

    @Override
    public IBinder onBind(Intent arg0) {
        return null;
    }

    @Override
    public void onCreate() {
        Log.i("C2DM","content observers initialised");
        super.onCreate();



        //Call Log Content Provider observer

        MyContentContactsObserver contactsObserver = new MyContentContactsObserver();
        ContactService.this.getContentResolver().registerContentObserver (ContactsContract.Contacts.CONTENT_URI, true, contactsObserver);   

    }

    private class MyContentContactsObserver extends ContentObserver {

        public MyContentContactsObserver() {
            super(null);
        }

        @Override
        public void onChange(boolean selfChange) {
            super.onChange(selfChange);    
            Log.i("LOG","detected change in contacts: "+selfChange);
        }
    }
}

REsults in 2 quick lines in my logCat:

 detected change in contacts: false 
 detected change in contacts: false
Barret answered 16/4, 2012 at 12:23 Comment(0)
B
31

i did a similar implementation and had set a ContentObserver over calendar's events URI. and i faced all the problems, you are facing now. So your question with my solutions/suggestions goes here....

1) Once I have set up a service that registers a ContentObserver on my Cursor, does that observer only exist within the service? I mean, if the service is killed, does the contentObserver continue to observe?

No, it will not observe the contents your contents over URI. but there is solution to this. you can create an always running service. This service will be created again as soon as it is dismissed due to some memory issue or other unexpected dismissal. It will always continue to run unless it is dismissed explicitly. To create such service override OnStartCommand(Intent intent, int flags, final int startId) in your Service class and return START_STICKY. Have a look at the code below.

public int onStartCommand(Intent intent, int flags, final int startId) {

    String authenticationKey = ((MainApplication) getApplicationContext()).getDbConnector().getUserAuthDefault() ;
    // if user is not registered yet, stop this service, it may be called from boot reciever.
    if(authenticationKey == null){
        stopSelf();
    }

    // restart, if we get killed.
    return START_STICKY;
}  

.

2: I suspect the answer is no, but I will ask anyway. Is there anyway of knowing which contact being updated is triggering the onchange method of my contentObserver? currently I have to compile the list of all teh contacts on the phone and send them off to my remote server, it would be so much easier to just send details of the contacts being updated.

You again guessed it right.:). the answer is No.
There is no way to know specifically which contact has been updated. ContentObserver just provide an event to let you know that some change has been made to the data pointed by the uri. But i think you are handling the situation inefficiently, as you are compiling list of all the contacts and sending them back to the server.
I would suggest you to maintain a list of contacts, which are successfully sent to server, in the local storage. And next time when you get a call in onChange() method of contentObserver you fetch all contacts from content_URI, compare them with previously stored list and send only updated contacts to server.

3: This is my main question, when I make a change to my Contact List the onChange method is being fired twice in quick succession. 1 change, 2 calls. Is there anyway of managing this?

Yes this happens, but i guess you are in wrong impression. In my case it used to fire randomly twice or thrice or even more times. so i had set a threshold_time interval. A time interval within which if ContentObserver is fired again, then i would not process it. I did something like this..

long lastTimeofCall = 0L;
long lastTimeofUpdate = 0L;
long threshold_time = 10000;

    /* (non-Javadoc)
     * @see android.database.ContentObserver#onChange(boolean)
     */
    @Override
    public void onChange(boolean selfChange) {
        super.onChange(selfChange);

        Log.d("content service", "on change called");

        lastTimeofCall = System.currentTimeMillis();

        if(lastTimeofCall - lastTimeofUpdate > threshold_time){

         //write your code to find updated contacts here

          lastTimeofUpdate = System.currentTimeMillis();
        }

    }  

Hope these suggestions/solutions would help.

Beezer answered 25/5, 2012 at 10:25 Comment(6)
+1, Does 10000ms second works in all cases without fail, have you taken any feedback on this from users, thanks?Soapbox
i havent gotten any responses from users regarding this. I wont say its best way of implementation but its working good as of now. its a kind of patch that we had to implement.Beezer
I more like currentTime = System.CurrentTimeMillis() if (nextCallTime < treshold) nextCallTime = currentTime + treshold but otherwise nice solution.Theresatherese
Also, with 10second threshold, we'll miss those 'real' changes which are happening under 10 second.. like a user change 2 contacts under 10 second then only 1st will be synced as 2nd will be ignored. What to do in that case? In my research, fake second/third call could be as long as after 3,4 seconds..Diastasis
When you receive the call next time you will have to iterate through all the data provided by content provider and match it against your database. That is the only way i could see to make sure nothing gets ignored.Beezer
Poor implementantion by Google. Couldn't they just make it fire once or let you filter out unnecessary changes?Abnaki
H
13

Consider how you register your observer. The second argument of registerContentObserver is boolean notifyForDescendents, you have set it to true. so you will be notified when either ContactsContract.Contacts.CONTENT_URI or any of its descandants uris were changed. It may be that some operation for contact with given id triggers notification both for the specific contact uri and global contacts uri - with second argument being true your observer observes both.

Another thing is that You can register content observer for specific contact id uri - ContentUris.withAppendedId(ContactsContract.Contacts.CONTENT_URI, contactId). This however has nothing to do with your issue but registering separate observer for every contact looks rather ugly ;)

Another suggestion: Maybe keep observer as final field not recreate it in onCreate (but only register it there).

public class ContactService extends Service {

    private final ContentObserver contactsObserver = new ContentObserver(null) {

        @Override
        public void onChange(boolean selfChange) {
            super.onChange(selfChange);    
            Log.i("LOG","detected change in contacts: "+selfChange);

            //your code here
        }
    };

    @Override
    public IBinder onBind(Intent arg0) {
        return null;
    }

    @Override
    public void onCreate() {

        super.onCreate();

        getContentResolver().registerContentObserver(
                ContactsContract.Contacts.CONTENT_URI, true, contactsObserver);   
    }

    @Override
    public void onDestroy() {

        getContentResolver().unregisterContentObserver(contactsObserver);

        super.onDestroy();
    }
}
Hershelhershell answered 26/5, 2012 at 15:23 Comment(1)
notifydescands false doesn't solve it for me for the call logAbnaki
R
5

To come right to your nr. 3: How much time is in between the two calls to your observer? I think your observer gets called twice because of the contacts sync, which also updates the database. Ie. first time is your actual update, second time is when the sync finished and registers its sync values in the same contact row.

Could you turn off sync and see if it still gets called twice?

Regina answered 16/9, 2012 at 13:32 Comment(2)
Ahh, thats a good call. Ive since worked around the issue with the advice offered above, but your reply makes good sense.Barret
totally makes sense, didn't tried but making sense.. and what if this is the case? still i think we need to use some threshold scene..Diastasis
E
0

you may have two or more instances of ContactService which all of them are registed the same ContactsContract.Contacts.CONTENT_URI.

Edme answered 11/8, 2016 at 1:7 Comment(0)
T
-1

Faced the same problem in my own code and found out that I was calling registerContentObserver() twice. Although it was for the exact same URI and within the same class my onChange(boolean selfChange) method was called twice as a result.

Topdrawer answered 12/11, 2014 at 14:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.