[Android/kotlin]Retrofit2

최지원·2024년 1월 31일
0

[Android/Kotlin]

목록 보기
6/9
post-thumbnail

Retrofit이 뭘까?

Retrofit은 안드로이드 및 Java용 HTTP클라이언트 라이브러리입니다. 주로 RESTful API를 통신하기 위한 간편하고 강력한 도구로 사용됩니다.

Retrofit2 API 정의 및 특징

Retrofit2 API는 주로 인터페이스를 정의하는 것으로 시작합니다. 이 인터페이스는 서버와의 통신을 위한 HTTP요청 메소드와 해당 요청에 필요한 매개변수를 정의합니다.

📌 HTTP 요청 메소드

  1. GET 요청
  • HTTP GET 요청은 서버로부터 정보를 요청할 때 사용됩니다.
  • 보통 쿼리 매개변수를 사용하여 데이터를 요청하며, URL에 매개변수가 추가됩니다.
  • 예 : @GET("/users")는 "/users" 엔드포인트로 GET 요청을 보냅니다.
  1. POST 요청
  • HTTP POST 요청은 서버로 데이터를 제출할 때 사용됩니다.
  • 주로 HTML 폼을 통해 데이터를 서버로 전송하거나, JSON 또는 XML 데이터를 서버로 전송할 때 사용됩니다.
  • POST 요청은 @POST 어노테이션을 사용하여 정의됩니다.
  1. PUT 요청
  • HTTP PUT 요청은 지정된 리소스의 상태를 업데이트합니다.
  • 일반적으로 리소스를 생성하거나 업데이트하기 위해 사용됩니다.
  • PUT 요청은 @PUT 어노테이션을 사용하여 정의됩니다.
  1. DELETE 요청
  • HTTP DELETE 요청은 서버에서 리소스를 제거할 때 사용됩니다.
  • DELETE 요청은 @DELETE 어노테이션을 사용하여 정의됩니다.

📌 URL 다루기

  1. @Url 어노테이션
  • @Url 어노테이션을 사용하여 동적인 URL을 지정할 수 있습니다. 즉, 매번 요청할 때마다 동적으로 URL을 설정할 수 있습니다.
  • 예 : @GET 요청에 @Url 어노테이션을 사용하여 URL을 동적으로 지정할 수 있습니다.
  1. @Path 어노테이션
  • @Path 어노테이션을 사용하여 URL 경로에 변수를 삽입할 수 있습니다. 동적으로 변하는 URL 경로를 처리할 때 유용합니다.
  1. @Query 어노테이션
  • @Query 어노테이션을 사용하여 쿼리 매개변수를 추가할 수 있습니다.
  • 쿼리 매개변수는 URL에 데이터를 전달할 때 사용됩니다.
  • 예 : @Query("page") int page는 ?page=1과 같은 형태로 URL에 쿼리를 추가합니다.
  1. @QueryMap 어노테이션
  • 여러 쿼리 매개변수를 Map으로 전달할 때 사용됩니다.
  • Map의 키-값 쌍이 쿼리 매개변수로 전환됩니다.
  1. @QueryName 어노테이션
  • @QueryName 어노테이션을 사용하여 쿼리 매개변수의 이름을 지정할 수 있습니다.
  • 이 방식을 사용하면 쿼리 매개변수의 이름을 동적으로 설정할 수 있습니다.

예시

interface ApiService {

    @GET("posts")
    suspend fun getPosts(): Response<List<Post>>

    @GET("posts/{id}")
    suspend fun getPostById(@Path("id") postId: Int): Response<Post>

    @POST("posts")
    suspend fun createPost(@Body post: Post): Response<Post>

    @PUT("posts/{id}")
    suspend fun updatePost(@Path("id") postId: Int, @Body post: Post): Response<Post>

    @DELETE("posts/{id}")
    suspend fun deletePost(@Path("id") postId: Int): Response<Void>

    @GET("search")
    suspend fun searchPosts(
        @Query("q") query: String,
        @Query("page") page: Int,
        @Query("limit") limit: Int
    ): Response<List<Post>>
}

📌 @Body 어노테이션

  • @Body 어노테이션은 HTTP 요청의 본문을 정의합니다.
  • 주로 POST 및 PUT 요청에서 사용되며, 요청의 본문으로 전송될 객체를 지정합니다.
  • 예 : @Body user: User는 User 객체를 요청의 본문으로 전송합니다.

📌 @Header 어노테이션

  • @Header 어노테이션은 HTTP 요청의 헤더를 정의합니다.
  • 요청에 특정 헤더를 추가하여 서버에 추가 정보를 전달할 수 있습니다.
  • 예 : @Header("Authorization") token: String은 Authorization 헤더에 토큰 값을 포함시킵니다.

