Welcome to the Friendly Chat codelab. In this codelab, you'll learn how to use the Firebase platform to create iOS applications. You will implement a chat client, and monitor its performance using Firebase.

This codelab is also available in Swift.

What you'll learn

What you'll need

How will you use this tutorial?

Read it through only Read it and complete the exercises

How would rate your experience with building iOS apps?

Novice Intermediate Proficient

Clone the GitHub repository from the command line.

$ git clone https://github.com/firebase/friendlychat

To build the starter app:

  1. In a terminal window, navigate to the ios-starter/objc-starter directory from your sample code download
  2. Run pod update
  3. Open the FriendlyChatObjc.xcworkspace file to open the project in Xcode.
  4. Click the Run button.

You should see the Friendly Chat home screen appear after a few seconds. The UI should appear. However, at this point you cannot send or receive messages.

Create project

From Firebase console select Create New Project.

Call the project FriendlyChat, then click on Create Project.

Connect your iOS app

From the welcome screen of your new project, click Add Firebase to your iOS app.

Enter the bundle ID, e.g. "com.google.firebase.codelab.FriendlyChatObjC".

Enter the Appstore id as "123456".

Click Continue for all the steps of the initial configuration (you don't need to copy the initialization code), then finally click Finish.

Add GoogleService-Info.plist file to your app

After adding the bundle id and App store id and clicking Continue your browser will automatically download a configuration file that contains all the necessary Firebase metadata for your app. Copy that file to your application and add it to the FriendlyChatObjC target.

Import Firebase module

Start by making sure the Firebase module is imported.

AppDelegate.m, FCViewController.m

@import Firebase;

Configure Firebase in AppDelegate

Use the "configure" method in FIRApp inside the application:didFinishLaunchingWithOptions function to configure underlying Firebase services from your .plist file.

AppDelegate.m

- (BOOL)application:(UIApplication *)application
    didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  [FIRApp configure];
  [GIDSignIn sharedInstance].delegate = self;
  return YES;
}

Use Rules To Restrict To Authenticated Users

We will now add a rule to require authentication before reading or writing any messages. To do this we add the following rules to our messages data object. From within the Database section of Firebase console select the RULES tab. Then update the rules so they look like this:

{
  "rules": {
        "messages": {
            ".read": "auth != null",
            ".write": "auth != null"
        }
  }
}

For more information on how this works (including documentation on the "auth" variable) see the Firebase security documentation.

Configure Authentication APIs

Before your application can access the Firebase Authentication APIs on behalf of your users, you will have to enable it

  1. Navigate to the Firebase console and select your project
  2. Select Authentication
  3. Select the Sign In Method tab
  4. Toggle the Google switch to enabled (blue)
  5. Press Save on the resulting dialog

If you get errors later in this codelab with the message "CONFIGURATION_NOT_FOUND", come back to this step and double check your work.

Confirm Firebase Auth dependency

Confirm that the Firebase Auth dependencies has been added to the Podfile file.

Podfile

pod 'Firebase/Auth'

Setup your Info.plist for Google Sign In.

You'll need to add a custom URL scheme to your XCode project.

  1. Open your project configuration: double-click the project name in the left tree view. Select your app from the TARGETS section, then select the Info tab, and expand the URL Types section.
  2. Click the + button, and add a URL scheme for your reversed client ID. To find this value, open the GoogleService-Info.plist configuration file, and look for the REVERSED_CLIENT_ID key. Copy the value of that key, and paste it into the URL Schemes box on the configuration page. Leave the other fields blank.
  3. When completed, your config should look something similar to the following (but with your application-specific values):

Set clientID for Google Sign In

After Firebase is configured, we can use the clientID to set up the Google Sign In inside the "didFinishLaunchingWithOptions:" method.

AppDelegate.m

- (BOOL)application:(UIApplication *)application
    didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  [FIRApp configure];
  [GIDSignIn sharedInstance].clientID = [FIRApp defaultApp].options.clientID;
  [GIDSignIn sharedInstance].delegate = self;
  return YES;
}

