728x90

https://mingule.tistory.com/65

 

React에서 setInterval 현명하게 사용하기(feat. useInterval)

들어가기 Babble의 방 목록 페이지에 들어가면 유저가 생성한 방들이 쭉 나열되어 있는 것을 볼 수 있다. (안타깝게도 유저가 없으면 방도 없다ㅜㅜ) 그리고 이 방들은 5초마다 서버에 요청을 보

mingule.tistory.com

위 블로그에서 언급한 원인으로

불완전한 함수인 setInterval로 인해

버그를 맞이할수있다.

 

간략히 말하자면

setInterval이 지연 시간을 보장해주지 않기 때문

 

따라서 ,

나는 빠른 주기로

state를 업데이트 하는

stopWatch 훅스를 만들때

아래와 같이 처리해서 목적을

달성하였따.

export function useStopWatch(start: boolean) {
  const [time, setTime] = useState(0);

  useEffect(() => {
    let interval: any = null;
        
    if (start) {
      const startedTime = Date.now();
      interval = setInterval(() => {
        setTime(Date.now() - startedTime);
      }, 50);
    } else {
      if (interval) clearInterval(interval);
    }

    return () => {
      if (interval) clearInterval(interval);
    }
  }, [start]);

  return { time, setTime };
}
728x90
반응형
728x90

[react-native-code-push 적용하기]

https://honeystorage.tistory.com/290

 

[앱 버전 관리, 어떻게 하는게 좋을까?]

https://honeystorage.tistory.com/310

 

위 두 포스팅을 통해

code-push를 적용하는 방법과

적용 후 어떻게 버전관리를 할지에 대한

저의 고민들을 살펴볼 수 있습니다.

 

마지막으로

저의 코드푸시 세팅을 공유하고

왜 이런 세팅을 하게되었는지

저의 생각을 정리하고자 포스팅합니다.

 

 

코드 푸시 & 버전 체크 플로우

1. 앱이 실행됨

2. 상위 앱 버전이 출시되었는지 API를 호출해 체크

2-1. 상위 앱 버전 존재 -> A

2-2. 상위 앱 버전 없음 -> B

 

A-1. 필수 업데이트  Alert 오픈

A-2. Alert 버튼 클릭 -> 스토어로 이동

 

B-1. checkForUpdate메서드로 업데이트 버전이 있는지 체크

B-2-1. 앱 업데이트 버전 없음 -> ㄱ

B-2-2. 앱 업데이트 버전 있음 -> ㄴ

 

ㄱ-1. 앱 메인 실행

 

ㄴ-1. 업데이트 패키지  download 진행

ㄴ-2-1. 기준 시간(ex. 10초)이내에 앱 업데이트 완료 -> a

ㄴ-2-2. 기준 시간(ex. 10초)이내에 앱 업데이트 실패 (지연됨) -> b

 

a-1. 앱 메인 실행

 

b-1. 앱 메인 실행

b-2. 백그라운드에서 앱 업데이트 패키지 다운로드 계속 실행

b-3. 패키지 다운로드 완료

b-4. 앱 업데이트 진행 안내  Alert 오픈

b-5-1. Alert, 취소 버튼 터치 -> 앱 계속 사용

b-5-2. Alert, 업데이트 버튼 터치 -> 앱 재실행

 

도식화

악필이지만

좀더 쉬운 이해를 위해

도식화해봤습니다.

 

위의 흐름에서 저의 고민의 흐름은 이렇습니다.

1) 버전 관리 전략을 어떻게할까?

2) 코드푸시는 핫픽스에만 사용, 최신버전 필수 업데이트는 스토어에서

3) 어떻게 최신버전을 서빙할까? 서버에서 체크

4) 코드푸시가 종종 지연되네...

5) 코드푸시 서버 상태로 업데이트가 지연되면 어쩌지?

6) 업데이트 페이지 뛰어넘기 + 백그라운드 설치

7) 백그라운드 설치 완료 후 업데이트 안내 팝업

8) 중요 업데이트 진행 안내 + 업데이트 권장

 

좋은 처리 방안인지는

조금 더 생각해볼 여지가 있지만

자체 코드푸시 서버를 구축하지 않는한

이정도의 처리가 최선이 아닐까 하는 생각이 듭니다.

