-
Notifications
You must be signed in to change notification settings - Fork 1
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
4주차 과제 #4
4주차 과제 #4
Changes from all commits
45f5c41
9bb8c16
9c4c8e6
6d30f25
570fc44
456cb7f
166364c
c38fd5f
6cf6f0e
df71590
1bdaa01
3f79f7a
62c4df2
49e7937
15caab8
90e1ea6
d787e18
1279cf8
d099448
834c5ab
392f586
19f90f1
0c1c9a6
05f69b1
743bb86
c37bf31
c25a680
0aff239
dc2a5e5
9550255
a583091
243cc60
f2d7cbd
ffd39f2
c6f0a26
8afb1dc
2ccae19
902a06e
d2ec5b0
29c9025
53a1f6e
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,9 +1,16 @@ | ||
import java.util.Properties | ||
|
||
plugins { | ||
alias(libs.plugins.android.application) | ||
alias(libs.plugins.kotlin.android) | ||
alias(libs.plugins.kotlin.compose) | ||
alias(libs.plugins.kotlin.kapt) | ||
alias(libs.plugins.hilt) | ||
kotlin("plugin.serialization") version "2.0.20" | ||
} | ||
|
||
val localProperties = Properties().apply { | ||
load(project.rootProject.file("local.properties").inputStream()) | ||
} | ||
|
||
android { | ||
|
@@ -18,6 +25,7 @@ android { | |
versionName = "1.0" | ||
|
||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" | ||
buildConfigField("String", "BASE_URL", "String.valueOf(\"${localProperties["base_url"]}\")") | ||
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. "String.valueOf("${localProperties["base_url"]}")" => localProperties["base_url"].toString() 로 작성하는건 어떨까요?! 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. local.properties에 키 작성할 때 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. 따옴표 붙이지 않는 이유가 있나요? 궁금해요! 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. manifestPlaceholders로 Manifest에 변수 넣을 때는 따옴표를 넣으면 안되더라구여. 그래서 저는 통일성을 위해 전부 따옴표 없이 사용합니당 |
||
} | ||
|
||
buildTypes { | ||
|
@@ -36,6 +44,9 @@ android { | |
kotlinOptions { | ||
jvmTarget = "11" | ||
} | ||
buildFeatures { | ||
buildConfig = true | ||
} | ||
} | ||
|
||
kapt { | ||
|
@@ -66,4 +77,14 @@ dependencies { | |
|
||
implementation(libs.hilt) | ||
kapt(libs.hilt.compiler) | ||
|
||
implementation(platform(libs.okhttp.bom)) | ||
implementation(libs.okhttp) | ||
implementation(libs.okhttp.logging.interceptor) | ||
implementation(libs.retrofit) | ||
implementation(libs.retrofit.kotlin.serialization.converter) | ||
implementation(libs.kotlinx.serialization.json) | ||
|
||
implementation(libs.androidx.security.crypto.ktx) | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
package org.sopt.and.adapter | ||
|
||
import kotlinx.serialization.KSerializer | ||
import kotlinx.serialization.json.Json | ||
import kotlinx.serialization.json.jsonObject | ||
import kotlinx.serialization.serializer | ||
import okhttp3.ResponseBody | ||
import retrofit2.Converter | ||
import retrofit2.Retrofit | ||
import java.lang.reflect.Type | ||
|
||
class ResultConverter<T>( | ||
private val serializer: KSerializer<T>, | ||
private val json: Json | ||
) : Converter<ResponseBody, T> { | ||
override fun convert(responseBody: ResponseBody): T? { | ||
return responseBody.use { | ||
val jsonObject = json.parseToJsonElement(responseBody.string()).jsonObject | ||
val result = jsonObject["result"] ?: return null | ||
json.decodeFromJsonElement(serializer, result) | ||
} | ||
} | ||
} | ||
|
||
class ResultConverterFactory : Converter.Factory() { | ||
|
||
override fun responseBodyConverter( | ||
type: Type, | ||
annotations: Array<out Annotation>, | ||
retrofit: Retrofit | ||
): Converter<ResponseBody, *> { | ||
val json = Json { ignoreUnknownKeys = true } | ||
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. coerceInputValues, encodeDefaults, isLenient 등등 option 값들이 정말 많습니다! 해당 옵션들이 정말 필요한 경우가 많기 때문에 한 번 알아보시면 좋을 것 같아요! 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. 직렬화가 공부할 게 참 많은 것 같습니다..! |
||
val resultSerializer = json.serializersModule.serializer(type) | ||
return ResultConverter(resultSerializer, json) | ||
} | ||
} |
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. 저는 모듈 구현을 거의 안해봐서 처음 보는 코드들이 많은데 많이 배워갑니다..! 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. 저도 사실 과제를 통해서 처음해보는 것들이 대부분입니다...! 새로운 코드에 너무 부담 안느끼고 막 다 해보는 게 좋은 것 같아요 !! |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
package org.sopt.and.di | ||
|
||
import com.sopt.data.api.remote.UserApi | ||
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 ApiModule { | ||
|
||
@Singleton | ||
@Provides | ||
fun provideUserApi(retrofit: Retrofit): UserApi { | ||
return retrofit.create(UserApi::class.java) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package org.sopt.and.di | ||
|
||
import com.sopt.data.IODispatcher | ||
import dagger.Module | ||
import dagger.Provides | ||
import dagger.hilt.InstallIn | ||
import dagger.hilt.components.SingletonComponent | ||
import kotlinx.coroutines.Dispatchers | ||
import javax.inject.Singleton | ||
|
||
@Module | ||
@InstallIn(SingletonComponent::class) | ||
object CoroutineModule { | ||
|
||
@IODispatcher | ||
@Singleton | ||
@Provides | ||
fun provideIODispatcher() = Dispatchers.IO | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,6 +2,9 @@ package org.sopt.and.di | |
|
||
import android.content.Context | ||
import android.content.SharedPreferences | ||
import androidx.security.crypto.EncryptedSharedPreferences | ||
import androidx.security.crypto.MasterKey | ||
import com.sopt.data.TokenEncryptedSharedPref | ||
import com.sopt.data.UserSharedPref | ||
import dagger.Module | ||
import dagger.Provides | ||
|
@@ -17,9 +20,29 @@ object LocalApiModule { | |
@UserSharedPref | ||
@Provides | ||
@Singleton | ||
fun provideUserLocalDataSource( | ||
fun provideUserSharedPreferences( | ||
@ApplicationContext context: Context | ||
): SharedPreferences { | ||
return context.getSharedPreferences("user.pref", Context.MODE_PRIVATE) | ||
} | ||
|
||
@TokenEncryptedSharedPref | ||
@Provides | ||
@Singleton | ||
fun provideTokenEncryptedSharedPreferences( | ||
@ApplicationContext context: Context | ||
): SharedPreferences { | ||
val masterKeyAlias = MasterKey | ||
.Builder(context, MasterKey.DEFAULT_MASTER_KEY_ALIAS) | ||
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM) | ||
.build() | ||
|
||
return EncryptedSharedPreferences.create( | ||
context, | ||
"token.pref.enc", | ||
masterKeyAlias, | ||
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, | ||
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM | ||
) | ||
} | ||
Comment on lines
+23
to
+47
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. 오 좋네요. 암호화된 SharedPreferences 저도 다음에 한번 써봐야겠군요.. |
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
package org.sopt.and.di | ||
|
||
import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory | ||
import com.sopt.domain.exception.NetworkError | ||
import dagger.Module | ||
import dagger.Provides | ||
import dagger.hilt.InstallIn | ||
import dagger.hilt.components.SingletonComponent | ||
import kotlinx.serialization.SerialName | ||
import kotlinx.serialization.Serializable | ||
import kotlinx.serialization.json.Json | ||
import okhttp3.Interceptor | ||
import okhttp3.MediaType.Companion.toMediaType | ||
import okhttp3.OkHttpClient | ||
import okhttp3.logging.HttpLoggingInterceptor | ||
import org.sopt.and.BuildConfig | ||
import org.sopt.and.adapter.ResultConverterFactory | ||
import retrofit2.Retrofit | ||
import java.util.concurrent.TimeUnit | ||
import javax.inject.Singleton | ||
|
||
@Module | ||
@InstallIn(SingletonComponent::class) | ||
object NetworkModule { | ||
|
||
@Singleton | ||
@Provides | ||
fun provideClient( | ||
responseInterceptor: Interceptor | ||
): OkHttpClient { | ||
return OkHttpClient.Builder() | ||
.connectTimeout(10, TimeUnit.SECONDS) | ||
.readTimeout(30, TimeUnit.SECONDS) | ||
.writeTimeout(15, TimeUnit.SECONDS) | ||
Comment on lines
+32
to
+34
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. 해당 �timeout 이 각각 10, 30, 15인 이유가 있을까요?? 지금은 상관 없지만 파일 같은걸 업로드 하는 데에 writeTimeout 이 15초로 부족할 수도 있긴합니다! (물론 그 때는 업로드용 OkhttpClient를 timeOut이 널널하게 만들면 좋겠죠!) 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. 딱히 큰 의미를 둔 건 아닙니다 ㅎㅎ.. |
||
.addInterceptor(HttpLoggingInterceptor().apply { | ||
level = HttpLoggingInterceptor.Level.BODY | ||
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. level 은 항상 BODY 를 출력하는게 좋진 않을 것 같아요! 개발을 하는 동안 DEBUG 단계에서만 출력 되어도 좋을 것 같습니다! 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. BuildConfig로 구분하겠습니다 ! |
||
}).addInterceptor(responseInterceptor) | ||
.build() | ||
} | ||
|
||
@Singleton | ||
@Provides | ||
fun provideResponseInterceptor( | ||
) : Interceptor = Interceptor { chain -> | ||
val response = chain.proceed(chain.request()) | ||
|
||
if (response.isSuccessful.not()) { | ||
val errorBody = response.body?.string() | ||
val errorResponse = try { | ||
Json.decodeFromString<ErrorResponse>(errorBody ?: "") | ||
} catch (e: Exception) { | ||
null | ||
} | ||
|
||
throw NetworkError( | ||
statusCode = response.code, | ||
errorCode = errorResponse?.code?.toInt() ?: -1, | ||
message = response.message, | ||
) | ||
} | ||
return@Interceptor response | ||
} | ||
|
||
@Singleton | ||
@Provides | ||
fun provideRetrofit( | ||
client: OkHttpClient | ||
): Retrofit { | ||
val json = Json { ignoreUnknownKeys = true } | ||
return Retrofit.Builder() | ||
.baseUrl(BuildConfig.BASE_URL) | ||
.client(client) | ||
.addConverterFactory(ResultConverterFactory()) | ||
.addConverterFactory(json.asConverterFactory("application/json".toMediaType())) | ||
.build() | ||
} | ||
} | ||
Comment on lines
+64
to
+77
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. val json = Json { ignoreUnknownKeys = true } 이거 어떤이유로 사용한건지 여쭤봐도 될까요? 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. 데이터 클래스로 지정해두지 않은 필드가 API 통신을 통해 들어왔을 때, 그 필드를 무시하는 속성입니다 ! |
||
|
||
@Serializable | ||
private data class ErrorResponse( | ||
val code: String | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
package com.sopt.data.api.remote | ||
|
||
import com.sopt.data.dto.user.HobbyDto | ||
import com.sopt.data.dto.user.SignInDto | ||
import com.sopt.data.dto.user.SignUpDto | ||
import com.sopt.data.request.SignInRequest | ||
import com.sopt.data.request.SignUpRequest | ||
import com.sopt.data.request.UpdateProfileRequest | ||
import retrofit2.http.Body | ||
import retrofit2.http.GET | ||
import retrofit2.http.Header | ||
import retrofit2.http.POST | ||
import retrofit2.http.PUT | ||
import retrofit2.http.Path | ||
|
||
interface UserApi { | ||
|
||
@POST("user") | ||
suspend fun signUp(@Body signUpRequest: SignUpRequest): SignUpDto | ||
|
||
@POST("login") | ||
suspend fun signIn(@Body signInRequest: SignInRequest): SignInDto | ||
|
||
@GET("user/my-hobby") | ||
suspend fun fetchMyHobby(@Header("token") token: String): HobbyDto | ||
|
||
@GET("user/{no}/hobby") | ||
suspend fun fetchUserHobby(@Header("token") token: String, @Path("no") no: Int): HobbyDto | ||
|
||
@PUT("user") | ||
suspend fun updateProfile( | ||
@Header("token") token: String, | ||
@Body updateProfileRequest: UpdateProfileRequest | ||
) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
package com.sopt.data.datasource.local | ||
|
||
import android.content.SharedPreferences | ||
import com.sopt.data.TokenEncryptedSharedPref | ||
import javax.inject.Inject | ||
|
||
class TokenLocalDataSource @Inject constructor( | ||
@TokenEncryptedSharedPref private val tokenEncryptedSharedPref: SharedPreferences | ||
) { | ||
|
||
fun saveToken(token: String) { | ||
tokenEncryptedSharedPref.edit() | ||
.putString(TOKEN, token) | ||
.apply() | ||
} | ||
|
||
fun getToken(): String { | ||
return tokenEncryptedSharedPref.getString(TOKEN, "").orEmpty() | ||
} | ||
|
||
companion object { | ||
private const val TOKEN = "token" | ||
} | ||
} | ||
Comment on lines
+7
to
+24
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. 최고다 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. 최고십니다. 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. 최고짱짱맨이십니다 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. 네? |
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.
이렇게 추가할 수도 있군요!
위 코드 처럼 alias(libs.plugins.kotlin.serialization) 이런식으로 추가해주는게 어떨까요..?!
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.
수정했습니다 !