본문 바로가기

Android

Android Network Programming 1

 이 글 을 마지막으로 기본적인 Docker 기반의 서버 구축이 완료 되었습다. 추후 테이블도 변경하고 수정할 사항이 많겠지만 빨리 내가 직접 만든 서버와 통신을 해보고 싶어서 메다닥 달려왔습니다.  안드로이드 파트인 만큼 기초부터 탄탄히 쌓고 가고 싶어서 우선적으로 책에 있는 내용을 정리하려 합니다. 개인적으로 책을 처음부터 쭉 훑어보고 필요할 때 그 내용을 보는게 가장 기억에 잘 남는거 같네요 :)

 

Error: Connect econnrefused

예... 처음엔 누구나 그럴싸한 계획을 갖고 있죠... 금방 끝날 줄 알았던 도커는 저를 참교육 하기 시작했습니다. 범인은 바로 요녀석입니다. Node에서 MySQL과 연결이 무슨 짓을 해도 안되던 겁니다

chanho-study.tistory.com

 

Do it ! Android Programming의 18장 내용 중 Retrofit을 정리했습니다.

[retrofit 한글판 문서]

1.  Permission

앱에서 네트워크 통신을 구현하려면 우선 매니페스트 파일에 다음처럼 퍼미션을 선언해야 합니다.

<uses-permission android:name="android.permission.INTERNET"/>

 

안드로이드 앱은 네트워크 통신을 할 떄 기본적으로 HTTPS 보안 프로토콜을 사용합니다. 만약 일반 HTTPS 프로토콜로 사용하고 싶다면 특정 도메인만 허용하도록 선언하기 위해 <domain> 태그에 HTTP 통신을 허용할 서버의 IP나 도메인을 작성하고 Androidmanifest.xml 파일의 <application> 태그에 networkSecurityConfig 속성으로 선언합니다.

<network-security-config>
    <domain-config cleartextTrafficPermitted="true">
        <domain includeSubdomains="true">xxx.xxx.xxx.xxx</domain>
    </domain-config>
</network-security-config>
<application
        android:networkSecurityConfig="@xml/newtork_security_config"/>

 

manifest에서 usescleartextTraffic="true"로 설정하면 앱 전체에서 모든 도메인의 서버와 HTTP 통신을 할 수 있습니다.

<application
        android:usesCleartextTraffic="true"/>

2.  Retrofit

Retrofit은 HTTP 통신을 위한 라이브러리 입니다. Retrofit은 네트워크 통신 정보만 주면 그대로 네트워킹 프로그래밍을 구현해 줍니다. 처음 안드로이드 네트워크 프로그래밍은데 이게 독이 될런지 ...?

이 그림에서 인터페이스는 코틀린의 interface 키워드로 만들고 이 인터페이스는 통신할 때 필요합니다.  인터페이스에는 함수를 선언만 하고 통신시 필요한 코드를 담지 않습니다. 이 인터페이스를 Retrofit에 전달하면 인터페이스를 내용을 기반으로 실제 통신시 필요한 코드를 담은 서비스 객체를 만들어 줍니다. 이 함수를 호출하면 Call 객체를 반환하는데, 이 Call 객체의  enqueue( ) 함수를 호출하면 통신을 수행합니다.

 

다시한번 정리하면

1. 통신용 interface 생성
2. Retrofit에 전달
3. Retrofit은 통신을 위한 서비스 객체 반환
4. 서비스 객체의 통신용 함수를 호출 후 Call 객체 반환
5. Call 객체의 enqueue() 호출해 네트워크 통신 수행

 

라이브러리 선언

Retrofit 사용을 위해 build.gradle의 dependencies 항목에 라이브러리를 등록 해줍니다.

    implementation 'com.squareup.retrofit2:retrofit:2.9.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
    implementation 'com.google.code.gson:gson:2.8.6'

모델 클래스 선언

 모델 클래스란 서버와 주고 받는 데이터를 표현하는 클래스로 흔히 VO(value-object) 클래스 라고도 하며 Json, XML 데이터를 파싱해 모델 클래스 객체에 담는 것을 자동화 해줍니다. 모델 클래스의 프로퍼티에 데이터가 자동으로 저장되기 위해선 key와 프로퍼티 이름을 매칭해주고 키와 프로퍼티 네임이 다를 땐 @SerializedName 어노테이션을 명시해줍니다. 