728x90
반응형
728x90

scrollview, flatlist 등 

스크롤 기반 컴포넌트 위에서

텍스트 selectable 기능이 먹지 않는 증상이 있다.

 

해당 문제는 

스크롤뷰 컴포넌트에

removeClippedSubviews={false}

위의 속성 하나만 추가해주면 해결 가능하다.

 

728x90
반응형
728x90

한달 전 앱을 론칭해 운영중에있다.

초기 서비스인 만큼 많은

굉장히 짧은 주기로 버전 업데이트를 진행하고있다.

 

문제는

1. 사용자들이 배포된 업데이트를 진행할것인가?

2. 코드 푸시는 만능일까?

3. 어떤게 최선의 방법이지?

 


 

문제 1. 사용자들이 배포된 업데이트를 진행할것인가?

사용자들이 와이파이를 환경에서

자동업데이트를 하는게 아니라면

직접 업데이트를 진행하는 경우는 매우 드물었다.

따라서, 사용자들 간에 다른 버전을 쓰는 경우가

많아지기 시작했다.

 

문제 2. 코드 푸시는 만능일까?

우리는 사용자가 업데이트를

잘 하지 않는다는것을 확인하고

코드 푸시를 통해 업데이트를 진행하기 시작했다.

 

아주 만족스럽게도

업데이트 내용을 대다수의 사용자에게

온전히 제공할 수 있었다.

 

문제는 다른곳에서 발생했다.

코드 푸시는 앱 버전 단위로 진행되게 되는데,

한 버전에서 많은(3회 이상) 푸시를 하고나니

업데이트를 내려 받는 속도가 굉장히 느려졌다.

 

또한, ms appcenter의 서버가

해외에 있다보니 업데이트 체크 및 진행 속도가

들쭉날쭉 해졌다.

 

결국 업데이트에 3-5분이상

소요되어버리는 지경에 이르렀다.

 


 

해결책. 버전관리 전략 수립 및 배포전략 수립

간혹 보면 상용 서비스에서

앱 업데이트를 강제하는 경우가있다.

거기에는 다 이유가 있었다.

 

더 질 좋은 서비스를 잘 만들어 놨는데

사용자가 쓰지를 않는다면...

혹은, 문제점을 개선해서 배포를 했는데

사용자가 구버전에서 계속 에러리포트를 한다면...

 

우리 개발자들은 현타가 올것이다.

 

따라서, 우리는 버전관리와 배포전략을 통해

위와 같은 문제들을 해결하기로 했다.

 

앱 버전은 a.b.c로 구성된다.

a: 주요 업데이트 (스토어에서 배포)

b: 서브기능 추가 (스토어에서 배포)

c: 핫픽스, 워딩 수정 (code push후 버전 올려서 스토어에서 배포)

 

가장 작은 단위의 수정인 c부터 보면

간혹 미흡한 예외처리나 오타를 위해

수정 배포해야되는 경우가 있다.

이때는 코드푸시를 통해 버전의 문제를 즉시 처리하고, 

앱 버전 c 를 하나 올려 빌드해 스토어에 배포한다.

이렇게 하면 기존 버전 사용자도 문제 수정이 진행되고

새 버전 다운로드 사용자도 문제 수정 버전을 설치하게된다.

 

 

앱의 큰 축의 변화는 아니지만

list만 되던 부분이 추가/수정도 가능하게 된다는지 등의 기능 업데이트가 발생하면

b를 하나 올려 빌드해 스토어에 배포한다.

 

업데이트 심사의 경우 앱스토어/플레이스토어 모두

1 영업일 (길게는 2영업일) 이내면 처리되기 때문에

앱 버전 관리를 배포를 통해 하더라도

크게 답답할 일이 없다.

 

다만, 위해서 언급한것처럼 사용자들은

앱 업데이트를 잘 진행하지 않는다.

그렇기에 우리는 강제 업데이트 (필수 업데이트)

기능을 추가해둘 필요가 있다.

 

a단위로 강제 업데이트를 할지

b단위로 강제 업데이트를 할지는

회사 내규 운영방책을 정해서 진행하면 될듯하다.

 

우리는 프리미엄 서비스 + 소수의 사용자를

타겟으로 하고있어, b단위로 강제업데이트를 진행한다.

 

