ios Google Cloud Messaging (GCM) not receiving remote notifications
Asked Answered
J

2

15

Problem:
iOS isn't receiving any remote notifications from GCM, but can't find any information relating to why this would be the case.
First time implementing push notifications, not sure what the cause of problem is.

Situation:
I am currently working on the iOS version of an app that uses GCM for push notifications. Notifications are being received fine on Android, however, it doesn't appear to be receiving at all on iOS.

When I run the application, the console shows me that everything is fine, has a token, connected to GCM and subscribed to topics

app[579:45511] Registration Token: bk3RNwTe3H0:CI2k_HHwgIpoDKCIZvvDMExUdFQ3P1pLTQ/8t-5QNiXbYwZYEWiSFD-frQKlsV8lgI
app[579:45511] Connected to GCM
app[579:45511] Already subscribed to /topics/global

However it doesn't receive any notifications and when I pull down Notifications Center or pull up Control Center, the following message occurs in console

app[579:45511] Could not connect to GCM: The operation couldn’t be completed. (com.google.gcm error 2001.)

which doesn't tell me much other than referring to...

/// Missing KeyPair.
kGGLInstanceIDOperationErrorCodeMissingKeyPair = 2001,

On the other-hand, when I send it to the background with the multi-task feature and bring it back, I get this again:

app[579:45511] Connected to GCM
app[579:45511] Already subscribed to /topics/global

Setup:
I've followed the GCM instructions to set up in iOS and even referred to the GcmExample.xcodeproj for the implementation (to the point that the code is exact same).

Set the info.plist for 'Required background modes' -> 'App downloads content in response to push notifications'

Came across another stackoverflow question (can't find now) about GCM and IPs not being whitelisted but ruled that out to not be the issue.

Code:

#import "AppDelegate.h"

@interface AppDelegate ()

@property(nonatomic, strong) void (^registrationHandler) (NSString *registrationToken, NSError *error);
@property(nonatomic, assign) BOOL connectedToGCM;
@property(nonatomic, strong) NSString* registrationToken;
@property(nonatomic, assign) BOOL subscribedToTopic;

@end

NSString *const SubscriptionTopic = @"/topics/global";

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  // Override point for customization after application launch.

  // [START_EXCLUDE]
  _registrationKey = @"onRegistrationCompleted";
  _messageKey = @"onMessageReceived";
  // Configure the Google context: parses the GoogleService-Info.plist, and initializes
  // the services that have entries in the file
  NSError* configureError;
  [[GGLContext sharedInstance] configureWithError:&configureError];
  if (configureError != nil) {
    NSLog(@"Error configuring the Google context: %@", configureError);
  }
  _gcmSenderID = [[[GGLContext sharedInstance] configuration] gcmSenderID];
  // [END_EXCLUDE]
  // Register for remote notifications
  UIUserNotificationType allNotificationTypes =
  (UIUserNotificationTypeSound | UIUserNotificationTypeAlert | UIUserNotificationTypeBadge);
  UIUserNotificationSettings *settings =
  [UIUserNotificationSettings settingsForTypes:allNotificationTypes categories:nil];
  [[UIApplication sharedApplication] registerUserNotificationSettings:settings];
  [[UIApplication sharedApplication] registerForRemoteNotifications];
  // [END register_for_remote_notifications]
  // [START start_gcm_service]
  [[GCMService sharedInstance] startWithConfig:[GCMConfig defaultConfig]];
  // [END start_gcm_service]
  __weak typeof(self) weakSelf = self;
  // Handler for registration token request
  _registrationHandler = ^(NSString *registrationToken, NSError *error){
    if (registrationToken != nil) {
      weakSelf.registrationToken = registrationToken;
      NSLog(@"Registration Token: %@", registrationToken);
      [weakSelf subscribeToTopic];
      NSDictionary *userInfo = @{@"registrationToken":registrationToken};
      [[NSNotificationCenter defaultCenter] postNotificationName:weakSelf.registrationKey
                                                          object:nil
                                                        userInfo:userInfo];
    } else {
      NSLog(@"Registration to GCM failed with error: %@", error.localizedDescription);
      NSDictionary *userInfo = @{@"error":error.localizedDescription};
      [[NSNotificationCenter defaultCenter] postNotificationName:weakSelf.registrationKey
                                                          object:nil
                                                        userInfo:userInfo];
    }
  };

  [[NSNotificationCenter defaultCenter] postNotificationName:_messageKey
                                                      object:nil
                                                    userInfo:nil];
  return YES;
}