✨ 여기서 헤더란? HTTP 요청이나 응답 메시지에 추가적인 정보를 포함하는데 사용되는 부분입니다. HTTP헤더는 클라이언트와 서버 간의 통신을 원활하게 하고, 요청이나 응답에 대한 다양한 메타데이터를 제공합니다.

📌 Converter

  • Retrofit은 다양한 데이터 형식을 처리할 수 있도록 컨버터를 제공합니다.
  • Gson, Moshi, Jackson 등의 라이브러리를 통해 JSON 데이터를 자바 객체로 변환하거나 그 반대로 처리할 수 있습니다.
  • 그 중, Gson 컨버터는 JSON 데이터를 자바 객체로 변환하고, 자바 객체를 JSON으로 직렬화하여 요청 본문에 포함할 수 있습니다.

📌 비동기적 실행

  • Retrofit은 기본적으로 HTTP 요청을 비동기적으로 처리합니다. 즉, 요청을 보낼 때 앱의 주 스레드를 차단하지 않고 백그라운드에서 요청을 처리합니다.
  • 요청의 응답은 콜백 형식으로 받으며, 이를 통해 비동기적으로 응답을 처리할 수 있습니다.

Retrofit2 사용예시

ProvideAPI.kt

fun provideMapApiService(retrofit: Retrofit): MapApiService{
    return retrofit.create(MapApiService::class.java)
}

fun provideFoodApiService(retrofit: Retrofit): FoodApiService{
    return retrofit.create(FoodApiService::class.java)
}

// provideMapApiService, provideFoodApiService는 각각 매개변수로 Retrofit 객체를 받고, 이를 사용하여 각각 MapApiService, FoodApiService 인터페이스를 생성합니다.

fun provideMapRetrofit(
    okHttpClient: OkHttpClient,
    gsonConverterFactory: GsonConverterFactory
): Retrofit{
    return Retrofit.Builder()
        .baseUrl(Url.TMAP_URL)
        .addConverterFactory(gsonConverterFactory)
        .client(okHttpClient)
        .build()
}

fun provideFoodRetrofit(
    okHttpClient: OkHttpClient,
    gsonConverterFactory: GsonConverterFactory
): Retrofit{
    return Retrofit.Builder()
        .baseUrl(Url.FOOD_URL)
        .addConverterFactory(gsonConverterFactory)
        .client(okHttpClient)
        .build()
}

// provideMapRetrofit, provideFoodRetrofit는 OkHttpClient 및 GsonConverterFactory를 사용하여 지도용 Retrofit 인스턴스를 설정하는 함수입니다.
// GsonConverterFactory를 추가하여 JSON 데이터를 변환합니다.

fun provideGsonConvertFactory(): GsonConverterFactory{
    return GsonConverterFactory.create()
}

// GsonConverterFactory를 생성하여 Gson을 사용하여 JSON을 직렬화하고 역직렬화하는 데 사용됩니다.

fun buildOkHttpClient(): OkHttpClient{
    val interceptor = HttpLoggingInterceptor()
    if (BuildConfig.DEBUG){
        interceptor.level = HttpLoggingInterceptor.Level.BODY
    }else{
        interceptor.level = HttpLoggingInterceptor.Level.NONE
    }
    return OkHttpClient.Builder()
        .connectTimeout(5, TimeUnit.SECONDS)
        .addInterceptor(interceptor)
        .build()
}

// OkHttpClient를 설정하는 함수입니다. HttpLoggingInterceptor를 사용하여 네트워크 요청 및 응답을 로깅합니다.

MapApiService.kt

interface MapApiService {

    @GET(Url.GET_TMAP_POIS_AROUND)
    suspend fun getSearchLocationAround(
        @Header("appKey") appKey: String = Key.TMAP_API,
        @Query("version") version: Int = 1,
        @Query("categories") categories: String? = null,
        @Query("callback") callback: String? = null,
        @Query("count") count: Int = 20,
        @Query("searchKeyword") keyword: String? = null,
        @Query("areaLLCode") areaLLCode: String? = null,
        @Query("areaLMCode") areaLMCode: String? = null,
        @Query("resCoordType") resCoordType: String? = null,
        @Query("searchType") searchType: String? = null,
        @Query("multiPoint") multiPoint: String? = null,
        @Query("searchtypCd") searchtypCd: String? = null,
        @Query("radius") radius: String? = null,
        @Query("reqCoordType") reqCoordType: String? = null,
        @Query("centerLon") centerLon: String? = null,
        @Query("centerLat") centerLat: String? = null
    ): Response<SearchResponse>