강제 업데이트를 하는 방식

1) 서버에 현재 배포된 각 플랫폼의 최신 버전을 응답하는 api추가

2) 앱이 켜질때 앱 버전과 서버에서 응답한 버전을 비교

3) 앱 버전이 서버에서 응답 받은 최신버전 보다 구 버전이라면 강제 업데이트 진행

4) 강제 업데이트는 Alert + Redirect(store로 linking) 을 통해 진행했다.

5) 주의 사항은 지속적으로 서버 버전을 잘 관리해야된다는 것

+ 새 버전으로 서버 버전을 올릴때 새 버전이 스토어에 반영됐는지 꼭 확인할것
(앱 심사가 통과되더라도 새 버전이 스토어에 반영되기까지 어느정도 시간이 소요되기 때문)

728x90
반응형
728x90

react-native-audio

라이브러리를 적용중에

ios에서는 문제없지만

android에서 위와 같은 이슈가 발생하였다.

 

문제는 github에서도 논의 중

별다른 해결책 없이 끝난것같다

(github issue tracking: 링크)

 

에러 추적결과

위 문제는 필요한 권한이 부족하여 나타난 현상이었다.

 

위 문제 해결을 위해서는

react-native-permissions를 통해

라이브러리 사용전 필요한 권한 요청을

사용자로 부터 허락받아야한다.

 

const prepare = (path: string) => {
  const config = {
    SampleRate: 22050,
    AudioEncoding: 'aac',
    Channels: 1,
    AudioQuality: 'Low',
    AudioEncodingBitRate: 32000
  };
  
  return new Promise((resolve, reject) => {
    if (Platform.OS === 'android') {
      requestMultiple([
        PERMISSION.ANDROID.WRITE_EXTERNAL_STORAGE,
        PERMISSION.ANDROID.READ_EXTERNAL_STORAGE,
        PERMISSION.ANDROID.RECORD_AUDIO
      ]).then((isAuthorized) => {
        if (
          isAuthorized['android.permission.READ_EXTENRAL_STORAGE'] === 'denied'
          || isAuthorized['android.permission.WRITE_EXTENRAL_STORAGE'] === 'denied' 
          || isAuthorized['android.permission.RECORD_AUDIO'] === 'denied'
        ) {
          reject('no permission')
        } else {
          AudioRecorder.prepareRecordingPath(path, config);
          resolve('has permission')
        }
      })
    } else {
      AudioRecorder.prepareRecordingPath(path, config);
      resolve('has permission')
    }
  })
}

 

이런식으로

외부 저장소 읽기/쓰기, 오디오 레코딩 권한을 획득해야만

"Exception in native call from JS" 에러없이 진행할 수 있다.

728x90
반응형
728x90

위 에러를 겪었다.

 

https://github.com/software-mansion-labs/reanimated-2-playground/commit/71642dbe7bd96eb41df5b9f59d661ab15f6fc3f8

 

Android · software-mansion-labs/reanimated-2-playground@71642db

This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

github.com

 

위 링크에 변경사항을 따라서

라이브러리 관련 작업을 해서

많은 사람들이 해결됐다고 하는데

 

 

나는  proguard설정에 따른 이슈였어서

아래의 코드를 추가해줌으로써

이슈를 해결할 수 있었다.

// android/app/proguard-rules.pro

-keep class com.swmansion.reanimated.** { *; }
-keep class com.facebook.react.turbomodule.** { *; }
728x90
반응형
728x90

RN Firebase messaging 포스팅의 최종판이다.

 

[이전 관련글]

https://honeystorage.tistory.com/255

https://honeystorage.tistory.com/276

https://honeystorage.tistory.com/292

https://honeystorage.tistory.com/301

https://honeystorage.tistory.com/305

 

왜 이렇게나 한가지 기능에 대해

많은 포스팅을 하게 된것일까...

 

첫째,

아마도 너무나도 다양한

환경설정 속에서

내게 맞는 솔루션을 찾지 못했던것

 

둘째,

공식 가이드라인을

꼼꼼히 읽어보지않고

stackoverflow에 너무 크게 의존한것

 

셋째,

내 상황을 정확히 분석하지 못하고

해답만 찾으려고 시도했던것

 

넷째,

클라인지 서버인지

무엇이 잘못된건지모르고