아래 예시에선 @SerializedName("first_name")을 사용해 firstName과 매칭 시켰습니다. 모델 클래스 생성시 서버측 데이터와 상관없는 프로퍼티를 선언해도 됩니다.

import com.google.gson.annotations.SerializedName

data class UserModel(
    var id:String,
    @SerializedName("first_name")
    var firstName:String,
    //@SerializedName("last_name")
    var lastName: String,
    var avatar:String,
    var avatarBitmap: String
)

 

서버의 데이터가 복잘할 때는 모든 데이터를 하나의 모델 클래스로 표현하지 않고 여러 클래스로 분리한 후 조합해서 사용할 수도 있습니다. 아래 예시는 이전에 본 강의에서 model class를 생성하는 과정을 설명한것으로 Json 데이터와 key name을 모두 일치시켜 주고 CurrentPrice Model Class를 분리해서 만들었습니다.

모델 class를 직접 만들기 어렵다면 Jetbrains 계열의 IDE에서 사용할 수 있는 Json to Kotlin class 플러그인을 사용할 수 있습니다.  ctrl+alt+s  를 눌러 설정 창으로 가신다음, plugin 탭에서 아래 사진과 같이 'json to kotlin'을 검색해 설치합니다.

 

원하는 Json Data 형태를 입력 후 Class Name을 입력하고 생성하면 Data class를 생성 해줍니다.

서비스 인터페이스 정의

인터페이스를 정의하면 Retrofit이 실제 통신을 위한 클래스를 자동으로 만들어줍니다.

interface InetworkService {
    @GET("api/users")
    fun GetUserList(@Query("page") page:String): Call<UserModel>
    
    @GET
    fun getAvatarImage(@Url url:String): Call<ResponseBody>
}

Retrofit 객체 생성

baseurl은 URL을 설정하면 이후에 URL 뒤에 올 경로만 지정해서 서버와 연동할 수 있습니다. 예를 들어 baseUrl을  코드처럼 선언하고 @GET("api/users")처럼 경로를 지정했다면 서버 요청 URL은 https://reqres.in/api/users가 가 됩니다.

addConverterFactory는 데이터를 파싱해 모델 객체에 담습니다. 

val retrofit = Retrofit
                .Builder()
                .baseUrl("https://reqres.in/")
                .addConverterFactory(GsonConverterFactory.create())
                .build()

인터페이스 타입의 서비스 객체 얻기

Retrofit 객체를 생성한 다음에는 이 객체로 서비스 인터페이스를 구현한 클래스의 객체를 얻습니다.  create() 함수에 앞에서 만든 서비스 인터페이스 타입을 전달하면 이 인터페이스를 구현한 클래스의 객체를 반환 해주고 네트워크 통신시 이 객체의 함수를 호출하면 됩니다.

var networkService = retrofit.create(InetworkService::class.java)

네트워크 통신 시도

네트워크 통신이 필요할 대 Retrofit 객체로 얻은 서비스 객체의 함수를 호출 해줍니다. 서비스 클래스 객체는 Retrofit이 만들어 주지만 우리가 만든 인터페이스를 구현한 클래스이므로 인터페이스의 함수를 호출하면 네트워크 통신을 시도합니다.

// Call 객체 얻기 
val userListCall = networkService.GetUserList("1")

// 네트워크 통신 수행
userListCall.enqueue(object : Callback<UserModel>{
    override fun onResponse(call: Call<UserModel>, response: Response<UserModel>) {
        val list = response.body()
    }

    override fun onFailure(call: Call<UserModel>, t: Throwable) {
        call.cancel()
    }

})

Call 객체의  enqueue  함수를 호출하면 통신을 수행합니다. 통신에 성공하면 서버로 부터 전달 받은 데이터가  onResponse()  함수의 매개변수인 Response 객체로 전달되 이 데이터를  response.body()  함수로 얻을 수 있습니다.

