Open API로부터 GET해오기 - (1) 서버 통신 기초

주효은·2024년 6월 29일
1

서버로부터 통신을 받아오려면 어떤게 필요할까?

서버통신이 너무 어려워서 제가 보려고 쓰는 글입니다…

1) 서버 주소를 안드로이드 스튜디오에 입력하기

2) DTO

3) Interface

4) Retrofit Builder

1번 과정부터 생각을 해보자

https://reqres.in/ 이 주소를 이용할 것이다.

DTO

우리가 앞서 연습하고자 한 부분의 데이터 구조를 확인해보자.

{
    "page": 2,
    "per_page": 6,
    "total": 12,
    "total_pages": 2,
    "data": [
        {
            "id": 7,
            "email": "michael.lawson@reqres.in",
            "first_name": "Michael",
            "last_name": "Lawson",
            "avatar": "https://reqres.in/img/faces/7-image.jpg"
        },
        {
            "id": 8,
            "email": "lindsay.ferguson@reqres.in",
            "first_name": "Lindsay",
            "last_name": "Ferguson",
            "avatar": "https://reqres.in/img/faces/8-image.jpg"
        },
        {
            "id": 9,
            "email": "tobias.funke@reqres.in",
            "first_name": "Tobias",
            "last_name": "Funke",
            "avatar": "https://reqres.in/img/faces/9-image.jpg"
        },
        {
            "id": 10,
            "email": "byron.fields@reqres.in",
            "first_name": "Byron",
            "last_name": "Fields",
            "avatar": "https://reqres.in/img/faces/10-image.jpg"
        },
        {
            "id": 11,
            "email": "george.edwards@reqres.in",
            "first_name": "George",
            "last_name": "Edwards",
            "avatar": "https://reqres.in/img/faces/11-image.jpg"
        },
        {
            "id": 12,
            "email": "rachel.howell@reqres.in",
            "first_name": "Rachel",
            "last_name": "Howell",
            "avatar": "https://reqres.in/img/faces/12-image.jpg"
        }
    ],
    "support": {
        "url": "https://reqres.in/#support-heading",
        "text": "To keep ReqRes free, contributions towards server costs are appreciated!"
    }

크게 핵심으로 나눠보면, page, per_page, total, total_pages,data,support이고, 잘 보면 data는 안에 리스트 형식의 내용을, support는 url과 text를 지닌 형태이다.

@GET 이니까 데이터를 받아올테고 따라서 데이터 클래스 이름도 ResponseFriendListDto 로 작성했다.

@Serializable
data class ResponseFriendListDto(
    @SerialName("page")
    val page: Int,
    @SerialName("per_page")
    val per_page: Int,
    @SerialName("total")
    val total: Int,
    @SerialName("total_pages")
    val total_pages: Int,
    @SerialName("data")
    val data: List<FriendListData>,
    @SerialName("support")
    val support: Support,
) {
    @Serializable
    data class FriendListData(
        @SerialName("id")
        val id: Int,
        @SerialName("email")
        val email: String,
        @SerialName("first_name")
        val first_name: String,
        @SerialName("last_name")
        val last_name: String,
        @SerialName("avatar")
        val avatar: String,
    )
    @Serializable
    data class Support(
        @SerialName("url")
        val url: String,
        @SerialName("text")
        val text: String,
    )
    }

잠깐 설명을 덧붙여 보자면, 아래에 FriendListData를 따로 만든 뒤에 해당 클래스를 List 형식으로 바꿔줬고 (val data: List), Support도 따로 만들어서 전체를 넣어준 형태로 만들어줬다.

Q. Serializable은 뭐지? 꼭 필요한 어노테이션인가요?

A. Kotlin의 kotlinx.serialization 라이브러리를 사용하여 객체를 JSON으로 직렬화 또는 역직렬화를 시킬 수 있게 해줍니다. 다시 말해 서버에서 받은 JSON 데이터를 Kotlin 객체로 변환하거나, 객체를 JSON으로 변환해서 서버에 전송할 수 있다는 뜻입니다. (JSON은 서버와 클라이언트간의 소통을 할 수 있게 해주는 만국공통어 같은 느낌!! 위애서 보여준 응답 구조가 JSON으로 쓰인 것 입니다.)


그럼 이번엔 @POST을 연습해봅시다!

{
		"authenticationId" : "lyny123",
		"password" : "password34!!", 
		"nickname" : "닉네임적어주세요",
		"phone" : "010-0000-0000"
}

회원가입시에 이런 구조를 서버에 보내고 싶다면?

@Serializable
data class RequestSignUpDto(
    @SerialName("authenticationId")
    val authenticationId: String,
    @SerialName("password")
    val password: String,
    @SerialName("nickname")
    val nickname: String,
    @SerialName("phone")
    val phone: String,
)

이렇게 작성할 수 있겠네요


Interface

interface FriendListService {
    @GET("api/users")
    suspend fun getFriendList(
        @Query("page") page: Int,
    ): ResponseFriendListDto
}

위의 그림을 통해 interface는 이렇게 쓸 수 있겠죠?

@Query는 나중에 다른 게시물로 다루도록 하겠습니다.

해당 인터페이스는 Retrofit을 통해 네트워크 요청을 정의하고, DTO는 그 요청의 응답을 처리하는 데이터 모델 클래스입니다.

Retrofit Instance

Retrofit Builder를 통해 Retrofit 인스턴스를 생성하고

그 Retrofit 인스턴스로 인터페이스 객체(아까 위에서 만든 FriendListService)를 구현합니다!

→ url 숨기는 법은 아래에 나옵니다!

object ApiFactory {
 
    private const val FRIEND_BASE_URL: String = BuildConfig.FRIEND_BASE_URL

    val loggingInterceptor = HttpLoggingInterceptor().apply {
        level = HttpLoggingInterceptor.Level.BODY
    }

    val client = OkHttpClient.Builder()
        .addInterceptor(loggingInterceptor)
        .build()

    val retrofit: Retrofit by lazy {
        Retrofit.Builder()
            .client(client)
            .baseUrl(FRIEND_BASE_URL)
            .addConverterFactory(Json.asConverterFactory("application/json".toMediaType()))
            .build()
    }

    inline fun <reified T> create(): T = retrofit.create(T::class.java)
}

object ServicePool {
    val friendListService = ApiFactory.create<FriendListService>() //이 부분입니다!!
}

URL 숨기는 방법

1) url 넣기

