31

Following the last section in the GCM: Getting Started guide, there's some book-keeping to be done after receiving the results.

Quoting from the guide:

It's now necessary to parse the result and take the proper action in the following cases:

  • If the message was created but the result returned a canonical registration ID, it's necessary to replace the current registration
    ID with the canonical one.
  • If the returned error is NotRegistered, it's necessary to remove that registration ID, because the application was uninstalled from the device.

Here's a code snippet that handles these 2 conditions:

if (result.getMessageId() != null) {
 String canonicalRegId = result.getCanonicalRegistrationId();
 if (canonicalRegId != null) {
   // same device has more than on registration ID: update database
 }
} else {
 String error = result.getErrorCodeName();
 if (error.equals(Constants.ERROR_NOT_REGISTERED)) {
   // application has been removed from device - unregister database
 }
}

The guide above refers to a single result, and not to the multicast case. I'm not sure how to handle the multicast case:

    ArrayList<String> devices = new ArrayList<String>();

    for (String d : relevantDevices) {
        devices.add(d);
    }

    Sender sender = new Sender(myApiKey);
    Message message = new Message.Builder().addData("hello", "world").build();
    try {
        MulticastResult result = sender.send(message, devices, 5);

        for (Result r : result.getResults()) {
            if (r.getMessageId() != null) {
                String canonicalRegId = r.getCanonicalRegistrationId();
                if (canonicalRegId != null) {
                    // same device has more than on registration ID: update database
                    // BUT WHICH DEVICE IS IT?
                }
            } else {
                String error = r.getErrorCodeName();
                if (error.equals(Constants.ERROR_NOT_REGISTERED)) {
                    // application has been removed from device - unregister database
                    // BUT WHICH DEVICE IS IT?
                }
            }
        }
    } catch (IOException ex) {
        Log.err(TAG, "sending message failed", ex);
    }

I submit a list of devices, and receive back a list of results. The Result object doesn't contain the registration id, but only a canonical id if the first is obsolete. It is undocumented if the two lists are co-related (ie. preserves order and size).

How can I be sure which result refer to which device?

-- UPDATE

I've pasted a snippet of the solution in a separate answer below

Amir Uval
  • 14,425
  • 4
  • 50
  • 74

3 Answers3

21

The results are in the order of your registration_id array that you sent to GCM server. e.g. if your registration_ids are:

[id1, id4, id7, id8]

Then the results array you get will have same order for id1, id4, id7, and id8.

You just need to parse each result accordingly, e.g. if the 2nd result has 'message_id' and 'registration_id' of 'id9', you know 'id4' is now obsolete and should be replaced by id9.

azgolfer
  • 15,087
  • 4
  • 49
  • 46
  • thanks! I've just found this information in the GCM google group (https://groups.google.com/forum/#!topic/android-gcm/DCHHQwqTs8M). It takes time for new API's to get proper docs.. – Amir Uval Jul 21 '12 at 20:50
5

For the readers convenience, here is a snippet that handles response for multiple devices

public void sendMessageToMultipleDevices(String key, String value, ArrayList<String> devices) {

        Sender sender = new Sender(myApiKey);
        Message message = new Message.Builder().addData(key, value).build();
        try {
            MulticastResult result = sender.send(message, devices, 5);
            MTLog.info(TAG, "result " + result.toString());


            for (int i = 0; i < result.getTotal(); i++) {
                Result r = result.getResults().get(i);

                if (r.getMessageId() != null) {
                    String canonicalRegId = r.getCanonicalRegistrationId();
                    if (canonicalRegId != null) {
                        // devices.get(i) has more than on registration ID: update database

                    }
                } else {
                    String error = r.getErrorCodeName();
                    if (error.equals(Constants.ERROR_NOT_REGISTERED)) {
                        // application has been removed from devices.get(i) - unregister database
                    }
                }
            }
        } catch (IOException ex) {
            MTLog.err(TAG, "sending message failed", ex);
        }
    }
