하나아빠의 안드로이드

Android Firebase Chat(6) : FCM - 클라이언트에서 직접 전송하기. 본문

Android/Firebase

Android Firebase Chat(6) : FCM - 클라이언트에서 직접 전송하기.

하나아빠. 2017. 1. 25. 23:30

안녕하세요. 하나 아빠입니다. ㅎㅎ


지난번에 작성했던 FCM - Console을 이용하여 전송하기. 에 이어서

이번에는 클라이언트(안드로이드 앱)에서 직접 FCM 메시지를 전송하는 예제를 진행하도록 하겠습니다.


아래 진행될 예제는 별도의 개인 서버를 두지않고 Firebase 만 이용하여 메시지를 전달하도록 할건데요 생각보다 간단합니다.


1. FCM 등록토큰 값을 저장합니다.

FCM 등록토큰 값을 저장하는 방법에는 여러가지가 있겠지만. Firebase의 Database에 값을 저장하도록 구현을 하고,
저장된 토큰값을 이용하여 FCM 메시지를 전달하게끔 진행하고자 합니다.. (개인서버가 없으니 Firebase를 최대한 이용..)

우선 MainActivity 에서 로그인을 한 이후에 로그인정보와 함께 토큰값을 Database에 저장하도록 수정 하겠습니다.

UserData 클래스를 아래처럼 만들어 주시고..

1
2
3
4
public class UserData {
    public String userEmailID; // email 주소에서 @ 이전까지의 값.
    public String fcmToken;
}
cs


기존 updateProfile() 소스코드를 수정합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private void updateProfile() {
    FirebaseUser user = mAuth.getCurrentUser();
    if (user == null) {
        // 비 로그인 상태 (메시지를 전송할 수 없다.)
        ...
    } else {
        // 로그인 상태
        ...
        
        UserData userData = new UserData();
        userData.userEmailID = email.substring(0, email.indexOf('@'));
        userData.fcmToken = FirebaseInstanceId.getInstance().getToken();
 
        mFirebaseDatabase.getReference("users").child(userData.userEmailID).setValue(userData);
    }
}
cs


로그인이 되어 있다면 이메일주소의 @를 뺀 실제 ID 부분만 추출해서 토큰값과 함께 Firebase Database에 저장하도록 합니다.


저장할때 소스코드를 보시면 getReference("users").child(userData.userEmailID).setValue(userData); 라고 되어있는데요.

구조적으로 보면 아래와 같습니다.

1
2
3
4
5
6
"users" : {
    "fatherhana" : {
      "fcmToken" : "ceWUhK_fO0Q:APA91bG2fqt1DNtT1_9ldvXxZUhsUSsFtyG7usvkybVF-NFiSapSV4VxRgPDnbaf59EIyXv7DGHvav-dcStMEa61NHxIHl_4GKdk0tgrT3rbZeoUXxxGoHwhxUCXcHoKqFExjg8p7ytE",
      "userEmailID" : "fatherhana"
    }
  }
cs


이처럼 구조를 잡게 되면 사용자 ID를 검색해서 fcmToken값을 가져올때 수월해집니다.


실제로 여기까지 진행해서 앱을 실행 후 로그인해 보시면 Firebase Realtime Database 에는 아래처럼 데이터가 저장된걸 확인하실 수 있습니다.




2. 저장된 FCM 토큰값을 활용하여 클라이언트에서 FCM 메시지를 전송해 봅니다.

우선 채팅 대화를 클릭하여 상대방에게 메시지를 보낼 수 있도록 소스를 수정 합니다.


2-1. 채팅 대화를 클릭할 경우 다이얼로그를 팝업하여 메시지를 입력하게끔 구현 합니다.

initViews() 함수를 아래처럼 수정해 봅니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
private void initViews() {
    mListView = (ListView) findViewById(R.id.list_message);
    mAdapter = new ChatAdapter(this0);
    mListView.setAdapter(mAdapter);
    mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
        @Override
        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
            final ChatData chatData = mAdapter.getItem(position);
            if (!TextUtils.isEmpty(chatData.userEmail)) {
                final EditText editText = new EditText(MainActivity.this);
                new AlertDialog.Builder(MainActivity.this)
                        .setMessage(chatData.userEmail + " 님 에게 메시지 보내기")
                        .setView(editText)
                        .setPositiveButton("보내기"new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                sendPostToFCM(chatData, editText.getText().toString());
                            }
                        })
                        .setNegativeButton("취소"new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                // not thing..
                            }
                        }).show();
            }
        }
    });