해결하려고 삽질했던것

 

각설하고

앞으로 한국 RN 유저들이 더이상

나와 같은 시행착오를

겪지 않았으면 하는 마음으로

상세히 포스팅을 작성해봅니다.

 

[ 환경 설정 - client ]
* RN: 0.63
* @react-native-firebase/app: ^12.9.0
* @react-native-firebase/messaging: ^12.9.0
* @react-native-community/push-notification-ios: ^1.10.1
* @types/react-native-push-notification: *7.3.3


[환경 설정 - server]
* firebase-admin: ^10.0.0

 

자, 그럼 지금부터

길고  긴 push notification 구현을 위한

여정을 떠나보도록 하겠습니다.

 

1. 개발환경 설정

먼저, 구현 단계 이전의 환경설정 파트는

https://honeystorage.tistory.com/255

위 글에서 매우 상세히 다루었습니다.

 

위 글을 참고하여 코드 구현 이외의

환경설정을 완료하도록 하면 되겠습니다.

 

 

2. 코드 구현 - 서버

서버에서는 간략 하지만 강력한

푸시메시지 발송 방법을 소개하겠습니다.

(아래와 같은 설명은 그 어디에서도 명쾌하게 설명된것을 본적이 없습니다.)

 

서버에서 발송하는 메시지는

2가지로 나뉩니다.

 

Silent / None Silent

 

Silent, 말 그대로

쥐도새도모르게 푸시가 와서

Status Bar에 푸시가 떠있습니다.

 

이 경우엔 화면에 푸시 팝업이 뜨지 않으며,

진동이나 소리도 나지 않습니다.

 

조용히 알려주는 광고 알림등에서는

이를 이용하여 사용자가 기분 상하지 않는선에서

광고를 할수 있겠죠.

 

None Silent는 Silent와 반대입니다.

진동이나 소리가 나며 푸시가 도착합니다.

핸드폰 화면이 켜져있는 상태에서는

푸시 팝업이 나타나며 푸시가 왔음을 명확히 알려줍니다.

 

(저는 None Silent 기능이 필요했는데

대부분의 자료에서는 Silent관련 내용만 나와있더군요 ...)

 

어떻게 해야 알맞는 방법으로

메시지를 보낼 수 있는지

코드로 확인해보겠습니다.

// Silent Push Notification

import fbAdmin from 'firebase-admin';

type PushMessage = {
  token: string;
  title: string;
  body: string;
}

const sendPushNotification = ({ token, title, body }: PushMessage) => {
  const message = {
    token,
    notification: {
      title,
      body,
    },
    data: {
      title,
      body,
    },
    android: {
      priority: 'high',
      notification: {
        title,
        body,
        sound: 'default'
      }
    },
    apns: {
      headers: {
        'apns-priority': 5,
        'apns-push-type': 'background'
      },
      payload: {
        aps: {
          sound: 'default',
          'content-available': 1
        }
      }
    }
  };


  fbAdmin
    .messaging()
    .send(message)
    .catch(error => {
      console.log('push error: ', error);
      // delete token or do something
    })
}
// None Silent Push Notification

import fbAdmin from 'firebase-admin';

type PushMessage = {
  token: string;
  title: string;
  body: string;
}

const sendPushNotification = ({ token, title, body }: PushMessage) => {
  const message = {
    token,
    notification: {
      title,
      body,
    },
    android: {
      priority: 'high',
      notification: {
        sound: 'default'
      }
    },
    apns: {
      headers: {},
      payload: {
        aps: {
          sound: 'default',
          'content-available': 1
        }
      }
    }
  };


  fbAdmin
    .messaging()
    .send(message)
    .catch(error => {
      console.log('push error: ', error);
      // delete token or do something
    })
}

Silent Message가 설정해줄것들이

조금 더 많은것을 볼 수 있습니다.

 

Docs나 Stackoverflow에 많은 정보들이 있지만

이대로만 하면 위 두가지 사항중

원하는 목적을 달성할 수 있습니다.

 

3. 코드 구현 - 클라이언트

정확하게 알맞는 디바이스에

푸시를 발송해주기 위해서는

token을 사용자 디바이스별로

잘 관리해주어야합니다.

 

token일 불일치 할 경우,