- (void)subscribeToTopic {
  // If the app has a registration token and is connected to GCM, proceed to subscribe to the
  // topic
  if (_registrationToken && _connectedToGCM) {
    [[GCMPubSub sharedInstance] subscribeWithToken:_registrationToken
                                             topic:SubscriptionTopic
                                           options:nil
                                           handler:^(NSError *error) {
                                             if (error) {
                                               // Treat the "already subscribed" error more gently
                                               if (error.code == 3001) {
                                                 NSLog(@"Already subscribed to %@",
                                                       SubscriptionTopic);
                                               } else {
                                                 NSLog(@"Subscription failed: %@",
                                                       error.localizedDescription);
                                               }
                                             } else {
                                               self.subscribedToTopic = true;
                                               NSLog(@"Subscribed to %@", SubscriptionTopic);
                                             }
                                           }];
  }
}

- (void)applicationWillResignActive:(UIApplication *)application {
  // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
  // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
}

// [START disconnect_gcm_service]
- (void)applicationDidEnterBackground:(UIApplication *)application {
  // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
  // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.

  [[GCMService sharedInstance] disconnect];
  // [START_EXCLUDE]
  _connectedToGCM = NO;
  // [END_EXCLUDE]
}
// [END disconnect_gcm_service]

- (void)applicationWillEnterForeground:(UIApplication *)application {
  // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
}

- (void)applicationDidBecomeActive:(UIApplication *)application {
  // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.

  // Connect to the GCM server to receive non-APNS notifications
  [[GCMService sharedInstance] connectWithHandler:^(NSError *error) {
    if (error) {
      NSLog(@"Could not connect to GCM: %@", error.localizedDescription);
    } else {
      _connectedToGCM = true;
      NSLog(@"Connected to GCM");
      // [START_EXCLUDE]
      [self subscribeToTopic];
      // [END_EXCLUDE]
    }
  }];
}
// [END connect_gcm_service]

- (void)applicationWillTerminate:(UIApplication *)application {
  // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}


// [START receive_apns_token]
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
  // [END receive_apns_token]
  // [START get_gcm_reg_token]
  // Start the GGLInstanceID shared instance with the default config and request a registration
  // token to enable reception of notifications
  [[GGLInstanceID sharedInstance] startWithConfig:[GGLInstanceIDConfig defaultConfig]];
  _registrationOptions = @{kGGLInstanceIDRegisterAPNSOption:deviceToken,
                           kGGLInstanceIDAPNSServerTypeSandboxOption:@YES};
  [[GGLInstanceID sharedInstance] tokenWithAuthorizedEntity:_gcmSenderID
                                                      scope:kGGLInstanceIDScopeGCM
                                                    options:_registrationOptions
                                                    handler:_registrationHandler];
  // [END get_gcm_reg_token]
}

// [START receive_apns_token_error]
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error {
  NSLog(@"Registration for remote notification failed with error: %@", error.localizedDescription);
  // [END receive_apns_token_error]
  NSDictionary *userInfo = @{@"error" :error.localizedDescription};
  [[NSNotificationCenter defaultCenter] postNotificationName:_registrationKey
                                                      object:nil
                                                    userInfo:userInfo];
}

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {
  NSLog(@"Notification received: %@", userInfo);
  // This works only if the app started the GCM service
  [[GCMService sharedInstance] appDidReceiveMessage:userInfo];
  // Handle the received message
  // [START_EXCLUDE]
  [[NSNotificationCenter defaultCenter] postNotificationName:_messageKey
                                                      object:nil
                                                    userInfo:userInfo];
  // [END_EXCLUDE]
}

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))handler {
  NSLog(@"Notification received: %@", userInfo);
  // This works only if the app started the GCM service
  [[GCMService sharedInstance] appDidReceiveMessage:userInfo];
  // Handle the received message
  // Invoke the completion handler passing the appropriate UIBackgroundFetchResult value
  // [START_EXCLUDE]
  [[NSNotificationCenter defaultCenter] postNotificationName:_messageKey
                                                      object:nil
                                                    userInfo:userInfo];
  handler(UIBackgroundFetchResultNoData);
  // [END_EXCLUDE]
}
// [END ack_message_reception]

// [START on_token_refresh]
- (void)onTokenRefresh {
  // A rotation of the registration tokens is happening, so the app needs to request a new token.
  NSLog(@"The GCM registration token needs to be changed.");
  [[GGLInstanceID sharedInstance] tokenWithAuthorizedEntity:_gcmSenderID
                                                      scope:kGGLInstanceIDScopeGCM
                                                    options:_registrationOptions
                                                    handler:_registrationHandler];
}
// [END on_token_refresh]

