> For the complete documentation index, see [llms.txt](https://docs.dfinery.ai/developer-guide/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.dfinery.ai/developer-guide/platform/flutter/action/push-notification.md).

# 푸시

이 문서는 Flutter에 대한 푸시 알림을 설정하는 방법을 다룹니다. 푸시 알림을 통합하려면 각 기본 플랫폼을 별도로 설정해야합니다. 나열된 각 안내서를 따라 설치를 완료하십시오.

## 1. 플랫폼 별 초기 설정 <a href="#platform-setup" id="platform-setup"></a>

{% tabs %}
{% tab title="Android" %}

#### Android 프로젝트에 Firebase 추가하기

Firebase에서 제공되는 [Android 프로젝트에 Firebase 추가](https://firebase.google.com/docs/android/setup) 의 지시사항에 따라 Android 프로젝트에 Firebase를 추가합니다.

{% hint style="info" %}
가이드 완료시 확인해야 할것

* [google-services.json](https://support.google.com/firebase/answer/7015592) 파일이 앱에 추가되어 있는지 확인
* 앱 레벨의 `build.gradle`에 `com.google.gms.google-services` 플러그인이 추가되어 있는지 확인
  {% endhint %}

#### Dfinery Console에서 Firebase 정보 등록하기

Console에서 [부가설정/채널 부가 설정/푸시/Android 설정관리](https://console.dfinery.ai)에 들어가 발신자 ID를 입력하고 JSON 형식의 Firebase 사용자 인증 정보 비공개 키 파일을 업로드합니다.

#### 발신자 ID 확인 방법

1. [**Firebase Console**](https://console.firebase.google.com/)로 이동합니다.
2. 프로젝트를 선택합니다.
3. 좌측 패널에서 프로젝트 개요 오른쪽에 ⚙️ 아이콘을 클릭합니다.
4. 프로젝트 설정을 클릭합니다.
5. 프로젝트 설정 상단 탭에서 클라우드 메시징을 클릭합니다.
6. [**발신자 ID**](https://firebase.google.com/docs/cloud-messaging/concept-options?hl=ko#senderid)를 확인합니다.

#### Firebase 사용자 인증 정보 비공개 키 파일 발급 방법

Firebase에서 제공하는 [수동으로 사용자 인증 정보 제공](https://firebase.google.com/docs/cloud-messaging/auth-server?hl=ko#provide-credentials-manually)을 참고하여 JSON 형식으로 키를 저장한다음 Console에 파일을 업로드 해주세요.

해당 페이지에서 확인할 수 있는 주요 내용은 다음과 같습니다.

1. [Firebase Console](https://console.firebase.google.com/)에서 설정 > [서비스 계정](https://console.cloud.google.com/iam-admin/)을 엽니다.
2. 새 비공개 키 생성을 클릭한 다음 키 생성을 클릭하여 확인합니다.
3. 키가 들어 있는 JSON 파일을 안전하게 저장합니다.

만약 비공개 키 파일 발급에 어려움을 겪으실 경우 [Firebase 사용자 인증 정보 비공개 키 파일 발급 따라해보기](https://github.com/IGAWorksDev/dfn-v2-gitbook-sync/tree/main/dev_guide_ko/platform/flutter/action/push_notification.md#firebase-사용자-인증-정보-비공개-키-파일-발급-따라해보기)를 참고하여 주시기 바랍니다.

#### 앱 매니페스트에 FirebaseMessagingService를 상속받는 서비스 추가

```xml
<application>
    ...
    <service
        android:name="com.igaworks.dfinery.DfineryFirebaseMessagingService"
        android:exported="false">
        <intent-filter>
            <action android:name="com.google.firebase.MESSAGING_EVENT" />
        </intent-filter>
    </service>
</application>
```

#### 앱 매니페스트에 `POST_NOTIFICATIONS` 권한 추가하기

Android 13이상의 기기일 경우 `POST_NOTIFICATIONS` 권한이 필요합니다. `android/app/src/main/AndroidManifest.xml`에 해당 권한을 추가해주시기 바랍니다.

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

다음은 작성이 완료된 `android/app/src/main/AndroidManifest.xml` 예시입니다.

```xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.myapp">

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
    <uses-permission android:name="com.google.android.gms.permission.AD_ID"/>

    <application
      android:name=".MainApplication"
      android:label="@string/app_name"
      android:icon="@mipmap/ic_launcher"
      android:allowBackup="false"
      android:theme="@style/AppTheme">
      <activity
        android:name=".MainActivity"
        android:label="@string/app_name"
        android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode"
        android:launchMode="singleTask"
        android:windowSoftInputMode="adjustResize"
        android:exported="true">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
      </activity>
      <service
        android:name="com.igaworks.dfinery.DfineryFirebaseMessagingService"
        android:exported="false">
        <intent-filter>
            <action android:name="com.google.firebase.MESSAGING_EVENT" />
        </intent-filter>
      </service>
    </application>
</manifest>
```

### 푸시 알림 채널을 Dfinery에 연동하기

#### 푸시 알림 채널 생성

Android 8.0부터는 알림을 수신받기 위해 알림 채널 생성이 필요합니다. `createNotificationChannel()` 메소드를 사용하여 알림 채널을 생성할 수 있습니다.

{% hint style="info" %}
알림 채널 API는 [Android 8.0](https://developer.android.com/about/versions/oreo?hl=ko)이상에서 지원하며 그 미만 기기에서는 아무런 동작을 하지 않습니다.
{% endhint %}

```dart
final properties = {
    DFAndroidNotificationChannelProperty.id: 'dfinery_channel',
    DFAndroidNotificationChannelProperty.name: 'Default Notification Channel',
    DFAndroidNotificationChannelProperty.importance: DFAndroidNotificationChannelImportance.high,
    DFAndroidNotificationChannelProperty.badge: true,
    DFAndroidNotificationChannelProperty.sound: true,
    DFAndroidNotificationChannelProperty.visibility: DFAndroidNotificationChannelVisibility.public,
    DFAndroidNotificationChannelProperty.vibration: true
};
Dfinery.createNotificationChannel(properties: properties);
```

<details>

<summary>createNotificationChannel() 정보</summary>

| 값                                                | 설명                                                                                                                                                                                  | 타입      |
| ------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- |
| DFAndroidNotificationChannelProperty.id          | [알림 채널의 ID](https://developer.android.com/reference/android/app/NotificationChannel#NotificationChannel\(java.lang.String,%20java.lang.CharSequence,%20int\))                       | String  |
| DFAndroidNotificationChannelProperty.name        | [알림 채널의 이름](https://developer.android.com/reference/android/app/NotificationChannel#setName\(java.lang.CharSequence\))                                                              | String  |
| DFAndroidNotificationChannelProperty.description | [알림 채널의 설명](https://developer.android.com/reference/android/app/NotificationChannel#setDescription\(java.lang.String\))                                                             | String  |
| DFAndroidNotificationChannelProperty.badge       | [알림 채널의 뱃지 표시 유무](https://developer.android.com/reference/android/app/NotificationChannel#setShowBadge\(boolean\))                                                                  | Boolean |
| DFAndroidNotificationChannelProperty.sound       | [알림 채널의 알림음 재생 유무](https://developer.android.com/reference/android/app/NotificationChannel#setSound\(android.net.Uri,%20android.media.AudioAttributes\))                            | Boolean |
| DFAndroidNotificationChannelProperty.soundUri    | [알림 채널의 알림음 URI](https://developer.android.com/reference/android/app/NotificationChannel#setSound\(android.net.Uri,%20android.media.AudioAttributes\)), 알림음 파일은 `res/raw`에 위치해야 합니다 | String  |
| DFAndroidNotificationChannelProperty.importance  | [알림 채널의 중요도](https://developer.android.com/reference/android/app/NotificationChannel#setImportance\(int\))                                                                          | Integer |
| DFAndroidNotificationChannelProperty.lights      | [알림 채널의 표시등 표시 여부](https://developer.android.com/reference/android/app/NotificationChannel#enableLights\(boolean\))                                                                 | Boolean |
| DFAndroidNotificationChannelProperty.vibration   | [알림 채널의 진동 여부](https://developer.android.com/reference/android/app/NotificationChannel#enableVibration\(boolean\))                                                                  | Boolean |
| DFAndroidNotificationChannelProperty.visibility  | [알림 채널의 잠금 채널 표시 유무](https://developer.android.com/reference/android/app/NotificationChannel#setLockscreenVisibility\(int\))                                                        | Boolean |
| DFAndroidNotificationChannelProperty.bypassDnd   | [알림 채널의 방해금지 시 노출 여부](https://developer.android.com/reference/android/app/NotificationChannel#setBypassDnd\(boolean\))                                                              | Boolean |
| DFAndroidNotificationChannelProperty.groupId     | [알림 채널의 알림 채널 그룹 ID](https://developer.android.com/reference/android/app/NotificationChannel#setGroup\(java.lang.String\))                                                          | String  |

</details>

#### 생성한 푸시 알림 채널 ID 등록

Dfinery 초기화시 `initWithConfig()`메소드를 사용하여 사용하여 생성한 알림 채널의 ID를 등록합니다.

```dart
  final config = {
    DFConfig.androidNotificationChannelId: 'dfinery_channel'
  };
  Dfinery.initWithConfig(serviceId: "YOUR_SERVICE_ID", config: config);
  
```

#### 푸시 알림 채널 그룹 생성 (선택사항)

Andorid 알림 채널에는 선택적으로 알림 채널 그룹을 설정할 수 있습니다.

{% hint style="info" %}
알림 채널 그룹 API는 [Android 8.0](https://developer.android.com/about/versions/oreo?hl=ko)이상에서 지원하며 그 미만 기기에서는 아무런 동작을 하지 않습니다.
{% endhint %}

```dart
//알림 채널 그룹 생성
final notificationChannelGroupProperties = {
    DFAndroidNotificationChannelGroupProperty.id : 'dfinery_channel_group',
    DFAndroidNotificationChannelGroupProperty.name : 'Default Notification Channel Group'
};
Dfinery.createNotificationChannelGroup(properties: notificationChannelGroupProperties);

//알림 채널에 그룹 할당
final notificationChannelProperties = {
    DFAndroidNotificationChannelProperty.id: 'dfinery_channel',
    DFAndroidNotificationChannelProperty.groupId: 'dfinery_channel_group'
};
Dfinery.createNotificationChannel(properties: notificationChannelProperties);
```

<details>

<summary>createNotificationChannelGroup() 정보</summary>

| 값                                              | 설명                                                                                                                                                                  | 타입     |
| ---------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ |
| DFAndroidNotificationChannelGroupProperty.id   | [알림 채널 그룹의 ID](https://developer.android.com/reference/android/app/NotificationChannelGroup#NotificationChannelGroup\(java.lang.String,%20java.lang.CharSequence\)) | String |
| DFAndroidNotificationChannelGroupProperty.name | [알림 채널 그룹의 이름](https://developer.android.com/reference/android/app/NotificationChannelGroup#NotificationChannelGroup\(java.lang.String,%20java.lang.CharSequence\)) | String |

</details>

#### 푸시 알림 아이콘 설정하기

Android에서 푸시 알림을 표시하기 위해서는 아이콘 설정이 필요합니다. init시 config를 설정할때 아이콘 [Drawable](https://developer.android.com/guide/topics/resources/drawable-resource)의 이름을 입력하여 설정합니다.

{% hint style="info" %}
알림 아이콘은 시스템이 색조(tint)를 적용할 수 있으므로, 투명한 배경을 사용하는 것이 좋습니다.
{% endhint %}

{% hint style="warning" %}
아이콘 설정은 drawable 만을 지원합니다. mipmap은 지원하지 않습니다.
{% endhint %}

```dart

  final config = {
    DFConfig.androidNotificationIconName: 'ic_dfinery',
  };
  Dfinery.initWithConfig(serviceId: "YOUR_SERVICE_ID", config: config);
  
```

## 푸시 알림 강조색 지정하기 <a href="#push_accent_color" id="push_accent_color"></a>

알림 아이콘 강조색은 init시 config에 색상의 hexString 값을 입력하여 적용할 수 있습니다. 색상을 지정하지 않으면 시스템 기본 색상으로 표시됩니다.

{% hint style="info" %}

* 푸시 알림 강조색은 사용 중인 OS 버전과 런처에 따라 다르게 표시될 수 있습니다.
* 값은 Hex color codes 형태로 입력해야 합니다.
  {% endhint %}

```dart
  final config = {
    DFConfig.androidNotificationAccentColor: '#E00052',
  };
  Dfinery.initWithConfig(serviceId: "YOUR_SERVICE_ID", config: config);
  
```

{% endtab %}

{% tab title="iOS" %}

#### Dfinery Console에 APNs 인증키 등록

푸시메시지를 사용하기 위해 Apple로부터 발급받은 .p8 APNs 인증키를 [DFINERY 콘솔](https://console.dfinery.ai/)에 등록해주세요. 콘솔의 부가 설정 / 채널 부가 설정 / 푸시 / iOS 설정 관리에서 등록할 수 있습니다.

#### iOS 네이티브 코드 수정

`FlutterAppDelegate`를 채택하는 AppDelegate 클래스의 메서드인 `application(application:launchOptions)` 안에 다음과 같이 코드를 추가해 주세요

```swift
@main
@objc class AppDelegate: FlutterAppDelegate {
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
      GeneratedPluginRegistrant.register(with: self)
      
      //추가
      UNUserNotificationCenter.current().delegate = self
      
    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }
}
```

#### Capabilities 설정

아래 스크린샷과 같이 PushNotification, Background Modes/Remote Notifications에 대한 Capabilities를 추가해 주세요.

![Capabilities설정](/files/6t9hqleUUWB3DvPsSqOT)

#### Notification Service Extension 추가

아래 스크린샷과 같이 Notification Service Extension을 추가해주세요

![ServiceExtension추가](/files/bVGEJBNSsWKDNsmCZ5sz)

![ServiceExtension추가](/files/0lO6byMxKeR4UYMWwajt)

![donotactivate](/files/5XJXR57iSwRqvTyomwsm)

{% hint style="info" %}
ServiceExtension 타겟의 Minimum Deployments 설정을 한번 더 확인해 주세요.
{% endhint %}

#### cocoapods 연동

ios 디렉토리 내 podfile에 추가한 NotificationServiceExtension에 대한 타겟과 디파이너리 의존성을 추가합니다.

```ruby
target 'NotificationServiceExtension' do
  pod 'DfinerySDKServiceExtension'
end
```

이후 pod install을 통해 SDK를 설치합니다.

#### iOS 네이티브 코드 수정

생성한 Notification Service Extension 프로젝트 내에 DfinerySDKServiceExtension을 import 후 코드를 수정합니다.

**Swift**

```swift
import UserNotifications
import DfinerySDKServiceExtension

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)
        
        if let bestAttemptContent = bestAttemptContent {
            
            if DfineryServiceExtension.canHandle(content: bestAttemptContent, contentHandler: contentHandler) {
                return
            }
            
            contentHandler(bestAttemptContent)
        }
    }
    // ...
}
```

**Objective-C**

```objective-c
#import "NotificationService.h"
#import <DfinerySDKServiceExtension/DfinerySDKServiceExtension.h>

// ...

- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {
    self.contentHandler = contentHandler;
    self.bestAttemptContent = [request.content mutableCopy];
    
    if (self.bestAttemptContent) {
        if ([DfineryServiceExtension canHandleWithContent:self.bestAttemptContent contentHandler:contentHandler]) {
            return;
        }
        self.contentHandler(self.bestAttemptContent);
    }
}
// ...
```

{% endtab %}
{% endtabs %}

## 2. 런타임 알림 권한 요청하기 <a href="#request-notification-permission" id="request-notification-permission"></a>

[permission\_handler](https://pub.dev/packages/permission_handler) 패키지를 활용한 푸시 알림 권한 요청 예시입니다.

```dart
import 'package:permission_handler/permission_handler.dart';

Future<void> requestNotificationPermission() async {
  final status = await Permission.notification.request();
  if (status.isGranted) {
    Dfinery.getPushToken().then((token) {
      if (token != null) {
        Dfinery.setPushToken(pushToken: token);
      }
    });
  } 
}
```

## 3. 토큰을 Dfinery에 연동하기 <a href="#integrate-token" id="integrate-token"></a>

Firebase/APNS에서 발급된 토큰을 Dfinery에 연동하기 위해서 다음과 같이 작성해주시기 바랍니다.

아래는 가장 최근에 발급받은 푸시 토큰을 가져오고 Dfinery에 연동하는 예제입니다.

{% hint style="info" %}
`Dfinery.getPushToken()`은 [Firebase Messaging](https://mvnrepository.com/artifact/com.google.firebase/firebase-messaging) API를 사용하여 푸시 토큰을 가져옵니다. 다른 방법으로 푸시 토큰을 가져와 설정하셔도 무관합니다.
{% endhint %}

```dart
import 'package:flutter/material.dart';
import 'package:dfinery_plugin/dfinery_plugin.dart';

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  @override
  void initState() {
    super.initState();
    Dfinery.getPushToken().then((token) {
      if (token != null) {
        Dfinery.setPushToken(pushToken: token);
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      // your app content
    );
  }
}
```

## 4. 푸시 클릭시 호출되는 딥링크 연동하기 <a href="#deeplink-integration" id="deeplink-integration"></a>

1. Flutter 설정

[app\_links](https://pub.dev/packages/app_links) 패키지를 사용하여 딥링크를 처리할 수 있습니다.

pubspec.yaml에 패키지를 추가하세요:

```yaml
dependencies:
  app_links: ^3.4.3
```

2. Android 설정

[앱 콘텐츠 딥 링크 만들기](https://developer.android.com/training/app-links/deep-linking)에 따라 딥링크 설정을 해주세요.

```xml
<activity
    android:name=".MainActivity"
    android:exported="true"
    android:launchMode="singleTop"
    android:theme="@style/LaunchTheme">
    
    <!-- 기존 intent-filter -->
    <intent-filter android:autoVerify="true">
        <action android:name="android.intent.action.MAIN"/>
        <category android:name="android.intent.category.LAUNCHER"/>
    </intent-filter>
    
    <!-- 딥링크를 위한 intent-filter -->
    <intent-filter android:autoVerify="true">
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <data android:scheme="yourapp" />
    </intent-filter>
</activity>
```

3. iOS 설정

`ios/Runner/Info.plist`에 URL scheme을 추가합니다:

```xml
<key>CFBundleURLTypes</key>
<array>
    <dict>
        <key>CFBundleURLName</key>
        <string>yourapp.deeplink</string>
        <key>CFBundleURLSchemes</key>
        <array>
            <string>yourapp</string>
        </array>
    </dict>
</array>
```

4. Flutter 코드

```dart
import 'package:app_links/app_links.dart';

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  late AppLinks _appLinks;
  StreamSubscription<Uri>? _linkSubscription;

  @override
  void initState() {
    super.initState();
    _initDeepLinks();
  }

  Future<void> _initDeepLinks() async {
    _appLinks = AppLinks();

    // 앱이 실행 중일 때 딥링크 처리
    _linkSubscription = _appLinks.uriLinkStream.listen(
      (Uri uri) {
        print('Received deep link: $uri');
        _handleDeepLink(uri);
      },
      onError: (err) {
        print('Deep link error: $err');
      },
    );

    // 앱이 종료된 상태에서 딥링크로 실행될 때 처리
    final Uri? initialUri = await _appLinks.getInitialLink();
    if (initialUri != null) {
      print('Initial deep link: $initialUri');
      _handleDeepLink(initialUri);
    }
  }

  void _handleDeepLink(Uri uri) {
    // 딥링크 처리 로직
    print('Handling deep link: $uri');
  }

  @override
  void dispose() {
    _linkSubscription?.cancel();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      // your app content
    );
  }
}
```

## 5. 푸시 알림 수신 허용 유무 반영하기 <a href="#push-opt-in" id="push-opt-in"></a>

광고성 목적이 있는 푸시의 경우 [정보통신망법](https://www.law.go.kr/%EB%B2%95%EB%A0%B9/%EC%A0%95%EB%B3%B4%ED%86%B5%EC%8B%A0%EB%A7%9D%20%EC%9D%B4%EC%9A%A9%EC%B4%89%EC%A7%84%20%EB%B0%8F%20%EC%A0%95%EB%B3%B4%EB%B3%B4%ED%98%B8%20%EB%93%B1%EC%97%90%20%EA%B4%80%ED%95%9C%20%EB%B2%95%EB%A5%A0/%EC%A0%9C50%EC%A1%B0) 에 따라 사용자에게 사전 수신동의를 받아야 합니다. 사용자에게 푸시 알림이 허용되었다는 정보를 Dfinery에 반영하기 위해서 다음의 일련의 작업을 수행하여 주시기 바랍니다.

1. 사용자에게 푸시 알림 허용에 대한 고지하기
2. 사용자의 허용/거부 의사에 대한 값을 반영하기 위해 다음의 코드를 작성

[알림 수신 동의 설정하기](https://github.com/IGAWorksDev/dfn-v2-gitbook-sync/tree/main/dev_guide_ko/platform/flutter/action/user_profile.md#consent)를 참고하여 사용자가 동의한 항목에 대해 값을 입력해주시기 바랍니다. 아래 코드는 광고성 수신 동의를 허용한 예시입니다.

```dart
final param = {
    DFUserProfile.pushAdsOptin: true
};
Dfinery.setUserProfiles(values: param);
```

## 6. 완료 <a href="#complete" id="complete"></a>

이제 Dfinery에서 알림을 사용하기 위한 모든 준비가 완료되었습니다.