아무리 올바르게 fcm 메시징 요청을 하더라도

디바이스에서 수신을 못합니다.

 

token을 관리하는 코드를

작성해보도록 하겠습니다.

import messaging from '@react-native-firebase/messaging'
import { Platform } from 'react-native';
import axios from 'axios';


const [_permission, setPermission] = useState(0);

useEffect(() => {
  _requestPermissionHandler();
}, []);

useEffect(() => {
  requestNotiPermission();
}, [_permission])

const _requestPermissionHandler = () => {
  const perm = await messaging().hasPermission();
  setPermission(perm);
}

const requestNotiPermission = async () => {
  // 사용자 푸시 권한 수락/거절상태 갱신
  await axios.put('url...', { uId: userId, permission: _permission });
  
  // 사용자 푸시 권한이 수락 상태면 토큰 얻어서 갱신
  if (_permission >= 1) {
    const token = await messaging().getToken();
    await axios.put('url...', { uId: userId, token });
    Platform.OS && messaging().setAutoInitEnabled(true);
  }
}

 

이런식으로

사용자의 푸시 권한 상태에 대한 확인 및 갱신과

푸시 토큰의 갱신을 위한 코드를 작성하면 되겠습니다.

 

얼마나 자주, 언제 토큰이나 권한을 갱신해줄지는

앱의 특성이나 상황에 따라 설정해주면 될것입니다.

 

 

4. background 알림 수신 처리 (optional)

마지막 파트는 선택사항입니다.

백그라운드 알림을 수신해

badge를 갱신한다던지 무언가 처리를 원할때

처리하시면 되겠습니다.

(* badge: 앱 아이콘 위에 나타나는 숫자)

 

바로, 말도 많고 탈도 많은

setBackgroundMessageHandler 기능을 활용한

background 알림 처리를 다뤄보겠습니다.

 

최근까지도 계속해서

되니 안되니 이슈가 많은것으로 보이는 기능입니다.

 

계속 연구를 해보니

아래와 같은 경우의 수를 체크해서

알람이 정상인지 확인할수가 있습니다.

  aos - 알림 aos - 백그라운드 ios - 알림 ios - 백그라운드
App - foreground        
App - background        
App - quit State        

 

앱의 각 상태별로, 그리고 OS별로

알림은 오는지, 백그라운드에서 수신은 되는지를 확인하는 것입니다.

 

모두 Ok라면 푸시는 완벽히 설정됐다고 볼 수 있습니다.

 

// index.js

import { Platform, Vibration, AppRegistry } from 'react-native';

messaging().setBackgroundMessageHandler(async remoteMessage => {
    onMessageReceived(remoteMessage);
});

function HeadlessCheck({ isHeadless }) {
  if (isHeadless) {
    <AppFake />;
  }
  
  return (
    <App />
  );
}

const AppFake = () => {
    return null;
};

const onMessageReceived = message => {
  console.log('background message: ', message);
  
  // 저는 ios에서 진동이 정확히 울리지 않는것으로 보여
  // background에서 알림이 수신될 경우 진동이 울리는 코드를 추가하였습니다.
  Platform.OS === 'ios' && Vibration.vibrate([400]);
}

AppRegistry.registerComponent(appName, () => HeadlessCheck);

 

 

 

[ 참고 ]

https://sweetdev.tistory.com/476

https://stackoverflow.com/questions/15834581/ios-push-notification-custom-format

https://firebase.google.com/docs/cloud-messaging/concept-options

https://firebase.google.com/docs/cloud-messaging/send-message#example-notification-message-with-platform-specific-delivery-options

https://mrgamza.tistory.com/837

https://hryang.tistory.com/34

728x90
반응형
728x90

rootview의 백그라운드 색상을 설정하는 법을 알아보기전에

 

왜 이것을 설정하게 되었는지

문제의 원인, 근원을 짚어본다.

 

react-native로 개발된 앱을 실행했을때

iOS 에서만 white flash 현상이 나타났다.

 

https://stackoverflow.com/questions/5618163/displaying-splash-screen-for-longer-than-default-seconds

 

Displaying splash screen for longer than default seconds

Is it possible to display the Default.png for a specified number of seconds? I have a client that wants the splash screen displayed for longer than its current time. They would like it displayed f...

stackoverflow.com

 

