From 33bbd3b9d19826ff177ca6a0310e726db9fad42a Mon Sep 17 00:00:00 2001
From: ikseong00 <127182222+ikseong00@users.noreply.github.com>
Date: Wed, 25 Feb 2026 18:36:27 +0900
Subject: [PATCH 1/9] =?UTF-8?q?[feat]=20Firebase=20Performance=20Monitorin?=
=?UTF-8?q?g=20=EC=B6=94=EA=B0=80?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
#116
---
app/build.gradle.kts | 6 ++++--
app/src/main/AndroidManifest.xml | 8 +++++++-
build.gradle.kts | 1 +
gradle/libs.versions.toml | 3 +++
4 files changed, 15 insertions(+), 3 deletions(-)
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 6af02ee7..9a239546 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -10,6 +10,7 @@ plugins {
alias(libs.plugins.navigationSafeArgs)
alias(libs.plugins.googleServices)
alias(libs.plugins.firebaseCrashlytics)
+ alias(libs.plugins.firebasePerf)
}
val properties = Properties().apply {
@@ -24,8 +25,8 @@ android {
applicationId = "com.kuit.findu"
minSdk = 28
targetSdk = 35
- versionCode = 19
- versionName = "1.1.4"
+ versionCode = 20
+ versionName = "1.1.5"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
buildConfigField("String", "GPT_KEY", properties["GPT_KEY"].toString())
@@ -171,6 +172,7 @@ dependencies {
implementation(libs.firebase.analytics.ktx)
implementation(libs.firebase.config.ktx)
implementation(libs.firebase.crashlytics)
+ implementation(libs.firebase.perf)
// AdMob
implementation("com.google.android.gms:play-services-ads:23.1.0")
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 7979c5fa..651a7f66 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -11,7 +11,8 @@
-
@@ -47,6 +48,11 @@
android:name="com.google.android.gms.ads.APPLICATION_ID"
android:value="ca-app-pub-7675272869453438~5374193050" />
+
+
+
Date: Wed, 25 Feb 2026 19:05:44 +0900
Subject: [PATCH 2/9] =?UTF-8?q?[feat]=20=ED=99=88=20=ED=99=94=EB=A9=B4=20?=
=?UTF-8?q?=EC=BB=A4=EC=8A=A4=ED=85=80=20=EC=84=B1=EB=8A=A5=20=ED=8A=B8?=
=?UTF-8?q?=EB=A0=88=EC=9D=B4=EC=8A=A4=20=EC=B6=94=EA=B0=80?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- home_data_load: 홈 API 호출 소요 시간 및 데이터 개수 측정
- home_protect_image_load: 보호 동물 카드 이미지 로딩 시간 측정
- home_report_image_load: 제보 동물 카드 이미지 로딩 시간 측정
#116
---
.../home/component/HomeProtectAnimalCard.kt | 21 ++++++++++++++++++-
.../home/component/HomeReportedAnimalCard.kt | 21 ++++++++++++++++++-
.../ui/home/viewmodel/HomeViewModel.kt | 9 ++++++++
3 files changed, 49 insertions(+), 2 deletions(-)
diff --git a/app/src/main/java/com/kuit/findu/presentation/ui/home/component/HomeProtectAnimalCard.kt b/app/src/main/java/com/kuit/findu/presentation/ui/home/component/HomeProtectAnimalCard.kt
index 441e43a3..bd72769d 100644
--- a/app/src/main/java/com/kuit/findu/presentation/ui/home/component/HomeProtectAnimalCard.kt
+++ b/app/src/main/java/com/kuit/findu/presentation/ui/home/component/HomeProtectAnimalCard.kt
@@ -15,6 +15,7 @@ import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
@@ -22,8 +23,11 @@ import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import coil.compose.AsyncImage
+import coil.request.ImageRequest
+import com.google.firebase.perf.FirebasePerformance
import com.kuit.findu.R
import com.kuit.findu.domain.model.ProtectAnimal
import com.kuit.findu.presentation.type.AnimalStateType
@@ -48,8 +52,23 @@ fun HomeProtectAnimalCard(
Box(
modifier = Modifier.size(height = 100.dp, width = 120.dp),
) {
+ val context = LocalContext.current
+ val imageRequest = remember(animal.thumbnailImageUrl) {
+ val trace = FirebasePerformance.getInstance().newTrace("home_protect_image_load")
+ ImageRequest.Builder(context)
+ .data(animal.thumbnailImageUrl)
+ .listener(
+ onStart = { trace.start() },
+ onSuccess = { _, _ -> trace.stop() },
+ onError = { _, _ ->
+ trace.putAttribute("status", "error")
+ trace.stop()
+ }
+ )
+ .build()
+ }
AsyncImage(
- model = animal.thumbnailImageUrl,
+ model = imageRequest,
contentDescription = "Animal Image",
contentScale = ContentScale.Crop,
modifier = Modifier
diff --git a/app/src/main/java/com/kuit/findu/presentation/ui/home/component/HomeReportedAnimalCard.kt b/app/src/main/java/com/kuit/findu/presentation/ui/home/component/HomeReportedAnimalCard.kt
index 0c2d009f..4f548bef 100644
--- a/app/src/main/java/com/kuit/findu/presentation/ui/home/component/HomeReportedAnimalCard.kt
+++ b/app/src/main/java/com/kuit/findu/presentation/ui/home/component/HomeReportedAnimalCard.kt
@@ -13,6 +13,7 @@ import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
@@ -20,8 +21,11 @@ import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import coil.compose.AsyncImage
+import coil.request.ImageRequest
+import com.google.firebase.perf.FirebasePerformance
import com.kuit.findu.R
import com.kuit.findu.domain.model.ReportAnimal
import com.kuit.findu.presentation.type.AnimalStateType
@@ -43,8 +47,23 @@ fun HomeReportedAnimalCard(
.background(shape = RoundedCornerShape(10.dp), color = FindUTheme.colors.white)
.noRippleClickable { navigateToReportDetail(animal) }
) {
+ val context = LocalContext.current
+ val imageRequest = remember(animal.thumbnailImageUrl) {
+ val trace = FirebasePerformance.getInstance().newTrace("home_report_image_load")
+ ImageRequest.Builder(context)
+ .data(animal.thumbnailImageUrl)
+ .listener(
+ onStart = { trace.start() },
+ onSuccess = { _, _ -> trace.stop() },
+ onError = { _, _ ->
+ trace.putAttribute("status", "error")
+ trace.stop()
+ }
+ )
+ .build()
+ }
AsyncImage(
- model = animal.thumbnailImageUrl,
+ model = imageRequest,
contentDescription = "Animal Image",
contentScale = ContentScale.Crop,
modifier = Modifier
diff --git a/app/src/main/java/com/kuit/findu/presentation/ui/home/viewmodel/HomeViewModel.kt b/app/src/main/java/com/kuit/findu/presentation/ui/home/viewmodel/HomeViewModel.kt
index c4cda3eb..8c47a961 100644
--- a/app/src/main/java/com/kuit/findu/presentation/ui/home/viewmodel/HomeViewModel.kt
+++ b/app/src/main/java/com/kuit/findu/presentation/ui/home/viewmodel/HomeViewModel.kt
@@ -14,6 +14,7 @@ import com.kuit.findu.presentation.type.HomeReportDurationType
import com.kuit.findu.presentation.type.HomeUserStatusType
import com.kuit.findu.presentation.type.view.LoadState
import com.kuit.findu.presentation.util.Nickname.GUEST_NAME
+import com.google.firebase.perf.FirebasePerformance
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.MutableStateFlow
@@ -137,8 +138,14 @@ class HomeViewModel @Inject constructor(
private fun loadHomeData() {
viewModelScope.launch {
_uiState.update { it.copy(loadState = LoadState.Loading) }
+ val trace = FirebasePerformance.getInstance().newTrace("home_data_load")
+ trace.start()
homeUseCase().fold(
onSuccess = { data ->
+ trace.putAttribute("status", "success")
+ trace.putMetric("protect_animal_count", data.protectAnimalCards.size.toLong())
+ trace.putMetric("report_animal_count", data.reportAnimalCards.size.toLong())
+ trace.stop()
_uiState.update {
it.copy(
loadState = LoadState.Success,
@@ -148,6 +155,8 @@ class HomeViewModel @Inject constructor(
}
},
onFailure = { error ->
+ trace.putAttribute("status", "failure")
+ trace.stop()
Log.e("HomeViewModel", "loadHomeData: $error")
if(error.message?.contains("401") == true) {
_uiEffect.send(HomeUiEffect.NavigateToLogin)
From 48e68163713ad7196dfb77e11c3f89bef4546ced Mon Sep 17 00:00:00 2001
From: ikseong00 <127182222+ikseong00@users.noreply.github.com>
Date: Wed, 25 Feb 2026 20:21:03 +0900
Subject: [PATCH 3/9] =?UTF-8?q?[feat]=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20?=
=?UTF-8?q?=EB=A1=9C=EB=94=A9=20=EC=8B=9C=EA=B0=84=20Logcat=20=EB=A1=9C?=
=?UTF-8?q?=EA=B7=B8=20=EC=B6=94=EA=B0=80?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 보호동물/제보동물 이미지 로딩 소요 시간(ms) 로그 출력
- Logcat 태그: ImagePerf
#116
---
.../ui/home/component/HomeProtectAnimalCard.kt | 16 ++++++++++++++--
.../ui/home/component/HomeReportedAnimalCard.kt | 16 ++++++++++++++--
2 files changed, 28 insertions(+), 4 deletions(-)
diff --git a/app/src/main/java/com/kuit/findu/presentation/ui/home/component/HomeProtectAnimalCard.kt b/app/src/main/java/com/kuit/findu/presentation/ui/home/component/HomeProtectAnimalCard.kt
index bd72769d..43da4003 100644
--- a/app/src/main/java/com/kuit/findu/presentation/ui/home/component/HomeProtectAnimalCard.kt
+++ b/app/src/main/java/com/kuit/findu/presentation/ui/home/component/HomeProtectAnimalCard.kt
@@ -25,6 +25,8 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
+import android.os.SystemClock
+import android.util.Log
import coil.compose.AsyncImage
import coil.request.ImageRequest
import com.google.firebase.perf.FirebasePerformance
@@ -55,12 +57,22 @@ fun HomeProtectAnimalCard(
val context = LocalContext.current
val imageRequest = remember(animal.thumbnailImageUrl) {
val trace = FirebasePerformance.getInstance().newTrace("home_protect_image_load")
+ var startTime = 0L
ImageRequest.Builder(context)
.data(animal.thumbnailImageUrl)
.listener(
- onStart = { trace.start() },
- onSuccess = { _, _ -> trace.stop() },
+ onStart = {
+ startTime = SystemClock.elapsedRealtime()
+ trace.start()
+ },
+ onSuccess = { _, _ ->
+ val duration = SystemClock.elapsedRealtime() - startTime
+ Log.d("ImagePerf", "보호동물 이미지 로딩 완료: ${duration}ms | ${animal.thumbnailImageUrl}")
+ trace.stop()
+ },
onError = { _, _ ->
+ val duration = SystemClock.elapsedRealtime() - startTime
+ Log.e("ImagePerf", "보호동물 이미지 로딩 실패: ${duration}ms | ${animal.thumbnailImageUrl}")
trace.putAttribute("status", "error")
trace.stop()
}
diff --git a/app/src/main/java/com/kuit/findu/presentation/ui/home/component/HomeReportedAnimalCard.kt b/app/src/main/java/com/kuit/findu/presentation/ui/home/component/HomeReportedAnimalCard.kt
index 4f548bef..55e00bb3 100644
--- a/app/src/main/java/com/kuit/findu/presentation/ui/home/component/HomeReportedAnimalCard.kt
+++ b/app/src/main/java/com/kuit/findu/presentation/ui/home/component/HomeReportedAnimalCard.kt
@@ -23,6 +23,8 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
+import android.os.SystemClock
+import android.util.Log
import coil.compose.AsyncImage
import coil.request.ImageRequest
import com.google.firebase.perf.FirebasePerformance
@@ -50,12 +52,22 @@ fun HomeReportedAnimalCard(
val context = LocalContext.current
val imageRequest = remember(animal.thumbnailImageUrl) {
val trace = FirebasePerformance.getInstance().newTrace("home_report_image_load")
+ var startTime = 0L
ImageRequest.Builder(context)
.data(animal.thumbnailImageUrl)
.listener(
- onStart = { trace.start() },
- onSuccess = { _, _ -> trace.stop() },
+ onStart = {
+ startTime = SystemClock.elapsedRealtime()
+ trace.start()
+ },
+ onSuccess = { _, _ ->
+ val duration = SystemClock.elapsedRealtime() - startTime
+ Log.d("ImagePerf", "제보동물 이미지 로딩 완료: ${duration}ms | ${animal.thumbnailImageUrl}")
+ trace.stop()
+ },
onError = { _, _ ->
+ val duration = SystemClock.elapsedRealtime() - startTime
+ Log.e("ImagePerf", "제보동물 이미지 로딩 실패: ${duration}ms | ${animal.thumbnailImageUrl}")
trace.putAttribute("status", "error")
trace.stop()
}
From 5e5733d322cf166a6659a737cdae408f42f3ccd4 Mon Sep 17 00:00:00 2001
From: ikseong00 <127182222+ikseong00@users.noreply.github.com>
Date: Wed, 25 Feb 2026 21:02:24 +0900
Subject: [PATCH 4/9] =?UTF-8?q?[chore]=20=EC=95=B1=20=EB=B2=84=EC=A0=84=20?=
=?UTF-8?q?=EC=A0=95=EB=B3=B4=20=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8=20(1.?=
=?UTF-8?q?1.6)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
#116
---
app/build.gradle.kts | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 9a239546..28651f61 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -25,8 +25,8 @@ android {
applicationId = "com.kuit.findu"
minSdk = 28
targetSdk = 35
- versionCode = 20
- versionName = "1.1.5"
+ versionCode = 21
+ versionName = "1.1.6"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
buildConfigField("String", "GPT_KEY", properties["GPT_KEY"].toString())
From 743e0a572d371a034dfb405b5e2c843217f02962 Mon Sep 17 00:00:00 2001
From: ikseong00 <127182222+ikseong00@users.noreply.github.com>
Date: Thu, 19 Mar 2026 00:49:43 +0900
Subject: [PATCH 5/9] =?UTF-8?q?[feat]=20=EC=84=B8=EC=85=98=20=EB=A7=8C?=
=?UTF-8?q?=EB=A3=8C=20=EC=9D=B4=EB=B2=A4=ED=8A=B8=20=EB=A7=A4=EB=8B=88?=
=?UTF-8?q?=EC=A0=80=20=EC=B6=94=EA=B0=80?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
SharedFlow 기반으로 OkHttp 스레드에서도 안전하게 세션 만료를 알릴 수 있는 SessionExpiredEventManager 추가
---
.../util/SessionExpiredEventManager.kt | 21 +++++++++++++++++++
1 file changed, 21 insertions(+)
create mode 100644 app/src/main/java/com/kuit/findu/data/dataremote/util/SessionExpiredEventManager.kt
diff --git a/app/src/main/java/com/kuit/findu/data/dataremote/util/SessionExpiredEventManager.kt b/app/src/main/java/com/kuit/findu/data/dataremote/util/SessionExpiredEventManager.kt
new file mode 100644
index 00000000..6cac351d
--- /dev/null
+++ b/app/src/main/java/com/kuit/findu/data/dataremote/util/SessionExpiredEventManager.kt
@@ -0,0 +1,21 @@
+package com.kuit.findu.data.dataremote.util
+
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.SharedFlow
+import kotlinx.coroutines.flow.asSharedFlow
+import javax.inject.Inject
+import javax.inject.Singleton
+
+@Singleton
+class SessionExpiredEventManager @Inject constructor() {
+ private val _sessionExpiredEvent = MutableSharedFlow(
+ replay = 0,
+ extraBufferCapacity = 1,
+ onBufferOverflow = kotlinx.coroutines.channels.BufferOverflow.DROP_OLDEST
+ )
+ val sessionExpiredEvent: SharedFlow = _sessionExpiredEvent.asSharedFlow()
+
+ fun notifySessionExpired() {
+ _sessionExpiredEvent.tryEmit(Unit)
+ }
+}
From fbfc09ead4cb8210f21b1d66c41bdf24a77d8cc4 Mon Sep 17 00:00:00 2001
From: ikseong00 <127182222+ikseong00@users.noreply.github.com>
Date: Thu, 19 Mar 2026 00:49:55 +0900
Subject: [PATCH 6/9] =?UTF-8?q?[refactor]=20=ED=86=A0=ED=81=B0=20401=20?=
=?UTF-8?q?=EB=8F=99=EC=8B=9C=EC=84=B1=20=EC=A0=9C=EC=96=B4=20=EB=B0=8F=20?=
=?UTF-8?q?403=20=EC=B2=98=EB=A6=AC=20=EC=B6=94=EA=B0=80?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- AuthAuthenticator에 synchronized + 토큰 버전 비교로 동시 401 race condition 해결
- 토큰 갱신 실패 시 SessionExpiredEventManager로 세션 만료 알림
- 403 응답 처리를 위한 AuthErrorInterceptor 추가
- NetworkModule에 AuthErrorInterceptor 및 SessionExpiredEventManager 연결
---
.../data/dataremote/util/AuthAuthenticator.kt | 25 +++++++++++++------
.../dataremote/util/AuthErrorInterceptor.kt | 22 ++++++++++++++++
.../java/com/kuit/findu/di/NetworkModule.kt | 16 +++++++++++-
3 files changed, 54 insertions(+), 9 deletions(-)
create mode 100644 app/src/main/java/com/kuit/findu/data/dataremote/util/AuthErrorInterceptor.kt
diff --git a/app/src/main/java/com/kuit/findu/data/dataremote/util/AuthAuthenticator.kt b/app/src/main/java/com/kuit/findu/data/dataremote/util/AuthAuthenticator.kt
index ae9c3111..d1aa8dd1 100644
--- a/app/src/main/java/com/kuit/findu/data/dataremote/util/AuthAuthenticator.kt
+++ b/app/src/main/java/com/kuit/findu/data/dataremote/util/AuthAuthenticator.kt
@@ -13,14 +13,23 @@ import javax.inject.Inject
class AuthAuthenticator @Inject constructor(
private val tokenLocalDataSource: TokenLocalDataSource,
private val reissueService: ReissueService,
+ private val sessionExpiredEventManager: SessionExpiredEventManager,
) : Authenticator {
override fun authenticate(route: Route?, response: Response): Request? {
- // 401 Unauthorized 에러 감지
- if (response.code == 401) {
+ val staleToken = tokenLocalDataSource.accessToken
+
+ synchronized(LOCK) {
+ val currentToken = tokenLocalDataSource.accessToken
+
+ if (currentToken != staleToken) {
+ return response.request.newBuilder()
+ .header("Authorization", "Bearer $currentToken")
+ .build()
+ }
+
return try {
val refreshToken = tokenLocalDataSource.refreshToken
- // 토큰 재발급 요청
val reissueResponse = runBlocking {
reissueService.postReissueToken(
TokenReissueRequestDto(refreshToken)
@@ -28,26 +37,26 @@ class AuthAuthenticator @Inject constructor(
}
if (reissueResponse.success) {
- // 새로운 토큰으로 저장
tokenLocalDataSource.accessToken = reissueResponse.data.accessToken
tokenLocalDataSource.refreshToken = reissueResponse.data.refreshToken
- // 새로운 토큰으로 원래 요청 재시도
response.request.newBuilder()
.header("Authorization", "Bearer ${reissueResponse.data.accessToken}")
.build()
} else {
- // 토큰 재발급 실패 - 로그인 화면으로 이동
tokenLocalDataSource.clearToken()
+ sessionExpiredEventManager.notifySessionExpired()
null
}
} catch (e: Exception) {
- // 토큰 재발급 중 오류 발생 - 로그인 화면으로 이동
tokenLocalDataSource.clearToken()
+ sessionExpiredEventManager.notifySessionExpired()
null
}
}
+ }
- return null
+ companion object {
+ private val LOCK = Any()
}
}
diff --git a/app/src/main/java/com/kuit/findu/data/dataremote/util/AuthErrorInterceptor.kt b/app/src/main/java/com/kuit/findu/data/dataremote/util/AuthErrorInterceptor.kt
new file mode 100644
index 00000000..33e5ba55
--- /dev/null
+++ b/app/src/main/java/com/kuit/findu/data/dataremote/util/AuthErrorInterceptor.kt
@@ -0,0 +1,22 @@
+package com.kuit.findu.data.dataremote.util
+
+import com.kuit.findu.data.datalocal.datasource.TokenLocalDataSource
+import okhttp3.Interceptor
+import okhttp3.Response
+import javax.inject.Inject
+
+class AuthErrorInterceptor @Inject constructor(
+ private val tokenLocalDataSource: TokenLocalDataSource,
+ private val sessionExpiredEventManager: SessionExpiredEventManager,
+) : Interceptor {
+ override fun intercept(chain: Interceptor.Chain): Response {
+ val response = chain.proceed(chain.request())
+
+ if (response.code == 403) {
+ tokenLocalDataSource.clearToken()
+ sessionExpiredEventManager.notifySessionExpired()
+ }
+
+ return response
+ }
+}
diff --git a/app/src/main/java/com/kuit/findu/di/NetworkModule.kt b/app/src/main/java/com/kuit/findu/di/NetworkModule.kt
index 11fd2163..7ace6d50 100644
--- a/app/src/main/java/com/kuit/findu/di/NetworkModule.kt
+++ b/app/src/main/java/com/kuit/findu/di/NetworkModule.kt
@@ -7,9 +7,11 @@ import com.kuit.findu.BuildConfig.DEBUG
import com.kuit.findu.data.datalocal.datasource.TokenLocalDataSource
import com.kuit.findu.data.dataremote.service.ReissueService
import com.kuit.findu.data.dataremote.util.AuthAuthenticator
+import com.kuit.findu.data.dataremote.util.AuthErrorInterceptor
import com.kuit.findu.data.dataremote.util.AuthInterceptor
import com.kuit.findu.data.dataremote.util.DiscordLogger
import com.kuit.findu.data.dataremote.util.ErrorTrackingInterceptor
+import com.kuit.findu.data.dataremote.util.SessionExpiredEventManager
import com.kuit.findu.di.qualifier.ReissueRetrofit
import dagger.Module
import dagger.Provides
@@ -45,6 +47,7 @@ object NetworkModule {
loggingInterceptor: HttpLoggingInterceptor,
authInterceptor: AuthInterceptor,
authAuthenticator: AuthAuthenticator,
+ authErrorInterceptor: AuthErrorInterceptor,
errorTrackingInterceptor: ErrorTrackingInterceptor,
): OkHttpClient =
OkHttpClient.Builder().apply {
@@ -52,6 +55,7 @@ object NetworkModule {
writeTimeout(10, TimeUnit.SECONDS)
readTimeout(10, TimeUnit.SECONDS)
addInterceptor(authInterceptor)
+ addInterceptor(authErrorInterceptor)
if (DEBUG) addInterceptor(loggingInterceptor)
else addInterceptor(errorTrackingInterceptor)
authenticator(authAuthenticator)
@@ -75,8 +79,18 @@ object NetworkModule {
fun provideAuthAuthenticator(
tokenLocalDataSource: TokenLocalDataSource,
reissueService: ReissueService,
+ sessionExpiredEventManager: SessionExpiredEventManager,
): AuthAuthenticator {
- return AuthAuthenticator(tokenLocalDataSource, reissueService)
+ return AuthAuthenticator(tokenLocalDataSource, reissueService, sessionExpiredEventManager)
+ }
+
+ @Provides
+ @Singleton
+ fun provideAuthErrorInterceptor(
+ tokenLocalDataSource: TokenLocalDataSource,
+ sessionExpiredEventManager: SessionExpiredEventManager,
+ ): AuthErrorInterceptor {
+ return AuthErrorInterceptor(tokenLocalDataSource, sessionExpiredEventManager)
}
@Provides
From 40ffd6be0d17bdfd5c86ec3ee074af7af7a62c44 Mon Sep 17 00:00:00 2001
From: ikseong00 <127182222+ikseong00@users.noreply.github.com>
Date: Thu, 19 Mar 2026 00:50:04 +0900
Subject: [PATCH 7/9] =?UTF-8?q?[feat]=20=EC=84=B8=EC=85=98=20=EB=A7=8C?=
=?UTF-8?q?=EB=A3=8C=20=EC=8B=9C=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=ED=99=94?=
=?UTF-8?q?=EB=A9=B4=20=EC=9E=90=EB=8F=99=20=EC=9D=B4=EB=8F=99=20=EC=B2=98?=
=?UTF-8?q?=EB=A6=AC?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
FindUApp에서 ActivityLifecycleCallbacks로 현재 Activity를 추적하고, 세션 만료 이벤트 수신 시 LoginActivity로 자동 이동 (Login/Splash에서는 무시)
---
app/src/main/java/com/kuit/findu/FindUApp.kt | 49 ++++++++++++++++++++
1 file changed, 49 insertions(+)
diff --git a/app/src/main/java/com/kuit/findu/FindUApp.kt b/app/src/main/java/com/kuit/findu/FindUApp.kt
index 2c6b8c5e..d53ed8bb 100644
--- a/app/src/main/java/com/kuit/findu/FindUApp.kt
+++ b/app/src/main/java/com/kuit/findu/FindUApp.kt
@@ -1,14 +1,33 @@
package com.kuit.findu
+import android.app.Activity
import android.app.Application
+import android.content.Intent
+import android.os.Bundle
import androidx.appcompat.app.AppCompatDelegate
import com.google.android.gms.ads.MobileAds
import com.kakao.sdk.common.KakaoSdk
+import com.kuit.findu.data.dataremote.util.SessionExpiredEventManager
+import com.kuit.findu.presentation.ui.login.LoginActivity
+import com.kuit.findu.presentation.ui.splash.SplashActivity
import com.naver.maps.map.NaverMapSdk
import dagger.hilt.android.HiltAndroidApp
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.launch
+import java.lang.ref.WeakReference
+import javax.inject.Inject
@HiltAndroidApp
class FindUApp : Application() {
+
+ @Inject
+ lateinit var sessionExpiredEventManager: SessionExpiredEventManager
+
+ private val applicationScope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
+ private var currentActivity: WeakReference? = null
+
override fun onCreate() {
super.onCreate()
@@ -21,5 +40,35 @@ class FindUApp : Application() {
// AdMob 초기화
MobileAds.initialize(this)
+
+ registerActivityLifecycleCallbacks(object : ActivityLifecycleCallbacks {
+ override fun onActivityResumed(activity: Activity) {
+ currentActivity = WeakReference(activity)
+ }
+
+ override fun onActivityPaused(activity: Activity) {
+ if (currentActivity?.get() === activity) {
+ currentActivity = null
+ }
+ }
+
+ override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {}
+ override fun onActivityStarted(activity: Activity) {}
+ override fun onActivityStopped(activity: Activity) {}
+ override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {}
+ override fun onActivityDestroyed(activity: Activity) {}
+ })
+
+ applicationScope.launch {
+ sessionExpiredEventManager.sessionExpiredEvent.collect {
+ val activity = currentActivity?.get() ?: return@collect
+ if (activity is LoginActivity || activity is SplashActivity) return@collect
+
+ val intent = Intent(activity, LoginActivity::class.java).apply {
+ flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
+ }
+ activity.startActivity(intent)
+ }
+ }
}
}
From f01d52c815f5d9ca3ab9fe5dd846726f4dd03b3e Mon Sep 17 00:00:00 2001
From: ikseong00 <127182222+ikseong00@users.noreply.github.com>
Date: Thu, 19 Mar 2026 00:50:14 +0900
Subject: [PATCH 8/9] =?UTF-8?q?[refactor]=20HomeViewModel=20=EC=9E=84?=
=?UTF-8?q?=EC=8B=9C=20401=20=EC=B2=98=EB=A6=AC=20=EC=A0=9C=EA=B1=B0?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
글로벌 세션 만료 처리로 대체되어 HomeViewModel의 401 문자열 매칭 코드, NavigateToLogin effect, HomeFragment의 startLoginActivity() 제거
---
.../com/kuit/findu/presentation/ui/home/HomeFragment.kt | 8 --------
.../findu/presentation/ui/home/viewmodel/HomeViewModel.kt | 6 ------
2 files changed, 14 deletions(-)
diff --git a/app/src/main/java/com/kuit/findu/presentation/ui/home/HomeFragment.kt b/app/src/main/java/com/kuit/findu/presentation/ui/home/HomeFragment.kt
index 09fc114f..c6a0fc49 100644
--- a/app/src/main/java/com/kuit/findu/presentation/ui/home/HomeFragment.kt
+++ b/app/src/main/java/com/kuit/findu/presentation/ui/home/HomeFragment.kt
@@ -24,7 +24,6 @@ import com.kuit.findu.presentation.ui.home.composeview.HomeScreen
import com.kuit.findu.presentation.ui.home.viewmodel.HomeUiEffect
import com.kuit.findu.presentation.ui.home.viewmodel.HomeUiEvent
import com.kuit.findu.presentation.ui.home.viewmodel.HomeViewModel
-import com.kuit.findu.presentation.ui.login.LoginActivity
import com.kuit.findu.presentation.util.permission.LocationPermissionManager.hasLocationPermission
import dagger.hilt.android.AndroidEntryPoint
@@ -96,7 +95,6 @@ class HomeFragment : Fragment() {
}
is HomeUiEffect.Dial -> call120()
- is HomeUiEffect.NavigateToLogin -> startLoginActivity()
}
}
}
@@ -176,12 +174,6 @@ class HomeFragment : Fragment() {
)
}
- private fun startLoginActivity() {
- val intent = Intent(requireContext(), LoginActivity::class.java)
- startActivity(intent)
- requireActivity().finish()
- }
-
private fun navigateToProtectDetail(id: String, tag: String, name: String) {
when (tag) {
AnimalStateType.PROTECT.state -> {
diff --git a/app/src/main/java/com/kuit/findu/presentation/ui/home/viewmodel/HomeViewModel.kt b/app/src/main/java/com/kuit/findu/presentation/ui/home/viewmodel/HomeViewModel.kt
index 8c47a961..88f77df6 100644
--- a/app/src/main/java/com/kuit/findu/presentation/ui/home/viewmodel/HomeViewModel.kt
+++ b/app/src/main/java/com/kuit/findu/presentation/ui/home/viewmodel/HomeViewModel.kt
@@ -3,7 +3,6 @@ package com.kuit.findu.presentation.ui.home.viewmodel
import android.util.Log
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
-import com.kuit.findu.data.dataremote.util.AuthenticationException
import com.kuit.findu.domain.model.HomeData
import com.kuit.findu.domain.model.ProtectAnimal
import com.kuit.findu.domain.model.ReportAnimal
@@ -80,7 +79,6 @@ sealed class HomeUiEffect {
data class ShowToast(val message: String) : HomeUiEffect()
data object Dial : HomeUiEffect()
- data object NavigateToLogin : HomeUiEffect()
}
@HiltViewModel
@@ -158,10 +156,6 @@ class HomeViewModel @Inject constructor(
trace.putAttribute("status", "failure")
trace.stop()
Log.e("HomeViewModel", "loadHomeData: $error")
- if(error.message?.contains("401") == true) {
- _uiEffect.send(HomeUiEffect.NavigateToLogin)
- return@fold
- }
_uiState.update {
it.copy(
loadState = LoadState.Error,
From 3aa96da99bcf06683c9cafde20b1fb28819d32b8 Mon Sep 17 00:00:00 2001
From: ikseong00 <127182222+ikseong00@users.noreply.github.com>
Date: Thu, 19 Mar 2026 00:50:22 +0900
Subject: [PATCH 9/9] =?UTF-8?q?[refactor]=20AuthenticationException=20?=
=?UTF-8?q?=EB=A9=94=EC=8B=9C=EC=A7=80=20=ED=8C=8C=EB=9D=BC=EB=AF=B8?=
=?UTF-8?q?=ED=84=B0=20=EC=B6=94=EA=B0=80?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../main/java/com/kuit/findu/data/dataremote/util/Exceptions.kt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/src/main/java/com/kuit/findu/data/dataremote/util/Exceptions.kt b/app/src/main/java/com/kuit/findu/data/dataremote/util/Exceptions.kt
index 1483eda3..f978de7f 100644
--- a/app/src/main/java/com/kuit/findu/data/dataremote/util/Exceptions.kt
+++ b/app/src/main/java/com/kuit/findu/data/dataremote/util/Exceptions.kt
@@ -1,3 +1,3 @@
package com.kuit.findu.data.dataremote.util
-class AuthenticationException: Exception()
\ No newline at end of file
+class AuthenticationException(message: String = "Session expired") : Exception(message)
\ No newline at end of file