React-Native (expo) firebase fcm 푸시 메세지 디바이스에서 나오지 않는 이슈 해결 (문제 해결)

Devinix·2024년 6월 7일
1

[문제 해결]

목록 보기
25/29

개요

React Native(expo)와 firebase의 fcm을 통해서 푸시 메세지 기능을 구현하고 있었다.
https://docs.expo.dev/push-notifications/fcm-credentials/ <= 공식문서와
https://youtu.be/yCBecuxzUuU?si=GXzR5VVURsYEQ4yR <= 유튜브 영상을 통해 기능을 구현했고, 안드로이드 에뮬레이터에서 푸시메세지가 오는 것을 확인할 수 있었다.

코드를 살펴보도록 하자.

App.tsx

import React, { useRef } from 'react';
import { View, Text } from 'react-native';
import WebView from 'react-native-webview';
import useHardwareBack from './hooks/useHardwareBack';
import useSplash from './hooks/useSplash';
import useIsConnected from './hooks/useIsConnected';
import useNotification from './hooks/useNotification';

const INJECTEDJAVASCRIPT = `const meta = document.createElement('meta'); meta.setAttribute('content', 'width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0'); meta.setAttribute('name', 'viewport'); document.getElementsByTagName('head')[0].appendChild(meta); `;

export default function Native() {
  const webViewRef = useRef<WebView>(null);
  const handleNavigationStateChange = useHardwareBack(webViewRef);
  const handleLoad = useSplash();
  const isConnected = useIsConnected();
  useNotification();

  return (
    <View style={{ flex: 1 }}>
      {isConnected ? (
        <WebView
          style={{ flex: 1 }}
          textZoom={100}
          source={{
            uri: 'https://today-s-horoscope.vercel.app/',
          }}
          injectedJavaScript={INJECTEDJAVASCRIPT}
          onNavigationStateChange={handleNavigationStateChange}
          ref={webViewRef}
          onLoadEnd={handleLoad}
        />
      ) : (
        <View
          style={{
            display: 'flex',
            justifyContent: 'center',
            alignItems: 'center',
            width: '100%',
            height: '100%',
            backgroundColor: '#191a18',
          }}>
          <View
            style={{
              display: 'flex',
              alignItems: 'center',
              gap: 24,
            }}>
            <Text style={{ fontSize: 18, fontWeight: '500', color: '#d3d3d3' }}>인터넷 연결</Text>
            <Text style={{ fontSize: 14, fontWeight: '500', color: '#838f90' }}>
              오프라인 상태입니다. 인터넷 연결을 확인하세요.
            </Text>
          </View>
        </View>
      )}
    </View>
  );
}

useNotification.ts

import { useEffect } from 'react';
import messaging from '@react-native-firebase/messaging';
import * as Notifications from 'expo-notifications';

export default function useNotification() {
  const requestUserPermission = async () => {
    const settings = await Notifications.requestPermissionsAsync();
    const enabled = settings.granted || settings.ios?.status === Notifications.IosAuthorizationStatus.PROVISIONAL;

    if (enabled) {
      console.log('Authorization status:', settings);
    }

    return enabled;
  };

  const setupNotificationHandler = async () => {
    Notifications.setNotificationHandler({
      handleNotification: async () => ({
        shouldShowAlert: true,
        shouldPlaySound: true,
        shouldSetBadge: true,
      }),
    });
  };

  useEffect(() => {
    (async () => {
      await setupNotificationHandler();

      const permissionGranted = await requestUserPermission();
      if (permissionGranted) {
        const token = await messaging().getToken();
        console.log(token);
      } else {
        console.log('Permission not granted');
      }

      const initialNotification = await messaging().getInitialNotification();
      if (initialNotification) {
        console.log('Notification caused app to open from quit state:', initialNotification.notification);
      }

      messaging().onNotificationOpenedApp(remoteMessage => {
        console.log('Notification caused app to open from background state:', remoteMessage.notification);
      });

      messaging().setBackgroundMessageHandler(async remoteMessage => {
        console.log('Message handled in the background!', remoteMessage);
      });

      const unsubscribe = messaging().onMessage(async remoteMessage => {
        await Notifications.scheduleNotificationAsync({
          content: {
            title: remoteMessage.notification?.title,
            body: remoteMessage.notification?.body,
            data: remoteMessage.data,
          },
          trigger: null,
        });
      });

      return () => unsubscribe();
    })();
  }, []);
}

문제 상황

파이어베이스 콘솔에서 메세지를 보낼때 안드로이드 에뮬레이터에서는 푸시메세지가 잘 작동하였는데, apk 파일로 빌드해서 실제 디바이스에서 테스트 해보니 푸시메세지가 오지 않는 이슈가 발생했다. SHA-1, SHA-256과 같은 인증서 지문의 문제일까 싶어서 파이어베이스와 expo.dev에서 값들을 확인해 봤는데 정확히 일치되어있어서 원인을 파악하기가 힘들었다.

원인

원인을 파악하기 위해 expo-dev-client를 도입하였다.

npx expo install expo-dev-client

이후 다시 빌드 하였다.

eas build -p android --profile preview

expo-dev-client 도입하기 전에는 확인할 수 없었던 에러가 발생했고, 빌드 로그에서 원인을 파악할 수 있었다.

google-services.json 파일이 누락되었다고 한다... EAS 빌드는 깃에서 추적하는 파일만 업로드한다고 한다. gitignore 파일에 google-services.json 파일을 추가한 것이 원인이었다..

해결 과정

해결법은 매우 간단했다. google-services.json 파일을 gitignore에서 google-services.json을 주석처리하니 빌드에 성공했고, 푸시 메세지도 잘 작동하였다.

결론

하루를 고생하며 이 문제를 해결하려 시간을 보냈는데, 코드 한줄만 수정하면 되는거였다니.. expo-dev-client를 도입하지 않았다면 훨씬 더 오랜 기간이 걸렸을 것 같다. google-services.json 파일에는 api key와 같은 민감한 정보들도 포함되어있기 때문에 빌드를 할 때에만 gitignore에서 제외시키고, 코드를 커밋할 때에는 gitignore를 통해 버전관리되지 않게끔 보안에 신경써야할 것 같다.

profile
프론트엔드 개발

0개의 댓글