Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 10 additions & 10 deletions app/src/main/java/com/wafflestudio/snutt2/di/NetworkModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import com.wafflestudio.snutt2.R
import com.wafflestudio.snutt2.data.SNUTTStorage
import com.wafflestudio.snutt2.data.addNetworkLog
import com.wafflestudio.snutt2.lib.data.serializer.Serializer
import com.wafflestudio.snutt2.lib.network.GlobalNetworkEventHandler
import com.wafflestudio.snutt2.lib.network.GlobalNetworkExceptionInterceptor
import com.wafflestudio.snutt2.lib.network.DisplayMessageResolver
import com.wafflestudio.snutt2.lib.network.DisplayMessageResolverImpl
import com.wafflestudio.snutt2.lib.network.SNUTTRestApi
import com.wafflestudio.snutt2.lib.network.call_adapter.ErrorParsingCallAdapterFactory
import com.wafflestudio.snutt2.lib.network.createNewNetworkLog
Expand Down Expand Up @@ -42,7 +42,6 @@ object NetworkModule {
fun provideOkHttpClient(
@ApplicationContext context: Context,
snuttStorage: SNUTTStorage,
globalNetworkExceptionInterceptor: GlobalNetworkExceptionInterceptor,
): OkHttpClient {
val cache = Cache(File(context.cacheDir, "http"), SIZE_OF_CACHE)
return OkHttpClient.Builder()
Expand Down Expand Up @@ -95,7 +94,6 @@ object NetworkModule {
.build()
chain.proceed(newRequest)
}
.addInterceptor(globalNetworkExceptionInterceptor)
.addInterceptor { chain ->
val response = chain.proceed(chain.request())
if (BuildConfig.DEBUG) snuttStorage.addNetworkLog(chain.createNewNetworkLog(context, response))
Expand Down Expand Up @@ -140,12 +138,6 @@ object NetworkModule {
return retrofit.create(SNUTTRestApi::class.java)
}

@Provides
@Singleton
fun provideGlobalNetworkEventHandler(): GlobalNetworkEventHandler {
return GlobalNetworkEventHandler()
}

private const val SIZE_OF_CACHE = (
10 * 1024 * 1024 // 10 MB
).toLong()
Expand All @@ -158,4 +150,12 @@ object NetworkModule {
): ConnectivityManager {
return (context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager)
}

@Provides
@Singleton
fun provideDisplayMessageResolver(
@ApplicationContext context: Context,
): DisplayMessageResolver {
return DisplayMessageResolverImpl(context)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import com.wafflestudio.snutt2.data.user.UserRepository
import com.wafflestudio.snutt2.data.user.UserRepositoryImpl
import com.wafflestudio.snutt2.data.vacancy_noti.VacancyRepository
import com.wafflestudio.snutt2.data.vacancy_noti.VacancyRepositoryImpl
import com.wafflestudio.snutt2.test.TestRepository
import com.wafflestudio.snutt2.test.TestRepositoryImpl
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
Expand Down Expand Up @@ -48,4 +50,7 @@ abstract class RepositoryModule {

@Binds
abstract fun bindsThemeRepository(impl: ThemeRepositoryImpl): ThemeRepository

@Binds
abstract fun bindsTestRepository(impl: TestRepositoryImpl): TestRepository
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,18 @@ import android.content.Context
import android.widget.Toast
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import com.squareup.moshi.Moshi
import com.wafflestudio.snutt2.R
import com.wafflestudio.snutt2.data.user.UserRepository
import com.wafflestudio.snutt2.lib.android.MessagingError
import com.wafflestudio.snutt2.lib.android.runOnUiThread
import com.wafflestudio.snutt2.lib.network.call_adapter.ErrorParsedHttpException
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import okio.IOException
import timber.log.Timber
import javax.inject.Inject
import javax.inject.Singleton
Expand All @@ -19,13 +26,22 @@ import javax.inject.Singleton
@Singleton
class ApiOnError @Inject constructor(
@ApplicationContext private val context: Context,
private val moshi: Moshi,
private val userRepository: UserRepository,
) : (Throwable) -> Unit {

override fun invoke(error: Throwable) {
runOnUiThread {
Timber.e(error)

when (error) {
is IOException -> { // network error
Toast.makeText(
context,
context.getString(R.string.error_no_network),
Toast.LENGTH_SHORT,
).show()
}
is MessagingError -> {
Toast.makeText(
context,
Expand All @@ -35,14 +51,11 @@ class ApiOnError @Inject constructor(
}
is ErrorParsedHttpException -> {
when (error.errorDTO?.code) {
// ApiOnError와 GlobalNetworkHandler 이중 동작 방지
ErrorCode.SERVER_FAULT -> {}
ErrorCode.WRONG_API_KEY -> {}
ErrorCode.NO_USER_TOKEN -> {}
ErrorCode.WRONG_USER_TOKEN -> {}
ErrorCode.NO_ADMIN_PRIVILEGE -> {}
ErrorCode.UNKNOWN_APP -> {}

ErrorCode.SERVER_FAULT -> Toast.makeText(
context,
context.getString(R.string.error_server_fault),
Toast.LENGTH_SHORT,
).show()
ErrorCode.INVALID_EMAIL -> Toast.makeText(
context,
context.getString(R.string.error_invalid_email),
Expand Down Expand Up @@ -113,6 +126,42 @@ class ApiOnError @Inject constructor(
context.getString(R.string.error_wrong_verification_code),
Toast.LENGTH_SHORT,
).show()
ErrorCode.WRONG_API_KEY -> Toast.makeText(
context,
context.getString(R.string.error_wrong_api_key),
Toast.LENGTH_SHORT,
).show()
ErrorCode.NO_USER_TOKEN -> Toast.makeText(
context,
context.getString(R.string.error_no_user_token),
Toast.LENGTH_SHORT,
).show()
ErrorCode.WRONG_USER_TOKEN -> {
Toast.makeText(
context,
context.getString(R.string.error_wrong_user_token),
Toast.LENGTH_SHORT,
).show()
CoroutineScope(Dispatchers.IO).launch {
try {
userRepository.performLogout()
} catch (e: Exception) {
withContext(Dispatchers.Main) {
Toast.makeText(
context,
"로그아웃에 실패하였습니다.",
Toast.LENGTH_SHORT,
)
.show()
}
}
}
}
ErrorCode.NO_ADMIN_PRIVILEGE -> Toast.makeText(
context,
context.getString(R.string.error_no_admin_privilege),
Toast.LENGTH_SHORT,
).show()
ErrorCode.WRONG_ID -> Toast.makeText(
context,
context.getString(R.string.error_wrong_id),
Expand All @@ -128,6 +177,11 @@ class ApiOnError @Inject constructor(
context.getString(R.string.error_wrong_fb_token),
Toast.LENGTH_SHORT,
).show()
ErrorCode.UNKNOWN_APP -> Toast.makeText(
context,
context.getString(R.string.error_unknown_app),
Toast.LENGTH_SHORT,
).show()
ErrorCode.INVALID_ID -> Toast.makeText(
context,
context.getString(R.string.error_invalid_id),
Expand Down Expand Up @@ -267,7 +321,14 @@ class ApiOnError @Inject constructor(
).show()
}
}
else -> {}
is kotlinx.coroutines.CancellationException -> {} // do nothing
else -> {
Toast.makeText(
context,
context.getString(R.string.error_unknown),
Toast.LENGTH_SHORT,
).show()
}
}
}
}
Expand All @@ -280,6 +341,7 @@ object ErrorCode {
const val INVALID_EMAIL = 0x300F
const val VACANCY_PREV_SEMESTER = 0x9C45
const val VACANCY_DUPLICATE = 0x9FC4
const val USED_EMAIL = 0x9FC5
const val INVALID_NICKNAME = 0x9C48

/* 401 - Request was invalid */
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.wafflestudio.snutt2.lib.network

interface DisplayMessageResolver {
fun getDisplayMessage(error: DomainError): String
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.wafflestudio.snutt2.lib.network

import android.content.Context
import com.wafflestudio.snutt2.R
import dagger.hilt.android.qualifiers.ApplicationContext
import javax.inject.Inject

// DisplayMessageResolver는 서버 displayMessage가 없거나, 있는데 클라이언트의 displayMessage와 다른 경우를 커버한다.
// 추후 테스트를 용이하게 하기 위해 interface 분리되어 있음.
class DisplayMessageResolverImpl @Inject constructor(
@ApplicationContext private val context: Context,
) : DisplayMessageResolver {
override fun getDisplayMessage(error: DomainError): String {
return when (error) {
is NetworkDisconnect -> context.getString(R.string.error_no_network)
is ServerFault -> context.getString(R.string.error_server_fault)
is NoAdminPrivilege -> context.getString(R.string.error_no_admin_privilege)
is UnknownApp -> context.getString(R.string.error_unknown_app)
is AuthError.WrongApiKey -> context.getString(R.string.error_wrong_api_key)
is AuthError.NoUserToken -> context.getString(R.string.error_no_user_token)
is AuthError.WrongUserToken -> context.getString(R.string.error_wrong_user_token)
is Unknown -> context.getString(R.string.error_unknown)
else -> error.displayMessage
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.wafflestudio.snutt2.lib.network

sealed interface DomainError {
val displayMessage: String
}

// 1. Global Errors (모든 API에서 발생 가능)
// Auth 관련 오류
sealed interface AuthError : DomainError {
data class WrongApiKey(override val displayMessage: String) : AuthError
data class NoUserToken(override val displayMessage: String) : AuthError // 사실 발생하지 않는 오류일 수 있음. empty token을 보내도 WrongUserToken 발생.
data class WrongUserToken(override val displayMessage: String) : AuthError
}

// 기타 오류
data class NetworkDisconnect(override val displayMessage: String) : DomainError
data class ServerFault(override val displayMessage: String) : DomainError
data class NoAdminPrivilege(override val displayMessage: String) : DomainError
data class UnknownApp(override val displayMessage: String) : DomainError
data class Unknown(override val displayMessage: String) : DomainError
data class Nothing(override val displayMessage: String) : DomainError

// 2. Local Errors (특정 API에서만 발생)
// 회원가입 API
sealed interface SignupError : DomainError {
data class InvalidId(override val displayMessage: String) : SignupError
data class InvalidPassword(override val displayMessage: String) : SignupError
data class DuplicateId(override val displayMessage: String) : SignupError
data class UsedEmail(override val displayMessage: String) : SignupError
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.wafflestudio.snutt2.lib.network

import com.wafflestudio.snutt2.lib.network.call_adapter.ErrorParsedHttpException
import kotlinx.coroutines.CancellationException
import okio.IOException

fun Exception.toDomainError(): DomainError {
return when (this) {
is IOException -> NetworkDisconnect("")
is CancellationException -> Nothing("")
is ErrorParsedHttpException -> {
val displayMessage = this.errorDTO?.displayMessage ?: ""
return when (this.errorDTO?.code) {
ErrorCode.SERVER_FAULT -> ServerFault(displayMessage)
ErrorCode.NO_ADMIN_PRIVILEGE -> NoAdminPrivilege(displayMessage)
ErrorCode.UNKNOWN_APP -> UnknownApp(displayMessage)
ErrorCode.WRONG_API_KEY -> AuthError.WrongApiKey(displayMessage)
ErrorCode.NO_USER_TOKEN -> AuthError.NoUserToken(displayMessage)
ErrorCode.WRONG_USER_TOKEN -> AuthError.WrongUserToken(displayMessage)

ErrorCode.INVALID_ID -> SignupError.InvalidId(displayMessage)
ErrorCode.INVALID_PASSWORD -> SignupError.InvalidPassword(displayMessage)
ErrorCode.DUPLICATE_ID -> SignupError.DuplicateId(displayMessage)
ErrorCode.USED_EMAIL -> SignupError.UsedEmail(displayMessage)
else -> Unknown(displayMessage)
}
}
else -> Unknown("")
}
}

This file was deleted.

This file was deleted.

Loading