我正在向我的项目添加 FCM 图像通知支持,因此我正在监听来自 firebase 的消息并使用 flutter_image_notifications 进行显示。
对于 Android,它正在使用大图片 flutter_local_notifications 插件未在通知中显示大图片 我们该怎么做以及是否需要添加 Ios 通知服务扩展?如果是的话我需要快速程序
flutter 本地通知适用于 iOS 和 Android 如果你配置你的项目
首先这样做
然后实现 https://pub.dev/packages/flutter_local_notifications
关于添加图像的部分描述得很糟糕,因此如果您想从网络下载图像,它是要粘贴的代码的主要部分, 重点是您需要在电话文件系统中提供存储图片的路径。
final http.Response response = await http.get(Uri.parse(
"https:PICTURE_ADRESS_IN_THE_WEB.jpg"));
// Get temporary directory
final dir = await getTemporaryDirectory();
// Create an image name
var filename = '${dir.path}/image.png';
// Save to filesystem
final file = File(filename);
await file.writeAsBytes(response.bodyBytes);
DarwinNotificationDetails iosNotificationDetails = DarwinNotificationDetails(
attachments: [DarwinNotificationAttachment(filename)]);
对于 IOS,您需要为您的项目创建一个NotificationExtension,以便能够处理多媒体(图像、视频...),好的一点是即使终止,它也能工作。
步骤如下(在 XCode 中打开 IOS 文件夹): 1-导航到文件 -> 新建 -> 目标 2-搜索“通知服务扩展” 3-确保选择跑步者作为目标,然后单击“完成” 4-单击扩展目标,然后转到BuildSettings->打包(产品包标识符)并确保包名称前缀与Runner包名称相同。 5- 转到“签名和功能”-> + 功能-> 应用程序组 -> 创建新的/或选择现有的 6- 通过选择您在步骤 5 中选择的相同应用程序组,为跑步者目标重复步骤 5
以下代码可以放在扩展 swift 文件中:
import UserNotifications
导入意图
类NotificationService:UNNotificationServiceExtension {
var contentHandler: ((UNNotificationContent) -> Void)?
var bestAttemptContent: UNMutableNotificationContent?
/// This is called when a notification is received.
/// - Parameters:
/// - request: The notification request.
/// - contentHandler: The callback that needs to be called when the notification is ready to be displayed.
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
self.contentHandler = contentHandler
// the OS already did some work for us so we do make a work copy. If something does not go the way we expect or we run in a timeout we still have an attempt that can be displayed.
bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
// unwrapping makes the compiler happy
if(bestAttemptContent == nil) {
return;
}
// this is the FCM / APNS payload defined by the server / caller.
// Its the custom '"data"' object you provide in the FCM json.
// adjust it to your needs. This is just an example.
let payload: [AnyHashable : Any] = bestAttemptContent!.userInfo
// we assume that we get a type in the payload
// either remove this line or add a type to your payload
let type: String? = payload["type"] as? String
// this is set by the server to indicate that this is a chat message
if(type == "chat") {
_handleChatMessage(payload: payload)
return;
}
// if we do not know the type we just pass the notification through
// this is the case when we get a plain FCM / APNS notification
guard let attachmentURL = payload["iosRichLink"] as? String else {
contentHandler(request.content)
return
}
do {
let url = URL(string: attachmentURL)
let fileName = url!.lastPathComponent // same here => we hope it contains a proper file extension.
let imageData = try? Data(contentsOf: url!)
if(imageData != nil) {
let attachment = createNotificationAttachment(identifier: "media", fileName: fileName, data: imageData!, options: nil)
if attachment != nil {
bestAttemptContent?.attachments.append(attachment!)
}
}
contentHandler(bestAttemptContent?.copy() as! UNNotificationContent)
}
if let bestAttemptContent = bestAttemptContent {
contentHandler(bestAttemptContent)
}
}
/// Handles a chat message notification. It tries to display it as a communication notification.
/// - Parameter payload: The FCM / APNS payload, defined by the server / caller.
func _handleChatMessage(payload: [AnyHashable : Any]) {
guard let content = bestAttemptContent else {
return
}
guard let contentHandler = contentHandler else {
return
}
// add your custom logic here. Read the payload information and act accordingly.
// all following code assumes you provide this information in the payload.
// let chatRoomName: String? = payload["chatRoomName"] as? String
// guard let chatRoomName: String = chatRoomName, !chatRoomName.isEmpty else {
// return
// }
let chatRoomName: String? = payload["chatRoomName"] as? String //"My Custom Room" // this can be a senders name or the name of a channel or group
let senderId: String? = payload["senderId"] as? String //"f91840a2-a1bd-4d7a-a7ea-b4c08f7292e0" // use whatever value you have from your backend
let senderDisplayName: String? = payload["senderDisplayName"] as? String //"Sender A"
let senderThumbnail: String = payload["senderThumbnail"] as! String //"https://picsum.photos/300"
guard let senderThumbnailUrl: URL = URL(string: senderThumbnail) else {
return
}
let senderThumbnailFileName: String = senderThumbnailUrl.lastPathComponent // we grab the last part in the hope it contains the actual filename (any-picture.jpg)
guard let senderThumbnailImageData: Data = try? Data(contentsOf: senderThumbnailUrl),
let senderThumbnailImageFileUrl: URL = try? downloadAttachment(data: senderThumbnailImageData, fileName: senderThumbnailFileName),
let senderThumbnailImageFileData: Data = try? Data(contentsOf: senderThumbnailImageFileUrl) else {
return
}
// example for adding attachments. Will be displayed by the communication notification.
var attachments: [UNNotificationAttachment] = [];
// Note: TODO -> Make sure that it has a file extension. Otherwise the creation of an attachment will fail and return nil.
let link: String? = payload["iosRichLink"] as? String
if let link = link, !link.isEmpty {
let url = URL(string: link)
let fileName = url!.lastPathComponent // same here => we hope it contains a proper file extension.
let imageData = try? Data(contentsOf: url!)
if(imageData != nil) {
let attachment = createNotificationAttachment(identifier: "media", fileName: fileName, data: imageData!, options: nil)
if attachment != nil {
attachments.append(attachment!)
}
}
}
// Add a preview to the notification.
// Maybe the sender attached a picture or a video.
// Handle attachments here before converting it to a communication notification
// as I had issues when trying adding attachments afterwards.
// Note: Those can be reused in the Notification Content Extension
content.attachments = attachments
// profile picture that will be displayed in the notification (left side)
let senderAvatar: INImage = INImage(imageData: senderThumbnailImageFileData)
var personNameComponents = PersonNameComponents()
personNameComponents.nickname = senderDisplayName
// the person that sent the message
// we need that as it is used by the OS trying to identify/match the sender with a contact
// Setting ".unknown" as type will prevent the OS from trying to match the sender with a contact
// as here this is an internal identifier and not a phone number or email
let senderPerson = INPerson(
personHandle: INPersonHandle(
value: senderId,
type: .unknown
),
nameComponents: personNameComponents,
displayName: senderDisplayName,
image: senderAvatar,
contactIdentifier: nil,
customIdentifier: nil,
isMe: false, // this makes the OS recognize this as a sender
suggestionType: .none
)
// this is just a dummy person that will be used as the recipient
let selfPerson = INPerson(
personHandle: INPersonHandle(
value: "00000000-0000-0000-0000-000000000000", // no need to set a real value here
type: .unknown
),
nameComponents: nil,
displayName: nil,
image: nil,
contactIdentifier: nil,
customIdentifier: nil,
isMe: true, // this makes the OS recognize this as "US"
suggestionType: .none
)
// the actual message. We use the OS to send us ourselves a message.
let incomingMessagingIntent = INSendMessageIntent(
recipients: [selfPerson],
outgoingMessageType: .outgoingMessageText, // This marks the message as outgoing
content: content.body, // this will replace the content.body
speakableGroupName: nil,
conversationIdentifier: chatRoomName, // this will be used as the conversation title
serviceName: nil,
sender: senderPerson, // this marks the message sender as the person we defined above
attachments: []
)
incomingMessagingIntent.setImage(senderAvatar, forParameterNamed: \.sender)
let interaction = INInteraction(intent: incomingMessagingIntent, response: nil)
interaction.direction = .incoming
do {
// we now update / patch / convert our attempt to a communication notification.
bestAttemptContent = try content.updating(from: incomingMessagingIntent) as? UNMutableNotificationContent
// everything went alright, we are ready to display our notification.
contentHandler(bestAttemptContent!)
} catch let error {
print("error \(error)")
}
}
/// Called just before the extension will be terminated by the system.
/// Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used.
override func serviceExtensionTimeWillExpire() {
if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent {
contentHandler(bestAttemptContent)
}
}
/// Shorthand for creating a notification attachment.
/// - Parameters:
/// - identifier: Unique identifier for the attachment. So it can be referenced within a Notification Content extension for example.
/// - fileName: The name of the file. This is the name that will be used to store the name on disk.
/// - data: A Data object based on the remote url.
/// - options: A dictionary of options. See Apple's documentation for more information.
/// - Returns: A UNNotificationAttachment object.
func createNotificationAttachment(identifier: String, fileName: String, data: Data, options: [NSObject : AnyObject]?) -> UNNotificationAttachment? {
do {
if let fileURL: URL = downloadAttachment(data: data, fileName: fileName) {
let attachment: UNNotificationAttachment = try UNNotificationAttachment.init(identifier: identifier, url: fileURL, options: options)
return attachment
}
return nil
} catch let error {
print("error \(error)")
}
return nil
}
/// Downloads a file from a remote url and stores it in a temporary folder.
/// - Parameters:
/// - data: A Data object based on the remote url.
/// - fileName: The name of the file. This is the name that will be used to store the name on disk.
/// - Returns: A URL object pointing to the temporary file on the phone. This can be used by a Notification Content extension for example.
func downloadAttachment(data: Data, fileName: String) -> URL? {
// Create a temporary file URL to write the file data to
let fileManager = FileManager.default
let tmpSubFolderName = ProcessInfo.processInfo.globallyUniqueString
let tmpSubFolderURL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(tmpSubFolderName, isDirectory: true)
do {
// prepare temp subfolder
try fileManager.createDirectory(at: tmpSubFolderURL, withIntermediateDirectories: true, attributes: nil)
let fileURL: URL = tmpSubFolderURL.appendingPathComponent(fileName)
// Save the image data to the local file URL
try data.write(to: fileURL)
return fileURL
} catch let error {
print("error \(error)")
}
return nil
}
}
此代码处理图像和通信头像图像,下面是我用于此代码的 json 结构:
{
// "to": "",
// The value should be an array of registration tokens to which to send the multicast message. The array must contain at least 1 and at most 1000 registration tokens. To send a message to a single device, use the to parameter.
"registration_ids":[""],
"collapseKey": "com.mondee.abhi.uat",
"contentAvailable": true,
"data": {
//IOS Image and Media and Multiple Icons -> "type": "chat"
"type": "chat",
"displayInApp": true,
"inAppType":"connectingFlightTimer", //Flight delayed, flight added to trip
//based on "inAppType" value, will be determined
"content":{
"images":["img1","img2"],
"remainingTime" : "3.3",
"airlinesLogo":"https://image-url.com"
},
"channelName": "High Importance Notifications",
"channelDescription": "This channel is used for important notifications.",
///////////////////////IOS//////////////////////
//IOS Image and Media and Multiple Icons
// "type": "chat",
//"My Custom Room" // this can be a senders name or the name of a channel or group
"chatRoomName": "any name",
// use whatever value you have from your backend
"senderId": "f91840a2-a1bd-4d7a-a7ea-b4c08f7292e0",
"senderThumbnail": "https://picsum.photos/300",
"senderDisplayName": "Bara Batta",
//https://fastly.picsum.photos/id/368/536/354.jpg?hmac=2b0UU6Y-8XxkiRBhatgBJ-ni3aWJ5CcVVENpX-mEiIA
"iosRichLink": "https://downloads.intercomcdn.com/i/o/149067330/c8067812d60c979598f4b225/PushVideo.gif",
///////////////////////END IOS////////////////////
//use always
"click_action": "FLUTTER_NOTIFICATION_CLICK",
//if we need to show avatar for android - flutter local notification
"avatar": "https://picsum.photos/30/30"
},
//it "type" chat ,sender user id
"from": "201322439669",
//it "type" chat
"messageId": "0:1711565124543896%6b4165a56b4165a5",
"mutableContent": true,
// This parameter specifies how long (in seconds) the message should be kept in FCM storage if the device is offline. The maximum time to live supported is 4 weeks, and the default value is 4 weeks.
"time_to_live":2419200,
"notification": {
//specify the channel, so diferrent icon and diferrent group of noifications will be handeled
"channelId": "high_importance_channel",//normal channel
//for BigPicture notification, Android
"image": "https://images.trippro.com/deals/destination/300/SFO.png",
//for IOS and Android - if type is "chat" then SenderName will be the title
"title": "Flight Alert",
//for localization
"titleLocArgs": [],
"titleLocKey": null,
//for IOS and Android
"body": "We regret to inform you that your Flight {Flight Number} scheduled to depart on {date} from {Origin code} to {Destination code} has been cancelled by the airline. We apologize for the inconvenience caused.",
//for localization
"bodyLocArgs": [],
"bodyLocKey": null,
"android": {
"channelId": "high_importance_channel",
"clickAction": null,
"color": "primaryColor",
"count": null,
// "imageUrl": "https://images.trippro.com/deals/destination/300/SFO.png",
"link": null,
"priority": "high",
"smallIcon": "abhi_notify_general",
"sound": "default",
"ticker": null,
"tag": "campaign_collapse_key_9125358200005252361",
"visibility": "public"
},
"web": null
},
//extra params we can pass to IOS
"acme1": "bar",
"acme2": [
"bang",
"whiz"
],
"sentTime": 1711565124534,
"threadId": null,
"ttl": 86400
}
要使用邮递员发送通知,您可以使用旧方法(目前有效):
端点:
https://fcm.googleapis.com/fcm/send
在标题中:授权:key=