From 11443f1d72de9fd9ec95c296a85601fac721375a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=EC=95=84=ED=81=AC?=
<37167652+re4rk@users.noreply.github.com>
Date: Sat, 7 Oct 2023 14:37:29 +0900
Subject: [PATCH] =?UTF-8?q?[AN/USER]=20feat:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8?=
=?UTF-8?q?=20FCM=20=EC=97=B0=EB=8F=99=20=EC=B6=94=EA=B0=80(#458)=20(#474)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* chore: 파이어 베이스 메세지 라이브러리 추가
* feat: 입장 티켓 서비스 클래스 추가
* feat: 입장완료 메세지가 오면 화면이 나가지도록 처리
* refactor: 인증 로그인 요청 데이터 클래스 추가 후 리포지터리에 반영
* feat: 파이어베이스 fcmToken을 로그인할 때 전송하도록 변경
* feat: fcm 토큰 추가후 에러 발생 시 throw를 던지지 않도록 변경
* feat: 레거시 제거
* refactor: dev 브랜치 머지후 실행되지 않는 코드 변경
* refactor: fcm을 data layer에서 처리하도록 변경
* refactor: fcm을 data layer에서 처리하도록 변경
* refactor: isExactlyInstanceOf을 사용하여 타입 체크
* fix: 테스트코드 빌드에러 수정
* refactor: fcm 토큰을 TokenRepository에서만 접근하도록 변경
* refactor: 머지 후 생긴 문제 해결
* refactor: 추가되는 api에 대해서 에러가 발생하지 않도록 변경
* refactor: 머지전 필요없는 사항 변경
* refactor: 머지전 필요없는 사항 변경
---
android/festago/app/build.gradle.kts | 1 +
.../festago/app/src/main/AndroidManifest.xml | 9 +++++++
.../data/di/singletonscope/ApiModule.kt | 21 ++++++++++++---
.../data/di/singletonscope/FirebaseModule.kt | 18 +++++++++++++
.../festago/festago/data/dto/OauthRequest.kt | 1 +
.../data/repository/TokenDefaultRepository.kt | 11 +++++---
.../festago/data/retrofit/TokenManager.kt | 5 +++-
.../service/TicketEntryService.kt | 23 ++++++++++++++++
.../presentation/ui/signin/SignInViewModel.kt | 9 ++++++-
.../ui/ticketentry/TicketEntryActivity.kt | 13 +++++++++
.../ui/signin/SignInViewModelTest.kt | 27 +++++++++++--------
.../festago/repository/TokenRepository.kt | 2 +-
12 files changed, 119 insertions(+), 21 deletions(-)
create mode 100644 android/festago/app/src/main/java/com/festago/festago/data/di/singletonscope/FirebaseModule.kt
create mode 100644 android/festago/app/src/main/java/com/festago/festago/presentation/service/TicketEntryService.kt
diff --git a/android/festago/app/build.gradle.kts b/android/festago/app/build.gradle.kts
index 6b8c2aa7f..204f9d29f 100644
--- a/android/festago/app/build.gradle.kts
+++ b/android/festago/app/build.gradle.kts
@@ -139,6 +139,7 @@ dependencies {
implementation(platform("com.google.firebase:firebase-bom:32.2.0"))
implementation("com.google.firebase:firebase-analytics-ktx")
implementation("com.google.firebase:firebase-crashlytics-ktx")
+ implementation("com.google.firebase:firebase-messaging-ktx:23.2.1")
// swiperefreshlayout
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
diff --git a/android/festago/app/src/main/AndroidManifest.xml b/android/festago/app/src/main/AndroidManifest.xml
index 6d80b0e80..a0a346ffe 100644
--- a/android/festago/app/src/main/AndroidManifest.xml
+++ b/android/festago/app/src/main/AndroidManifest.xml
@@ -62,6 +62,15 @@
+
+
+
+
+
+
+
diff --git a/android/festago/app/src/main/java/com/festago/festago/data/di/singletonscope/ApiModule.kt b/android/festago/app/src/main/java/com/festago/festago/data/di/singletonscope/ApiModule.kt
index e3b852420..c23311bda 100644
--- a/android/festago/app/src/main/java/com/festago/festago/data/di/singletonscope/ApiModule.kt
+++ b/android/festago/app/src/main/java/com/festago/festago/data/di/singletonscope/ApiModule.kt
@@ -38,12 +38,24 @@ object ApiModule {
.addInterceptor(AuthInterceptor(tokenManager))
.build()
+ @Provides
+ @Singleton
+ fun provideRetrofitConverterFactory(): retrofit2.Converter.Factory {
+ val json = Json {
+ ignoreUnknownKeys = true
+ }
+ return json.asConverterFactory("application/json".toMediaType())
+ }
+
@Provides
@Singleton
@NormalRetrofitQualifier
- fun providesNormalRetrofit(@BaseUrlQualifier baseUrl: String): Retrofit = Retrofit.Builder()
+ fun providesNormalRetrofit(
+ @BaseUrlQualifier baseUrl: String,
+ converterFactory: retrofit2.Converter.Factory,
+ ): Retrofit = Retrofit.Builder()
.baseUrl(baseUrl)
- .addConverterFactory(Json.asConverterFactory("application/json".toMediaType()))
+ .addConverterFactory(converterFactory)
.build()
@Provides
@@ -51,11 +63,12 @@ object ApiModule {
@AuthRetrofitQualifier
fun providesAuthRetrofit(
@BaseUrlQualifier baseUrl: String,
- okHttpClient: OkHttpClient
+ okHttpClient: OkHttpClient,
+ converterFactory: retrofit2.Converter.Factory,
): Retrofit = Retrofit.Builder()
.baseUrl(baseUrl)
.client(okHttpClient)
- .addConverterFactory(Json.asConverterFactory("application/json".toMediaType()))
+ .addConverterFactory(converterFactory)
.build()
@Provides
diff --git a/android/festago/app/src/main/java/com/festago/festago/data/di/singletonscope/FirebaseModule.kt b/android/festago/app/src/main/java/com/festago/festago/data/di/singletonscope/FirebaseModule.kt
new file mode 100644
index 000000000..ecbeafc94
--- /dev/null
+++ b/android/festago/app/src/main/java/com/festago/festago/data/di/singletonscope/FirebaseModule.kt
@@ -0,0 +1,18 @@
+package com.festago.festago.data.di.singletonscope
+
+import com.google.firebase.messaging.FirebaseMessaging
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import javax.inject.Singleton
+
+@InstallIn(SingletonComponent::class)
+@Module
+object FirebaseModule {
+ @Provides
+ @Singleton
+ fun provideFirebaseMessaging(): FirebaseMessaging {
+ return FirebaseMessaging.getInstance()
+ }
+}
diff --git a/android/festago/app/src/main/java/com/festago/festago/data/dto/OauthRequest.kt b/android/festago/app/src/main/java/com/festago/festago/data/dto/OauthRequest.kt
index b54e95ef1..622559726 100644
--- a/android/festago/app/src/main/java/com/festago/festago/data/dto/OauthRequest.kt
+++ b/android/festago/app/src/main/java/com/festago/festago/data/dto/OauthRequest.kt
@@ -6,4 +6,5 @@ import kotlinx.serialization.Serializable
data class OauthRequest(
val socialType: String,
val accessToken: String,
+ val fcmToken: String,
)
diff --git a/android/festago/app/src/main/java/com/festago/festago/data/repository/TokenDefaultRepository.kt b/android/festago/app/src/main/java/com/festago/festago/data/repository/TokenDefaultRepository.kt
index f65299b37..84661bf13 100644
--- a/android/festago/app/src/main/java/com/festago/festago/data/repository/TokenDefaultRepository.kt
+++ b/android/festago/app/src/main/java/com/festago/festago/data/repository/TokenDefaultRepository.kt
@@ -6,12 +6,15 @@ import com.festago.festago.data.service.TokenRetrofitService
import com.festago.festago.data.util.onSuccessOrCatch
import com.festago.festago.data.util.runCatchingResponse
import com.festago.festago.repository.TokenRepository
+import com.google.firebase.messaging.FirebaseMessaging
import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.tasks.await
import javax.inject.Inject
class TokenDefaultRepository @Inject constructor(
private val tokenLocalDataSource: TokenDataSource,
private val tokenRetrofitService: TokenRetrofitService,
+ private val firebaseMessaging: FirebaseMessaging,
) : TokenRepository {
override var token: String?
get() = tokenLocalDataSource.token
@@ -21,12 +24,14 @@ class TokenDefaultRepository @Inject constructor(
override suspend fun signIn(socialType: String, token: String): Result =
runCatchingResponse {
- tokenRetrofitService.getOauthToken(OauthRequest(socialType, token))
+ val fcmToken = firebaseMessaging.token.await()
+ tokenRetrofitService.getOauthToken(OauthRequest(socialType, token, fcmToken))
}.onSuccessOrCatch { tokenLocalDataSource.token = it.accessToken }
- override fun refreshToken(token: String): Result = runBlocking {
+ override fun refreshToken(socialType: String, token: String): Result = runBlocking {
runCatchingResponse {
- tokenRetrofitService.getOauthToken(OauthRequest("KAKAO", token))
+ val fcmToken = firebaseMessaging.token.await()
+ tokenRetrofitService.getOauthToken(OauthRequest(socialType, token, fcmToken))
}.onSuccessOrCatch { tokenLocalDataSource.token = it.accessToken }
}
}
diff --git a/android/festago/app/src/main/java/com/festago/festago/data/retrofit/TokenManager.kt b/android/festago/app/src/main/java/com/festago/festago/data/retrofit/TokenManager.kt
index 5832c8c38..df5eff843 100644
--- a/android/festago/app/src/main/java/com/festago/festago/data/retrofit/TokenManager.kt
+++ b/android/festago/app/src/main/java/com/festago/festago/data/retrofit/TokenManager.kt
@@ -4,13 +4,16 @@ import com.festago.festago.repository.TokenRepository
import com.kakao.sdk.auth.TokenManagerProvider
import javax.inject.Inject
-class TokenManager @Inject constructor(private val tokenRepository: TokenRepository) {
+class TokenManager @Inject constructor(
+ private val tokenRepository: TokenRepository,
+) {
val token: String
get() = tokenRepository.token ?: NULL_TOKEN
fun refreshToken() {
tokenRepository.refreshToken(
+ socialType = "KAKAO",
token = TokenManagerProvider.instance.manager.getToken()?.accessToken ?: NULL_TOKEN,
)
}
diff --git a/android/festago/app/src/main/java/com/festago/festago/presentation/service/TicketEntryService.kt b/android/festago/app/src/main/java/com/festago/festago/presentation/service/TicketEntryService.kt
new file mode 100644
index 000000000..b2d1e0ac6
--- /dev/null
+++ b/android/festago/app/src/main/java/com/festago/festago/presentation/service/TicketEntryService.kt
@@ -0,0 +1,23 @@
+package com.festago.festago.presentation.service
+
+import com.google.firebase.messaging.FirebaseMessagingService
+import com.google.firebase.messaging.RemoteMessage
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.runBlocking
+
+class TicketEntryService : FirebaseMessagingService() {
+ override fun onMessageReceived(remoteMessage: RemoteMessage) {
+ runBlocking {
+ // TODO: 입장완료 로직인지 확인하는 로직 추가 필요
+ ticketStateChangeEvent.emit(true)
+ }
+ }
+
+ override fun onNewToken(token: String) {
+ // TODO: 토큰이 변경되었을 때 처리
+ }
+
+ companion object {
+ val ticketStateChangeEvent: MutableSharedFlow = MutableSharedFlow()
+ }
+}
diff --git a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/signin/SignInViewModel.kt b/android/festago/app/src/main/java/com/festago/festago/presentation/ui/signin/SignInViewModel.kt
index 1a1ff5e7a..cb2aba5eb 100644
--- a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/signin/SignInViewModel.kt
+++ b/android/festago/app/src/main/java/com/festago/festago/presentation/ui/signin/SignInViewModel.kt
@@ -8,6 +8,7 @@ import com.festago.festago.presentation.util.MutableSingleLiveData
import com.festago.festago.presentation.util.SingleLiveData
import com.festago.festago.repository.AuthRepository
import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.launch
import javax.inject.Inject
@@ -20,12 +21,18 @@ class SignInViewModel @Inject constructor(
private val _event = MutableSingleLiveData()
val event: SingleLiveData = _event
+ private val exceptionHandler: CoroutineExceptionHandler =
+ CoroutineExceptionHandler { _, throwable ->
+ _event.setValue(SignInEvent.SignInFailure)
+ analyticsHelper.logNetworkFailure(KEY_SIGN_IN_LOG, throwable.message.toString())
+ }
+
fun signInKakao() {
_event.setValue(SignInEvent.ShowSignInPage)
}
fun signIn(token: String) {
- viewModelScope.launch {
+ viewModelScope.launch(exceptionHandler) {
authRepository.signIn(SOCIAL_TYPE_KAKAO, token)
.onSuccess {
_event.setValue(SignInEvent.SignInSuccess)
diff --git a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/ticketentry/TicketEntryActivity.kt b/android/festago/app/src/main/java/com/festago/festago/presentation/ui/ticketentry/TicketEntryActivity.kt
index 5458d2185..076646850 100644
--- a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/ticketentry/TicketEntryActivity.kt
+++ b/android/festago/app/src/main/java/com/festago/festago/presentation/ui/ticketentry/TicketEntryActivity.kt
@@ -3,11 +3,14 @@ package com.festago.festago.presentation.ui.ticketentry
import android.content.Context
import android.content.Intent
import android.os.Bundle
+import android.widget.Toast
import androidx.activity.OnBackPressedCallback
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.res.ResourcesCompat
import com.festago.festago.databinding.ActivityTicketEntryBinding
+import com.festago.festago.presentation.service.TicketEntryService
+import com.festago.festago.presentation.util.repeatOnStarted
import com.google.zxing.BarcodeFormat
import com.journeyapps.barcodescanner.BarcodeEncoder
import dagger.hilt.android.AndroidEntryPoint
@@ -58,6 +61,16 @@ class TicketEntryActivity : AppCompatActivity() {
}
}
}
+ repeatOnStarted(this) {
+ TicketEntryService.ticketStateChangeEvent.collect { event ->
+ if (event) {
+ // TODO 티켓 스캔 화면 처리
+ Toast.makeText(this, "티켓이 스캔되었습니다.", Toast.LENGTH_SHORT).show()
+ setResult(RESULT_OK, intent)
+ finish()
+ }
+ }
+ }
}
private fun initView(currentTicketId: Long) {
diff --git a/android/festago/app/src/test/java/com/festago/festago/presentation/ui/signin/SignInViewModelTest.kt b/android/festago/app/src/test/java/com/festago/festago/presentation/ui/signin/SignInViewModelTest.kt
index fe3e8a73b..8c23f486a 100644
--- a/android/festago/app/src/test/java/com/festago/festago/presentation/ui/signin/SignInViewModelTest.kt
+++ b/android/festago/app/src/test/java/com/festago/festago/presentation/ui/signin/SignInViewModelTest.kt
@@ -28,6 +28,7 @@ class SignInViewModelTest {
@Before
fun setUp() {
Dispatchers.setMain(UnconfinedTestDispatcher())
+
authRepository = mockk(relaxed = true)
analyticsHelper = mockk(relaxed = true)
vm = SignInViewModel(authRepository, analyticsHelper)
@@ -42,17 +43,13 @@ class SignInViewModelTest {
@Test
fun `로그인 성공하면 성공 이벤트가 발생한다`() {
// given
- coEvery {
- authRepository.signIn(any(), any())
- } answers {
- Result.success(Unit)
- }
+ coEvery { authRepository.signIn(any(), any()) } answers { Result.success(Unit) }
// when
vm.signIn("testToken")
// then
- assertThat(vm.event.getValue() is SignInEvent.SignInSuccess).isTrue
+ assertThat(vm.event.getValue()).isExactlyInstanceOf(SignInEvent.SignInSuccess::class.java)
}
@Test
@@ -60,15 +57,13 @@ class SignInViewModelTest {
// given
coEvery {
authRepository.signIn(any(), any())
- } answers {
- Result.failure(Exception())
- }
+ } answers { Result.failure(Exception()) }
// when
vm.signIn("testToken")
// then
- assertThat(vm.event.getValue() is SignInEvent.SignInFailure).isTrue
+ assertThat(vm.event.getValue()).isExactlyInstanceOf(SignInEvent.SignInFailure::class.java)
}
@Test
@@ -78,6 +73,16 @@ class SignInViewModelTest {
vm.signInKakao()
// then
- assertThat(vm.event.getValue() is SignInEvent.ShowSignInPage).isTrue
+ assertThat(vm.event.getValue()).isExactlyInstanceOf(SignInEvent.ShowSignInPage::class.java)
+ }
+
+ @Test
+ fun `FCM 토큰을 불러오지 못하면 실패 이벤트가 발생한다`() {
+ // given
+ // when
+ vm.signIn("testToken")
+
+ // then
+ assertThat(vm.event.getValue()).isExactlyInstanceOf(SignInEvent.SignInFailure::class.java)
}
}
diff --git a/android/festago/domain/src/main/java/com/festago/festago/repository/TokenRepository.kt b/android/festago/domain/src/main/java/com/festago/festago/repository/TokenRepository.kt
index bf915f5be..5876951f1 100644
--- a/android/festago/domain/src/main/java/com/festago/festago/repository/TokenRepository.kt
+++ b/android/festago/domain/src/main/java/com/festago/festago/repository/TokenRepository.kt
@@ -2,6 +2,6 @@ package com.festago.festago.repository
interface TokenRepository {
var token: String?
- fun refreshToken(token: String): Result
+ fun refreshToken(socialType: String, token: String): Result
suspend fun signIn(socialType: String, token: String): Result
}