-
Notifications
You must be signed in to change notification settings - Fork 0
Feat/#13 retrofit base api #19
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
Changes from all commits
9b3e759
78f229d
04c82e2
046affd
2823513
c69b99b
3da61e0
b7cace9
8c88716
abc95d2
964592e
b2d500b
39473ba
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,5 @@ | ||
| import com.android.build.gradle.internal.cxx.configure.gradleLocalProperties | ||
|
|
||
| plugins { | ||
| alias(libs.plugins.android.application) | ||
| alias(libs.plugins.kotlin.android) | ||
|
|
@@ -28,6 +30,12 @@ android { | |
| getDefaultProguardFile("proguard-android-optimize.txt"), | ||
| "proguard-rules.pro", | ||
| ) | ||
| buildConfigField("String", "NEAR_URL", getProperty("NEAR_PROD_URL")) | ||
| buildConfigField("String", "TEMP_TOKEN", getProperty("TEMP_TOKEN")) // TODO 추후 삭제 필요 | ||
| } | ||
| debug { | ||
| buildConfigField("String", "NEAR_URL", getProperty("NEAR_DEV_URL")) | ||
| buildConfigField("String", "TEMP_TOKEN", getProperty("TEMP_TOKEN")) // TODO 추후 삭제 필요 | ||
| } | ||
| } | ||
| compileOptions { | ||
|
|
@@ -38,6 +46,7 @@ android { | |
| jvmTarget = "11" | ||
| } | ||
| buildFeatures { | ||
| buildConfig = true | ||
| compose = true | ||
| } | ||
| } | ||
|
|
@@ -63,9 +72,10 @@ dependencies { | |
| implementation(libs.hilt.android) | ||
| kapt(libs.hilt.android.compiler) | ||
| implementation(libs.hilt.navigation.compose) | ||
| // Retrofit | ||
| // Retrofit & OkHttp | ||
| implementation(libs.retrofit) | ||
| implementation(libs.retrofit.converter.gson) | ||
| implementation(libs.retrofit.kotlin.serialization.converter) | ||
| implementation(libs.logging.interceptor) | ||
| // Glide | ||
| implementation(libs.glide) | ||
| kapt(libs.glide.compiler) | ||
|
|
@@ -79,3 +89,5 @@ dependencies { | |
| // Serialization | ||
| implementation(libs.kotlin.serialization.json) | ||
| } | ||
|
|
||
| fun getProperty(propertyKey: String): String = gradleLocalProperties(rootDir, providers).getProperty(propertyKey) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
fun getProperty(propertyKey: String): String {
val properties = java.util.Properties()
val localPropertiesFile = rootProject.file("local.properties")
if (localPropertiesFile.exists()) {
localPropertiesFile.inputStream().use { input ->
properties.load(input)
}
}
return properties.getProperty(propertyKey) ?: error("'$propertyKey' not found in local.properties")
} |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| package com.alarmy.near.data.mapper | ||
|
|
||
| import com.alarmy.near.model.Friend | ||
| import com.alarmy.near.network.response.FriendEntity | ||
|
|
||
| fun FriendEntity.toModel(): Friend = | ||
| Friend( | ||
| friendId = friendId, | ||
| position = position, | ||
| source = source, | ||
| name = name, | ||
| imageUrl = imageUrl, | ||
| fileName = fileName, | ||
| checkRate = checkRate, | ||
| lastContactAt = lastContactAt, | ||
| ) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| package com.alarmy.near.data.repository | ||
|
|
||
| import com.alarmy.near.data.mapper.toModel | ||
| import com.alarmy.near.model.Friend | ||
| import com.alarmy.near.network.service.FriendService | ||
| import kotlinx.coroutines.flow.Flow | ||
| import kotlinx.coroutines.flow.flow | ||
| import javax.inject.Inject | ||
|
|
||
| class DefaultFriendRepository | ||
| @Inject | ||
| constructor( | ||
| private val friendService: FriendService, | ||
| ) : FriendRepository { | ||
| override fun fetchFriends(): Flow<List<Friend>> = | ||
| flow { | ||
| emit( | ||
| friendService.fetchFriends().map { | ||
| it.toModel() | ||
| }, | ||
| ) | ||
| } | ||
|
Comment on lines
+15
to
+22
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 현재 override fun fetchFriends(): Flow<List<Friend>> =
flow {
try {
emit(
friendService.fetchFriends().map {
it.toModel()
},
)
} catch (e: Exception) {
// TODO: 도메인에 맞는 에러로 변환하여 처리하거나 로깅 필요
throw e
}
} |
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| package com.alarmy.near.data.repository | ||
|
|
||
| import com.alarmy.near.model.Friend | ||
| import kotlinx.coroutines.flow.Flow | ||
|
|
||
| interface FriendRepository { | ||
| fun fetchFriends(): Flow<List<Friend>> | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| package com.alarmy.near.model | ||
|
|
||
| data class Friend( | ||
| val friendId: String, | ||
| val position: Int, | ||
| val source: String, | ||
| val name: String, | ||
| val imageUrl: String? = null, | ||
| val fileName: String? = null, | ||
| val checkRate: Int, | ||
| val lastContactAt: String? = null, | ||
| ) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| package com.alarmy.near.network.auth | ||
|
|
||
| import com.alarmy.near.BuildConfig | ||
| import okhttp3.Interceptor | ||
| import okhttp3.Response | ||
| import javax.inject.Inject | ||
|
|
||
| class TokenInterceptor | ||
| @Inject | ||
| constructor() : Interceptor { | ||
| override fun intercept(chain: Interceptor.Chain): Response { | ||
| val request = chain.request() | ||
| return chain.proceed( | ||
| request = | ||
| request | ||
| .newBuilder() | ||
| .addHeader("Authorization", "Bearer ${BuildConfig.TEMP_TOKEN}") | ||
| .build(), | ||
| ) | ||
| } | ||
| } |
This file was deleted.
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,62 @@ | ||||||||||||||||||||||||||||||||||
| package com.alarmy.near.network.di | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| import com.alarmy.near.BuildConfig | ||||||||||||||||||||||||||||||||||
| import com.alarmy.near.network.auth.TokenInterceptor | ||||||||||||||||||||||||||||||||||
| import dagger.Module | ||||||||||||||||||||||||||||||||||
| import dagger.Provides | ||||||||||||||||||||||||||||||||||
| import dagger.hilt.InstallIn | ||||||||||||||||||||||||||||||||||
| import dagger.hilt.components.SingletonComponent | ||||||||||||||||||||||||||||||||||
| import kotlinx.serialization.json.Json | ||||||||||||||||||||||||||||||||||
| import okhttp3.MediaType.Companion.toMediaType | ||||||||||||||||||||||||||||||||||
| import okhttp3.OkHttpClient | ||||||||||||||||||||||||||||||||||
| import okhttp3.logging.HttpLoggingInterceptor | ||||||||||||||||||||||||||||||||||
| import retrofit2.Retrofit | ||||||||||||||||||||||||||||||||||
| import retrofit2.converter.kotlinx.serialization.asConverterFactory | ||||||||||||||||||||||||||||||||||
| import javax.inject.Singleton | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| @Module | ||||||||||||||||||||||||||||||||||
| @InstallIn(SingletonComponent::class) | ||||||||||||||||||||||||||||||||||
| object NetworkModule { | ||||||||||||||||||||||||||||||||||
| private val json = | ||||||||||||||||||||||||||||||||||
| Json { | ||||||||||||||||||||||||||||||||||
| encodeDefaults = true | ||||||||||||||||||||||||||||||||||
| ignoreUnknownKeys = true | ||||||||||||||||||||||||||||||||||
| prettyPrint = true | ||||||||||||||||||||||||||||||||||
| isLenient = true | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| private val jsonConverterFactory = json.asConverterFactory("application/json".toMediaType()) | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| @Provides | ||||||||||||||||||||||||||||||||||
| @Singleton | ||||||||||||||||||||||||||||||||||
| fun provideOkHttpClient( | ||||||||||||||||||||||||||||||||||
| loggingInterceptor: HttpLoggingInterceptor, | ||||||||||||||||||||||||||||||||||
| tokenInterceptor: TokenInterceptor, | ||||||||||||||||||||||||||||||||||
| ): OkHttpClient = | ||||||||||||||||||||||||||||||||||
| OkHttpClient | ||||||||||||||||||||||||||||||||||
| .Builder() | ||||||||||||||||||||||||||||||||||
| .addInterceptor(loggingInterceptor) | ||||||||||||||||||||||||||||||||||
| .addInterceptor(tokenInterceptor) | ||||||||||||||||||||||||||||||||||
| .build() | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| @Provides | ||||||||||||||||||||||||||||||||||
| @Singleton | ||||||||||||||||||||||||||||||||||
| fun provideLoggingInterceptor(): HttpLoggingInterceptor = | ||||||||||||||||||||||||||||||||||
| HttpLoggingInterceptor().let { | ||||||||||||||||||||||||||||||||||
| if (BuildConfig.DEBUG) { | ||||||||||||||||||||||||||||||||||
| it.setLevel(HttpLoggingInterceptor.Level.BODY) | ||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||
| it.setLevel(HttpLoggingInterceptor.Level.NONE) | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
Comment on lines
+44
to
+51
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 현재
Suggested change
|
||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| @Provides | ||||||||||||||||||||||||||||||||||
| @Singleton | ||||||||||||||||||||||||||||||||||
| fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit = | ||||||||||||||||||||||||||||||||||
| Retrofit | ||||||||||||||||||||||||||||||||||
| .Builder() | ||||||||||||||||||||||||||||||||||
| .baseUrl(BuildConfig.NEAR_URL) | ||||||||||||||||||||||||||||||||||
| .client(okHttpClient) | ||||||||||||||||||||||||||||||||||
| .addConverterFactory(jsonConverterFactory) | ||||||||||||||||||||||||||||||||||
| .build() | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| package com.alarmy.near.network.di | ||
|
|
||
| import com.alarmy.near.network.service.FriendService | ||
| import dagger.Module | ||
| import dagger.Provides | ||
| import dagger.hilt.InstallIn | ||
| import dagger.hilt.components.SingletonComponent | ||
| import retrofit2.Retrofit | ||
| import javax.inject.Singleton | ||
|
|
||
| @Module | ||
| @InstallIn(SingletonComponent::class) | ||
| object ServiceModule { | ||
| @Provides | ||
| @Singleton | ||
| fun provideFriendService(retrofit: Retrofit): FriendService = retrofit.create(FriendService::class.java) | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| package com.alarmy.near.network.response | ||
|
|
||
| import kotlinx.serialization.Serializable | ||
|
|
||
| @Serializable | ||
| data class FriendEntity( | ||
| val friendId: String, | ||
| val position: Int, | ||
| val source: String, | ||
| val name: String, | ||
| val imageUrl: String? = null, | ||
| val fileName: String? = null, | ||
| val checkRate: Int, | ||
| val lastContactAt: String? = null, | ||
| ) |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,9 @@ | ||||||||||||||||||||||||
| package com.alarmy.near.network.service | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| import com.alarmy.near.network.response.FriendEntity | ||||||||||||||||||||||||
| import retrofit2.http.GET | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| interface FriendService { | ||||||||||||||||||||||||
| @GET("/friend/list") | ||||||||||||||||||||||||
| suspend fun fetchFriends(): List<FriendEntity> | ||||||||||||||||||||||||
|
Comment on lines
+6
to
+8
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 엔드포인트 경로
Suggested change
|
||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -2,52 +2,36 @@ package com.alarmy.near.presentation.feature.home | |||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| import androidx.lifecycle.ViewModel | ||||||||||||||||||||||||||||||||||||||||||||
| import androidx.lifecycle.viewModelScope | ||||||||||||||||||||||||||||||||||||||||||||
| import com.alarmy.near.data.repository.ExampleRepository | ||||||||||||||||||||||||||||||||||||||||||||
| import com.alarmy.near.data.repository.FriendRepository | ||||||||||||||||||||||||||||||||||||||||||||
| import com.alarmy.near.model.Friend | ||||||||||||||||||||||||||||||||||||||||||||
| import com.alarmy.near.presentation.feature.home.model.HomeUiState | ||||||||||||||||||||||||||||||||||||||||||||
| import dagger.hilt.android.lifecycle.HiltViewModel | ||||||||||||||||||||||||||||||||||||||||||||
| import kotlinx.coroutines.flow.MutableStateFlow | ||||||||||||||||||||||||||||||||||||||||||||
| import kotlinx.coroutines.flow.SharingStarted | ||||||||||||||||||||||||||||||||||||||||||||
| import kotlinx.coroutines.flow.StateFlow | ||||||||||||||||||||||||||||||||||||||||||||
| import kotlinx.coroutines.flow.asStateFlow | ||||||||||||||||||||||||||||||||||||||||||||
| import kotlinx.coroutines.flow.catch | ||||||||||||||||||||||||||||||||||||||||||||
| import kotlinx.coroutines.flow.map | ||||||||||||||||||||||||||||||||||||||||||||
| import kotlinx.coroutines.flow.stateIn | ||||||||||||||||||||||||||||||||||||||||||||
| import kotlinx.coroutines.launch | ||||||||||||||||||||||||||||||||||||||||||||
| import javax.inject.Inject | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| @HiltViewModel | ||||||||||||||||||||||||||||||||||||||||||||
| class HomeViewModel | ||||||||||||||||||||||||||||||||||||||||||||
| @Inject | ||||||||||||||||||||||||||||||||||||||||||||
| constructor( | ||||||||||||||||||||||||||||||||||||||||||||
| private val exampleRepository: ExampleRepository, | ||||||||||||||||||||||||||||||||||||||||||||
| private val friendRepository: FriendRepository, | ||||||||||||||||||||||||||||||||||||||||||||
| ) : ViewModel() { | ||||||||||||||||||||||||||||||||||||||||||||
| // Example: 여러번 초기화되는 StateFlow | ||||||||||||||||||||||||||||||||||||||||||||
| private val _uiStateFlow = MutableStateFlow(HomeUiState.Loading) | ||||||||||||||||||||||||||||||||||||||||||||
| val uiStateFlow = _uiStateFlow.asStateFlow() | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| // Example: 한 번만 초기화되는 StateFlow | ||||||||||||||||||||||||||||||||||||||||||||
| private val exampleStateFlow = | ||||||||||||||||||||||||||||||||||||||||||||
| exampleRepository | ||||||||||||||||||||||||||||||||||||||||||||
| .getData() | ||||||||||||||||||||||||||||||||||||||||||||
| .map { | ||||||||||||||||||||||||||||||||||||||||||||
| // Mapping to UIState | ||||||||||||||||||||||||||||||||||||||||||||
| }.stateIn( | ||||||||||||||||||||||||||||||||||||||||||||
| val friendsFlow: StateFlow<List<Friend>> = | ||||||||||||||||||||||||||||||||||||||||||||
| friendRepository | ||||||||||||||||||||||||||||||||||||||||||||
| .fetchFriends() | ||||||||||||||||||||||||||||||||||||||||||||
| .stateIn( | ||||||||||||||||||||||||||||||||||||||||||||
| scope = viewModelScope, | ||||||||||||||||||||||||||||||||||||||||||||
| started = SharingStarted.WhileSubscribed(5_000), | ||||||||||||||||||||||||||||||||||||||||||||
| initialValue = HomeUiState.Loading, | ||||||||||||||||||||||||||||||||||||||||||||
| initialValue = emptyList(), | ||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+26
to
33
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| fun fetchContacts() { | ||||||||||||||||||||||||||||||||||||||||||||
| viewModelScope.launch { | ||||||||||||||||||||||||||||||||||||||||||||
| exampleRepository | ||||||||||||||||||||||||||||||||||||||||||||
| .getData() | ||||||||||||||||||||||||||||||||||||||||||||
| .catch { | ||||||||||||||||||||||||||||||||||||||||||||
| // handle error | ||||||||||||||||||||||||||||||||||||||||||||
| }.collect { | ||||||||||||||||||||||||||||||||||||||||||||
| // updateUI | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| fun removeContact(id: Long) { | ||||||||||||||||||||||||||||||||||||||||||||
| // contactRepository.removeContact(id) | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BuildConfig에 토큰과 같은 민감 정보를 저장하는 것은 보안상 매우 위험합니다. APK를 디컴파일하면 이 값을 쉽게 추출할 수 있기 때문입니다. 임시 토큰이라 할지라도, 이러한 방식은 지양하는 것이 좋습니다. 향후에는 Android Keystore 시스템을 사용하거나 Secrets Gradle Plugin과 같은 라이브러리를 활용하여 보다 안전하게 민감 정보를 관리하는 것을 강력히 권장합니다.TODO주석으로 이미 인지하고 계신 점은 좋지만, 이는 높은 수준의 보안 위험이므로 프로덕션 출시 전 반드시 해결해야 합니다.