이런 내용도 있었지만

살펴보니 내게는 모두 적용되지 않는일들이었다.

 

기본적으로 black 컬러를 가져가는 우리 앱의 특성상

배경색만 검정으로 설정하면 되는데,

위 글에서는

splash-screen의 설정에 대해 다루는 내용이 대부분이 었기 때문.

 

 

그러던중

https://medium.com/modus-create-front-end-development/changing-the-react-native-rootview-background-color-for-ios-and-android-7da9acc4e502

 

Changing the React Native RootView Background Color (for iOS and Android)

While developing my React Native KeyGen Music Player app for iOS, I decided to create a custom 90’s retro theme. This required me to change…

medium.com

이런 글을 찾게되었다.

 

rootview의 색상을 지정하는것!

// AppDelegate.m -> didFinishLaunchingWithOptions

if (@available(iOS 13.0, *)) {
  rootView.backgroundColor = [UIColor blackColor];
} else {
  rootView.backgroundColor = [UIColor blackColor];
}

systemcolor, whitecolor 등으로

설정되어있던 값을 모두

blackColor로 변경해주었다.

728x90
반응형
728x90

앱 개발이 완료되어가는 시점이다

 

눈이 안좋은 사람들이

시스템 설정상의 폰트 크기를 조정해서

앱의 텍스트나 디자인이 깨지는것을

방지하기위한 체크를 하는 도중

 

(안드로이드에서만)

앱을 백그라운드로 실행해둔 상태에서

환경 설정에서 폰트 크기를 변경한 뒤

다시 앱으로 돌아오니

앱이 튕겨버리는 현상이 나타났다.

 

추적결과

react-native-screens에서

문제가 있는것으로 발견되었다.

 

 rn-screens docs에도 나와 있듯이

// MainAcitivity.java

import android.os.Bundle;

@Override
protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(null); <-- 여기
}

 

이 부분을 수정해줌으로써

튕겨버리는 이슈가 해결되었다.

 

그런데 여기서 파생된 이슈가 있었다.

우리는 기본적으로 폰트에

dp 단위  + font scale 무시를 적용하는데,

 

위의 null 처리로 인해

백그라운드의 앱이 열렸을때

무조건 그 당시의  font-scale을

1로 간주해버리는 것이 아닌가...

 

바꿔 말하자면,

사용자가 시스템 설정에서

폰트 크기를

1 -> 2로 변경한뒤 앱을 열면

앱이 2라는 것을 1이라는 기준으로 잡아버린다.

 

따라서, font scale을 무시하는

우리의 코드는 오작동을 했다.

(우리의 폰트 크기 = dp / font scale)

 

따라서, storage에 항상

이전 폰트 크기를 저장해뒀다가

폰트 크기가 변경된 경우

앱을 재실행 하는 코드를 추가하였다.

(재실행하면 font-scale 기준을 제대로 잡게됨)

 

const detectChangeFontScaleOnAndroid = async () => {
  const fontScale = PixelRatio.getFontScale();
  const _fontScale = await storage.getItem('fontScale');
  storage.setItem('fontScale', fontScale?.toString());
        
  if (_fontScale && _fontScale !== fontScale.toString()) {
    console.log('changed font scale');
    Platform.OS === 'android' && CodePush.restartApp();
  }
}

 

728x90
반응형
728x90

[ 푸시알림 완벽구현 - 최종판 ]

https://honeystorage.tistory.com/306

 

notification 기능도 구현하기 나름인것같다.

firebase의 remote notification만 있으면

만능일줄 알았건만

 

local notification을 쓸일이 생겨

지웠던 라이브러리를 다시 설치하고

셋팅하는 상황이 발생했다.

 

워낙 간단했지만

그래도 필요한 이들을 위해서 공유한다.

(혹은, 미래의 나를 위해...)

 

먼저, remote notification이

구현되어 있음에도 local notification이 필요했던 이유는

앱 안에서 채널톡 SDK를 통해

채널톡이 구현되어있는데...

 

운영자가 답장을 해줘도 사용자가 답장이 왔는지

알길이 없는 상황에 봉착했다.

(알림을 주던지, 카톡을 주던, 문자를 보내주던

무언가는 있어야지

사용자가 답변이 왔음을 알것이다.)

 

그래서 나는