object ApiFactory {
    val loggingInterceptor = HttpLoggingInterceptor().apply {
        level = HttpLoggingInterceptor.Level.BODY
    }

    val client = OkHttpClient.Builder()
        .addInterceptor(loggingInterceptor)
        .build()

    val retrofit: Retrofit by lazy {
        Retrofit.Builder()
            .client(client)
            .baseUrl(https://reqres.in/) //이 부분!!
            .addConverterFactory(Json.asConverterFactory("application/json".toMediaType()))
            .build()
    }

    inline fun <reified T> create(): T = retrofit.create(T::class.java)
}

Retrofit을 이용하여 서버통신을 구현하는 ApiFactory에서 직접 URL을 넣고 Git에 올리면 url이 다 보이게 된다.. 그러면 이제 다른 사람들이 내 git에서 URL보고 악용해서 서버 비용이 엄청 많이 나올 수 있게 되겠죠..? 그러면 나만 알게 하려면 어떻게 해야 할까요

1-1) URL 숨기는 방법

1) git에 올라가지 않는 local properties를 이용하면 됩니다.

friend List를 받아오는 url의 이름을 friend.base.url라고 해보겠습니다.

보통 local properties에 작성하는 객체들은 소문자로 작성한다고 하니 유의!!

//local properties
friend.base.url="https://reqres.in/"

1-2) app수준의 build.gradle에서 로컬 프로퍼티에 접근한다

# plugin{}과 andriod{} 사이
Properties properties = new Properties()
properties.load(project.rootProject.file('local.properties').newDataInputStream())

3) build.gradle의 android에서 사용할 객체들을 가져온다

android {

    defaultConfig {
        ...

        buildConfigField "String", "Friend_BASE_URL", properties["friend.base.url"]
    }
		
		buildFeatures {
				...
        buildConfig true
    }

다시 아까의 안드로이드 ApiFactory로 이동해보자

object ApiFactory {

    private const val FRIEND_BASE_URL: String = BuildConfig.FRIEND_BASE_URL //추가해준 부분

    val loggingInterceptor = HttpLoggingInterceptor().apply {
        level = HttpLoggingInterceptor.Level.BODY
    }

    val client = OkHttpClient.Builder()
        .addInterceptor(loggingInterceptor)
        .build()

    val retrofit: Retrofit by lazy {
        Retrofit.Builder()
            .client(client)
            .baseUrl(FRIEND_BASE_URL) //바뀐 부분
            .addConverterFactory(Json.asConverterFactory("application/json".toMediaType()))
            .build()
    }

    inline fun <reified T> create(): T = retrofit.create(T::class.java)
}

주석 부분을 잘 보면

private const val FRIEND_BASE_URL: String = BuildConfig.*FRIEND_BASE_URL*

BuildConfig에 만들어 놓은 *FRIEND_BASE_URL* 를 불러와서 변수에 할당한다는 의미입니다.

그 뒤에 아까 url 자체를 직접적으로 넣었던 곳에 url이 담긴 변수명 *FRIEND_BASE_URL* 을 넣어주면 됩니다!!


마지막 정리

1) 먼저 서버로부터 데이터를 주거나, 받아올 데이터의 구조를 작성해준다

2) 사용할 HTTP Method를 정의하는 인터페이스를 작성한다.

→ HTTP Method : @POST, @GET.. 등등

3) 인터페이스 객체를 구현한다.

다음은 Retrofit으로부터 불러온 데이터들을 List Adapter를 이용해서 화면에 표시하는 방법에 대해 게시하겠습니당

0개의 댓글