Add the sign in handler

Once the result of the Google Sign-In was successful, use the account to authenticate with Firebase.

AppDelegate.m

- (void)signIn:(GIDSignIn *)signIn
didSignInForUser:(GIDGoogleUser *)user
     withError:(NSError *)error {
  if (error == nil) {
    GIDAuthentication *authentication = user.authentication;
    FIRAuthCredential *credential =
    [FIRGoogleAuthProvider credentialWithIDToken:authentication.idToken
                                     accessToken:authentication.accessToken];
    [[FIRAuth auth] signInWithCredential:credential completion:^(FIRUser * _Nullable user, NSError * _Nullable error) {
      if (error) {
        NSLog(@"Error %@", error.localizedDescription);
      }
    }];
  } else {
    NSLog(@"Error %@", error.localizedDescription);
  }
}

Automatically sign in the user. Then add a listener to Firebase Auth, to let the user into the app, after successful sign in. And remove the listener on deinit.

SignInViewController.m

- (void)viewDidLoad {
  [super viewDidLoad];
  [GIDSignIn sharedInstance].uiDelegate = self;
  [[GIDSignIn sharedInstance] signInSilently];
  self.handle = [[FIRAuth auth]
                 addAuthStateDidChangeListener:^(FIRAuth *_Nonnull auth, FIRUser *_Nullable user) {
                   if (user) {
                     [MeasurementHelper sendLoginEvent];
                     [self performSegueWithIdentifier:SeguesSignInToFp sender:nil];
                   }
                 }];
}

- (void)dealloc {
  [[FIRAuth auth] removeAuthStateDidChangeListener:_handle];
}

Sign Out

Add the Sign out method

FCViewController.m

  - (IBAction)signOut:(UIButton *)sender {
  FIRAuth *firebaseAuth = [FIRAuth auth];
  NSError *signOutError;
  BOOL status = [firebaseAuth signOut:&signOutError];
  if (!status) {
    NSLog(@"Error signing out: %@", signOutError);
    return;
  }
  [self dismissViewControllerAnimated:YES completion:nil];
}

Test Reading Messages as Signed In User

  1. Click Run.
  2. You should be immediately sent to the sign-in screen. Tap the Google Sign-In button.
  3. You should then be sent to the messaging screen if everything worked well.

Import Messages

In your project in Firebase console select the Database item on the left navigation bar. In the overflow menu of the Database select Import JSON. Browse to the initial_messages.json file in the firebase-codelabs directory, select it then click the Import button. This will replace any data currently in your database. You could also edit the database directly, using the green + and red x to add and remove items.

After importing your database should look like this:

Confirm Firebase Database Dependency

In the dependencies block of the Podfile file, confirm that Firebase/Database is included.

Podfile

pod 'Firebase/Database'

Synchronize Existing Messages

Add code that synchronizes newly added messages to the app UI.

The code you add in this section will:

Modify your FCViewController's "dealloc, configureDatabase, tableView" methods, by replacing theme with the code defined below:

FCViewController.m

- (void)dealloc {
  [[_ref child:@"messages"] removeObserverWithHandle:_refHandle];
}


- (void)configureDatabase {
  _ref = [[FIRDatabase database] reference];
  // Listen for new messages in the Firebase database
  _refHandle = [[_ref child:@"messages"] observeEventType:FIRDataEventTypeChildAdded withBlock:^(FIRDataSnapshot *snapshot) {
    [_messages addObject:snapshot];
    [_clientTable insertRowsAtIndexPaths:@[[NSIndexPath indexPathForRow:_messages.count-1 inSection:0]] withRowAnimation: UITableViewRowAnimationAutomatic];
  }];
}


- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(nonnull NSIndexPath *)indexPath {
  // Dequeue cell
  UITableViewCell *cell = [_clientTable dequeueReusableCellWithIdentifier:@"tableViewCell" forIndexPath:indexPath];

  // Unpack message from Firebase DataSnapshot
  FIRDataSnapshot *messageSnapshot = _messages[indexPath.row];
  NSDictionary<NSString *, NSString *> *message = messageSnapshot.value;
  NSString *name = message[MessageFieldsname];
  NSString *text = message[MessageFieldstext];
  cell.textLabel.text = [NSString stringWithFormat:@"%@: %@", name, text];
  cell.imageView.image = [UIImage imageNamed: @"ic_account_circle"];
  NSString *photoURL = message[MessageFieldsphotoURL];
  if (photoURL) {
    NSURL *URL = [NSURL URLWithString:photoURL];
    if (URL) {
      NSData *data = [NSData dataWithContentsOfURL:URL];
      if (data) {
        cell.imageView.image = [UIImage imageWithData:data];
      }
    }
  }

  return cell;
}

Test Message Sync

  1. Click the Run button.
  2. Click the Sign in to get started button to go to the messages window.
  3. Add new messages directly in Firebase console by clicking on the green + symbol next to the "messages" entry and adding an object like the following:
  4. Confirm that they show up in the Friendly-Chat UI.

Implement Send Message

Push values to the database. When you use the push method to add data to Firebase Realtime Database, an automatic ID will be added. These auto generated IDs are sequential, which ensures that new messages will be added in the correct order.

Modify your FCViewController's "sendMessage" method; replace with the code defined below:

FCViewController.m

- (void)sendMessage:(NSDictionary *)data {
  NSMutableDictionary *mdata = [data mutableCopy];
  mdata[MessageFieldsname] = [FIRAuth auth].currentUser.displayName;
  NSURL *photoURL = [FIRAuth auth].currentUser.photoURL;
  if (photoURL) {
    mdata[MessageFieldsphotoURL] = [photoURL absoluteString];
  }

  // Push data to Firebase Database
  [[[_ref child:@"messages"] childByAutoId] setValue:mdata];
}

Test Sending Messages

  1. Click the Run button.
  2. Click Sign In to go to the messages window.
  3. Type out a message and hit send. The new message should be visible in the app UI and in the Firebase console.

Confirm Firebase Storage Dependency

In the dependencies block of the Podfile file, confirm the Firebase/Storage is included.

Configure FirebaseStorage

FCViewController.m

- (void)configureStorage {
  self.storageRef = [[FIRStorage storage] reference];
}

Receive images in existing messages

Add code that downloads images from Firebase Storage.

Modify your FCViewController's "tableView" method, replace with the code defined below:

FCViewController.m

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(nonnull NSIndexPath *)indexPath {
  // Dequeue cell
  UITableViewCell *cell = [_clientTable dequeueReusableCellWithIdentifier:@"tableViewCell" forIndexPath:indexPath];

  // Unpack message from Firebase DataSnapshot
  FIRDataSnapshot *messageSnapshot = _messages[indexPath.row];
  NSDictionary<NSString *, NSString *> *message = messageSnapshot.value;
  NSString *name = message[MessageFieldsname];
  NSString *imageURL = message[MessageFieldsimageURL];
  if (imageURL) {
    if ([imageURL hasPrefix:@"gs://"]) {
      [[[FIRStorage storage] referenceForURL:imageURL] dataWithMaxSize:INT64_MAX
                                                            completion:^(NSData *data, NSError *error) {
        if (error) {
          NSLog(@"Error downloading: %@", error);
          return;
        }
        cell.imageView.image = [UIImage imageWithData:data];
        [tableView reloadData];
      }];
    } else {
      cell.imageView.image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:imageURL]]];
    }
    cell.textLabel.text = [NSString stringWithFormat:@"sent by: %@", name];
  } else {
    NSString *text = message[MessageFieldstext];
    cell.textLabel.text = [NSString stringWithFormat:@"%@: %@", name, text];
    cell.imageView.image = [UIImage imageNamed: @"ic_account_circle"];
    NSString *photoURL = message[MessageFieldsphotoURL];
    if (photoURL) {
      NSURL *URL = [NSURL URLWithString:photoURL];
      if (URL) {
        NSData *data = [NSData dataWithContentsOfURL:URL];
        if (data) {
          cell.imageView.image = [UIImage imageWithData:data];
        }
      }
    }
  }
  return cell;
}

