Firebase Cloud Messagingで画像つきプッシュ通知を送信する

## はじめに

この間行われたtry! SwiftでFirebaseさんがいらしてたので頑張っていろいろ話していたらなんと
「 `mutable-content` に対応したよ」と言われたのでちょっとやってみました！

ちなみに前回「[iOSアプリからFirebase Cloud Messaging経由でプッシュ通知を送る](http://qiita.com/nnsnodnb/items/5171d9e5d73522a50aea)」という
記事を書いたのでその時に書いたプログラムを再利用しています。

## 環境

- macOS Sierra
- Xcode 8.2.1
- Swift3.0.2
- iOS10.x

## サンプルソース

### プッシュ通知設定関連

```swift:AppDelegate.swift
import UIKit
import UserNotifications

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        if #available(iOS 10.0, *) {
            UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound], completionHandler: { (grand, error) in
                guard error == nil else {
                    return
                }
                if grand {
                    UIApplication.shared.registerForRemoteNotifications()
                }
            })
        } else {
            let settings = UIUserNotificationSettings(types: [.alert, .badge, .sound],
                                                      categories: nil)
            application.registerUserNotificationSettings(settings)
            application.registerForRemoteNotifications()
        }
    }

    func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
        #if DEBUG
        FIRInstanceID.instanceID().setAPNSToken(deviceToken, type: .sandbox)
        #else
        FIRInstanceID.instanceID().setAPNSToken(deviceToken, type: .prod)
        #endif
    }
}
・・・

// MARK: - UNUserNotificationCenterDelegate

extension AppDelegate : UNUserNotificationCenterDelegate {
    @available(iOS 10.0, *)
    func userNotificationCenter(_ center: UNUserNotificationCenter,
                                willPresent notification: UNNotification,
                                withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
        completionHandler([.alert, .badge, .sound])
    }

    @available(iOS 10.0, *)
    func userNotificationCenter(_ center: UNUserNotificationCenter,
                                didReceive response: UNNotificationResponse,
                                withCompletionHandler completionHandler: @escaping () -> Void) {
        completionHandler()
    }
}
```

とりあえずこんなふうなソースコードを書いておく。

### プッシュ通知受け取ったときの動作(iOS10以上)

「 __File__ 」→「 __New__ 」→「 __Target...__ 」で「 __Notification Service Extension__ 」を追加する。

今回はFirebaseのpayloadに `image_url` を付けてプッシュ通知を送信してみる。
今回のpayloadサンプルはここで必要なソースコードの下に示します。

```swift:Notification/NotificationService.swift
import UserNotifications

class NotificationService: UNNotificationServiceExtension {

    let imageKey = AnyHashable("gcm.notification.image_url")

    var contentHandler: ((UNNotificationContent) -> Void)?
    var bestAttemptContent: UNMutableNotificationContent?

    override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
        self.contentHandler = contentHandler
        bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)

        if let imageUrl = request.content.userInfo[imageKey] as? String {
            let session = URLSession(configuration: URLSessionConfiguration.default)
            let task = session.dataTask(with: URL(string: imageUrl)!, completionHandler: { [weak self] (data, response, error) in
                if let data = data {
                    do {
                        let writePath = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("push.png")
                        try data.write(to: writePath)
                        guard let wself = self else {
                            return
                        }
                        if let bestAttemptContent = wself.bestAttemptContent {
                            let attachment = try UNNotificationAttachment(identifier: "nnsnodnb_demo", url: writePath, options: nil)
                            bestAttemptContent.attachments = [attachment]
                            contentHandler(bestAttemptContent)
                        }
                    } catch let error as NSError {
                        print(error.localizedDescription)

                        guard let wself = self else {
                            return
                        }
                        if let bestAttemptContent = wself.bestAttemptContent {
                            contentHandler(bestAttemptContent)
                        }
                    }
                } else if let error = error {
                    print(error.localizedDescription)
                }
            })
            task.resume()
        } else {
            if let bestAttemptContent = bestAttemptContent {
                contentHandler(bestAttemptContent)
            }
        }
    }

    override func serviceExtensionTimeWillExpire() {
        if let contentHandler = contentHandler, let bestAttemptContent =  bestAttemptContent {
            contentHandler(bestAttemptContent)
        }
    }
}
```

### 今回のpayload

Firebase Cloud Messagingのエンドポイントに向けて送るpayloadサンプルプログラムです。

プログラムの説明は前回の記事に書いてあるので参照ください。

```python:payload.py
import daemon
import json
import pyrebase
import requests

config = {
    # GoogleService-info.plistのAPI_KEY
    "apiKey": "API_KEY",
    "authDomain": "PROJECT_NAME",
    "databaseURL": "https://PROJECT_NAME.firebaseio.com/",
    # いらないかもしれない
    "storageBucket": "gs://PROJECT_NAME.appspot.com"
}

fcm_header = {
    'Content-type': 'application/json; charset=UTF-8',
    # このコードの下の画像を参照してください！
    'Authorization': 'key=API_KEY'
}


firebase = pyrebase.initialize_app(config)


def stream_handler(message):
    if message['data'] == None:
        return
    if message['path'] == '/':
        return

    if 'title' in message['data']:
        title = message['data']['title']
    else:
        title = '配信'

    if 'body' in message['data']:
        body = message['data']['body']
    else:
        body = ''

    payload = {'to': '/topics/news', 
               'priority': 'high', 
               'notification': {
                   'title': title,
                   'body': body,
                   'badge': 1,
                   'sound': 'default',
                   'mutable_content': True,
               }
              }
    if 'image_url' in message['data']:
        payload['notification']['image_url'] = message['data']['image_url']

    r = requests.post('https://fcm.googleapis.com/fcm/send', \
                      headers=fcm_header, \
                      data = json.dumps(payload))
    print(r.json())


if __name__ == '__main__':
    db = firebase.database()
    dc = daemon.DaemonContext()
    with dc:
        stream = db.child("message").stream(stream_handler)
```

## 実行結果

### Firebase Databaseにデータを追加

![スクリーンショット_2017-03-14_21_36_30.png](https://qiita-image-store.s3.amazonaws.com/0/97280/ce7707f4-48c1-7d59-41c1-d5ac86816d79.png "スクリーンショット_2017-03-14_21_36_30.png")

前回と同じくこんな感じにFirebase Databaseにデータ追加します。

### プッシュ通知結果

![C6aiGhnVMAAvZVl.jpg](https://qiita-image-store.s3.amazonaws.com/0/97280/ce7befd3-7cf5-0367-4a09-199fdaf67925.jpeg "C6aiGhnVMAAvZVl.jpg")

![C6aiGffUwAAONdg.jpg](https://qiita-image-store.s3.amazonaws.com/0/97280/8834a8b2-a138-96b8-da7b-750b9b801edf.jpeg "C6aiGffUwAAONdg.jpg")

やったね！

## 最後に

今回はPythonのプログラムからまた送信してみました。
また、一応FirebaseConsole(Web)からもテストしてみたのですが、できなさそうなのでやはりこっちで対応する形になりそうです。

それとおまけで、Firebaseの中の人から
「近いうちに1to1プッシュ通知に対応するよ〜」
との情報もいただき、Firebaseのソース(タバスコ)などの大切なものもいただきました！