    @GET(Url.GET_TMAP_REVERSE_GEO_CODE)
    suspend fun getReverseGeoCode(
        @Header("appKey") appKey: String = Key.TMAP_API,
        @Query("version") version: Int = 1,
        @Query("callback") callback: String? = null,
        @Query("lat") lat: Double,
        @Query("lon") lon: Double,
        @Query("coordType") coordType: String? = null,
        @Query("addressType") addressType: String? = null
    ): Response<AddressInfoResponse>
}

DefaultMapRepository.kt

class DefaultMapRepository(
    private val mapApiService: MapApiService,
    private val ioDispatcher: CoroutineDispatcher
) : MapRepository {
    override suspend fun getReverseGeoInformation(locationLatLngEntity: LocationLatLngEntity): AddressInfo? = withContext(ioDispatcher){
        // 지리적 좌표에 해당하는 주소 정보를 가져오는 메서드
        val response = mapApiService.getReverseGeoCode( // mapApiService를 사용하여 지리적 좌표를 기반으로 역지오코딩 요청
            lat = locationLatLngEntity.latitude,
            lon = locationLatLngEntity.longitude
        )
        if (response.isSuccessful){ // 요청이 성공적이면 응답 바디에서 AddressInfo 객체를 추출한다.
            response.body()?.addressInfo
        }else{ // 실패한 경우 null
            null
        }
    }
}

AddressInfo.kt

data class AddressInfo(
    // 역지오코딩 결과를 나타내는 데이터 클래스
    // 각 필드는 주소와 관련된 다양한 정보를 포함하고 있으며, Gson라이브러리의 @SerializedName 어노테이션을 사용하여 JSON키와 매핑된다.
    // @Expose 어노테이션은 해당 필드가 직렬화 및 역직렬화에 포함되어야 함을 나타낸다.

    // fullAddress, addressType과 같은 부분은 모두 주소의 다양한 요소를 나타내는 옵셔널(String?)필드이다.
    @SerializedName("fullAddress")
    @Expose
    val fullAddress: String?,
    @SerializedName("addressType")
    @Expose
    val addressType: String?,
    @SerializedName("city_do")
    @Expose
    val cityDo: String?,
    @SerializedName("gu_gun")
    @Expose
    val guGun: String?,
    @SerializedName("eup_myun")
    @Expose
    val eupMyun: String?,
    @SerializedName("adminDong")
    @Expose
    val adminDong: String?,
    @SerializedName("adminDongCode")
    @Expose
    val adminDongCode: String?,
    @SerializedName("legalDong")
    @Expose
    val legalDong: String?,
    @SerializedName("legalDongCode")
    @Expose
    val legalDongCode: String?,
    @SerializedName("ri")
    @Expose
    val ri: String?,
    @SerializedName("bunji")
    @Expose
    val bunji: String?,
    @SerializedName("roadName")
    @Expose
    val roadName: String?,
    @SerializedName("buildingIndex")
    @Expose
    val buildingIndex: String?,
    @SerializedName("buildingName")
    @Expose
    val buildingName: String?,
    @SerializedName("mappingDistance")
    @Expose
    val mappingDistance: String?,
    @SerializedName("roadCode")
    @Expose
    val roadCode: String?
){
    fun toSearchInfoEntity(locationLatLngEntity: LocationLatLngEntity) = MapSearchInfoEntity( // AddressInfo 객체를 MapSearchInfoEntity 객체로 변환하는 함수.
        fullAddress = fullAddress ?: "주소 정보 없음", // 주소 정보
        name = buildingName ?: "빌딩 정보 없음", // 건물 이름 정보
        locationLatLng = locationLatLngEntity, // 위치 데이터
    )
}

MapRepository.kt

interface MapRepository {
    // 인터페이스는 특정 기능을 구현해야 하는 메서드의 시그니처를 정의한다.

    suspend fun getReverseGeoInformation(locationLatLngEntity: LocationLatLngEntity): AddressInfo?
    // LocationLatLngEntity의 매개변수를 받아, 이를 기반으로 역지로코딩 수행.
    // 수행하여 얻은 정보를 AddressInfo?타입으로 반환.

}

HomeViewModel.kt

val addressInfo =  mapRepository.getReverseGeoInformation(currentLocation) // mapRepository를 사용하여 현재 위치에 대한 역지오코딩 수행

다음과 같이 Retrofit2으로 TMAP API와 손쉽게 통신할 수 있습니다.
TMAP API로 부터 위치 데이터를 가져와서 최종적으로 HomeViewModel에 전달합니다.
이제 가져온 데이터를 자유롭게 가공하여 사용하면 됩니다.

저는 공부용 배달앱 클론 코딩에 사용해 보았습니다.. 많은 곳에 쓰일것 같으니 더욱 공부를 해놔야겠군요

profile
안드로이드, 플러터 주니어입니다

0개의 댓글