티스토리 뷰
몇 년전 iOS 푸쉬를 사용하고 암 투병생활을 좀 해서 다시는 하고 싶지 않았다. 하지만 회사에서 다시 해야만 하는 상황이 생겼고 이 번에는 React Native(이하 RN) 환경에서 구축하게 되었다. 다행히 암(Push 설정)을 암(RN) 으로 치료하는 기적의 딜 교환으로 이틀 만에 AWS 를 통한 푸쉬 전송을 할 수 있었다.
현재 사용하고 있는 RN 버전은 아래와 같다.
"react": "16.3.1",
"react-native": "0.55.2"
RN의 버전은 사용할 다른 라이브러리에 맞춰서 좀 낮은 편인 듯 하지만 현재 0.59까지 나온걸 생각하면 괜찮은 편이다.
참고로 RN은 0.0.1 올라갈 때 마다 지각변동이 일어나기 때문에 0.0.1도 쉽게 허용해서는 안된다.
다음으로 react native push 로 구글링하면 바로 등장하는 오픈소스를 사용했다.
https://www.npmjs.com/package/react-native-push-notification
이제 첫 번째 난관인 빌드인데 역시나 최신 버전을 가져오니 온같 안드버전을 맞춰달라는 빨간줄 들로 빌드가 되지 않았다. 버전을 조금씩 낮춰가면서 잘 동작하는 2년전 버전인 3.0.2 를 찾을 수 있었다. (나는 안드 sdk 버전을 낮게 유지하기 위해서 이렇게 했지만 sdk 26 을 사용한다면 최신 버전도 동작하지 않을까...?)
"react-native-push-notification": "3.0.2"
빌드를 하고 실제 기기에 설치를 해보니 설치가 되지 않는 현상이 발생했다. virtual device 에서 잘 돌아가고 실제 기기에서 설치가 안되는 걸로 봐서는 왠지 AndroidManifest.xml 의 문제일 듯 싶었다. 라이브러리의 설명에서 추가해 달라는 것을 다 추가했지만 뭐가 문제인지 확인하기 위해 설정을 하나씩 지우다가 <permission android:name="${applicationId}.permission.C2D_MESSAGE" android:protectionLevel="signature" /> 를 삭제하니 정상적으로 설치되었다. 결국 내가 주가한 사항들은
<uses-permission android:name="${applicationId}.permission.C2D_MESSAGE" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<application
...
<receiver
android:name="com.google.android.gms.gcm.GcmReceiver"
android:exported="true"
android:permission="com.google.android.c2dm.permission.SEND">
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
<category android:name="${applicationId}" />
</intent-filter>
</receiver>
<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" />
</intent-filter>
</receiver>
<service android:name="com.dieam.reactnativepushnotification.modules.RNPushNotificationRegistrationService" />
<service
android:name="com.dieam.reactnativepushnotification.modules.RNPushNotificationListenerService"
android:exported="false">
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
</intent-filter>
</service>
</application>
추가로 android/build.gradle
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.2.3'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
mavenLocal()
jcenter()
maven {
// All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
url "$rootDir/../node_modules/react-native/android"
}
}
}
android/app/build.gradle (여기는 버전 정보만)
android {
compileSdkVersion 23
buildToolsVersion "23.0.1"
defaultConfig {
applicationId "com.westudy.tims"
minSdkVersion 21
targetSdkVersion 23
versionCode 1
versionName "1.0"
ndk {
abiFilters "armeabi-v7a", "x86"
}
}
...
dependencies {
compile project(':react-native-push-notification')
compile ('com.google.android.gms:play-services-gcm:8.1.0') {
force = true;
}
compile project(':react-native-bluetooth-status')
compile fileTree(dir: "libs", include: ["*.jar"])
compile "com.android.support:appcompat-v7:23.0.1"
compile "com.facebook.react:react-native:+" // From node_modules
}
RN 빌드가 잘 되는 것을 확인하고 우선 iOS 푸쉬를 설정하기 위해서 메뉴얼을 보니 이 라이브러리는 RN의 PushNotificationIOS 을 사용하고 있었다. RN 이 시키는 데로 xcode 에서 잘 진행한 뒤 이제 대망의 iOS 로 부터 푸쉬 인증서 등록 및 프로비저닝을 다운받는다(라고 쓰고 맥을 구입했는 지 인증받는다 로 읽는다. 네 구입했습니다!)
이 까다로운 절차를 친절하신 개발자분께서 정리해 주셨다. (이 부분 때문에 옛날에 투병생활을 했다..)
https://faith-developer.tistory.com/153
여기까지 하고 폰을 연결해서 xcode 에서 폰에 실행해 보면 위에서 추가한 RN push notification 라이브러리의 onRegister 로 토큰이 전달되는 것을 볼 수 있다.
PushNotification.configure({
// (optional) Called when Token is generated (iOS and Android)
onRegister: function (token) {
console.log('TOKEN:', token);
},
...
console log
2019-07-04 14:22:30.086 [info][tid:com.facebook.react.JavaScript] 'TOKEN:', { token: '내토큰',
os: 'ios' }
여기 까지 했으면 그 힘들다는 리액트에 푸쉬를 붙인것이다! 그럼 발송을 해볼까..
Push 발송 서버를 구축할 수 있지만 AWS 는 아주 저렴한 가격에 푸쉬를 발송해 주기 때문에 나도 여기에 동참했다. 우선 AWS Simple
Notification Service 에 들어가서 좌측 메뉴를 열어보면 모바일 푸쉬가 있는 것을 볼 수 있다.
서울 region 에도 있기 때문에 서울을 선택하면 된다. (서비스가 없는 region을 선택하면 푸시가 안보인다.)
우측에서 플랫폼 애플리케이션을 생성하는데 일단 push test 를 취해서 iOS sandbox 를 생성했다.
p12 인증서(애플은 대체 얼마나 인증을 받아야 만족할 것인가.) 이 부분은 카카오에 설명이 아주 잘 나와 있다.
https://developers.kakao.com/docs/ios/push-notification
생성된 플랫폼 애플리케이션을 목록에서 볼 수 있다. 이 부분의 arn 은 밑에서 사용된다.
이제 AWS 에 엔드포인트(모바일 등록)등록은 api 로 하면 되는데 gradle 에 dependency 를 추가해 주자.
compile ('com.amazonaws:aws-java-sdk-sns:1.11.586')
1. 모바일 푸쉬에 사용되는 토큰을 저장하는 코드는 (AWS. 에 엔드포인트가 생성된다.)
BasicAWSCredentials basicAwsCredentials = new BasicAWSCredentials("aws access key", "aws secret key");
AmazonSNS amazonSNS = AmazonSNSClient.builder()
.withRegion("ap-northeast-2")
.withCredentials(new AWSStaticCredentialsProvider(basicAwsCredentials))
.build();
CreatePlatformEndpointRequest request = new CreatePlatformEndpointRequest()
.withToken("iOS 디바이스 토큰")
.withPlatformApplicationArn("arn:aws:sns:아까 생성한 플랫폼 애플리케이션의 arn/APNS_SANDBOX/push-test");
CreatePlatformEndpointResult result = amazonSNS.createPlatformEndpoint(request);
System.out.println(result.getEndpointArn()); // 이 arn 은 푸쉬를 보낼 때 사용되므로 DB 에 저장하자
}
결과 endpoint arn 은 후에 푸쉬를 보낼 때 사용되므로 저장해 놓자. 그리고 aws access key 와 aws secret key 는 AWS IAM 에서 (이..이제 그만 너무 많아..ㅠㅠ) 생성해야한다.
AWA IAM 로 이동해서 사용자를 추가하고
추가된 사용자를 클릭하고 보안 자격 증명 탭으로 이동 후 액세스키 생성
여기서 secret key 는 한 번만 보여주고 이 후에 볼 수 없으므로 꼭 저장해 두자.
등록된 엔드포인트는 AWS SNS 푸쉬의 플랫폼 애플리케이션을 클릭해서 들어가면 볼 수 있다.
마지막으로 푸쉬를 보내면 되는데 이때 아까 받은 디바이스에 해당하는 arn 이 필요하다
public void push() throws Exception {
BasicAWSCredentials basicAwsCredentials = new BasicAWSCredentials("aws access key", "aws secret key");
AmazonSNS amazonSNS = AmazonSNSClient.builder()
.withRegion("ap-northeast-2")
.withCredentials(new AWSStaticCredentialsProvider(basicAwsCredentials))
.build();
Map<String, String> data = new HashMap<>();
data.put("test-data", "test-value");
Map<String, String> messageMap = new HashMap<String, String>();
messageMap.put("default", "안녕하세요");
messageMap.put("APNS_SANDBOX", getAppleMessage("안녕하세요", data));
PublishRequest request = new PublishRequest()
.withTargetArn("arn:aws:sns:아까 응답으로 받은 디바이스 arn")
.withMessageStructure("json")
.withMessage(new ObjectMapper().writeValueAsString(messageMap));
PublishResult result = amazonSNS.publish(request);
System.out.println(result.getMessageId());
}
private String getAppleMessage(final String message, Map<String, String> data) throws JsonProcessingException {
Map<String, Object> appMessageMap = new HashMap<>();
appMessageMap.put("alert", message);
appMessageMap.put("badge", 9);
appMessageMap.put("sound", "default");
Map<String, Object> appleMessageMap = new HashMap<>();
appleMessageMap.put("aps", appMessageMap);
appleMessageMap.put("extra", data); // 부가적으로 전달하고 싶은 값을 전달하기 위해서
return new ObjectMapper().writeValueAsString(appleMessageMap);
}
이제 RN push notification 라이브러리 (저 위쪽에서 추가한) 의 onNotification 으로 전달된다.
onNotification: function (notification) {
console.log('NOTIFICATION: ', notification);
console.log('extra data: ', notification.data.extra);
// ios app 실행 중 push 를 에서 받았을 때
// foreground: true, userInteraction: false,
// ios app 외부에서 터치로 들어왔을 떼
// foreground: false, userInteraction: true
// process the notification
// required on iOS only (see fetchCompletionHandler docs: https://facebook.github.io/react-native/docs/pushnotificationios.html)
notification.finish(PushNotificationIOS.FetchResult.NoData);
},
console log
2019-07-04 14:59:35.011 [info][tid:com.facebook.react.JavaScript] 'NOTIFICATION: ', { foreground: true,
userInteraction: false,
message: '안녕하세요',
data:
{ remote: true,
notificationId: '6C625AB5-EDFE-4242-97E5-08031ACF3905',
extra: { 'test-data': 'test-value' } },
badge: 9,
alert: '안녕하세요',
sound: 'default',
finish: [Function: finish] }
2019-07-04 14:59:35.012 [info][tid:com.facebook.react.JavaScript] 'extra data: ', { 'test-data': 'test-value' }
아직 좋아하기 이르다..이제 안드로이드를 시작해 보자.
안드로이드는 GCM 이 아닌 FCM 으로 변경이 됐기 때문에 일단 Firebase 에 접속해서 프로젝트를 생성해 준다.
https://firebase.google.com
우측 상단의 콘솔로 이동을 클릭하여 이동하고 프로젝트를 생성해 준다. 기존 프로젝트를 불러올 수 도 있지만 이름을 입력해서 하나 만들어 주었다. (한 번 작업이 금지되었다고 나오고 다시 누르면 생성되는 것은 구글의 컨셉인가...?)
이제 생성된 프로젝트로 이동한 후 프로젝트 설정으로 이동하자
그리고 클라우드 메시지라는 탭으로 이동한다.
여기에서 서버 키는 AWS 에 등록해야 하고 발신자 아이디는 앱의 sender id 에 등록해야 한다.
다시 AWS 로 와서 플랫폼 애플리케이션을 생성하는데 FCM 을 선택해 준다.
그리고 여기 API 키 자리에 위의 서버키를 입력해 준다. 이전 서버키도 동작하지만 그래도 더 긴 걸로 해주자.
iOS 와 같은 방법으로 토큰을 저장해 준다.
public String registerAndroidToken() {
BasicAWSCredentials basicAwsCredentials = new BasicAWSCredentials("aws access key", "aws secret key");
AmazonSNS amazonSNS = AmazonSNSClient.builder()
.withRegion("ap-northeast-2")
.withCredentials(new AWSStaticCredentialsProvider(basicAwsCredentials))
.build();
CreatePlatformEndpointRequest request = new CreatePlatformEndpointRequest()
.withToken("안드로이드 토큰")
.withPlatformApplicationArn("arn:aws:sns:안드로이드 플랫폼 애플리케이션 arn 입력");
CreatePlatformEndpointResult result = amazonSNS.createPlatformEndpoint(request);
System.out.println(result.getEndpointArn());
return result.getEndpointArn();
}
반환 받은 arn을 통해서 푸쉬를 날려보자
public void push(String arn) throws Exception {
BasicAWSCredentials basicAwsCredentials = new BasicAWSCredentials("aws access key", "aws secret key");
AmazonSNS amazonSNS = AmazonSNSClient.builder()
.withRegion("ap-northeast-2")
.withCredentials(new AWSStaticCredentialsProvider(basicAwsCredentials))
.build();
Map<String, String> data = new HashMap<>();
data.put("test-data", "test-value");
Map<String, String> messageMap = new HashMap<>();
messageMap.put("default", "안녕하세요");
messageMap.put("GCM", getAndroidMessage("안녕하세요", data)); // AWS 에 보낼 때는 GCM 으로 보내야 한다.
PublishRequest request = new PublishRequest()
.withTargetArn(arn)
.withMessageStructure("json")
.withMessage(new ObjectMapper().writeValueAsString(messageMap));
PublishResult result = amazonSNS.publish(request);
System.out.println(result.getMessageId());
}
private String getAndroidMessage(final String message, Map<String, String> extra) throws JsonProcessingException {
Map<String, Object> data = new HashMap<>();
data.put("message", message);
Map<String, Object> androidMessageMap = new HashMap<>();
androidMessageMap.put("data", data);
// androidMessageMap.put("collapse_key", "Welcome");
// androidMessageMap.put("delay_while_idle", true);
// androidMessageMap.put("time_to_live", 3600);
// androidMessageMap.put("dry_run", false);
return new ObjectMapper().writeValueAsString(androidMessageMap);
}
안드로이드, iOS 의 getAppleMessage, getAndroidMessage 의 반환값이 String 인 것을 볼 수 있다. 이 부분이 신기한게 json을 만들고 다시 json으로 변환한다.
return new ObjectMapper().writeValueAsString(appleMessageMap); // 이 값을 밑에서 다시 변환한다.
PublishRequest request = new PublishRequest()
.withTargetArn(arn)
.withMessageStructure("json")
.withMessage(new ObjectMapper().writeValueAsString(messageMap)); // 여기서 다시!
iOS 를 보낼 때는 큰 전체를 한 번만 해도 가던게 안드로이드는 꼭 2번을 해줘야 발송된다. 아마도 서버에서 꺼낸 객체를 다시 Object 로 변환하는 것 같다.
머자먼 여정이었다. 다음에는 덜 삽질 하겠지..
'IT > react native' 카테고리의 다른 글
React Native Push 추가 정보 보내기 (0) | 2019.07.05 |
---|---|
cmd 로 안드로이드 에뮬레이터 실행하기 (0) | 2019.05.14 |
react native 설정 변경 (0) | 2019.04.12 |
Could not get unknown property 'mergeResourcesProvider' for object of type com.android.build.gradle.internal.api.ApplicationVariantImpl (갑작스런 에러) (0) | 2019.03.20 |
react native 에서 eject 후 아이폰 실행 안될 때 (0) | 2018.10.11 |