티스토리 뷰
[Flutter, Android] FCM (push notification)
Push Notification은 Firebase에서 각 핸드폰으로 알림 메세지를 전달하는 것을 구현해보려고 합니다.
push notification을 하는 방법이 많아서 선택을 하여야 하는데 쉽게 여러 핸드폰으로 알림을 보낼 수 있는 방법이
Firebase를 이용한 것입니다.
삽질을 많이해서 다른 분들은 쉽게 진행 할 수 있기를 바라며 😄
공통 부분을 설정하고 Background or Foreground 방법을 선택해서 순서에 맞게 실행하면 됩니다.
전체 코드를 보실 분은 공통 + Foreground 방법을 보시면 됩니다.
개발환경
- IDE: Android Studio
- Flutter
- Webview
Android Studio로 개발을 진행을 진행하고 Flutter을 Webview로 개발하였습니다.
공통
우선 기본 세팅을 진행해야 합니다.
1. Firebase 프로젝트 생성
프로젝트 이름: push notification
애널리틱스: 자유
진행을 완료하면 다음과 같이 프로젝트가 생성됩니다.
2. Firebase를 프로젝트에 적용
여기서 Android 아이콘을 눌러 Android앱에 Firebase 추가합니다.
Android 패키지 이름은 android/app/build.gradle
의 defaultConfig
객체의 applicationId
를 사용하면 됩니다. (이미지의 빨간색 칠한 부분입니다)
google-services.json
을 다운받아서 자신의 프로젝트 android/app
에 넣어줍니다.
이후 작업을 꼭 다 진행하시면 설정이 완료가 됩니다.
(해당 부분은 코드 복사 붙여넣기 부분이라 생략했습니다.)
3. 프로젝트 내부 설정
firebase_core
, firebase_messaging
을 다음과 같이 terminal에서 설치합니다.
$ flutter pub add firebase_core
$ flutter pub add firebase_messaging
설치를 하면 pubspec.yaml
폴더에 설치된 것을 확인 할 수 있습니다.
android/app/build.gradle
에서minSdkVersion flutter.minSdkVersion
=> minSdkVersion 19
로 고정시킵니다.
(버전 호환문제가 발생할 수 있기 때문에 중요합니다.)
4. 연결 파일 구현 (firebase_api.dart)
lib/firebase_api.dart
폴더를 만들어 코드를 작성합니다.
[firebase_api.dart]
import 'package:firebase_messaging/firebase_messaging.dart';
class FirebaseApi {
//create an instance of Firebase Msg
final _firebaseMessaging = FirebaseMessaging.instance;
//function to init notification
Future<void> initNotifications() async {
//request permission from user (will prompt user)
await _firebaseMessaging.requestPermission(
badge: true,
alert: true,
sound: true
);
//fetch the FCM token for this device
final FCMToken = await _firebaseMessaging.getToken();
//print the token (normally you would send this to your server)
print('Token: $FCMToken');
}
}
이젠 main.dart
에서 사용을 해봅시다.
웹뷰로 개발을 진행하게 되어 해당 부분 제외하고 진행을 하여도 됩니다.
(웹뷰로 하니 뭔가 많이 떠 있어 좋았습니다.)
[main.dart]
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
import 'notification.dart';
import 'package:firebase_core/firebase_core.dart';
import 'firebase_api.dart';
final home_url = Uri.parse("웹뷰할 URL");
void main() async {
WidgetsFlutterBinding.ensureInitialized(); //앱 실행할 준비가 완료될 때까지 기다린다.
await Firebase.initializeApp();
await FirebaseApi().initNotifications();
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Test App',
home: const MyHomePage()
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
static WebViewController controller = WebViewController()
..setJavaScriptMode(JavaScriptMode.unrestricted)
..addJavaScriptChannel(
'Toaster',
onMessageReceived: (JavaScriptMessage msg) async {
// Webview 에서 받아온 javascript message
}
)
..loadRequest(home_url);
@override
State<MyHomePage> createState() => _MyHomePageState();
// State 객체가 최초 생성될 때 호출되는 메소드 (한번만 호출)
// 초기화 하는 부분 주로 사용
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: WebViewWidget(
controller: controller
)
);
}
}
해당 작업을 완료한 후 애뮬레이터를 실행을 하여 Token
을 파악 후 테스트를 진행해 보겠습니다.
다음 이미지의 순서에 맞춰서 진행하면 됩니다.
토큰은 Logcat
에서 Filter에 Token을 입력하여 찾으시면 됩니다.
번외 - 1 테스트하는 방법 (Firebase)
해당 과정은 밑에서 세팅을 한 후에 진행하는 과정이므로 번외로 만들었습니다.
참여(Engage)의 Messaging을 눌러 첫 번째 캠페인을 만듭니다.
Firebase 알림 메세지로 들어갑니다.
알림 제목, 알림 텍스트를 적어두고 텍스트 메시지 전송을 선택합니다.
파악한 Token을 해당 input에 넣고 테스트를 진행하면 됩니다.
번외 - 2 테스트하는 방법 (postman)
해당 부분은 너무 잘 나와있는 영상이 있어 영상으로 대체합니다.
Youtube: https://www.youtube.com/watch?app=desktop&v=rQzexLu0eLU
----------------------------------------------------------------------------------
Background Message
앱은 열려있지만, 뒷단에서 동작하고 있는 경우
firebase_api.dart
를 수정- 애뮬레이터를 켜서 Token 받기
- 테스트 하기(번외)
1. firebase_api.dart
를 수정
다음과 같이 /lib/firebase_api.dart
파일에 코드를 추가합니다.
[firebase_api.dart]
import 'package:firebase_messaging/firebase_messaging.dart';
<!-- 추가된 부분 -->
Future<void> handleBackgroundMessage(RemoteMessage message) async {
print('mattabu: Title: ${message.notification?.title}');
print('mattabu: Body: ${message.notification?.title}');
print('mattabu: Payload: ${message?.data}');
}
class FirebaseApi {
...
<!-- 추가된 부분 -->
FirebaseMessaging.onBackgroundMessag(handleBackgroundMessage);
}
Foreground Message
앱이 열려있고, 화면을 차지하고 있는 경우
flutter_local_notifications
설치firebase_api.dart
수정android/app/src/main/AndroidManifest.xml
수정- 테스트 하기(번외)
1. flutter_local_notifications
설치
$ flutter pub add flutter_local_notifications
2. firebase_api.dart
수정
다음과 같이 /lib/firebase_api.dart
파일에 코드를 추가합니다.
(구조를 background에서 살짝 바꿨으니 확인하며 사용하시면 됩니다.)
[firebase_api.dart]
import 'dart:convert';
import 'package:firebase_messaging/firebase_messaging.dart';
<!-- 추가된 부분 -->
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
class FirebaseApi {
//create an instance of Firebase Msg
final _firebaseMessaging = FirebaseMessaging.instance;
<!-- 추가된 부분 -->
final _androidChannel = const AndroidNotificationChannel(
'high_importance_channel',
'High Importance Notifications',
description: 'This channel is used for notification',
importance: Importance.defaultImportance,
);
final _localNotifications = FlutterLocalNotificationsPlugin();
//function to init notification
Future<void> initNotifications() async {
//request permission from user (will prompt user)
await _firebaseMessaging.requestPermission();
//fetch the FCM token for this device
final FCMToken = await _firebaseMessaging.getToken();
//print the token (normally you would send this to your server)
print('mattabu: Token: $FCMToken');
<!-- 추가된 부분 -->
initPushNotifications();
initLocalNotification();
}
<!-- 추가된 부분 -->
Future initLocalNotification() async {
const android = AndroidInitializationSettings('@drawable/ic_launcher');
const settings = InitializationSettings(android: android);
await _localNotifications.initialize(settings);
final platform = _localNotifications.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin>();
await platform?.createNotificationChannel(_androidChannel);
}
<!-- 추가된 부분 -->
//function to handle received Msg
void handleMessage(RemoteMessage? message) {
//if the message is null , do nothing
if (message == null) return ;
print('mattabu: $message');
}
<!-- 추가된 부분 -->
Future initPushNotifications() async {
await FirebaseMessaging.instance
.setForegroundNotificationPresentationOptions(
alert: true,
badge: true,
sound: true,
);
FirebaseMessaging.instance.getInitialMessage().then(handleMessage);
FirebaseMessaging.onMessageOpenedApp.listen(handleMessage);
//foreground
FirebaseMessaging.onMessage.listen((message) {
final notification = message.notification;
if (notification == null) return;
_localNotifications.show(
notification.hashCode,
notification.title,
notification.body,
NotificationDetails(
android: AndroidNotificationDetails(
_androidChannel.id,
_androidChannel.name,
channelDescription: _androidChannel.description,
icon: '@drawable/ic_launcher', //android/app/src/main/res/drawable 에 들어있는 아이콘
),
),
payload: jsonEncode(message.toMap())
);
});
}
}
3. AndroidManifest.xml
수정
다음과 같이 android/app/src/main/AndroidManifest.xml
파일에 코드를 추가합니다.
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<!--FCM-->
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<uses-permission android:name="android.permission.ACCESS_NOTIFICATION_POLICY"/>
<uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="USE_FULL_SCREEN_INTENT"/>
<application
android:label="push notification"
android:name="${applicationName}"
android:icon="@drawable/ic_launcher.png"
android:usesCleartextTraffic="true">
<activity
...
>
...
<!--추가된 부분-->
<meta-data
android:name="com.google.firebase.messaging.default_notification_channel_id"
android:value="high_importance_channel" />
</activity>
</application>
</manifest>
마치며
오랜만에 개발 글을 작성하게 되었는데 많은 도움이 되었으면 좋겠습니다.
항상 느끼는 거지만 쉽게 설명하고 작성하는 것이 쉽지 않은 것 같습니다.
그래도 이 글을 보고 한번에 해결을 하셨으면 하는 바람이 있습니다.
추가로 궁금한 점이 있으시면 연락 주세요!!!
'Tip and Error' 카테고리의 다른 글
flutter webview로 만들어보기(+ device 정보) (0) | 2024.02.18 |
---|---|
데스크톱 앱 위치, 사이즈 저장 (NW.js) (2) | 2024.01.26 |
Webview 양방향 통신 비동기 -> 동기 전환 (w. Flutter) (50) | 2023.09.24 |
많은 데이터로 인한 문제 (무한 스크롤 성능 개선) (4) | 2023.09.16 |
ESLint, Prettier 적용 (IntelliJ, WebStorm) (0) | 2023.09.08 |