※ 해당 포스팅의 내용은 아래 Setting이 적용된 상태에서 진행되었습니다.
Setting#01. [Android] Retrofit2를 사용한 API 통신 설정 및 Data 송수신
#1. Firebase 인증키 발급
파이어 베이스 사이트에 접속하여 프로젝트를 생성한다.
https://console.firebase.google.com/
다운 받은 google-services.json 파일은 FCM 서비스를 제작할
Android Project 디렉토리의 app 디렉토리에 위치시켜준다.
이제 Android Studio를 열고 google-services.json 파일이 정상적으로 위치하고 있는지 확인해 보자.
Android Studio의 프로젝트 영역에서 ① app( 기본값 ) → Project로 변경하면
app 디렉토리내에 google-services.json 파일이 존재하는 것을 확인 할 수 있다.
이제 다시 Firebase 프로젝트 생성을 진행한다.
콘솔로 이동하면 아래와 같은 페이지로 이동한다.
프로젝트 개요의 설정버튼을 클릭하고
Google Cloud Console에서 API 관리를 클릭한다.
Cloud Messaging 서비스가 활성화 되면 다시 Firebase 프로젝트 서비스 화면으로 이동한다.
그럼 위와같이 서버 키가 생성되고 Token 값을 확인 할 수 있다.
#2. FCM Token 생성 및 Message 수신 Application 제작
1) Android Device에서 FCM Token 생성하기
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<!-- INTERNET 퍼미션 추가 -->
<uses-permission android:name="android.permission.INTERNET"/>
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.FirebaseChatting"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<meta-data
android:name="android.app.lib_name"
android:value="" />
</activity>
<!-- Firebase Cloud Messaging -->
<!-- 푸시알림 아이콘 -->
<meta-data
android:name="com.google.firebase.messaging.default_notification_icon"
android:resource="@drawable/ic_stat_ic_notification" />
<!-- 푸시알림 아이콘 색상 -->
<meta-data
android:name="com.google.firebase.messaging.default_notification_color"
android:resource="@color/color_accent" />
<!-- 알림 채널 객체의 ID, 수신 메시지에 명시적으로 설정된 알림 채널이 없으면 FCM은 항상 이값을 사용한다.
<meta-data
android:name="com.google.firebase.messaging.default_notification_channel_id"
android:resource="fcm_default_channel" />
-->
<service
android:name=".service.MyFirebaseMessaging"
android:exported="false"
android:directBootAware="true">
/>
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
<!-- Firebase Cloud Messaging -->
</application>
</manifest>
build.gradle
plugins {
id 'com.android.application' version '7.3.1' apply false
id 'com.android.library' version '7.3.1' apply false
// 구글 서비스 플러그인
id 'com.google.gms.google-services' version '4.3.15' apply false
}
build.gradle
plugins {
id 'com.android.application'
// Google Service Plugin
id 'com.google.gms.google-services'
}
android {
namespace 'org.chatting.fcm'
compileSdk 33
defaultConfig {
applicationId "org.chatting.fcm"
minSdk 21
targetSdk 33
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
buildFeatures {
viewBinding = true
}
}
dependencies {
implementation 'androidx.appcompat:appcompat:1.6.0'
implementation 'com.google.android.material:material:1.8.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
// Firebase
implementation 'com.google.firebase:firebase-bom:31.2.0'
implementation 'com.google.firebase:firebase-messaging:23.1.1'
implementation 'com.google.firebase:firebase-analytics:21.2.0'
// Android WorkManager
implementation 'androidx.work:work-runtime:2.7.1'
// Gson
implementation 'com.google.code.gson:gson:2.8.5'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
android:id="@+id/btnToken"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:text="TOKEN CREATE"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="MissingConstraints" />
<TextView
android:id="@+id/txtToken"
android:layout_width="390dp"
android:layout_height="112dp"
android:layout_marginTop="20dp"
android:text=""
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.551"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/btnToken"
tools:ignore="MissingConstraints" />
</androidx.constraintlayout.widget.ConstraintLayout>
MainActivity.java
package org.chatting.fcm;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.view.View;
import android.widget.Toast;
import com.google.android.gms.tasks.OnCompleteListener;
import com.google.android.gms.tasks.Task;
import com.google.firebase.messaging.FirebaseMessaging;
import com.google.gson.Gson;
import org.chatting.fcm.databinding.ActivityMainBinding;
import org.chatting.fcm.model.FcmDataModel;
import org.chatting.fcm.utility.LogMsgOutput;
public class MainActivity extends AppCompatActivity {
public static Handler mHandler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityMainBinding binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
/** EVENT#01. FCM 메시지 토큰 생성 */
binding.btnToken.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
/** FCM 메시지 토큰 생성 */
FirebaseMessaging.getInstance().getToken().addOnCompleteListener(new OnCompleteListener<String>() {
@Override
public void onComplete(@NonNull Task<String> task) {
LogMsgOutput.logPrintOut(getApplicationContext(), "task.isSuccessful() : " + task.isSuccessful());
if(task.isSuccessful() == false) {
LogMsgOutput.logPrintOut(getApplicationContext(), "Fetching FCM registration token failed : " + task.getException());
return;
}
// Get new FCM registration token
String token = task.getResult();
LogMsgOutput.logPrintOut(getApplicationContext(), "Fetching FCM token : " + token);
binding.txtToken.setText(token);
}
});
}
});
/** FCM 메시지 수신 Handler */
mHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
FcmDataModel modelFcmData = new Gson().fromJson(new Gson().toJson(msg.obj), FcmDataModel.class);
LogMsgOutput.logPrintOut(getApplicationContext(), "modelFcmData : " + new Gson().toJson(modelFcmData));
Toast.makeText(getApplicationContext(), modelFcmData.getMessage(), Toast.LENGTH_SHORT).show();
}
};
}
/** FCM 메시지 수신 */
public static void receiveFcmMessageOutput(FcmDataModel modelFcmData) {
new Thread(new Runnable() {
@Override
public void run() {
try {
mHandler.post(new Runnable() {
@Override
public void run() {
Message msg = mHandler.obtainMessage();
Bundle bundle = new Bundle();
msg.obj = modelFcmData;
bundle.putString("fcmData", new Gson().toJson(modelFcmData));
msg.setData(bundle);
mHandler.sendMessage(msg);
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
}
MyFirebaseMessaging.java
package org.chatting.fcm.service;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.work.OneTimeWorkRequest;
import androidx.work.WorkManager;
import androidx.work.Worker;
import androidx.work.WorkerParameters;
import com.google.firebase.messaging.FirebaseMessagingService;
import com.google.firebase.messaging.RemoteMessage;
import com.google.gson.Gson;
import org.chatting.fcm.MainActivity;
import org.chatting.fcm.model.FcmDataModel;
import org.chatting.fcm.utility.LogMsgOutput;
import org.chatting.fcm.utility.NowUseActivity;
public class MyFirebaseMessaging extends FirebaseMessagingService {
// [START receive_message]
@Override
public void onMessageReceived(RemoteMessage remoteMessage) {
Context mContext = getApplicationContext();
LogMsgOutput.logPrintOut(mContext, this.getClass().getSimpleName() + "_시작_");
// TODO(developer): Handle FCM messages here.
// Not getting messages here? See why this may be: https://goo.gl/39bRNJ
LogMsgOutput.logPrintOut(mContext, "From : " + new Gson().toJson(remoteMessage));
// Check if message contains a data payload.
// 메시지에 데이터 페이로드가 포함되어 있는지 확인합니다.
if (remoteMessage.getData().size() > 0) {
// LogMsgOutput.logPrintOut(mContext, "Message data payload: " + remoteMessage.getData());
LogMsgOutput.logPrintOut(mContext, "From : " + new Gson().toJson(remoteMessage.getFrom()));
LogMsgOutput.logPrintOut(mContext, "Data : " + new Gson().toJson(remoteMessage.getData()));
LogMsgOutput.logPrintOut(mContext, "Message Type: " + new Gson().toJson(remoteMessage.getMessageType()));
LogMsgOutput.logPrintOut(mContext, "Sender ID : " + new Gson().toJson(remoteMessage.getSenderId()));
// Check if message contains a notification payload.
// 메시지에 알림 페이로드가 포함되어 있는지 확인합니다.
if (remoteMessage.getNotification() != null) {
LogMsgOutput.logPrintOut(mContext, "Message Notification Body : " + remoteMessage.getNotification().getBody());
}
// LogMsgOutput.logPrintOut(mContext, new Gson().toJson(remoteMessage.getData().get("firebaseService")));
// LogMsgOutput.logPrintOut(mContext, new Gson().toJson(remoteMessage.getData()));
FcmDataModel modelFcmData = new Gson().fromJson(remoteMessage.getData().toString(), FcmDataModel.class);
// 현재 사용중인 액티비티를 확인
LogMsgOutput.logPrintOut(mContext, "ActivityName : " + NowUseActivity.getNowUseActivity(mContext));
switch(NowUseActivity.getNowUseActivity(mContext)) {
// 메인 액티비티
case "MainActivity" :
MainActivity.receiveFcmMessageOutput(modelFcmData);
break;
// 위 경우가 아닌 모든 경우 알림창 띄우기
default :
// Also if you intend on generating your own notifications as a result of a received FCM
// 또한 수신된 FCM의 결과로 자체 알림을 생성하려는 경우에도 마찬가지입니다.
// message, here is where that should be initiated. See sendNotification method below.
// 메시지, 여기서 시작해야 합니다. 아래의 알림 전송 방법을 참조하십시오.
sendNotification(remoteMessage);
break;
}
// if(
// modelFcmData.getFirebaseService().equals(mContext.getString(R.string.chatting_msg)) == true ||
// modelFcmData.getFirebaseService().equals(mContext.getString(R.string.chatting_img)) == true ||
// modelFcmData.getFirebaseService().equals(mContext.getString(R.string.chatting_in_out)) == true
// ) {
//
// util_99_log.log_print_out(mContext, "ActivityName : " + NowUseActivity.getNowUseActivity(mContext));
//
// switch(NowUseActivity.getNowUseActivity(mContext)) {
//
// // 채팅방 리스트
// case "Activity_4_1_roomList" :
// util_99_log.log_print_out(mContext, "채팅방 리스트");
// Activity_4_1_roomList.receiveFcmMessageOutput(modelFcmData);
// break;
//
// // 채팅방
// case "Activity_5_1_chattingRoom" :
// Activity_5_1_chattingRoom.receiveFcmMessageOutput(modelFcmData);
// break;
//
// // 채팅방 초대( 아무 동작도 하지 않는다. )
// case "Activity_5_2_memberList" :
// break;
//
// // 위 경우가 아닌 모든 경우 알림창 띄우기
// default :
//
// // Also if you intend on generating your own notifications as a result of a received FCM
// // 또한 수신된 FCM의 결과로 자체 알림을 생성하려는 경우에도 마찬가지입니다.
// // message, here is where that should be initiated. See sendNotification method below.
// // 메시지, 여기서 시작해야 합니다. 아래의 알림 전송 방법을 참조하십시오.
// sendNotification(remoteMessage);
// break;
// }
// }
}
}
// [END receive_message]
// [START on_new_token]
/**
* There are two scenarios when onNewToken is called:
* 1) When a new token is generated on initial app startup
* 2) Whenever an existing token is changed
* Under #2, there are three scenarios when the existing token is changed:
* A) App is restored to a new device
* B) User uninstalls/reinstalls the app
* C) User clears app data
*/
// 새로운 토큰이 생성 될 때 호출
@Override
public void onNewToken(@NonNull String token) {
LogMsgOutput.logPrintOut(getApplicationContext(), "Refreshed token: " + token);
// If you want to send messages to this application instance or
// manage this apps subscriptions on the server side, send the
// FCM registration token to your app server.
sendRegistrationToServer(token);
}
// [END on_new_token]
private void scheduleJob() {
// [START dispatch_job]
OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(MyWorker.class)
.build();
WorkManager.getInstance(this).beginWith(work).enqueue();
// [END dispatch_job]
}
private void handleNow() {
LogMsgOutput.logPrintOut(getApplicationContext(), "Short lived task is done.");
}
private void sendRegistrationToServer(String token) {
// TODO: Implement this method to send token to your app server.
}
public static class MyWorker extends Worker {
public MyWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) {
super(context, workerParams);
}
@NonNull
@Override
public Result doWork() {
// TODO(developer): add long running task here.
return Result.success();
}
}
/**
* Create and show a simple notification containing the received FCM message.
* 수신된 FCM 메시지가 포함된 간단한 알림을 만들어 표시합니다.
*
* @param remoteMessage FCM RemoteMessage received.
*/
private void sendNotification(RemoteMessage remoteMessage) {
// Intent intent = new Intent(this, Activity_4_1_roomList.class);
//
// // 플래그 추가
// intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
//
// // PendingIntent는 Intent를 가지고 있는 클래스
// // 기본 목적은 다른 애플리케이션(다른 프로세스)의 권한을 허가하여 가지고 있는 Intent를 마치 본인 앱의 프로세스에서 실행하는 것처럼 사용하게 하는 것
// PendingIntent pendingIntent = PendingIntent.getActivity(
// this
// , 0 /* Request code */
// , intent
// , PendingIntent.FLAG_IMMUTABLE
// );
//
// String channelId = getString(R.string.default_notification_channel_id);
// Uri defaultSoundUri= RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
//
// NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this, channelId)
// .setSmallIcon(R.drawable.theboom_logo) // 알림 아이콘
// .setContentTitle(remoteMessage.getNotification().getTitle()) // 알림 제목
// .setContentText(remoteMessage.getNotification().getBody()) // 알림 내용
// .setAutoCancel(true) // 알림 터치시 자동으로 삭제
// .setSound(defaultSoundUri) //
// .setContentIntent(pendingIntent); // 알람을 눌렀을 때 실행할작업 인텐트를 설정합니다.
//
// NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
//
//
// // Since android Oreo notification channel is needed.
// // 안드로이드 오레오 알림 채널이 필요하기 때문이다.
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
// {
// NotificationChannel channel = new NotificationChannel(
// channelId
// ,channelId
// ,NotificationManager.IMPORTANCE_DEFAULT);
// notificationManager.createNotificationChannel(channel);
// }
//
// // 알림 표시
// notificationManager.notify(
// 20211007 /* ID of notification */
// , notificationBuilder.build()
// );
}
}
FcmDataModel.java
package org.chatting.fcm.model;
import com.google.gson.annotations.SerializedName;
public class FcmDataModel {
@SerializedName("chat_classification")
private String chatClassification;
private int sequence;
private String message;
private String writer;
@SerializedName("unix_timestamp")
private long unixTimestamp;
public String getChatClassification() {
return chatClassification;
}
public void setChatClassification(String chatClassification) {
this.chatClassification = chatClassification;
}
public int getSequence() {
return sequence;
}
public void setSequence(int sequence) {
this.sequence = sequence;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public String getWriter() {
return writer;
}
public void setWriter(String writer) {
this.writer = writer;
}
public long getUnixTimestamp() {
return unixTimestamp;
}
public void setUnixTimestamp(long unixTimestamp) {
this.unixTimestamp = unixTimestamp;
}
public FcmDataModel(String chatClassification, int sequence, String message, String writer, long unixTimestamp) {
this.chatClassification = chatClassification;
this.sequence = sequence;
this.message = message;
this.writer = writer;
this.unixTimestamp = unixTimestamp;
}
}
NowUseActivity.java
package org.chatting.fcm.utility;
import android.app.ActivityManager;
import android.content.ComponentName;
import android.content.Context;
import android.util.Log;
import java.util.List;
public class NowUseActivity {
// TODO [현재 사용중인 최상위 액티비티 명 확인]
public static String getNowUseActivity(Context mContext){
/**
* 참고 : [특정 클래스에서 본인 클래스명 확인 방법]
* getClass().getName()
* */
// [초기 리턴 결과 반환 변수 선언 실시]
String returnActivityName = "";
try {
ActivityManager manager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
String className = "";
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
className = String.valueOf(manager.getAppTasks().get(0).getTaskInfo().topActivity.getClassName());
}
else {
List<ActivityManager.RunningTaskInfo> info = manager.getRunningTasks(1);
ComponentName componentName= info.get(0).topActivity;
className = componentName.getClassName();
}
Log.i("---","---");
Log.w("//===========//","================================================");
Log.i("","\n"+"[C_Util >> getNowUseActivity() :: 현재 사용 중인 최상위 액티비티 확인]");
Log.i("","\n"+"[className :: " + String.valueOf(className)+"]");
Log.w("//===========//","================================================");
Log.i("---","---");
// -------------------------------------------------------------------------------
String[] classNameArray = className.split("\\.");
// [리턴 반환 데이터 삽입 실시]
returnActivityName = classNameArray[classNameArray.length - 1];
}
catch (Exception e){
//e.printStackTrace();
Log.i("---","---");
Log.e("//===========//","================================================");
Log.i("","\n"+"[C_Util >> getNowUseActivity() :: 현재 사용 중인 최상위 액티비티 확인]");
Log.i("","\n"+"[catch [에러] :: "+String.valueOf(e.getMessage())+"]");
Log.e("//===========//","================================================");
Log.i("---","---");
}
// [리턴 결과 반환 수행 실시]
return returnActivityName;
}
}
#3. Post Man을 이용한 Message 송수신 테스트
Post Man 프로그램을 이용하여 메시지 수신 테스트를 진행해 보자.
# Post Man 송신값 설정
① API
POST : https://fcm.googleapis.com/fcm/send
② Headers
KEY | VALUE |
Content-Type | application/json;UTF-8 |
Authorization | key=서비스_KEY |
③ Body
{
"notification": {
"channel_id" : "chatting_msg"
, "body" : "Firebasee Cloud Message Body"
, "title" : "FIrebase Cloud Message Title"
, "subtitle" : "Firebasee Cloud Message Subtitle"
}
, "data" : {
"chat_classification" : "chatting_msg"
, "sequence" : 22
, "message" : "Firebasee_Cloud_Message_Body"
, "writer" : "User_Name"
, "unix_timestamp" : 1675236144
}
, "registration_ids" : [
"dpdOFSSoR2m8dSGYATTUac:APA91bFtd1OJ7ExlkYTN5WTln2OW7oNyT2OBv6m5Ay6UwPf85-aF3ERgaDa1WvfMozw7NkDobLqfpB7fB3owPRnypdpCKXgyZ2Gx55GeW13UDB0n_sKpl-npgi6IZIAIKO_le3CN6Cpl"
]
}
# FCM 송신 결과
success 값이 1인경우 송신 성공, failure 값이 1인경우 송신 실패
#4. FCM 서비스를 이용한 Message 송신 Application 제작
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<LinearLayout
android:id="@+id/linearLayout"
android:layout_width="match_parent"
android:layout_height="50dp"
android:orientation="horizontal"
app:layout_constraintBottom_toBottomOf="parent">
<EditText
android:id="@+id/edit_msg_write"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text=""
android:hint="메시지를 입력하세요"
android:textColor="#000000">
</EditText>
<Button
android:id="@+id/btn_msg_submit"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:text="SUBMIT">
</Button>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
'Android > Java Code' 카테고리의 다른 글
[Android] Keyboard위에 Edit Text 올리기 (0) | 2023.02.06 |
---|---|
[Android] 출력 위치를 확인하는 Custom Log Message 제작 (0) | 2023.02.02 |
[Android] Activity에서 Dark Theme 비활성화기 (0) | 2023.01.04 |
[Android] 갤러리 이미지 가져오기 및 썸네일 생성 (0) | 2022.11.10 |
[Android] 카메라 사진 가져오기 및 썸네일 생성 (0) | 2022.11.10 |