...
cs


간단하게 EditText와 보내기, 취소 버튼이 있는 AlertDialog를 팝업하고, 보내기 버튼을 누를 경우

sendPostToFCM(..) 함수를 통해 FCM 메시지를 보낼 수 있도록 구현 되어있습니다.


sendPostToFCM(..) 함수는 아래와 같습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
private static final String FCM_MESSAGE_URL = "https://fcm.googleapis.com/fcm/send";
private static final String SERVER_KEY = "AAAAgGoRvj8:...";
private void sendPostToFCM(final ChatData chatData, final String message) {
    mFirebaseDatabase.getReference("users")
            .child(chatData.userEmail.substring(0, chatData.userEmail.indexOf('@')))
            .addListenerForSingleValueEvent(new ValueEventListener() {
                @Override
                public void onDataChange(DataSnapshot dataSnapshot) {
                    final UserData userData = dataSnapshot.getValue(UserData.class);
                    new Thread(new Runnable() {
                        @Override
                        public void run() {
                            try {
                                // FMC 메시지 생성 start
                                JSONObject root = new JSONObject();
                                JSONObject notification = new JSONObject();
                                notification.put("body", message);
                                notification.put("title", getString(R.string.app_name));
                                root.put("notification", notification);
                                root.put("to", userData.fcmToken);
                                // FMC 메시지 생성 end
 
                                URL Url = new URL(FCM_MESSAGE_URL);
                                HttpURLConnection conn = (HttpURLConnection) Url.openConnection();
                                conn.setRequestMethod("POST");
                                conn.setDoOutput(true);
                                conn.setDoInput(true);
                                conn.addRequestProperty("Authorization""key=" + SERVER_KEY);
                                conn.setRequestProperty("Accept""application/json");
                                conn.setRequestProperty("Content-type""application/json");
                                OutputStream os = conn.getOutputStream();
                                os.write(root.toString().getBytes("utf-8"));
                                os.flush();
                                conn.getResponseCode();
                            } catch (Exception e) {
                                e.printStackTrace();
                            }
                        }
                    }).start();
                }
 
                @Override
                public void onCancelled(DatabaseError databaseError) {
 
                }
            });
}
 
cs


와.. 다소 지저분해 보이긴 하지만 잘 동작 할겁니다..


설명하자면. 

1. 우선 대화메시지를 클릭하여 상대방의 EmailID 값을 알아 옵니다.

2. 알아온 EmailID값을 이용하여 Firebase Realtime Database 에서 해당 유저의 fcmToken값이 담겨있는 UserData 값을 조회합니다.

(조회할 때 사용된 addListenerForSingleValueEvent 함수는 딱 한번만 조회 가능한 이벤트 리스너 입니다.)

3. 상대방의 fcmToken값을 알아오면 JSON 데이터 만들어서 https://fcm.googleapis.com/fcm/send 주소로 데이터를 전송 합니다.


3. 위에서 사용된 SERVER_KEY는 아래에서 확인 가능합니다.

Firebase Console의 [프로젝트 설정]으로 들어가서 [클라우드 메시징] 탭을 선택 후 맨 위에 있는 서버 키 값을 사용하시면 됩니다.





앱 실행 후 테스트 해 보면 정상적으로 FCM 메시지가 전달되는것을 확인하실 수 있으실 겁니다.

(테스트화면에서는 제 자신에게 메시지를 보냈습니다.)





수고하셨습니다. ^^



