diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 62227e08c..894b63219 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -129,6 +129,9 @@ + diff --git a/app/src/main/java/com/eatssu/android/di/NetworkModule.kt b/app/src/main/java/com/eatssu/android/di/NetworkModule.kt index 683a1b5cd..11a460871 100644 --- a/app/src/main/java/com/eatssu/android/di/NetworkModule.kt +++ b/app/src/main/java/com/eatssu/android/di/NetworkModule.kt @@ -1,8 +1,10 @@ package com.eatssu.android.di +import android.content.Context import com.eatssu.android.BuildConfig import com.eatssu.android.BuildConfig.BASE_URL +import com.eatssu.android.di.network.NetworkErrorInterceptor import com.eatssu.android.di.network.TokenAuthenticator import com.eatssu.android.di.network.TokenInterceptor import com.eatssu.android.domain.usecase.auth.GetRefreshTokenUseCase @@ -13,6 +15,7 @@ import com.eatssu.android.domain.usecase.auth.SetRefreshTokenUseCase import dagger.Module import dagger.Provides import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent import okhttp3.OkHttpClient import okhttp3.ResponseBody @@ -55,12 +58,14 @@ object NetworkModule { @Provides fun provideAuthOkHttpClient( tokenInterceptor: TokenInterceptor, - tokenAuthenticator: TokenAuthenticator + tokenAuthenticator: TokenAuthenticator, + networkErrorInterceptor: NetworkErrorInterceptor ) = if (BuildConfig.DEBUG) { val loggingInterceptor = HttpLoggingInterceptor() loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY) OkHttpClient.Builder() + .addInterceptor(networkErrorInterceptor) .addInterceptor(loggingInterceptor) .addInterceptor(tokenInterceptor) .authenticator(tokenAuthenticator) @@ -68,6 +73,7 @@ object NetworkModule { } else { // 프로덕션 환경에서는 로깅 인터셉터를 추가하지 않음 OkHttpClient.Builder() + .addInterceptor(networkErrorInterceptor) .addInterceptor(tokenInterceptor) .authenticator(tokenAuthenticator) .build() @@ -77,8 +83,11 @@ object NetworkModule { @Singleton @Provides @NoToken - fun provideNoAuthOkHttpClient(): OkHttpClient { + fun provideNoAuthOkHttpClient( + networkErrorInterceptor: NetworkErrorInterceptor + ): OkHttpClient { val builder = OkHttpClient.Builder() + .addInterceptor(networkErrorInterceptor) if (BuildConfig.DEBUG) { builder.addInterceptor(HttpLoggingInterceptor().apply { level = HttpLoggingInterceptor.Level.BODY @@ -108,6 +117,14 @@ object NetworkModule { .build() } + @Provides + @Singleton + fun provideNetworkErrorInterceptor( + @ApplicationContext context: Context + ): NetworkErrorInterceptor { + return NetworkErrorInterceptor(context) + } + @Provides @Singleton fun provideTokenAuthenticator( diff --git a/app/src/main/java/com/eatssu/android/di/network/NetworkErrorInterceptor.kt b/app/src/main/java/com/eatssu/android/di/network/NetworkErrorInterceptor.kt new file mode 100644 index 000000000..47d45a8c9 --- /dev/null +++ b/app/src/main/java/com/eatssu/android/di/network/NetworkErrorInterceptor.kt @@ -0,0 +1,58 @@ +package com.eatssu.android.di.network + +import android.content.Context +import android.content.Intent +import com.eatssu.android.data.dto.response.BaseResponse +import com.eatssu.android.presentation.error.ErrorActivity +import com.google.gson.Gson +import okhttp3.Interceptor +import okhttp3.MediaType.Companion.toMediaTypeOrNull +import okhttp3.Protocol +import okhttp3.Response +import okhttp3.ResponseBody.Companion.toResponseBody +import java.io.IOException +import javax.inject.Inject + + +/** + * 네트워크 오류를 처리하는 인터셉터 + * IOException(SocketTimeoutException, UnknownHostException) 발생 시 AlertDialog를 띄우는 ErrorActivity로 이동 + */ +class NetworkErrorInterceptor @Inject constructor( + private val context: Context, +) : Interceptor { + + companion object { + private val gson = Gson() + } + + override fun intercept(chain: Interceptor.Chain): Response { + val request = chain.request() + + try { + return chain.proceed(request) + } catch (e: IOException) { + val intent = Intent(context, ErrorActivity::class.java).apply { + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK) + putExtra("message", "서버 통신에 실패했습니다. 잠시 후 다시 시도해 주세요.") + } + context.startActivity(intent) + + val baseResponse = BaseResponse( + isSuccess = false, + code = 500, // 서버 처리 오류인지 통신 불가인지 구분 + message = "서버 통신 실패", + ) + val json = gson.toJson(baseResponse) + val responseBody = json.toResponseBody("application/json".toMediaTypeOrNull()) + + return Response.Builder() + .request(request) + .protocol(Protocol.HTTP_1_1) + .code(200) // HTTP 응답 코드는 200으로 해야 Retrofit에서 에러로 처리하지 않음 + .message("서버 통신 실패") + .body(responseBody) + .build() + } + } +} diff --git a/app/src/main/java/com/eatssu/android/presentation/error/ErrorActivity.kt b/app/src/main/java/com/eatssu/android/presentation/error/ErrorActivity.kt new file mode 100644 index 000000000..08e32313e --- /dev/null +++ b/app/src/main/java/com/eatssu/android/presentation/error/ErrorActivity.kt @@ -0,0 +1,31 @@ +package com.eatssu.android.presentation.error + +import android.app.AlertDialog +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import com.eatssu.android.databinding.ActivityErrorBinding +import dagger.hilt.android.AndroidEntryPoint + +@AndroidEntryPoint +class ErrorActivity : AppCompatActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + val binding = ActivityErrorBinding.inflate(layoutInflater) + setContentView(binding.root) + + showDialog() + } + + private fun showDialog() { + val message = intent.getStringExtra("message") ?: "알 수 없는 문제가 발생했습니다. 잠시 후 다시 시도해 주세요." + + AlertDialog.Builder(this) + .setTitle("알림") + .setMessage(message) + .setCancelable(false) + .setPositiveButton("확인") { _, _ -> finish() } + .setOnDismissListener { finish() } + .show() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/eatssu/android/presentation/login/IntroViewModel.kt b/app/src/main/java/com/eatssu/android/presentation/login/IntroViewModel.kt index dcbcdbc8b..eb62b81a8 100644 --- a/app/src/main/java/com/eatssu/android/presentation/login/IntroViewModel.kt +++ b/app/src/main/java/com/eatssu/android/presentation/login/IntroViewModel.kt @@ -59,7 +59,7 @@ class IntroViewModel @Inject constructor( .collect { if (it.result == true) { //토큰이 있고 유효함 _uiState.value = UiState.Success(IntroState.ValidToken) - } else { //토큰이 있어도 유효하지 않음 + } else if (it.code != 500) { // 토큰이 있어도 유효하지 않음 _uiState.value = UiState.Error _uiEvent.emit(UiEvent.ShowToast("로그인이 필요합니다")) } diff --git a/app/src/main/res/layout/activity_error.xml b/app/src/main/res/layout/activity_error.xml new file mode 100644 index 000000000..95e0d8ef3 --- /dev/null +++ b/app/src/main/res/layout/activity_error.xml @@ -0,0 +1,21 @@ + + + + + + \ No newline at end of file