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 }