13 Comments
  • 프로필사진 opwewh 2017.04.22 16:24 fcm 도움말에서
    클라이언트에 서버키를 절대로 노출시키지 말라고 했는데
    노출안시키면서
    fcm/send 요청할 수 있는 방법이 있을까요 ?
  • 프로필사진 하나아빠. 2017.04.24 10:48 신고 안녕하세요. 저도 같은 내용으로 고민을 했던적이 있었는데요.

    저같은 경우 서버키를 Firebase Database에 저장하고, 앱 시작시에 불러와서 사용했습니다!
  • 프로필사진 익명 2017.04.26 16:03 비밀댓글입니다
  • 프로필사진 컴공 2017.05.03 20:40 05-03 20:34:29.414 31068-31068/com.jang.fcmtest D/ViewRootImpl: ViewPostImeInputStage processPointer 0
    05-03 20:34:29.474 31068-31068/com.jang.fcmtest D/ViewRootImpl: ViewPostImeInputStage processPointer 1
    05-03 20:34:29.659 31068-31068/com.jang.fcmtest W/ClassMapper: No setter/field for -KjD1jqcV5HIEvDvk_-b found on class com.jang.fcmtest.UserData
    05-03 20:34:29.669 31068-32680/com.jang.fcmtest I/System.out: (HTTPLog)-Static: isSBSettingEnabled false
    05-03 20:34:29.669 31068-32680/com.jang.fcmtest I/System.out: (HTTPLog)-Static: isSBSettingEnabled false

    이런 에러가 뜨는데 해결책을 알 수 있을까요???
    코드는 Firebase에 저장해놓은 UserEmail 에 sendPostToFCM 함수를 이용해서
    앱에서 특정ID 앱으로 푸시보내려고 UserData 클래스 만들어주고 MainActivity에 sendPostToFCM 함수 만들어서 사용했습니다.
    다른코드는 작성안했던거같고....

    public class UserData {
    public String userEmailID; // email 주소에서 @ 이전까지의 값.
    public String fcmToken;

    public UserData(){

    }
    public void setuserEmailID(String muserEmailID){
    this.userEmailID = muserEmailID;
    }
    public void setfcmToken(String mfcmToken){
    this.fcmToken = mfcmToken;
    }
    public String getuserEmailID(){
    return userEmailID;
    }
    public String getfcmToken(){
    return fcmToken;
    }
    }
    이게 UserData 코드입니다.. 혹시 무슨 문제인지 알수있을까요???
  • 프로필사진 하나아빠. 2017.05.08 14:56 신고 혹시 Firebase DB에 저장된 내용이 UserData 형태로 담을 수 없는 값들이 들어가있지는 않나요?
  • 프로필사진 2017.05.16 00:06 근데 위와같이 했을때 앱이 꺼져있을때도 Notification 메세지 오나요?
    만약 안온다면 오게 하는 방법은 얼케해야하나요?
  • 프로필사진 종열 2017.05.19 13:00 private void signOut() {
    mAuth.signOut();
    Auth.GoogleSignInApi.signOut(mGoogleApiClient).setResultCallback(
    new ResultCallback<Status>() {
    @Override
    public void onResult(@NonNull Status status) {
    updateProfile();
    }
    });
    }
    이부분에서 Status 는 임포트를 어떻게해야하나요??
  • 프로필사진 뉴비 2017.08.10 23:07 데이터베이스에 규칙을 풀어도 로그인을 해야 메세지가 보내지나요? 로그인 안하고 메시지를 보낼수 있는 방법은 없나요? 토큰은 데이터베이스에 저장되게 해놨어요
  • 프로필사진 진심으로 2018.09.26 12:06 fcm 데이터 메시징 때문에 골치를 썩었는데 덕분에 해결했습니다
    진심으로 감사드립니다 :)

    댁내 두루 평안하고 늘 건승하시기 바랍니다!
  • 프로필사진 신용환 2019.03.23 12:45 신고 ChatAdapter는 어디에 있는지요?
  • 프로필사진 beechang 2020.09.25 19:41 신고 정말 쉽게 구현이 가능했습니다. 귀중한 자료 공유해주셔서 진심으로 감사드립니다!!
  • 프로필사진 suha 2021.05.13 12:31 private void updateProfile() 에서 userData.fcmToken = FirebaseInstanceId.getInstance().getToken();
    에 부분에 FirebaseInstanceId 오류뜨는데 어느것으로 대체할수 있을까요?
  • 프로필사진 DSADSA 2021.10.19 19:23 혹시 그부분 해결했나여??
    FirebaseInstanceld 이게 지금은 안쓴다고 하더라고요
댓글쓰기 폼