From f8546672c1cf2ff33c8b292863921cfc7ca165df Mon Sep 17 00:00:00 2001 From: JJJoonngg Date: Wed, 18 Oct 2023 22:22:38 +0900 Subject: [PATCH] =?UTF-8?q?#26=20=EB=84=A4=ED=8A=B8=EC=9B=8C=ED=81=AC=20?= =?UTF-8?q?=EA=B8=B0=EC=B4=88=20=EC=9E=91=EC=97=85=20=EC=A7=84=ED=96=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - network 연결상태를 알 수 있는 checker 구현 - api 통신을 하기위한 di 의 module 정의 진행 - 기본적인 string을 가져다 쓸 수 있도록 정의 --- app/build.gradle.kts | 3 + .../java/com/owori/android/core/AppConfig.kt | 7 +++ .../owori/android/core/OworiApplication.kt | 35 +++++++++++- .../owori/android/core/di/NetworkModule.kt | 56 +++++++++++++++++++ .../owori/android/data/api/auth/AuthApi.kt | 9 +++ .../com/owori/android/module/DataResult.kt | 41 ++++++++++++++ .../com/owori/android/module/HandleApi.kt | 41 ++++++++++++++ .../android/module/HttpRequestInterceptor.kt | 21 +++++++ .../module/NetworkConnectionChecker.kt | 38 +++++++++++++ app/src/main/res/values/constants.xml | 4 ++ gradle/libs.versions.toml | 13 +++-- 11 files changed, 262 insertions(+), 6 deletions(-) create mode 100644 app/src/main/java/com/owori/android/core/AppConfig.kt create mode 100644 app/src/main/java/com/owori/android/core/di/NetworkModule.kt create mode 100644 app/src/main/java/com/owori/android/data/api/auth/AuthApi.kt create mode 100644 app/src/main/java/com/owori/android/module/DataResult.kt create mode 100644 app/src/main/java/com/owori/android/module/HandleApi.kt create mode 100644 app/src/main/java/com/owori/android/module/HttpRequestInterceptor.kt create mode 100644 app/src/main/java/com/owori/android/module/NetworkConnectionChecker.kt create mode 100644 app/src/main/res/values/constants.xml diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 612b46c..affd6e4 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -66,9 +66,12 @@ dependencies { implementation(libs.kotlinx.coroutines.android) implementation(libs.kotlinx.serialization.json) + implementation(libs.okhttp.logging) implementation(libs.retrofit) implementation(libs.retrofit.kotlin.serialization) + implementation(libs.retrofit.converter.gson) + implementation(libs.retrofit.converter.scalars) implementation(libs.coroutines.core) implementation(libs.fragment.ktx) diff --git a/app/src/main/java/com/owori/android/core/AppConfig.kt b/app/src/main/java/com/owori/android/core/AppConfig.kt new file mode 100644 index 0000000..74a09b6 --- /dev/null +++ b/app/src/main/java/com/owori/android/core/AppConfig.kt @@ -0,0 +1,7 @@ +package com.owori.android.core + + +object AppConfig { + const val TAG_DEBUG = "TAG_DEBUG" + +} \ No newline at end of file diff --git a/app/src/main/java/com/owori/android/core/OworiApplication.kt b/app/src/main/java/com/owori/android/core/OworiApplication.kt index 1a97b23..ea423a2 100644 --- a/app/src/main/java/com/owori/android/core/OworiApplication.kt +++ b/app/src/main/java/com/owori/android/core/OworiApplication.kt @@ -1,11 +1,42 @@ package com.owori.android.core +import android.annotation.SuppressLint import android.app.Application +import android.content.Context +import androidx.annotation.StringRes +import androidx.lifecycle.DefaultLifecycleObserver +import androidx.lifecycle.LifecycleOwner +import com.owori.android.R +import com.owori.android.module.NetworkConnectionChecker import dagger.hilt.android.HiltAndroidApp @HiltAndroidApp -class OworiApplication: Application() { +class OworiApplication : Application(), DefaultLifecycleObserver { override fun onCreate() { - super.onCreate() + super.onCreate() + context = applicationContext + networkConnectionChecker = NetworkConnectionChecker(context) + } + + override fun onStop(owner: LifecycleOwner) { + networkConnectionChecker.unregister() + super.onStop(owner) + } + + override fun onStart(owner: LifecycleOwner) { + networkConnectionChecker.register() + super.onStart(owner) + } + + companion object { + @SuppressLint("StaticFieldLeak") + private lateinit var context: Context + + fun getString(@StringRes stringResId: Int): String { + return context.getString(stringResId) + } + + private lateinit var networkConnectionChecker: NetworkConnectionChecker + fun isOnline() = networkConnectionChecker.isOnline() } } \ No newline at end of file diff --git a/app/src/main/java/com/owori/android/core/di/NetworkModule.kt b/app/src/main/java/com/owori/android/core/di/NetworkModule.kt new file mode 100644 index 0000000..1e45399 --- /dev/null +++ b/app/src/main/java/com/owori/android/core/di/NetworkModule.kt @@ -0,0 +1,56 @@ +package com.owori.android.core.di + +import com.owori.android.R +import com.owori.android.core.OworiApplication +import com.owori.android.data.api.auth.AuthApi +import com.owori.android.module.HttpRequestInterceptor +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import okhttp3.OkHttpClient +import retrofit2.Retrofit +import retrofit2.converter.gson.GsonConverterFactory +import retrofit2.converter.scalars.ScalarsConverterFactory +import javax.inject.Singleton + +/* +* Created by JJJoonngg +*/ + +@Module +@InstallIn(SingletonComponent::class) +object NetworkModule { + const val NETWORK_EXCEPTION_OFFLINE_CASE = "network status is offline" + const val NETWORK_EXCEPTION_BODY_IS_NULL = "result body is null" + + @Provides + @Singleton + fun provideOKHttpClient(): OkHttpClient { + return OkHttpClient.Builder() + .addInterceptor(HttpRequestInterceptor()) + .retryOnConnectionFailure(false) + .build() + } + + @Provides + @Singleton + fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit { + return Retrofit.Builder() + .client(okHttpClient) + .baseUrl(OworiApplication.getString(R.string.base_url)) + .addConverterFactory(ScalarsConverterFactory.create()) + .addConverterFactory(GsonConverterFactory.create()) + .build() + } + + @Provides + @Singleton + fun provideAuthApi(retrofit: Retrofit): AuthApi { + return retrofit.buildService() + } + + private inline fun Retrofit.buildService(): T { + return this.create(T::class.java) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/owori/android/data/api/auth/AuthApi.kt b/app/src/main/java/com/owori/android/data/api/auth/AuthApi.kt new file mode 100644 index 0000000..1d8cbb2 --- /dev/null +++ b/app/src/main/java/com/owori/android/data/api/auth/AuthApi.kt @@ -0,0 +1,9 @@ +package com.owori.android.data.api.auth + +/* +* Created by JJJoonngg +*/ + +interface AuthApi { + +} \ No newline at end of file diff --git a/app/src/main/java/com/owori/android/module/DataResult.kt b/app/src/main/java/com/owori/android/module/DataResult.kt new file mode 100644 index 0000000..c6f9439 --- /dev/null +++ b/app/src/main/java/com/owori/android/module/DataResult.kt @@ -0,0 +1,41 @@ +package com.owori.android.module + +/* +* Created by JJJoonngg +*/ + +sealed class DataResult { + data class Success(val data: T) : DataResult() + data class Fail(val statusCode: Int, val message: String) : DataResult() + data class Error(val exception: Exception) : DataResult() +} + +inline fun DataResult.onSuccess(action: (T) -> Unit): DataResult { + if (this is DataResult.Success) { + action(data) + } + return this +} + +inline fun DataResult.onFail(resultCode: (Int) -> Unit): DataResult { + if (this is DataResult.Fail) { + resultCode(this.statusCode) + } + return this +} + +inline fun DataResult.onError(action: (Exception) -> Unit): DataResult { + if (this is DataResult.Fail) { + action(IllegalArgumentException("code : ${this.statusCode}, message : ${this.message}")) + } else if (this is DataResult.Error) { + action(this.exception) + } + return this +} + +inline fun DataResult.onException(action: (Exception) -> Unit): DataResult { + if (this is DataResult.Error) { + action(this.exception) + } + return this +} \ No newline at end of file diff --git a/app/src/main/java/com/owori/android/module/HandleApi.kt b/app/src/main/java/com/owori/android/module/HandleApi.kt new file mode 100644 index 0000000..75e4f59 --- /dev/null +++ b/app/src/main/java/com/owori/android/module/HandleApi.kt @@ -0,0 +1,41 @@ +package com.owori.android.module + +import com.owori.android.core.OworiApplication +import com.owori.android.core.di.NetworkModule +import retrofit2.Response + +/* +* Created by JJJoonngg +*/ + +suspend fun handleApi( + execute: suspend () -> Response, + mapper: (T) -> R +): DataResult { + if (OworiApplication.isOnline().not()) { + return DataResult.Error(Exception(NetworkModule.NETWORK_EXCEPTION_OFFLINE_CASE)) + } + + return try { + val response = execute() + val body = response.body() + if (response.isSuccessful) { + body?.let { + DataResult.Success(mapper(it)) + } ?: run { + throw NullPointerException(NetworkModule.NETWORK_EXCEPTION_BODY_IS_NULL) + } + } else { + getFailDataResult(body, response) + } + } catch (e: Exception) { + DataResult.Error(e) + } +} + + +private fun getFailDataResult(body: T?, response: Response) = body?.let { + DataResult.Fail(statusCode = response.code(), message = it.toString()) +} ?: run { + DataResult.Fail(statusCode = response.code(), message = response.message()) +} \ No newline at end of file diff --git a/app/src/main/java/com/owori/android/module/HttpRequestInterceptor.kt b/app/src/main/java/com/owori/android/module/HttpRequestInterceptor.kt new file mode 100644 index 0000000..322548c --- /dev/null +++ b/app/src/main/java/com/owori/android/module/HttpRequestInterceptor.kt @@ -0,0 +1,21 @@ +package com.owori.android.module + +import android.util.Log +import com.owori.android.core.AppConfig +import okhttp3.Interceptor +import okhttp3.Response + + +class HttpRequestInterceptor : Interceptor { + override fun intercept(chain: Interceptor.Chain): Response { + try { + val originRequest = chain.request() + Log.d(AppConfig.TAG_DEBUG, "HttpRequestInterceptor: ${originRequest.url}") + + return chain.proceed(originRequest) + } catch (e: Exception) { + Log.d(AppConfig.TAG_DEBUG, "HttpRequestInterceptor error: ${e.message}") + throw e + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/owori/android/module/NetworkConnectionChecker.kt b/app/src/main/java/com/owori/android/module/NetworkConnectionChecker.kt new file mode 100644 index 0000000..667313c --- /dev/null +++ b/app/src/main/java/com/owori/android/module/NetworkConnectionChecker.kt @@ -0,0 +1,38 @@ +package com.owori.android.module + +import android.content.Context +import android.net.ConnectivityManager +import android.net.NetworkCapabilities +import android.net.NetworkRequest + +/* +* Created by JJJoonngg +*/ + +class NetworkConnectionChecker(context: Context) : ConnectivityManager.NetworkCallback() { + + private val networkRequest: NetworkRequest = NetworkRequest.Builder() + .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) + .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) + .build() + private val connectivityManager: ConnectivityManager = + context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager + + fun register() { + connectivityManager.registerNetworkCallback(networkRequest, this) + } + + fun unregister() { + connectivityManager.unregisterNetworkCallback(this) + } + + fun isOnline(): Boolean { + val nw = connectivityManager.activeNetwork ?: return false + val actNw = connectivityManager.getNetworkCapabilities(nw) ?: return false + return when { + actNw.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> true + actNw.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> true + else -> false + } + } +} \ No newline at end of file diff --git a/app/src/main/res/values/constants.xml b/app/src/main/res/values/constants.xml new file mode 100644 index 0000000..f776dbc --- /dev/null +++ b/app/src/main/res/values/constants.xml @@ -0,0 +1,4 @@ + + + http://owori.store/api/v1 + \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index fe47500..e1515c4 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -42,11 +42,16 @@ espresso-core = { group = "androidx.test.espresso", name = "espresso-core", vers appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" } material = { group = "com.google.android.material", name = "material", version.ref = "material" } constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" } + kotlinx-coroutines-android = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "kotlinxCoroutines" } -okhttp-logging = { group = "com.squareup.okhttp3", name = "logging-interceptor", version.ref = "okhttp"} kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" } -retrofit = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit"} + +okhttp-logging = { group = "com.squareup.okhttp3", name = "logging-interceptor", version.ref = "okhttp" } +retrofit = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit" } retrofit-kotlin-serialization = { group = "com.jakewharton.retrofit", name = "retrofit2-kotlinx-serialization-converter", version.ref = "retrofitKotlinxSerializationJson" } +retrofit-converter-gson = { group = "com.squareup.retrofit2", name = "converter-gson", version.ref = "retrofit" } +retrofit-converter-scalars = { group = "com.squareup.retrofit2", name = "converter-scalars", version.ref = "retrofit" } + work-runtime = { group = "androidx.work", name = "work-runtime-ktx", version.ref = "work-runtime" } support-compat = { group = "com.android.support", name = "support-compat", version.ref = "support-compat" } coroutines-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core", version.ref = "coroutines-core" } @@ -66,8 +71,8 @@ lottie = { group = "com.airbnb.android", name = "lottie", version.ref = "lottie- androidx-work-runtime-ktx = { group = "androidx.work", name = "work-runtime-ktx", version.ref = "workRuntimeKtx" } identity-credential = { group = "com.android.identity", name = "identity-credential", version = "20230420" } firebase-bom = { group = "com.google.firebase", name = "firebase-bom", version.ref = "firebase-bom" } -firebase-crashlytics = { group = "com.google.firebase", name = "firebase-crashlytics-ktx"} -firebase-analytics = { group = "com.google.firebase", name = "firebase-analytics-ktx"} +firebase-crashlytics = { group = "com.google.firebase", name = "firebase-crashlytics-ktx" } +firebase-analytics = { group = "com.google.firebase", name = "firebase-analytics-ktx" } firebase-auth = { group = "com.google.firebase", name = "firebase-auth-ktx" } play-service-auth = { group = "com.google.android.gms", name = "play-services-auth", version.ref = "play-service-auth" } dagger-hilt = { group = "com.google.dagger", name = "hilt-android", version.ref = "dagger-hilt" }