Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feat] data 모듈 생성 및 서버통신 기초 세팅 #11 #22

Merged
merged 11 commits into from
Feb 2, 2025
1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ plugins {
alias(libs.plugins.firebase.crashlytics) apply false
alias(libs.plugins.ksp) apply false
alias(libs.plugins.ktlint) apply false
alias(libs.plugins.android.library) apply false
}

allprojects {
Expand Down
1 change: 1 addition & 0 deletions data/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
54 changes: 54 additions & 0 deletions data/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
plugins {
alias(libs.plugins.android.library)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.hilt)
alias(libs.plugins.ksp)
alias(libs.plugins.kotlin.serialization)
}

android {
namespace = "com.nexters.ziine.android.data"
compileSdk = 34

defaultConfig {
minSdk = 28

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
}

buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro",
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = "11"
}
}

dependencies {
implementation(libs.androidx.core.ktx)

// hilt
implementation(libs.hilt)
ksp(libs.hilt.compiler)

// timber
implementation(libs.timber)

// HttpClientLibrary
implementation(libs.retrofit)
implementation(libs.okhttp)
implementation(libs.okhttp.logging.interceptor)
implementation(libs.kotlinx.serialization.json)
implementation(libs.retrofit.kotlinx.serialization.converter)
}
Empty file added data/consumer-rules.pro
Empty file.
21 changes: 21 additions & 0 deletions data/proguard-rules.pro
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable

# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package com.nexters.ziine.android.data.di

import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import kotlinx.serialization.json.Json
import okhttp3.Dispatcher
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.converter.kotlinx.serialization.asConverterFactory
import java.util.concurrent.TimeUnit
import javax.inject.Singleton

@Module
@InstallIn(SingletonComponent::class)
object RetrofitModule {
@Provides
fun providesHttpLoggingInterceptor(): HttpLoggingInterceptor =
HttpLoggingInterceptor().apply { level = HttpLoggingInterceptor.Level.BODY }

@Provides
fun providesAdjustMaxRequestPerHostOKHttpDispatcher(): Dispatcher = Dispatcher().apply {
maxRequestsPerHost = 10
}

@Provides
fun providesOKHttpClient(
httpLoggingInterceptor: HttpLoggingInterceptor,
oKHttpDispatcher: Dispatcher,
): OkHttpClient =
OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)
.writeTimeout(10, TimeUnit.SECONDS)
.readTimeout(10, TimeUnit.SECONDS)
.addInterceptor(httpLoggingInterceptor)
.dispatcher(oKHttpDispatcher)
.build()

@Provides
@Singleton
fun providesTripDrawRetrofit(
okHttpClient: OkHttpClient,
): Retrofit {
val contentType = "application/json".toMediaType()
/** baseurl 추후 추가 예정 */
return Retrofit.Builder()
.baseUrl("dummy")
.client(okHttpClient)
.addConverterFactory(Json.asConverterFactory(contentType))
.build()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package com.nexters.ziine.android.data.httpClient

import com.nexters.ziine.android.data.httpClient.dto.response.ErrorResponse
import kotlinx.serialization.json.Json
import retrofit2.HttpException
import timber.log.Timber
import java.io.IOException
import java.net.ConnectException
import java.net.SocketTimeoutException
import java.net.UnknownHostException
import java.net.UnknownServiceException

private const val UNKNOWN_HOST_EXCEPTION_LOG_MESSAGE = "네트워크 연결상태 혹은 잘못된 도메인 요청입니다."
private const val SOCKET_TIME_OUT_EXCEPTION_LOG_MESSAGE = "타임아웃 에러가 발생하였습니다."
private const val CONNECT_EXCEPTION_LOG_MESSAGE = "서버가 다운되었거나 방화벽 관련 문제가 발생하였습니다."
private const val UNKNOWN_IO_EXCEPTION_LOG_MESSAGE = "특정되지 않은 IO Exception이 발생하였습니다."

suspend fun <T : Any> convertClassifiedResult(apiBlock: suspend () -> T): Result<T> {
return runCatching { apiBlock() }
.fold(
onSuccess = { Result.success(it) },
onFailure = { Result.failure(classifyException(it)) },
)
}

private fun classifyException(throwable: Throwable): Exception {
return when (throwable) {
is IOException -> throwable.apply { defaultIoExceptionHandler(throwable) }
is HttpException -> httpExceptionProcessor(throwable)
else -> UnknownServiceException()
}
}

/**
* 추후 공통처리 필요시 만들어놓은 함수
* IOException 하위의 여러 분기되는 Exception을 처리
* 기획 추가사항에 따른 로직 추가가 필요
*/
private fun defaultIoExceptionHandler(exception: IOException) {
return when (exception) {
is UnknownHostException -> defaultUnknownHostExceptionHandler(exception)
is SocketTimeoutException -> defaultSocketTimeoutExceptionHandler(exception)
is ConnectException -> defaultConnectExceptionHandler(exception)
else -> restIOExceptionHandler(exception)
}
}

private fun defaultUnknownHostExceptionHandler(unknownHostException: UnknownHostException) {
// 인터넷 연결 끊김 혹은 잘못된 도메인 주소에 요청
Timber.e(unknownHostException, UNKNOWN_HOST_EXCEPTION_LOG_MESSAGE)
}

private fun defaultSocketTimeoutExceptionHandler(socketTimeoutException: SocketTimeoutException) {
// 타임아웃 발생
Timber.e(socketTimeoutException, SOCKET_TIME_OUT_EXCEPTION_LOG_MESSAGE)
}

private fun defaultConnectExceptionHandler(connectException: ConnectException) {
// 서버가 다운되었거나 방화벽 문제
Timber.e(connectException, CONNECT_EXCEPTION_LOG_MESSAGE)
}

private fun restIOExceptionHandler(ioException: IOException) {
// 특정되지않은 IO 에러
Timber.e(ioException, UNKNOWN_IO_EXCEPTION_LOG_MESSAGE)
}

/**Ziine 서버통신 처리 상황
* 에러시 응답내 HttpStatus code가 아닌 커스텀 코드 포함
* 관련처리 커스텀 코드를 이용
*/
private fun httpExceptionProcessor(exception: HttpException): Exception {
val rawErrorString = exception.response()?.errorBody()?.string() ?: ""
val errorBody: ErrorResponse = Json.decodeFromString(rawErrorString)
val cause = exception.cause

return when (errorBody.code) {
// 400 -> BadRequestException(message, cause)
// 401 -> UnAuthorizedException(message, cause)
// 403 -> ForbiddenException(message, cause) 예시들 상황에 맞는 도메인 커스텀 Exception 정의
else -> exception
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.nexters.ziine.android.data.httpClient.dto.response

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
data class ErrorResponse(
@SerialName("code")
val code: Int,
@SerialName("message")
val message: String,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
최초 패키지 유지용 파일 추후 제거 요망
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
최초 패키지 유지용 파일 추후 제거 요망
1 change: 1 addition & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ hilt-compiler = { group = "com.google.dagger", name = "hilt-compiler", version.r

# third-party
retrofit = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit" }
okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }
retrofit-kotlinx-serialization-converter = { group = "com.squareup.retrofit2", name = "converter-kotlinx-serialization", version.ref = "retrofit" }
okhttp-logging-interceptor = { group = "com.squareup.okhttp3", name = "logging-interceptor", version.ref = "okhttp" }
coil-compose = { group = "io.coil-kt", name = "coil-compose", version.ref = "coil" }
Expand Down
1 change: 1 addition & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,4 @@ dependencyResolutionManagement {
rootProject.name = "ziine-android"
include(":app")
include(":presentation")
include(":data")