header-img
Info :

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
๋”๋ณด๊ธฐ
CS/Android, Kotlin