setBackgroundMessagehandler에서

ChannelIO.isChannelPushNotification 메소드로

채널톡 메시지가 수신되었음이 감지됐을때

local에서 notification을 발생시켜

사용자에게 알려주는 전략을 취하기로했다.

 

// https://developers.channel.io/docs/mobile-channel-io#ischannelpushnotification

import messaging from '@react-native-firebase/messaging';
import {ChannelIO} from 'react-native-channel-plugin';

componentDidMount() {
    this.backgroundMessageHandler = messaging().setBackgroundMessageHandler(
      async (remoteMessage) => {
        ChannelIO.isChannelPushNotification(remoteMessage.data).then((result) => {
            if (result) {
              ChannelIO.receivePushNotification(remoteMessage.data).then((_) => { });
            } else {
              // TODO : Your FCM code
            }
          },
        );
      },
    );
}

componentWillUnmount() {
  this.backgroundMessageHandler();
}

 

채널톡에서도 그럴때 쓰라고 만들어 둔것인지

이렇게 예제를 제공하기도 한다.

 

result === true일때 

local notification이 작동되도록 코드를 설정했으며

아래와 같이

local notification을 세팅할수있다.

 

1. 설치 ( >= 0.60)

// react-native-push-notification 설치
npm install --save react-native-push-notification

// @react-native-community/push-notification-ios 설치
npm i @react-native-community/push-notification-ios --save

// pod file download
cd ios && pod install && cd..
// --- android ---

// android/app/src/main/AndroidManifest.xml
.....
    <uses-permission android:name="android.permission.VIBRATE" />
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>

    <application ....>
        <!-- local notification code -->
        <meta-data  android:name="com.dieam.reactnativepushnotification.notification_foreground" android:value="false"/>
        <meta-data  android:name="com.dieam.reactnativepushnotification.notification_color" android:resource="@color/white"/> <!-- or @android:color/{name} to use a standard color -->

        <!-- common notification code -->
        <receiver android:name="com.dieam.reactnativepushnotification.modules.RNPushNotificationActions" />
        <receiver android:name="com.dieam.reactnativepushnotification.modules.RNPushNotificationPublisher" />
        <receiver android:name="com.dieam.reactnativepushnotification.modules.RNPushNotificationBootEventReceiver">
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED" />
                <action android:name="android.intent.action.QUICKBOOT_POWERON" />
                <action android:name="com.htc.intent.action.QUICKBOOT_POWERON"/>
            </intent-filter>
        </receiver>

        <!-- common notification code -->
        <service
            android:name="com.dieam.reactnativepushnotification.modules.RNPushNotificationListenerService"
            android:exported="false" >
            <intent-filter>
                <action android:name="com.google.firebase.MESSAGING_EVENT" />
            </intent-filter>
        </service>
     .....
// ios

// AppDelegate.h
// --- local notification code ---
#import <UserNotifications/UNUserNotificationCenter.h>
@interface AppDelegate : UIResponder <UIApplicationDelegate, RCTBridgeDelegate, UNUserNotificationCenterDelegate>
UNUserNotificationCenterDelegate <-- 이거 추가됨




// AppDelegate.m
// --- local notification code ---
#import <UserNotifications/UserNotifications.h>
#import <RNCPushNotificationIOS.h>


// --- local notification code ---
// Required for the register event.
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
{
 [RNCPushNotificationIOS didRegisterForRemoteNotificationsWithDeviceToken:deviceToken];
}
// Required for the notification event. You must call the completion handler after handling the remote notification.
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
{
  [RNCPushNotificationIOS didReceiveRemoteNotification:userInfo fetchCompletionHandler:completionHandler];
}
// Required for the registrationError event.
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error
{
 [RNCPushNotificationIOS didFailToRegisterForRemoteNotificationsWithError:error];
}
// Required for localNotification event
- (void)userNotificationCenter:(UNUserNotificationCenter *)center
didReceiveNotificationResponse:(UNNotificationResponse *)response
         withCompletionHandler:(void (^)(void))completionHandler
{
  [RNCPushNotificationIOS didReceiveNotificationResponse:response];
}


- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
  ...
  // Define UNUserNotificationCenter
  UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
  center.delegate = self;

  return YES;
}