Implement Store and Send Images

Upload the image user picked, then sync this image's storage URL to database so this image is sent inside the message.

Modify your FCViewController's "imagePickerController didFinishPickingMediaWithInfo" method; replace with the code defined below:

FCViewController.m

- (void)imagePickerController:(UIImagePickerController *)picker
didFinishPickingMediaWithInfo:(NSDictionary *)info {
  [picker dismissViewControllerAnimated:YES completion:NULL];

  NSURL *referenceURL = info[UIImagePickerControllerReferenceURL];
  // if it's a photo from the library, not an image from the camera
  if (referenceURL) {
    PHFetchResult* assets = [PHAsset fetchAssetsWithALAssetURLs:@[referenceURL] options:nil];
    PHAsset *asset = assets.firstObject;
    [asset requestContentEditingInputWithOptions:nil
                               completionHandler:^(PHContentEditingInput *contentEditingInput, NSDictionary *info) {
                                 NSURL *imageFile = contentEditingInput.fullSizeImageURL;
                                 NSString *filePath = [NSString stringWithFormat:@"%@/%lld/%@",
                                                       [FIRAuth auth].currentUser.uid,
                                                       (long long)([NSDate date].timeIntervalSince1970 * 1000.0),
                                                       referenceURL.lastPathComponent];
                                 [[_storageRef child:filePath]
                                            putFile:imageFile metadata:nil
                                          completion:^(FIRStorageMetadata *metadata, NSError *error) {
                                            if (error) {
                                              NSLog(@"Error uploading: %@", error);
                                              return;
                                            }
                                            [self sendMessage:@{MessageFieldsimageURL:[_storageRef child:metadata.path].description}];
                                    }
                                ];
                             }];
  } else {
    UIImage *image = info[UIImagePickerControllerOriginalImage];
    NSData *imageData = UIImageJPEGRepresentation(image, 0.8);
    NSString *imagePath =
    [NSString stringWithFormat:@"%@/%lld.jpg",
     [FIRAuth auth].currentUser.uid,
     (long long)([NSDate date].timeIntervalSince1970 * 1000.0)];
    FIRStorageMetadata *metadata = [FIRStorageMetadata new];
    metadata.contentType = @"image/jpeg";
    [[_storageRef child:imagePath] putData:imageData metadata:metadata
                                completion:^(FIRStorageMetadata * _Nullable metadata, NSError * _Nullable error) {
                                  if (error) {
                                    NSLog(@"Error uploading: %@", error);
                                    return;
                                  }
                                  [self sendMessage:@{MessageFieldsimageURL:[_storageRef child:metadata.path].description}];
                                }];
  }
}

Test Sending and Receiving Image Messages

  1. Click the Run button.
  2. Click Sign In to go to the messages window.
  3. Click "add a photo" icon to pick a photo, the new message with the photo should be visible in the app UI and in the Firebase console.

Firebase Remote Config allows you to remotely configure your active clients. FriendlyChat messages are restricted to a maximum length. While this length can be defined directly in the client, defining this maximum length with Firebase Remote Config allows an update to the maximum length to be applied to active clients.

Add Config Rules in Firebase console

In Firebase console, select the "Remote Config" panel and click "Add your first parameter". Set the parameter key to friendly_msg_length and the parameter value to 10. Select Publish Changes to apply the updates.

Confirm Firebase RemoteConfig dependency

Confirm the pod 'Firebase/RemoteConfig' dependency exists in your Podfile file.

Configure Firebase Remote Config

FCViewController.m

- (void)configureRemoteConfig {
  _remoteConfig = [FIRRemoteConfig remoteConfig];
  // Create Remote Config Setting to enable developer mode.
  // Fetching configs from the server is normally limited to 5 requests per hour.
  // Enabling developer mode allows many more requests to be made per hour, so developers
  // can test different config values during development.
  FIRRemoteConfigSettings *remoteConfigSettings = [[FIRRemoteConfigSettings alloc] initWithDeveloperModeEnabled:YES];
  self.remoteConfig.configSettings = remoteConfigSettings;
}

