Rich Push Notifications (Push com Imagens)

Este guia descreve como configurar o Notification Service Extension em iOS e o suporte nativo a imagens no Android para exibir push notifications com imagens (rich content) usando o Inngage Flutter SDK

Visão Geral

Por padrão, o sistema operacional entrega notificações sem processar o payload antes de exibi-las. Para anexar uma imagem ao push, é necessário um Notification Service Extension — um processo separado que intercepta o payload, baixa a imagem e a anexa à notificação antes que ela apareça na tela.

PlataformaMecanismoRequisito mínimo
iOSUNNotificationServiceExtension + Firebase SDK helperiOS 10 / Xcode 14+
AndroidFirebase Messaging (nativo)API 21+

iOS

1. Criar o Notification Service Extension no Xcode

  1. Abra o projeto em Xcode (ios/Runner.xcworkspace).
  2. Vá em File → New → Target.
  3. Selecione Notification Service Extension e clique em Next.
  4. Preencha os campos:
    • Product Name: NotificationService
    • Language: Swift (recomendado) ou Objective-C
    • Team: selecione o mesmo App Team do Runner
  5. Clique em Finish e aceite Activate quando perguntado.

O Xcode criará um novo grupo NotificationService/ dentro de ios/ com os arquivos NotificationService.swift e Info.plist.


2. Implementar o NotificationService.swift

Substitua o conteúdo gerado pelo Xcode pelo código abaixo, que delega o processamento ao helper do Firebase:

import UserNotifications
import FirebaseMessaging

class NotificationService: UNNotificationServiceExtension {

    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)

        guard let bestAttemptContent = bestAttemptContent else {
            contentHandler(request.content)
            return
        }

        // Baixa e anexa a imagem enviada via `fcm_options.image` no payload do push.
        Messaging.serviceExtension().populateNotificationContent(
            bestAttemptContent,
            withContentHandler: contentHandler
        )
    }

    override func serviceExtensionTimeWillExpire() {
        // Entrega a melhor tentativa antes do sistema encerrar a extensão.
        if let contentHandler = contentHandler,
           let bestAttemptContent = bestAttemptContent {
            contentHandler(bestAttemptContent)
        }
    }
}

Objective-C (caso prefira — arquivos .h e .m):

NotificationService.h

#import <UserNotifications/UserNotifications.h>

@interface NotificationService : UNNotificationServiceExtension
@end

NotificationService.m

#import "NotificationService.h"
#import "FirebaseMessaging.h"

@interface NotificationService ()
@property (nonatomic, strong) void (^contentHandler)(UNNotificationContent *);
@property (nonatomic, strong) UNMutableNotificationContent *bestAttemptContent;
@end

@implementation NotificationService

- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request
                   withContentHandler:(void (^)(UNNotificationContent *))contentHandler {
    self.contentHandler = contentHandler;
    self.bestAttemptContent = [request.content mutableCopy];
    [[FIRMessaging extensionHelper] populateNotificationContent:self.bestAttemptContent
                                            withContentHandler:contentHandler];
}

- (void)serviceExtensionTimeWillExpire {
    self.contentHandler(self.bestAttemptContent);
}

@end

3. Configurar o Info.plist da Extensão

O arquivo NotificationService/Info.plist deve declarar o ponto de extensão. Verifique se contém:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
  "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>NSExtension</key>
    <dict>
        <key>NSExtensionPointIdentifier</key>
        <string>com.apple.usernotifications.service</string>
        <key>NSExtensionPrincipalClass</key>
        <!-- Swift: use o nome do módulo como prefixo -->
        <string>$(PRODUCT_MODULE_NAME).NotificationService</string>
        <!-- Objective-C: apenas o nome da classe -->
        <!-- <string>NotificationService</string> -->
    </dict>
</dict>
</plist>

4. Adicionar Firebase/Messaging ao Podfile

A extensão é um binário separado do Runner e precisa linkar o Firebase individualmente. Adicione o target no ios/Podfile:

target 'NotificationService' do
  use_frameworks!
  pod 'Firebase/Messaging'
end

Após editar o Podfile, execute:

cd ios && pod install