Amir Uval
  • 14,425
  • 4
  • 50
  • 74
  • The Result class has 3 fields: messageId, canonicalRegistrationId and errorCode. All relevant cases are covered in the code snippet. canonicalRegistrationId is an updated gcm key. – Amir Uval Nov 12 '12 at 13:14
  • `devices.get(i) has more than one registration ID: update database` - what should one do here? Delete devices.get(i) from the database or delete the device with the canonicalRegistrationId from the result? Or check if a device with the canonicalRegistrationId exists in the database? If yes, then delete the other one, if false, change the id of the other one? – Konsumierer Dec 10 '12 at 11:24
  • You should replace devices.get(i), which is the old regId, with the new canonicalRegistrationId = new regId. You might have already the new regId, if you registered the same user (thinking it's a new user) with a new regId, and than tried to reach him with the old id. – Amir Uval Dec 10 '12 at 15:24
3

This solution is done by google developer sample GCM Demo application note the asyncSend for multicasting handle

List<GcmUsers> devices=SearchRegisterdDevicesByCourseCommand.execute(instructorId, courseId);
    String status;
    if ( devices.equals(Collections.<GcmUsers>emptyList())) {    
      status = "Message ignored as there is no device registered!";
    } else {
      // NOTE: check below is for demonstration purposes; a real application
      // could always send a multicast, even for just one recipient
      if (devices.size() == 1) {
        // send a single message using plain post
        GcmUsers gcmUsers = devices.get(0);
        Message message = new Message.Builder().build();
        Result result = sender.send(message, gcmUsers.getGcmRegid(), 5);
        status = "Sent message to one device: " + result;
      } else {
        // send a multicast message using JSON
        // must split in chunks of 1000 devices (GCM limit)
        int total = devices.size();
        List<String> partialDevices = new ArrayList<String>(total);
        int counter = 0;
        int tasks = 0;
        for (GcmUsers device : devices) {
          counter++;
          partialDevices.add(device.getGcmRegid());
          int partialSize = partialDevices.size();
          if (partialSize == MULTICAST_SIZE || counter == total) {
            asyncSend(partialDevices);
            partialDevices.clear();
            tasks++;
          }
        }
        status = "Asynchronously sending " + tasks + " multicast messages to " +
            total + " devices";
      }
    }
    req.setAttribute(HomeServlet.ATTRIBUTE_STATUS, status.toString());






private void asyncSend(List<String> partialDevices) {
    // make a copy
    final List<String> devices = new ArrayList<String>(partialDevices);
    threadPool.execute(new Runnable() {

      public void run() {
        Message message = new Message.Builder().build();
        MulticastResult multicastResult;
        try {
          multicastResult = sender.send(message, devices, 5);
        } catch (IOException e) {
          logger.log(Level.SEVERE, "Error posting messages", e);
          return;
        }
        List<Result> results = multicastResult.getResults();
        // analyze the results
        for (int i = 0; i < devices.size(); i++) {
          String regId = devices.get(i);
          Result result = results.get(i);
          String messageId = result.getMessageId();
          if (messageId != null) {
            logger.fine("Succesfully sent message to device: " + regId +
                "; messageId = " + messageId);
            String canonicalRegId = result.getCanonicalRegistrationId();
            if (canonicalRegId != null) {
              // same device has more than on registration id: update it
              logger.info("canonicalRegId " + canonicalRegId);
              Datastore.updateRegistration(regId, canonicalRegId);
            }
          } else {
            String error = result.getErrorCodeName();
            if (error.equals(Constants.ERROR_NOT_REGISTERED)) {
              // application has been removed from device - unregister it
              logger.info("Unregistered device: " + regId);
              Datastore.unregister(regId);
            } else {
              logger.severe("Error sending message to " + regId + ": " + error);
            }
          }
        }
      }});
  }
shareef
  • 9,255
  • 13
  • 58
  • 89
  • there's a note in the sample app you link to: "The information in this document has been superseded by GCM Server and GCM Client. Please use the GoogleCloudMessaging API instead of the GCM client helper library. The GCM server helper library is still valid.". Is your answer up to date? – Amir Uval Jun 06 '13 at 19:56
  • its 2 weeks updated looks google has updated the library and the demo is not updated yet :) – shareef Jun 07 '13 at 12:17