Retrofit Annotation

@GET, @POST, @PUT, @DELETE

     * CRUD ( Create / Read / Update / Delete ) -> HTTP Method ( POST / GET / PUT / DELETE )

 

 @Path : URL의 경로를 동적으로 지정할 때 사용합니다.

최종 서버 요청 URL은 [ https://reqres.in/group/10/users/kkang ] 이 됩니다.

// 인터페이스 선언 함수
@GET("group{id}/users/{name}")
    fun test2(
        @Path("id") userId: String,
        @Path("name") userName: String
    ): Call<UserModel>

// Call 객체 반환
val call: Call<UserModel> = networkService.test2("10", "kkang")

 

@Query : 함수의 매개변숫값을 사용해 서버에 전달합니다. 함수의 매개변수에 @Query("name")이라고 선언하면 서버에 요청시 name을 key로 매개변수를 value로 해서 서버에 데이터를 전달합니다.

최종 서버 요청 URL: [ https://reqres.in/group/users?sort=age&name=kkang

@GET("group/users")
    fun test3(
        @Query("id") arg1: String,
        @Query("name") arg2: String
    ): Call<UserModel>
    
val call2: Call<UserModel> = networkService.test3("10", "kkang")

 

@QueryMap : 서버에 전송할 데이터가 많을 때 Map Type의 매개 변수로 받아 전송합니다.

최종 서버 요청 URL : [ https://reqres.in/group/users?one=hello&two=world&name=kkang ]

@GET("group/users")
    fun test4(
        @QueryMap options: Map<String, String>, 
        @Query("name") arg2: String
    ): Call<UserModel>
    
val call3: Call<UserModel> = networkService.test4(
            mapOf<String, String>("one" to "hello", "two" to "world"),
            "kkang"
        )

 

 

@Body : 서버에 전송할 데이터를 모델 객체로 지정합니다. 모델 객체의 프로퍼티 명을 Key로, 프로퍼티의 데이터를 Value로 지정해 JSON 문자열을 만들어 서버에 전송합니다. 이 때 JSON 문자열은 데이터 스트림으로 전송하므로  @Body는 @GET에서는 사용할 수 없으며 @POST와 함께 사용해야 합니다.

최종 서버 요청 URL : [ https://reqres.in/group/users?name=kkang

@GET("group/users")
    fun test5(
        @Body user: UserModel,
        @Query("name") name: String
    ): Call<UserModel>

val call4: Call<UserModel> = networkService.test5(
            UserModel(id="1", firstName = "myoungbo", lastName = "hong", "someurl", "abcd"),
            "kkang"
        )

 

@FormUrlEncoded, @Field : 데이터를 URL Encoding 형태로 만들어 전송합니다. @Field 어노테이션이 추가된 데이터를 인코딩해서 전송, @FormUrlEncoded 어노테이션을 사용할 때만 적용할 수 있고  POST 방식에서만 사용할 수 있습니다.

@FormUrlEncoded
    @POST("group/users")
    fun test6(
        @Field("first_name") first:String?,
        @Field("last_name") last:String?,
        @Query("name") name: String?
    ): Call<UserModel>

val call5: Call<UserModel> = networkService.test6(
            "myoungbo 명보",
            "hong 홍",
        "chan"
        )


@Field 어노테이션은 모델 객체에는 사용할 수 없고 다수의 데이터를 한번에 지정하려면 배열을 사용해야 합니다. 

최종 서버 요청 URL : [ https://reqres.in/task 

@FormUrlEncoded
    @POST("tasks")
    fun test7(@Field("title") titles: List<String>): Call<UserModel>

val list: MutableList<String> = ArrayList()
        list.add("홍명보")
        list.add("이강인")
        val call7: Call<UserModel> = networkService.test7(list)

 

'Android' 카테고리의 다른 글

안드로이드 HTTP 보안 정책  (0) 2023.12.14
Android Network Programming 2  (0) 2023.12.14
Android Studio MySQL 연동 6  (0) 2023.12.08
Android Studio MySQL 연동 5  (0) 2023.12.08
Android Studio MySQL 연동 4  (0) 2023.12.08