//Called when a notification is delivered to a foreground app.
-(void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler
{
  completionHandler(UNNotificationPresentationOptionSound | UNNotificationPresentationOptionAlert | UNNotificationPresentationOptionBadge);
}

 

 

2. 코드 작성

import PushNotificationIOS from '@react-native-community/push-notification-ios';
import PushNotification from 'react-native-push-notification';

type NotiOptoin = {
  title: string;
  message: string;
}

export class LocalNotification {
  private channel: any;

  constructor() {
    this.init();
  }

  private init() {
  // Must be outside of any component LifeCycle (such as `componentDidMount`).
    PushNotification.configure({
      // (optional) Called when Token is generated (iOS and Android)
      onRegister: function (token) {
        // console.log('TOKEN:', token);
      },

      // (required) Called when a remote is received or opened, or local notification is opened
      onNotification: function (notification) {
        // console.log('NOTIFICATION:', notification);

        // process the notification

        // (required) Called when a remote is received or opened, or local notification is opened
        notification.finish(PushNotificationIOS.FetchResult.NoData);
      },

      // (optional) Called when Registered Action is pressed and invokeApp is false, if true onNotification will be called (Android)
      onAction: function (notification) {
        // console.log('ACTION:', notification.action);
        // console.log('NOTIFICATION:', notification);

        // process the action
      },

      // (optional) Called when the user fails to register for remote notifications. Typically occurs when APNS is having issues, or the device is a simulator. (iOS)
      onRegistrationError: function (err) {
        console.error(err.message, err);
     },

      // IOS ONLY (optional): default: all - Permissions to register.
      permissions: {
        alert: true,
        badge: true,
        sound: true,
      },

      // Should the initial notification be popped automatically
      // default: true
      popInitialNotification: false,

      /**
      * (optional) default: true
      * - Specified if permissions (ios) and token (android and ios) will requested or not,
      * - if not, you must call PushNotificationsHandler.requestPermissions() later
      * - if you are not using remote notification or do not have Firebase installed, use this:
      *     requestPermissions: Platform.OS === 'ios'
      */
      requestPermissions: false,
    });

    PushNotification.createChannel({
      channelId: 'com.myApp',
      channelName: '앱이름',
    }, (created) => {
      console.log('noti channeld is created')
    })
  }

  fire = (option: NotiOptoin) => {
    PushNotification.localNotification({
      title: option.title,
      message: option.message,
      largeIcon: "ic_launcher",
      smallIcon: "ic_launcher",
      bigLargeIcon: "ic_launcher",

      /* Android Only Properties */
      channelId: "com.myApp", // (required) channelId, if the channel doesn't exist, notification will not trigger.
      vibrate: true,
      vibration: 300, // vibration length in milliseconds, ignored if vibrate=false, default: 1000
      priority: 'high',
      
      /* iOS and Android properties */
      id: 0, // (optional) Valid unique 32 bit integer specified as string. default: Autogenerated Unique ID
      playSound: true, // (optional) default: true
      soundName: "default", // (optional) Sound to play when the notification is shown. Value of 'default' plays the default sound. It can be set to a custom sound such as 'android.resource://com.xyz/raw/my_sound'. It will look for the 'my_sound' audio file in 'res/raw' directory and play it. default: 'default' (default sound is played)
    });
  }
}

export {PushNotification};

* 주의 : 채널을 생성해주지 않으면 알림이 뜨지 않습니다...

 

 

3. 코드 연동

const localNoti = new LocalNotification();
messaging().setBackgroundMessageHandler(async remoteMessage => {
  Platform.OS === 'ios' && Vibration.vibrate([400]);
  console.log('Message handled in the background!', remoteMessage);
  
  ChannelIO.isChannelPushNotification(remoteMessage.data).then(result => {
    if (result) {
      localNoti.fire({ title: '제목', message: '메시지' });
    } else {
      // TODO : Your FCM code
    }
  });
});

참고로,

Platform.OS === 'ios' && Vibration.vibrate([400]);

이 부분은 이 전편에서 다루었던 내용으로

ios에서 백그라운드 알림에 대한

진동이 울리지않아서

트릭용으로 추가한 코드입니다.

 

 

[ 푸시알림 완벽구현 - 최종판 ]

https://honeystorage.tistory.com/306

728x90
반응형