Request and Use Config

Create a fetch request for config and add a completion handler to pick up and use the config parameters.

FCViewController.m

- (void)fetchConfig {
  long expirationDuration = 3600;
  // If in developer mode cacheExpiration is set to 0 so each fetch will retrieve values from
  // the server.
  if (self.remoteConfig.configSettings.isDeveloperModeEnabled) {
    expirationDuration = 0;
  }

  // cacheExpirationSeconds is set to cacheExpiration here, indicating that any previously
  // fetched and cached config would be considered expired because it would have been fetched
  // more than cacheExpiration seconds ago. Thus the next fetch would go to the server unless
  // throttling is in progress. The default expiration duration is 43200 (12 hours).
  [self.remoteConfig fetchWithExpirationDuration:expirationDuration completionHandler:^(FIRRemoteConfigFetchStatus status, NSError *error) {
    if (status == FIRRemoteConfigFetchStatusSuccess) {
      NSLog(@"Config fetched!");
      [_remoteConfig activateFetched];
      FIRRemoteConfigValue *friendlyMsgLength = _remoteConfig[@"friendly_msg_length"];
      if (friendlyMsgLength.source != FIRRemoteConfigSourceStatic) {
        _msglength = friendlyMsgLength.numberValue.intValue;
        NSLog(@"Friendly msg length config: %d", _msglength);
      }
    } else {
      NSLog(@"Config not fetched");
      NSLog(@"Error %@", error);
    }
  }];
}

Test Remote Config

  1. Click the Run button.
  2. Check that the Friendly Message character limit has been set to 10. Update the Remote Config value from 10 to 30, then select Publish Changes. From the navigation bar tap fresh config and confirm that the Friendly Message character limit is now 30.

Firebase App Invites provide a simple way for your users to share your application with their friends through Email or SMS.

Confirm AppInvite dependency

Confirm the pod 'Firebase/Invites' dependency exists in your Podfile file:

Podfile

pod 'Firebase/Invites'

Implement App Invite

Present invite dialog when invite button is clicked:

FCViewController.m

- (IBAction)inviteTapped:(id)sender {
  id<FIRInviteBuilder> inviteDialog = [FIRInvites inviteDialog];
  [inviteDialog setInviteDelegate:self];

  // NOTE: You must have the App Store ID set in your developer console project
  // in order for invitations to successfully be sent.
  NSString *message =
  [NSString stringWithFormat:@"Try this out!\n -%@",
   [FIRAuth auth].currentUser.displayName];

  // A message hint for the dialog. Note this manifests differently depending on the
  // received invitation type. For example, in an email invite this appears as the subject.
  [inviteDialog setMessage:message];

  // Title for the dialog, this is what the user sees before sending the invites.
  [inviteDialog setTitle:@"FriendlyChat"];
  [inviteDialog setDeepLink:@"app_url"];
  [inviteDialog setCallToActionText:@"Install!"];
  [inviteDialog setCustomImage:@"https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png"];
  [inviteDialog open];
}

Add the inviteFinished handler to print out the result.

FCViewController.m

- (void)inviteFinishedWithInvitations:(NSArray *)invitationIds error:(NSError *)error {
  NSString *message =
  error ? error.localizedDescription
  : [NSString stringWithFormat:@"%lu invites sent", (unsigned long)invitationIds.count];
  [[[UIAlertView alloc] initWithTitle:@"Done"
                              message:message
                             delegate:nil
                    cancelButtonTitle:@"OK"
                    otherButtonTitles:nil] show];
}

Test App Invite

  1. Click the Run button.
  2. Click the "Invite" button.
  3. You should see the App Invites interface which will allow you to select Email and SMS contacts and send a custom invitation. You must have control of the receiving account to view the invitation once sent.
  4. Tap send and verify that the invitation is sent to the selected contact.
  5. Verify that selected contact is taken to app install screen from invite.