@end

UPDATE
backend php code to send a GCM message

//------------------------------
// Payload data you want to send 
// to Android device (will be
// accessible via intent extras)
//------------------------------
$msg = addslashes($_POST["msg"]);

//------------------------------
// The recipient registration IDs
// that will receive the push
// (Should be stored in your DB)
// 
// Read about it here:
// http://developer.android.com/google/gcm/
//------------------------------

//------------------------------
// Call our custom GCM function
//------------------------------

sendGoogleCloudMessage( $msg );
echo "send";

//------------------------------
// Define custom GCM function
//------------------------------

function sendGoogleCloudMessage( $msg )
{
    //------------------------------
    // Replace with real GCM API 
    // key from Google APIs Console
    // 
    // https://code.google.com/apis/console/
    //------------------------------

    $apiKey = 'abc';

    //------------------------------
    // Define URL to GCM endpoint
    //------------------------------

    $url = 'https://android.googleapis.com/gcm/send';

    //------------------------------
    // Set CURL request headers
    // (Authentication and type)
    //------------------------------

    $headers = array( 
                        'Authorization: key=' . $apiKey,
                        'Content-Type: application/json'
                    );

    //------------------------------
    // Initialize curl handle
    //------------------------------

    $ch = curl_init();

    //------------------------------
    // Set URL to GCM endpoint
    //------------------------------

    curl_setopt( $ch, CURLOPT_URL, $url );

    //------------------------------
    // Set request method to POST
    //------------------------------

    curl_setopt( $ch, CURLOPT_POST, true );

    //------------------------------
    // Set our custom headers
    //------------------------------

    curl_setopt( $ch, CURLOPT_HTTPHEADER, $headers );

    //------------------------------
    // Get the response back as 
    // string instead of printing it
    //------------------------------

    curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );

    //------------------------------
    // Set post data as JSON
    //------------------------------

    $post_json_encode = '{"data":{"message":"' . $msg . '"},"to":"/topics/global"}';

    curl_setopt( $ch, CURLOPT_POSTFIELDS, $post_json_encode );

    //------------------------------
    // Actually send the push!
    //------------------------------

    $result = curl_exec( $ch );

    //------------------------------
    // Error? Display it!
    //------------------------------

    if ( curl_errno( $ch ) )
    {
        echo 'GCM error: ' . curl_error( $ch );
    }

    //------------------------------
    // Close curl handle
    //------------------------------

    curl_close( $ch );

    //------------------------------
    // Debug GCM response
    //------------------------------
    $arr_result =   json_decode($result, true);
    foreach ($arr_result as $name => $value) {
        echo "<p>".$name .": ". $value ."</p>";
    }
}

Jacki answered 11/8, 2015 at 19:51 Comment(8)
I have exactly the same problem. However I do receive the good content in userInfo in ‘didReceiveRemoteNotification‘ and I can NSLog it but for some reason I get the same error as you if I try to post the notification. Do you also receive the content of the message?Rehabilitation
It would be useful to see how you send message, i.e. the contents of your http request (or xmpp stanza), with API key removed, of course.Mastery
@Rehabilitation unfortunately for me, it doesn't look like my app is receiving any notifications at all. I've put breakpoints in each of the didReceiveRemoteNotification functions to check, but nothing. I also noticed in the GcmExample provided by Google also generates the 'Could not connect' error 2001 under the same situation.Jacki
@Mastery As I mentioned above to Gannicus, the app isn't receiving any, so I can't see request coming in. I'm sure how else I can see the contents of the http request, but I've updated to include the PHP GCM file to show the authentication header and json, I hope it helps. Any suggestions or input?Jacki
@Jacki the error 2001 you get is NOT kGGLInstanceIDOperationErrorCodeMissingKeyPair but rather kGCMServiceErrorCodeAlreadyConnected. The latter means that you're already connected to GCM. To better debug this I would try to send a display notification to the device token i.e. send this $post_json_encode = '{"notification":{"body":"' . $msg . '"},"to":"/topics/global"}'; You should theoretically connect to GCM when your app is in the foreground and disconnect when you go to background. You can then reconnect again when you come to the foreground.Shaunteshave
@Shaunteshave Thank you so much, put this as answer and I will accept! iOS finally received a push notification and it went through - (void) application: didReceiveRemoteNotification: fetchCompletionHandler:. Unfortunately, the Android now prints null. I was initially under the impression that the data payload was for android and notification payload was for iOS, but documentation here says otherwise GCM downstream. I don't quite understand, can you explain how I can handle for both iOS and Android and why this is?Jacki
@Shaunteshave also, I tried setting on iOS NSString *const SubscriptionTopic = @"/topics/iOS; to test $post_json_encode = '{"notification":{"body":"' . $msg . '"},"to":"/topics/dev"}';, but iOS still receives the notifications. Am I misunderstanding? I thought iOS shouldn't receive the notification since it's not subscribed to "/topics/dev"Jacki
@Jacki I've added it as an answer please accept it. The data payload and notification payload are both applicable on iOS & Android. On iOS the difference is that notification payload is sent through APNS while data payload is sent through GCM's own connection which is only there when app is in foreground. In Android notification payload is the new display notification stuff added recently. Your topics bug is strange. It should not happen. You should try to unsubscribe from the topic on iOS and then send the message again. Just to make sure it's not subscribed.Shaunteshave
S
4