5. Configurar Bundle ID e Provisioning Profile

  1. No Xcode, selecione o target NotificationService.
  2. Em Signing & Capabilities:
    • Bundle Identifier: deve ser um sub-bundle do app principal.
      • Exemplo: se o app é com.empresa.meuapp, use com.empresa.meuapp.NotificationService.
    • Team: o mesmo do Runner.
    • Deployment Target: igual ou maior ao do Runner (mínimo iOS 10).
  3. O Provisioning Profile é gerado automaticamente quando Automatically manage signing está ativado.

6. Habilitar Push Notifications no App Principal (Runner)

  1. Selecione o target Runner.
  2. Em Signing & Capabilities, clique em + Capability.
  3. Adicione:
    • Push Notifications
    • Background Modes → marque Remote notifications
  4. Verifique que o Runner.entitlements contém:
<key>aps-environment</key>
<string>development</string>  <!-- ou "production" para release -->

7. Verificar o AppDelegate.swift

O AppDelegate deve registrar o delegate do UNUserNotificationCenter:

import UIKit
import Flutter

@main
@objc class AppDelegate: FlutterAppDelegate {
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
    if #available(iOS 10.0, *) {
      UNUserNotificationCenter.current().delegate = self as UNUserNotificationCenterDelegate
    }
    GeneratedPluginRegistrant.register(with: self)
    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }
}

Android

No Android, o Firebase Messaging suporta imagens ricas sem extensão adicional.

1. Enviar o campo image no payload

Inclua notification.image ou fcm_options.image ao criar o push:

{
  "message": {
    "token": "DEVICE_FCM_TOKEN",
    "notification": {
      "title": "Título da notificação",
      "body": "Corpo da notificação",
      "image": "https://exemplo.com/imagem.jpg"
    }
  }
}

O Firebase SDK no Android baixa e exibe a imagem automaticamente em notificações do sistema. Notificações recebidas em foreground precisam ser exibidas manualmente — o Inngage SDK gerencia isso internamente.

2. Permissões no AndroidManifest.xml

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />

Payload FCM Completo (iOS + Android)

{
  "message": {
    "token": "DEVICE_FCM_TOKEN",
    "notification": {
      "title": "Promoção especial",
      "body": "Confira nossa oferta de hoje!"
    },
    "fcm_options": {
      "image": "https://cdn.empresa.com/promo-banner.jpg"
    },
    "apns": {
      "payload": {
        "aps": {
          "mutable-content": 1
        }
      },
      "fcm_options": {
        "image": "https://cdn.empresa.com/promo-banner.jpg"
      }
    }
  }
}

Importante — iOS: o campo "mutable-content": 1 no payload APNS é obrigatório para que o sistema acione o Notification Service Extension. Sem ele, a extensão não é chamada e a imagem não aparece.

Especificações da imagem

CampoValor
ProtocoloHTTPS (sem autenticação)
FormatosJPEG, PNG, GIF estático
Tamanho máximo~1 MB (iOS limita o tempo de download a ~30 s)
Proporção recomendada2:1 — ex.: 1200 × 600 px

Checklist de Validação

iOS

  • Target NotificationService criado no Xcode
  • NotificationService.swift usa Messaging.serviceExtension().populateNotificationContent
  • Info.plist da extensão declara NSExtensionPointIdentifier = com.apple.usernotifications.service
  • Podfile inclui pod 'Firebase/Messaging' no target da extensão e pod install executado
  • Bundle ID da extensão é sub-bundle do app principal
  • Capability Push Notifications adicionada ao Runner
  • Capability Background Modes → Remote notifications adicionada ao Runner
  • Payload enviado contém mutable-content: 1 e fcm_options.image

Android

  • Permissões INTERNET e POST_NOTIFICATIONS no AndroidManifest.xml
  • Payload enviado contém notification.image ou fcm_options.image
  • URL da imagem é HTTPS e acessível publicamente

Solução de Problemas

SintomaCausa provávelSolução
Imagem não aparece no iOSmutable-content ausente no payloadAdicione "mutable-content": 1 no payload APNS
Extensão não é invocadaBundle ID incorreto ou Provisioning Profile inválidoVerifique Bundle ID e reconfigure Signing no Xcode
Crash na extensãoFirebase não linkado na extensãoAdicione pod 'Firebase/Messaging' ao target no Podfile e rode pod install
Imagem aparece em background mas não em foreground (Android)Comportamento esperado do FCMO SDK exibe internamente via flutter_local_notifications
pod install falhaPodfile.lock desatualizadoExecute pod repo update && pod install