-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[feat] FCM 푸쉬알림 / 푸쉬알림 구현 #229
Changes from 34 commits
4662a19
2b1738e
0dcb0a2
32fd741
a1ce97b
f6c64f1
6f52257
73fa15c
d90a1b5
c3cdaa0
01918bb
9eebf99
1869607
46949f1
a40c575
6228ae9
768942c
4f3623a
30f3eab
2c35766
9ea1a46
38b24d3
8f30fc2
52c2089
173d49f
cb34398
01d7885
39ba5d9
bc1835a
fa5332a
4235bb2
643ef55
a9c5f7a
c8ee570
a4312b0
c098203
a66700f
dd0c643
bc88e5f
d119f8a
ff8ba91
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
package org.go.sopt.winey.configuration | ||
|
||
import android.app.NotificationChannel | ||
import android.app.NotificationManager | ||
import android.app.PendingIntent | ||
import android.content.Context | ||
import android.content.Intent | ||
import android.media.RingtoneManager | ||
import android.os.Build | ||
import androidx.core.app.NotificationCompat | ||
import com.google.firebase.messaging.FirebaseMessagingService | ||
import com.google.firebase.messaging.RemoteMessage | ||
import dagger.hilt.android.AndroidEntryPoint | ||
import kotlinx.coroutines.CoroutineScope | ||
import kotlinx.coroutines.Dispatchers | ||
import kotlinx.coroutines.launch | ||
import org.go.sopt.winey.R | ||
import org.go.sopt.winey.WineyApplication | ||
import org.go.sopt.winey.domain.repository.DataStoreRepository | ||
import org.go.sopt.winey.presentation.splash.SplashActivity | ||
import javax.inject.Inject | ||
|
||
@AndroidEntryPoint | ||
class WineyMessagingService : FirebaseMessagingService() { | ||
|
||
@Inject | ||
lateinit var dataStoreRepository: DataStoreRepository | ||
|
||
override fun onNewToken(token: String) { | ||
super.onNewToken(token) | ||
|
||
CoroutineScope(Dispatchers.IO).launch { dataStoreRepository.saveDeviceToken(token) } | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Dispatchers.IO 으로 지정되어야하는 이유가 있을까요 ? FCM 자체가 백그라운드 스레드에서 호출된다고 알고 있어서 별도 스코프를 지정하지 않아도 될까하는데 궁금합니다 ! 자세히 공부해본 적은 없어서 아니라면 알려주세요 😮
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 맞습니다! 별도의 백그라운드 스레드에서 실행되기때문에 스코프 지정이 필요없습니다. 그런데 Dispatcher.default는 최대 12개의 스레드, 즉 12개의 작업만 한번에 할 수 있는 반면, Dispatcher.io는 최대 64개의 스레드를 사용한다고 합니다. 따라서 한 가지의 무거운 작업을 할때는 Dispatcher.default, 대기시간이 있는 가벼운 입출력 작업을 할때는 Dispatcher.io가 성능향상에 도움이 된다고 합니다. |
||
} | ||
|
||
override fun onMessageReceived(remoteMessage: RemoteMessage) { | ||
super.onMessageReceived(remoteMessage) | ||
if (remoteMessage.data.isNotEmpty() && !WineyApplication.isAppInForeground) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 외부 클래스에서 isAppInForeground 라는 변수를 참조할 때도 WineyApplication 클래스에서 직접 가져오는 것보다, |
||
sendNotification(remoteMessage) | ||
} | ||
} | ||
|
||
private fun sendNotification(remoteMessage: RemoteMessage) { | ||
val uniId: Int = (System.currentTimeMillis() / 7).toInt() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 혹시 uniId 가 어떤 뜻인지 알 수 있을까요?? 코드 맥락 상으로는 식별자 역할을 하는 임의의 숫자 같은 느낌인데, 변수명을 조금 더 구체적으로 적어도 좋을 거 같습니다 :) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. uniId라는 변수명은 unique identifier의 줄임말로, 고유 식별자를 의미하는 네이밍으로 쓰였는데 지금보니 모호해 보이는 부분이 있네요 ! 조금 더 구체적인 네이밍 고려해보겠습니다 ! |
||
|
||
val intent = Intent(this, SplashActivity::class.java) | ||
intent.putExtra(KEY_NOTI_TYPE, remoteMessage.data[KEY_NOTI_TYPE]) | ||
intent.putExtra(KEY_FEED_ID, remoteMessage.data[KEY_FEED_ID]) | ||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. apply 같은 스코프 함수를 이용하면 intent 라는 변수명의 중복을 줄일 수 있을 거 같아요! |
||
val pendingIntent = PendingIntent.getActivity( | ||
this, | ||
uniId, | ||
intent, | ||
PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE | ||
) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이 글을 통해 Intent vs. PendingIntent 차이점을 알 수 있었네요! 참고하면 좋을 거 같아요~! |
||
|
||
val channelId = CHANNEL_ID | ||
|
||
val soundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION) | ||
|
||
val notificationBuilder = NotificationCompat.Builder(this, channelId) | ||
.setSmallIcon(R.mipmap.ic_launcher) | ||
.setContentTitle(remoteMessage.data[KEY_TITLE]) | ||
.setContentText(remoteMessage.data[KEY_MESSAGE]) | ||
.setAutoCancel(true) | ||
.setSound(soundUri) | ||
.setContentIntent(pendingIntent) | ||
|
||
val notificationManager = | ||
getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager | ||
|
||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { | ||
val channel = | ||
NotificationChannel(channelId, NOTICE, NotificationManager.IMPORTANCE_DEFAULT) | ||
notificationManager.createNotificationChannel(channel) | ||
} | ||
|
||
notificationManager.notify(uniId, notificationBuilder.build()) | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. sendNotification 함수 안에서 스플래쉬 화면 띄우기, Notification 띄우기 등 여러 동작을 수행하고 있어서 |
||
|
||
companion object { | ||
private const val TAG = "FirebaseService" | ||
private const val KEY_FEED_ID = "feedId" | ||
private const val KEY_NOTI_TYPE = "notiType" | ||
private const val KEY_TITLE = "title" | ||
private const val KEY_MESSAGE = "message" | ||
private const val NOTICE = "Notice" | ||
private const val CHANNEL_ID = "channel" | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 상수화 👍👍 근데 NOTICE는 어떤 문자열인지 구분이 좀 어려워서 이름을 좀 더 구체적으로 적어도 좋을 거 같아요~! |
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
package org.go.sopt.winey.data.model.remote.request | ||
|
||
import kotlinx.serialization.SerialName | ||
import kotlinx.serialization.Serializable | ||
|
||
@Serializable | ||
data class RequestPatchAllowedNotificationDto( | ||
@SerialName("allowedPush") | ||
val allowedPush: Boolean | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
package org.go.sopt.winey.data.model.remote.request | ||
|
||
import kotlinx.serialization.SerialName | ||
import kotlinx.serialization.Serializable | ||
|
||
@Serializable | ||
data class RequestPatchFcmTokenDto( | ||
@SerialName("token") | ||
val fcmToken: String | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
package org.go.sopt.winey.data.model.remote.response | ||
|
||
import kotlinx.serialization.SerialName | ||
import kotlinx.serialization.Serializable | ||
|
||
@Serializable | ||
data class ResponsePatchAllowedNotificationDto( | ||
@SerialName("isAllowed") | ||
val isAllowed: Boolean | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,12 +2,15 @@ package org.go.sopt.winey.data.source | |
|
||
import org.go.sopt.winey.data.model.remote.request.RequestCreateGoalDto | ||
import org.go.sopt.winey.data.model.remote.request.RequestLoginDto | ||
import org.go.sopt.winey.data.model.remote.request.RequestPatchAllowedNotificationDto | ||
import org.go.sopt.winey.data.model.remote.request.RequestPatchFcmTokenDto | ||
import org.go.sopt.winey.data.model.remote.request.RequestPatchNicknameDto | ||
import org.go.sopt.winey.data.model.remote.response.ResponseCreateGoalDto | ||
import org.go.sopt.winey.data.model.remote.response.ResponseGetNicknameDuplicateCheckDto | ||
import org.go.sopt.winey.data.model.remote.response.ResponseGetUserDto | ||
import org.go.sopt.winey.data.model.remote.response.ResponseLoginDto | ||
import org.go.sopt.winey.data.model.remote.response.ResponseLogoutDto | ||
import org.go.sopt.winey.data.model.remote.response.ResponsePatchAllowedNotificationDto | ||
import org.go.sopt.winey.data.model.remote.response.ResponseReIssueTokenDto | ||
import org.go.sopt.winey.data.model.remote.response.base.BaseResponse | ||
import org.go.sopt.winey.data.service.AuthService | ||
|
@@ -43,4 +46,14 @@ class AuthDataSource @Inject constructor( | |
requestPatchNicknameDto: RequestPatchNicknameDto | ||
): BaseResponse<Unit> = | ||
authService.patchNickname(requestPatchNicknameDto) | ||
|
||
suspend fun patchAllowedNotification( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. patchAllowedNotification 보다는 patchNotificationAgreement와 같은 네이밍해줘도 좋을 것 같아요 ! |
||
requestPatchAllowedNotificationDto: RequestPatchAllowedNotificationDto | ||
): BaseResponse<ResponsePatchAllowedNotificationDto> = | ||
authService.patchAllowedNotification(requestPatchAllowedNotificationDto) | ||
|
||
suspend fun patchFcmToken( | ||
requestPatchFcmTokenDto: RequestPatchFcmTokenDto | ||
): BaseResponse<Unit> = | ||
authService.patchFcmToken(requestPatchFcmTokenDto) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
한 클래스는 최대한 하나의 기능만 담당해야 한다는 '단일 책임의 원칙'에 따라, ActivityLifecycleCallbacks 관련 코드는 별도의 클래스로 분리하는 건 어떨까요?!
관련 자료를 검색해봤을 때도 별도의 클래스를 만들어서 사용하는 경우가 많았던 거 같아요 :)