yarn add @react-native-firebase/app @react-native-firebase/messaging @notifee/react-native
설치시 이슈 1.
The Swift pod `FirebaseCoreInternal` depends upon `GoogleUtilities`, which does not define modules.
To opt into those targets generating module maps (which is necessary to import them from Swift when building as static libraries), you may set `use_modular_headers!` globally in your Podfile, or specify `:modular_headers => true` for particular dependencies.
// AppDelegate.mm에 추가
#import <Firebase.h>
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.moduleName = @"?????";
// You can add your custom initial props in the dictionary below.
// They will be passed down to the ViewController used by React Native.
self.initialProps = @{};
[FIRApp configure]; // << @react-native-firebase/messaging 을 사용하기 위해 추가함.
return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
설치시 이슈 4.
...
rescue => e
Pod:UI.warn e
end
end
config = use_native_modules!
pod 'GoogleUtilities', :modular_headers => true
pod 'FirebaseCore', :modular_headers => true
# Flags change depending on the env values.
flags = get_default_flags()
use_react_native!(
:path => config[:reactNativePath],
# Hermes is now enabled by default. Disable by setting this flag to false.
:hermes_enabled => flags[:hermes_enabled],
...
remote notification (push notification) 권한 명칭으로 안드로이드는 POST_NOTIFICATIONS , 아이폰은 notification
사실 FCM (Firebase Cloud Messaging) 을 이용하는 것이다.
그냥 notification 은 local notification 을 의미한다. 사용자와 인터렉션 하는 UI 같은 것. notifee 를 통해 이를 세밀히 다룰 수 있다.
따라서 용어구분을 위해 remote / local 모두 통합하는 용어로 push notification 으로 사용할 것.
setBackgroundMessageHandler 가 ios 에서 정상 동작하려면,
https://rnfirebase.io/messaging/usage#background-application-state 에서 ios 측 HeadlessCheck 구현 및 서버에 다음도 추가해주어야 한다.
apns: {
payload: {
aps: {
contentAvailable: true,
On iOS, when the app is in quit state, the setBackgroundMessageHandler is never invoked even when I receive the notification. How can I fix this?
https://github.com/invertase/react-native-firebase/blob/main/docs/faqs-and-tips.md
notifee 에서 다음은 ios 뱃지 관련 기능이다.
notifee.incrementBadgeCount(); // ios 는 확실히 1씩 증가했다.
notifee.setBadgeCount(0).then(() => console.log('Badge count removed')); // 이거는...
아직 다음 두개가 ios 에서 동작하지 않았다.
messaging().onNotificationOpenedApp()
messaging().getInitialNotification()
https://github.com/invertase/react-native-firebase/issues/6915 이러한 이슈가 있다.
@react-native-firebase/messaging 와 @notifee/react-native 를 함께 사용하면 저런 이벤트 리스너들이 동작을 제대로 안할 수 있다고 한다.
https://github.com/invertase/react-native-firebase/issues/5104 여기서 마지막 부분에 notifee 의 리스너들을 사용하라고 되어 있다.
한국 블로그에도 관련 이슈를 정리해놓았다.
https://velog.io/@bongbong2da/Fix-FCM-iOS-onNotificationOpenedApp-getInitialNotification-%ED%95%B8%EB%93%A4%EB%9F%AC%EA%B0%80-%EC%9E%91%EB%8F%99%ED%95%98%EC%A7%80-%EC%95%8A%EC%9D%84-%EB%95%8C
FCM 과 notifee 를 둘 다 설치했을 때 FCM 의 onNotificationOpenedApp, getInitialNotification 은 Notifee 의 onForegroundMessage 로 통합된다.
Notifee 삭제 후 빌드 시, 다시 FCM의 핸들러가 작동하게된다.
난 안드로이드 채널 생성 메소드를 사용해야 하므로 notifee 가 필요하다.
Foreground 라는 명칭 때문에 혼란이 있었다. (onForegroundMessage === onMessage라고 생각했기 때문)
FCM 의 각 핸들러는 다음과 같은 일을 한다.
onNotificationOpenedApp : AppState 가 background 인 상태에서 알림 클릭시 앱이 열리면 실행된다.
getInitialOpenedApp : AppState 가 terminated(exit)인 상태에서 알림 클릭시 앱이 열리면 실행된다.
Notifee 가 설치 되었다면 위 두 핸들러가 Notifee.onForegroundMessage 로 통합 되어,
분리해서 생각할 필요 없이 '알림을 엶으로써 앱이 Focus되었을 때' 라고만 생각 하고 할당 하면 된다.
Android 는 반대로 FCM 의 핸들러가 작동되고 Notifee 의 핸들러가 작동하지 않는다.
ios 에서 Notifee 에서도 리모트 노티 관련하여 지원하는 부분이 있는데 셋팅이 필요 하다. 이것도 셋팅 해야 한다.
https://notifee.app/react-native/docs/ios/remote-notification-support
>>> 이거 셋팅할 때
https://notifee.app/react-native/docs/ios/remote-notification-support#add-the-notification-service-extension
Cycle inside appname; building could produce unreliable results.
○ That command depends on command in Target 'yourprojectname': script phase “[CP-User] [RNFB] Core Configuration”
이런 오류가 날 수 있는데... TARGETS > yourprojectname > Build Phases 에서
[CP-User] [RNFB] Core Configuration 를 맨 아래로 드래그해주면 된다.
NotifeeNotificationService 이거 추가해주고 그게 Copy 설정을 [CP-User] [RNFB] Core Configuration 여기서 해주는데
NotifeeNotificationService 순서가 아래에 있다보니 오류난 것.
android 는 그냥 애초에 노티 떠있는 갯수가 앱아이콘 옆의 숫자가 시스템적으로 동기화 되어서 동작한다.
android 는 리스트의 노티를 클릭해서 들어가면 해당하는게 없어질 때 앱의 카운트도 감소하였다.
ios 는 foreground 인데도 노티가 보여졌다. foreground 에서 기본적으로 안보여지게 하려면 서버측에서 foregroundPresentationOptions 값들을 false 로 주어야 한다.
하지만 아래와 같이 메뉴얼의 notifee_options 가 아니라 __notifee_notification 명칭을 사용해야 했다.
https://github.com/invertase/notifee/blob/main/ios/NotifeeCore/NotifeeCore%2BUNUserNotificationCenter.m#L110
NSDictionary *foregroundPresentationOptions =
notifeeNotification[@"ios"][@"foregroundPresentationOptions"];
NSDictionary *notifeeNotification =
notification.request.content.userInfo[kNotifeeUserInfoNotification];
- (void)userNotificationCenter:(UNUserNotificationCenter *)center
willPresentNotification:(UNNotification *)notification
withCompletionHandler:
(void (^)(UNNotificationPresentationOptions options))completionHandler {
https://developer.apple.com/documentation/usernotifications/unusernotificationcenterdelegate/1649518-usernotificationcenter?language=objc
response.notification.request.content.userInfo 는 리모트 푸시 데이터이다.
메뉴얼에는 분명히 notifee_options 라고 적어놓고는
https://notifee.app/react-native/docs/ios/remote-notification-support#1-update-the-message-payload-sent-via-your-backend
실제 코드에는 __notifee_notification 이라는 명칭을 사용해야한다.
Permissions
import messaging from '@react-native-firebase/messaging';
async function checkApplicationPermission() {
const authorizationStatus = await messaging().requestPermission();
if (authorizationStatus === messaging.AuthorizationStatus.AUTHORIZED) {
console.log('User has notification permissions enabled.');
} else if (authorizationStatus === messaging.AuthorizationStatus.PROVISIONAL) {
console.log('User has provisional notification permissions.');
} else {
console.log('User has notification permissions disabled');
}
}
await messaging().requestPermission({
sound: false,
announcement: true,
// ... other permission settings
});
await messaging().requestPermission({
provisional: true,
});
await messaging().requestPermission({ providesAppNotificationSettings: true });
// index.js
import { AppRegistry } from 'react-native'
import messaging from '@react-native-firebase/messaging'
...
messaging().setOpenSettingsForNotificationsHandler(async () => {
// Set persistent value, using the MMKV package just as an example of how you might do it
MMKV.setBool(openSettingsForNotifications, true)
})
...
AppRegistry.registerComponent(appName, () => App)
// App.tsx
const App = () => {
const [openSettingsForNotifications] = useMMKVStorage('openSettingsForNotifications', MMKV, false)
useEffect(() => {
if (openSettingsForNotifications) {
navigate('NotificationsSettingsScreen')
}
}, [openSettingsForNotifications])
...
}
// App.tsx
const App = () => {
useEffect(() => {
messaging()
.getDidOpenSettingsForNotification()
.then(async didOpenSettingsForNotification => {
if (didOpenSettingsForNotification) {
navigate('NotificationsSettingsScreen')
}
})
}, [])
...
}
Cloud Messaging
import React, { useEffect } from 'react';
import { Alert } from 'react-native';
import messaging from '@react-native-firebase/messaging';
function App() {
useEffect(() => {
const unsubscribe = messaging().onMessage(async remoteMessage => {
Alert.alert('A new FCM message arrived!', JSON.stringify(remoteMessage));
});
return unsubscribe;
}, []);
}
// index.js
import { AppRegistry } from 'react-native';
import messaging from '@react-native-firebase/messaging';
import App from './App';
// Register background handler
messaging().setBackgroundMessageHandler(async remoteMessage => {
console.log('Message handled in the background!', remoteMessage);
});
AppRegistry.registerComponent('app', () => App);
admin.messaging().sendToDevice(
[], // device fcm tokens...
{
data: {
owner: JSON.stringify(owner),
user: JSON.stringify(user),
picture: JSON.stringify(picture),
},
},
{
// Required for background/quit data-only messages on iOS
contentAvailable: true,
// Required for background/quit data-only messages on Android
priority: 'high',
},
);
admin.messaging().send({
data: {
//some data
},
apns: {
payload: {
aps: {
contentAvailable: true,
},
},
headers: {
'apns-push-type': 'background',
'apns-priority': '5',
'apns-topic': '', // your app bundle identifier
},
},
//must include token, topic, or condition
//token: //device token
//topic: //notification topic
//condition: //notification condition
});
// index.js
import { AppRegistry } from 'react-native';
import messaging from '@react-native-firebase/messaging';
// Handle background messages using setBackgroundMessageHandler
messaging().setBackgroundMessageHandler(async remoteMessage => {
console.log('Message handled in the background!', remoteMessage);
});
// Check if app was launched in the background and conditionally render null if so
function HeadlessCheck({ isHeadless }) {
if (isHeadless) {
// App has been launched in the background by iOS, ignore
return null;
}
// Render the app component on foreground launch
return <App />;
}
// Your main application component defined here
function App() {
// Your application
}
AppRegistry.registerComponent('app', () => HeadlessCheck);
// add this import statement at the top of your `AppDelegate.m` file
#import "RNFBMessagingModule.h"
// in "(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions" method
// Use `addCustomPropsToUserProps` to pass in props for initialization of your app
// Or pass in `nil` if you have none as per below example
// For `withLaunchOptions` please pass in `launchOptions` object
// and use it to set `self.initialProps` (available with react-native >= 0.71.1, older versions need a more difficult style, upgrading is recommended)
self.initialProps = [RNFBMessagingModule addCustomPropsToUserProps:nil withLaunchOptions:launchOptions];
import React, { useState, useEffect } from 'react';
import messaging from '@react-native-firebase/messaging';
import { NavigationContainer, useNavigation } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
const Stack = createStackNavigator();
function App() {
const navigation = useNavigation();
const [loading, setLoading] = useState(true);
const [initialRoute, setInitialRoute] = useState('Home');
useEffect(() => {
// Assume a message-notification contains a "type" property in the data payload of the screen to open
messaging().onNotificationOpenedApp(remoteMessage => {
console.log(
'Notification caused app to open from background state:',
remoteMessage.notification,
);
navigation.navigate(remoteMessage.data.type);
});
// Check whether an initial notification is available
messaging()
.getInitialNotification()
.then(remoteMessage => {
if (remoteMessage) {
console.log(
'Notification caused app to open from quit state:',
remoteMessage.notification,
);
setInitialRoute(remoteMessage.data.type); // e.g. "Settings"
}
setLoading(false);
});
}, []);
if (loading) {
return null;
}
return (
<NavigationContainer>
<Stack.Navigator initialRouteName={initialRoute}>
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="Settings" component={SettingsScreen} />
</Stack.Navigator>
</NavigationContainer>
);
}
await messaging().registerDeviceForRemoteMessages();
const token = await messaging().getToken();
// save the token to the db
'REACT & NODE' 카테고리의 다른 글
React Native 0.73.1 변경내용. 20231219 (0) | 2023.12.19 |
---|---|
react native animation (0) | 2023.10.13 |
swagger nodejs express (0) | 2022.06.26 |
operator linebreak, prettier 와 eslint 를 동시에 사용할 때 (0) | 2022.06.26 |
React 는 왜 class 보다 function hook 을 쓰는가? (0) | 2022.06.26 |