You now know how to enable invites. Congrats!

Firebase Analytics provides a way for you to understand the way users move through your application, where they succeed and where they get stuck and turn back. It can also be used to understand the most used parts of your application.

Initialize Measurement

Add measurement helper methods.

MeasurementHelper.m

+ (void)sendLoginEvent {
  [FIRAnalytics logEventWithName:kFIREventLogin parameters:nil];
}

+ (void)sendLogoutEvent {
  [FIRAnalytics logEventWithName:@"logout" parameters:nil];
}

+ (void)sendMessageEvent{
  [FIRAnalytics logEventWithName:@"message" parameters:nil];
}

If you want to view this activity in your console, select Product ... Scheme... Edit Scheme in Xcode. In the Arguments Passed on Launch section, click the + to add a new argument and add add `-FIRAnalyticsDebugEnabled` as the new argument.

AdMob gives you a way to easily monetize the application, you simply add the AdView placeholder and Google handles the ad delivery for you.

Confirm the AdMob dependency

Confirm the pod 'Firebase/AdMob' dependency exists in your Podfile file.

Podfile

pod 'Firebase/AdMob'

Load Ad request

FCViewController.m

- (void)loadAd {
  self.banner.adUnitID = kBannerAdUnitID;
  self.banner.rootViewController = self;
  [self.banner loadRequest:[GADRequest request]];
}

Test AdMob

  1. Click the Run button.
  2. Verify that the Test Ad shows at the bottom of the screen.

Firebase Crash allows your application to report when crashes occur and log the events leading up to the crash.

Confirm Firebase Crash dependency

Confirm the pod 'Firebase/Crash' dependency exists in your Podfile file:

Log view load

FCViewController.m

- (void)logViewLoaded {
  // Log that the view did load, FIRCrashNSLog is used here so the log message will be
  // shown in the console output. If FIRCrashLog is used the message is not shown in
  // the console output.
  FIRCrashNSLog(@"View loaded");
}

Initiate crash

FCViewController.m

- (IBAction)didPressCrash:(id)sender {
  FIRCrashLog(@"Cause Crash button clicked");
  assert(NO);
}

Test Firebase Crash

  1. Click the Run button.
  2. Stop Xcode
  3. Run the app again without Xcode attached from the simulator.
  4. Click the "Crash" button.
  5. Verify that your app is crashed.
  6. Click the Run button again to run your app in Xcode
  7. After a moment, you should see the message "Firebase Crash Reporting: Crash successfully uploaded" in the console
  8. After a minute or two, visit the Crash section of the Firebase console. You should see your crash being logged
  9. Note that we did not do the work to upload the symbols file in this codelab. In a real situation, you would have a much more readable stack trace to help you find the error.

You have used Firebase to easily build a real-time chat application.

What we've covered

Optional Steps

Learn More

You can use Firebase Cloud Messaging (FCM) to send notifications to users of your app. In this section we will configure the application to receive reengagement notifications which you can send from Firebase console.

Add FCM dependency

The Firebase/Messaging dependency provides the ability to send and receive FCM messages. Confirm the pod 'Firebase/Messaging' dependency exists in your Podfile file.

Podfile

pod 'Firebase/Messaging'

Register for remote notifications

Implement UNUserNotificationCenterDelegate before "@implementation AppDelegate" to receive display notification via APNS for devices running iOS 10 and above.

AppDelegate.m

@import Firebase;

#if defined(__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
@import UserNotifications;
#endif
#if defined(__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
@interface AppDelegate () <UNUserNotificationCenterDelegate>
@end
#endif

@implementation AppDelegate

Register your app for remote notifications inside the "application:didFinishLaunchingWithOptions:" function

AppDelegate.m

- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  [FIRApp configure];
  [GIDSignIn sharedInstance].clientID = [FIRApp defaultApp].options.clientID;
  [GIDSignIn sharedInstance].delegate = self;

  if (floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_9_x_Max) {
    UIUserNotificationType allNotificationTypes =
    (UIUserNotificationTypeSound | UIUserNotificationTypeAlert | UIUserNotificationTypeBadge);
    UIUserNotificationSettings *settings =
    [UIUserNotificationSettings settingsForTypes:allNotificationTypes categories:nil];
    [[UIApplication sharedApplication] registerUserNotificationSettings:settings];
  } else {
    // iOS 10 or later
#if defined(__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
    UNAuthorizationOptions authOptions =
    UNAuthorizationOptionAlert
    | UNAuthorizationOptionSound
    | UNAuthorizationOptionBadge;
    [[UNUserNotificationCenter currentNotificationCenter] requestAuthorizationWithOptions:authOptions completionHandler:^(BOOL granted, NSError * _Nullable error) {
    }];

    // For iOS 10 display notification (sent via APNS)
    [UNUserNotificationCenter currentNotificationCenter].delegate = self;
#endif
  }

  [[UIApplication sharedApplication] registerForRemoteNotifications];
  return YES;
}

At this point, you'll receive notifications when the app is in background. Add notification handlers and alert function, to handle the incoming notification when the app is in foreground and show an alert with the message of the notification.

AppDelegate.m

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {
  // If you are receiving a notification message while your app is in the background,
  // this callback will not be fired till the user taps on the notification launching the application.
  [self showAlert:userInfo];
}

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
  // If you are receiving a notification message while your app is in the background,
  // this callback will not be fired till the user taps on the notification launching the application.
  [self showAlert:userInfo];

  completionHandler(UIBackgroundFetchResultNewData);
}

// Receive displayed notifications for iOS 10 devices.
#if defined(__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
// Handle incoming notification messages while app is in the foreground.
- (void)userNotificationCenter:(UNUserNotificationCenter *)center
       willPresentNotification:(UNNotification *)notification
         withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler {
  // Print message ID.
  NSDictionary *userInfo = notification.request.content.userInfo;
  [self showAlert:userInfo];

  // Change this to your preferred presentation option
  completionHandler(UNNotificationPresentationOptionNone);
}

// Handle notification messages after display notification is tapped by the user.
- (void)userNotificationCenter:(UNUserNotificationCenter *)center
didReceiveNotificationResponse:(UNNotificationResponse *)response
         withCompletionHandler:(void (^)())completionHandler {
  NSDictionary *userInfo = response.notification.request.content.userInfo;
  [self showAlert:userInfo];

  completionHandler();
}
#endif

- (void)showAlert:(NSDictionary *)userInfo {
  NSString *apsKey = @"aps";
  NSString *gcmMessage = @"alert";
  NSString *gcmLabel = @"google.c.a.c_l";

  NSDictionary *aps = userInfo[apsKey];
  if (aps) {
    NSString *message = aps[gcmMessage];
    if (message) {
      dispatch_async(dispatch_get_main_queue(), ^{
        UIAlertController *alert = [UIAlertController alertControllerWithTitle:userInfo[gcmLabel] message:message preferredStyle:UIAlertControllerStyleAlert];
        UIAlertAction *dismissAction = [UIAlertAction actionWithTitle:@"Dismiss" style:UIAlertActionStyleDestructive handler:nil];
        [alert addAction:dismissAction];
        [_window.rootViewController.presentedViewController presentViewController:alert animated: true completion: nil];
      });
    }
  }

That's it! FCM is all ready to receive messages.

Test Background Notifications

  1. Run the updated application.
  2. Hit the device's home button (or otherwise send the app to the background).
  3. Use the Composer in the Firebase console to send notifications.
  1. In Firebase console select Notifications from the left navigation bar.
  2. Select Send Your First Message.
  3. Set Message Text to "Friendly Chat?".
  4. Select the app we connected earlier as the App target.
  5. Click Send Message
  1. Confirm that message is received and notification is displayed on the device. The user should receive a notification that takes them back to the application when tapped. And the application should show an alert: "Friendly Chat?"

Hooray! You can re-engage your users easily with FCM. See the documentation for more on FCM.