OKHttp3, Retrofit2
- square์ฌ์์ ๊ฐ๋ฐํ ์คํ์์ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ.
- http ํด๋ผ์ด์ธํธ๋ก ์ฝ๊ฒ request, response๊ฐ ๊ฐ๋ฅํ๊ฒ ํจ.
- Retrofit ํน์ง
- ์ฑ๋ฅ์ด ์ข์. (Volley, AsyncTask์ ๋นํด)
- annotation์ ์ฌ์ฉํด HTTP ์์ ์ ํ๋ ๋ฉ์๋๋ฅผ ์ ์ํ๊ธฐ ๋๋ฌธ์ ๊ฐ๋ฐ์๋ค์ด ํ์๋ฅผ ์์ฝ๊ฒ ์์๋ณผ ์ ์์. (๊ฐ๋ ์ฑ ๐)
- ์ฃผ๊ณ ๋ฐ๋ ๋ฐ์ดํฐ(JSON)๋ฅผ ์๋์ผ๋ก ํ์ฑํด์ฃผ๊ธฐ ๋๋ฌธ์ ์ ์ง๋ณด์๊ฐ ํธ๋ฆฌํจ.
- ์บ์ฑ์ด ๋ถ๊ฐ๋ฅํจ
- image loading์ด ๋ถ๊ฐ๋ฅํ์ฌ glide์ ๊ฐ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํด์ผ ํจ.
OKHttp3 - ๋ด๊ฐ ์ฌ์ฉํ ๋ถ๋ถ
๋ด๊ฐ ์ฌ์ฉํ ๋ถ๋ถ์ ํด๋ผ์ด์ธํธ์์ request๋ฅผ ๋ ๋ฆด ๋ ํค๋๋ฅผ ๋ฃ์ด์ฃผ๋๋ก ๋ง๋ค์์.
// header ์ถ๊ฐ
private val okHttpClient = OkHttpClient.Builder().addInterceptor(
HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.NONE
}
).addInterceptor {
val request = it.request()
.newBuilder()
.addHeader("accessToken", "$token")
.build()
val response = it.proceed(request)
response
}.build()
private val getRetrofit by lazy {
Retrofit.Builder()
.baseUrl(BASE_URL)
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create(gson))
.build()
}
์๋์ ๊ฐ์ด getRetrofit ๋ณ์๋ฅผ ํตํด ๊ฐ์ ธ์ค๋ ๋ชจ๋ retrofit ๊ฐ์ฒด๋ .client(okHttpClient) ์ฝ๋๋ก ํค๋๋ฅผ ์ถ๊ฐํ๊ฒ ๋จ.
Token - Basic, Bearer
Basic์ username, password๋ฅผ Base64๋ก ์ธ์ฝ๋ฉํ๋ ๋ฐฉ์. Base64๋ก ๋จ์ํ ์ธ์ฝ๋ฉํ๋ ๊ฒ์ด๋ฏ๋ก ์ฝ๊ฒ ์๋ณธ์ผ๋ก ์ฐพ๋ ๋ณตํธํ๊ฐ ๊ฐ๋ฅํจ. Bearer์ OAuth ๋ฐฉ์์ ์ธ์ฆ์ ์ฌ์ฉํจ. ํ ํฐ์ ID, PW๋ฅผ ์ฌ์ฉํ์ง ์๊ณ Stateless, ๋ณด์์ฑ์ด ์ฅ์ ์.
Retrofit2 - ๋ด๊ฐ ์ฌ์ฉํ ๋ถ๋ถ
private val getRetrofit by lazy {
Retrofit.Builder()
.baseUrl(BASE_URL)
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create(gson))
.build()
}
val getSignUpRetrofitService: SignUpInterface by lazy {
getRetrofit.create(SignUpInterface::class.java)
}
interface SignUpInterface {
// ์ ์ ๋๋ค์ ์ค๋ณต ์กฐํ
@GET("/user/username/check")
suspend fun checkUserNameDuplicated(@Query("username") userName: String): Response<Void>
// ์ ์ ํ์๊ฐ์
@POST("/user/sign-up")
suspend fun makeAccount(@Body data: SignUpAccountData): Response<Void>
// ์ ์ ๊ด์ฌ์ฌ ์์
@PATCH("/user/interest")
suspend fun setInterest(@Body data: UserInterestData): Response<Void>
// ์ ์ ์ ๋ณด ํ์ธ
@PATCH("/user/info")
suspend fun setAdditionalInfo(@Body data: UserAdditionalInfo): Response<Void>
}
class SignUpRepository {
// ์ฑ๊ธํค ๊ตฌํ
companion object {
private var instance: SignUpRepository? = null
fun getInstance(): SignUpRepository? {
if(instance == null) instance = SignUpRepository()
return instance
}
}
suspend fun checkUserNameDuplicated(userName: String): Boolean {
val response = ConnectionObject
.getSignUpRetrofitService.checkUserNameDuplicated(userName)
return if (response.isSuccessful) {
true
}
else {
false
}
}
suspend fun makeAccount(signUpData: SignUpAccountData): String? {
val response = ConnectionObject
.getSignUpRetrofitService.makeAccount(signUpData)
return if (response.isSuccessful) {
response.headers().get("accessToken")
} else {
Log.d("make account error", response.errorBody().toString())
null
}
}
suspend fun setInterest(userInterestData: UserInterestData): Boolean {
val response = ConnectionObject
.getSignUpRetrofitService.setInterest(userInterestData)
if(!response.isSuccessful) {
Log.d("set account interest", response.errorBody().toString())
}
return response.isSuccessful
}
suspend fun setAdditionalInfo(userAdditionalInfo: UserAdditionalInfo): Boolean {
val response = ConnectionObject
.getSignUpRetrofitService.setAdditionalInfo(userAdditionalInfo)
if(!response.isSuccessful) {
Log.d("set additional info", response.errorBody().toString())
}
return response.isSuccessful
}
}
// ์ค์ ์ฌ์ฉํ ๋ถ๋ถ
private fun checkUserNameDuplicated(id: String) {
viewModelScope.launch {
if (signUpRepository.checkUserNameDuplicated(id)) {
guideText.value = R.string.signup_ok
errorText.value = null
} else {
guideText.value = null
errorText.value = R.string.signup_duplicate
}
}
}
ํ๋ก์ฐ๋ฅผ ์ค๋ช ํ๋ฉด ๋ค์๊ณผ ๊ฐ์. ์ค์ ๋ก ์ฌ์ฉํ ๋ ์ ๊ทผํ๋ ๋ถ๋ถ์ ๋ทฐ๋ชจ๋ธ์์ ์ง์ ์ ์ผ๋ก ์์ฒญํ๋ ๊ฒ์ด ์๋ Repository๋ฅผ ํตํด ํ๋ก์ฐ ์์. (repository ๊ฐ์ฒด๋ ๋ทฐ์์ ๋๊น.) ๋ฆฌํฌ์ checkUserNameDuplicated ๋ฉ์๋๋ฅผ ์์ฒญํ๊ณ → ํด๋น ๋ฉ์๋์์๋ SignUpInterface๋ก ์์ฑ๋ retrofit ๊ฐ์ฒด๋ฅผ ํตํด http ํต์ ์ ์์ํ๊ฒ ๋จ. ์ด๋ ์ฌ์ฉํ ๋์์ธ ํจํด์ Repository Pattern์.
Repository Pattern
์ด ์ด๋ฏธ์ง๋ฅผ ๊ธฐ์ค์ผ๋ก ๋ดค์ ๋ ViewModel์์๋ ๋ก์ปฌ ๋ฐ์ดํฐ์ธ์ง, ๋ฆฌ๋ชจํธ ๋ฐ์ดํฐ์ธ์ง ๊ด๊ณ ์์ด Repository๋ฅผ ํตํด ์ ๊ทผํด์ ํด๋น ๋ฐ์ดํฐ๋ฅผ ์ฌ์ฉํจ. ์ฆ ViewModel์์๋ ์ง์ Data์ ์ ๊ทผํ๋ ๊ฒ์ด ์๋ Repository๋ง ์ ๊ทผํ๊ฒ ํจ. Repository์์ ๋ก์ปฌ ํน์ ๋ฆฌ๋ชจํธ ๋ฐ์ดํฐ์ ์ ๊ทผํด์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ ๋ทฐ๋ชจ๋ธ์ ์ ๋ฌํด์ค. ์ฆ Data Layer๊ฐ ์บก์ํ๊ฐ ๋จ. ์บก์ํ๋ก ์ผ์ด๋๋ ์ฅ์ ์๋ ์ฌ๋ฌ๊ฐ์ง๊ฐ ์์ง๋ง ์ฐธ๊ณ ํ๋ ๋ ํผ๋ฐ์ค ์ค์ ๊ฐ์ฅ ํฌ๊ฒ ๋๊ผ๋ ๋ถ๋ถ์ ์ผ๊ด๋ ์ธํฐํ์ด์ค๋ก ๋ฐ์ดํฐ๋ฅผ ์์ฒญํ ์ ์๋ค๋ ์ฅ์ ์. ๋จ์ ๋ํ ํ๋ก์ ํธ๋ฅผ ์งํํ๋ฉฐ ๋๊ผ๋ ๋ถ๋ถ์ธ๋ฐ ๊ด๋ฆฌ๊ฐ ํ์ํ ์ฝ๋์ ํ์ผ์ด ์ฆ๊ฐํ๋ค๋ ์ ์. ๋ ํผ๋ฐ์ค์ ์ ํ ์ฅ์ ์ผ๋ก๋ ๋จ์ ํ ์คํธ๊ฐ ๊ฐ๋ฅํด์ง๋ค๋ ์ ์ด ์์์.
RESTful API?
REST API ์ค๊ณ ๊ท์น
- URI๋ ๋ฆฌ์์ค์ ์์์ ํํํด์ผ ํจ
- ์์์ ๋ํ ํ์๋ HTTP Method๋ก ํํํด์ผ ํจ
- / ์ฌ๋์ ๊ตฌ๋ถ์๋ ๊ณ์ธต ๊ด๊ณ๋ฅผ ๋ํ๋ด๋๋ฐ ์ฌ์ฉ
- URI ๋ง์ง๋ง ๋ฌธ์๋ก / ์ฌ๋์ ๊ตฌ๋ถ์๊ฐ ๋ค์ด๊ฐ์๋ ์ ๋จ
- ํ์ดํ์ URI ๊ฐ๋ ์ฑ์ ๋์ด๋๋ฐ ์ฌ์ฉ
- _ ๋ฐ์ค์ URI์ ์ฌ์ฉํ์ง ์์
- URI ๊ฒฝ๋ก์๋ ์๋ฌธ์๊ฐ ์ ํฉํจ
HTTP๋ฅผ ์ ํ์ฉํ๊ธฐ ์ํ ์์น์ URL๋ก ์์์ ํํํ๋ ๋ฐ์ ์ง์คํ๊ณ ์์์ ์ํ์ ๋ํ ์ ์๋ HTTP Method๋ก ํ๋ ๊ฒ์ด RESTํ API๋ฅผ ์ค๊ณํ๋ ๊ท์น. ์ฆ, RESTfulํ๊ฒ ์ค๊ณํ๋ค๋ ๊ฒ์ ์๋ฏธ๋ URI๋ก ์์์ ํํํ๊ณ ์์์ ๋ํ ํ์๋ HTTP Method(GET, POST, PUT, DELETE)๋ก ํํ
not RESTful API? → CRUD๋ฅผ ๋ชจ๋ POST๋ก ์ฒ๋ฆฌํ๋ API๊ฐ ๊ทธ ์์๊ฐ ๋ ์ ์์.
Reference
'CS > Android, Kotlin' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[Android] Hilt (0) | 2023.05.09 |
---|---|
[Android] Base Activity, Fragment (0) | 2023.04.16 |
[Android] Custom View (0) | 2023.04.15 |
[Android] Data Binding (0) | 2023.04.15 |
[Android] MVVM (0) | 2023.04.13 |