The error 2001 you get is NOT kGGLInstanceIDOperationErrorCodeMissingKeyPair but rather kGCMServiceErrorCodeAlreadyConnected. The latter means that you're already connected to GCM. To better debug this I would try to send a display notification to the device token i.e. send this

$post_json_encode = '{"notification":{"body":"' . $msg . '"},"to":"/topics/global"}';

You should theoretically connect to GCM when your app is in the foreground and disconnect when you go to background. You can then reconnect again when you come to the foreground.

The data payload and notification payload are both applicable on iOS & Android. On iOS the difference is that notification payload is sent through APNS while data payload is sent through GCM's own connection which is only there when app is in foreground. In Android notification payload is the new display notification stuff added recently.

Shaunteshave answered 18/8, 2015 at 3:31 Comment(10)
Does that mean if there is only data payload it is impossible to get notified while the app is in background? I hope not because I use this java library github.com/google/gcm which doesn't seem to feature notification payload.Rehabilitation
No, you can still use content_available to send the message via APNS even without having a notification key in your payload. For example this payload { "content_available" : true, "data" : { "key1" : "value1" } } would go via APNS and wake up the app in the background to allow it to fetch some new data.Shaunteshave
@Shaunteshave I try send push notification in background using: { "to":"/topics/ios-tests", "data":{ "m":"TEST 11112 WITH NOTIFICATION", "e":1443045599999 }, "content_available":true, "priority": 10 } but it only works on foreground. If my app go to the background or it's killed it doesn't receive any notification. Could you tell me what I'm doing wrong? My iOS app code is the same as in GCM examplePoop
My app gets notifications if I use notification payload but i want to download data without in background without showing user notificationPoop
@Poop You're using the priority parameter wrongly. It's value should be either high or low ("priority": "high"). high corresponds to 10 in APNS and low to 5. For more reference developers.google.com/cloud-messaging/http-server-refShaunteshave
@Shaunteshave thanks for so speed answer. Unfortunately the change "priority" value to "high" doesn't give the result that application work correctly in background.Poop
@Poop I just tried even setting priority: 10 should work fine. I'm not sure why it doesn't work for you. How are you trying to verify if you receive the message? Also your app should receive the message when it is in the background it wouldn't receive a content-available message if it has been explicitly killed by the user.Shaunteshave
@Shaunteshave How are you trying to verify if you receive the message? I have connected phone to debugger from Xcode and set instanceIDConfig.logLevel = kGAILogLevelVerbosePoop
@Shaunteshave I also receive the same notification many times(this happens often): 2015-09-16 22:09:51 GCMNotificationManager appDidReveiveNotification:] Notification received: { aps = { "content-available" = 1; }; e = 1464818399999; "gcm.message_id" = "0:1442434173399399%cb67e877cb67e877"; m = broda; } 2015-09-16 22:09:52 -[GCMNotificationManager appDidReveiveNotification:] Notification received: { aps = { "content-available" = 1; }; e = 1464818399999; "gcm.message_id" = "0:1442434174182253%cb67e877cb67e877"; m = broda; }Poop
@Poop Yeah that's what I do. I have a breakpoint in application:didReceiveRemoteNotification:fetchCompletionHandler:. It's weird that you receive the normal iOS notifications but just don't receive the content-available ones since they are all being delivered via APNS. The duplicate messages are not really duplicate it seems. They have a different gcm.message_id. If they were the same message being delivered twice they should have the same ID.Shaunteshave
D
0

I was facing the same issue on iOS. Then I found the solution on PushBots website. It is working fine for me now.

In XCode go to Targets > Build settings > Code Signing Identity and make sure it's not automatic and set to the profile matching certificate linked to the application ID for example

Diaconal answered 23/12, 2015 at 22:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.