Retrofit으로 다운받은 Json 데이터를 RecyclerView에 표시하기

이번 포스팅에서는 Retrofit 라이브러리로 HTTP 통신을 수행하는 법에 대해 알아보도록 하겠습니다.

안드로이드에서의 HTTP 통신에 대한 개괄적 설명과 Retrofit을 사용해야 하는 이유에 대해서는 안드로이드의 HTTP 통신 라이브러리 고찰에서 다루었으니 그것을 참조하시면 좋을 것 같습니다. 또한 Retrofit의 기술적 특징에 대해서는 라이브러리 제작자가 설명해 둔 Retrofit 2과 함께하는 정말 쉬운 HTTP 를 보는것이 가장 정확할 것 같습니다.

이 강의에서는 경기데이터드림에서 제공하는 코로나바이러스 감염증 선별진료소 운영 의료기관 현황 API를 통해 가져온 JSON 데이터를 Recyclerview에 표시해보면서 Retrofit을 어떻게 사용하는지 알아보도록 하겠습니다.

라이브러리 추가

build.gradle에 Retrofit과 JSON을 다루기 위한 Moshi 컨버터, 그리고 로그를 찍기위한 OkHttp 라이브러리를 추가해 줍니다.

1
2
3
4
5
6
7
// Retrofit
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-moshi:2.9.0'

// OkHttp
implementation 'com.squareup.okhttp3:okhttp:4.9.1'
implementation 'com.squareup.okhttp3:logging-interceptor:4.9.1'

권한 추가

HTTP 통신에 인터넷을 사용해야 하니 AndroidManefest.xml에서 권한을 추가해줍니다.

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

Data Transfer Object 준비

데이터 형식 확인

API의 주소인 https://openapi.gg.go.kr/EmgMedInfo?type=json 에 웹브라우저로 접속해보면 다음과 같은 값을 반환받게 됩니다. "head"안의 항목은 필요하지 않은 메타정보이고, "row" 안의 내용이 Recyclerview로 표시해야 하는 것들입니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
{
    "EmgMedInfo": [
        {
            "head": [
                {
                    "list_total_count": 172
                },
                {
                    "RESULT": {
                        "CODE": "INFO-000",
                        "MESSAGE": "정상 처리되었습니다."
                    }
                },
                {
                    "api_version": "1.0"
                }
            ]
        },
        {
            "row": [
                {
                    "SUM_DE": "2021-06-09",
                    "SIGUN_NM": "화성시",
                    "SIGUN_CD": "41590",
                    "MEDCARE_INST_NM": "센트럴아동병원",
                    "DISTRCT_DIV_NM": "국민안심병원+호흡기전담클리닉",
                    "REPRSNT_TELNO": null,
                    "EMGNCY_CENTER_TELNO": "031-8060-5000",
                    "REFINE_LOTNO_ADDR": "경기도 화성시 반송동 93-1번지 동탄위버폴리스",
                    "REFINE_ROADNM_ADDR": null,
                    "REFINE_WGS84_LOGT": "127.0720402235",
                    "REFINE_WGS84_LAT": "37.2046139805"
                },
                {
                    "SUM_DE": "2021-06-14",
                    "SIGUN_NM": "화성시",
                    "SIGUN_CD": "41590",
                    "MEDCARE_INST_NM": "한림대학교 동탄성심병원",
                    "DISTRCT_DIV_NM": "선별진료소+국민안심병원",
                    "REPRSNT_TELNO": "031-8086-3000",
                    "EMGNCY_CENTER_TELNO": "1522-2500",
                    "REFINE_LOTNO_ADDR": "경기도 화성시 석우동 40번지",
                    "REFINE_ROADNM_ADDR": "경기도 화성시 큰재봉길 7",
                    "REFINE_WGS84_LOGT": "127.0799416615",
                    "REFINE_WGS84_LAT": "37.2164957291"
                },
                {
                    "SUM_DE": "2021-06-09",
                    "SIGUN_NM": "구리시",
                    "SIGUN_CD": "41310",
                    "MEDCARE_INST_NM": "장스365의원",
                    "DISTRCT_DIV_NM": "호흡기전담클리닉",
                    "REPRSNT_TELNO": null,
                    "EMGNCY_CENTER_TELNO": "031-550-3105",
                    "REFINE_LOTNO_ADDR": "경기도 구리시 수택동 853번지",
                    "REFINE_ROADNM_ADDR": null,
                    "REFINE_WGS84_LOGT": "127.1426473636",
                    "REFINE_WGS84_LAT": "37.5912595849"
                },
                {
                    "SUM_DE": "2021-06-09",
                    "SIGUN_NM": "김포시",
                    "SIGUN_CD": "41570",
                    "MEDCARE_INST_NM": "김포아이제일병원",
                    "DISTRCT_DIV_NM": "호흡기전담클리닉",
                    "REPRSNT_TELNO": null,
                    "EMGNCY_CENTER_TELNO": "031-8083-7575",
                    "REFINE_LOTNO_ADDR": "경기도 김포시 구래동 6885-2번지",
                    "REFINE_ROADNM_ADDR": "경기도 김포시 김포한강8로 382",
                    "REFINE_WGS84_LOGT": "126.6266409118",
                    "REFINE_WGS84_LAT": "37.6435680201"
                },
                {
                    "SUM_DE": "2021-06-09",
                    "SIGUN_NM": "성남시",
                    "SIGUN_CD": "41130",
                    "MEDCARE_INST_NM": "분당성모이비인후과의원",
                    "DISTRCT_DIV_NM": "호흡기전담클리닉",
                    "REPRSNT_TELNO": null,
                    "EMGNCY_CENTER_TELNO": "031-712-9075",
                    "REFINE_LOTNO_ADDR": "경기도 성남시 분당구 야탑동 352-4번지",
                    "REFINE_ROADNM_ADDR": "경기도 성남시 분당구 성남대로925번길 11",
                    "REFINE_WGS84_LOGT": "127.1272733859",
                    "REFINE_WGS84_LAT": "37.4120484252"
                }
            ]
        }
    ]
}

DTO 클래스 작성

JSON 형식으로 받아온 텍스트는 앱에서 사용할 수 있는 데이터 객체로 변환하게 되는데 이 때 그 객체의 틀이 되는것이 Data Transfer Object (DTO)입니다.

그럼 이 JSON에 대응하는 DTO를 작성합니다. JSON To Kotlin Class 플러그인에 위의 JSON을 붙여넣고 EmgMedResponse라는 이름으로 변환하면 다음과 같이 5개의 데이터 클래스가 생성됩니다. 이 때 옵션은 Val, Nullable, MoShi (Reflect)를 선택해줍니다.

JSON은 객체를 중괄호{} 로 감싸서 표시하는데 이걸 DTO로 변환할 때에는 객체 하나를 데이터 클래스 하나로 대응시킵니다. Moshi로 객체 안의 Key-Value 쌍을 변환할 때는 @field:Json(name = "") 어노테이션을 사용합니다. name에 Key 값을 지정하면, 대응하는 Value를 val 프로퍼티에 매칭해 줍니다. 각 클래스의 내용이 짧기 때문에 여기서는 관리상 편의를 위해 EmgMedResponse.kt 안에 다른 클래스 내용을 다 붙여넣도록 하겠습니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
data class EmgMedResponse(
    @field:Json(name = "EmgMedInfo")
    val emgMedInfo: List<EmgMedInfo>?
)

data class EmgMedInfo(
    val head: List<Head>?,
    val row: List<Row>?
)

data class Head(
    @field:Json(name = "api_version")
    val apiVersion: String?, // 1.0
    @field:Json(name = "list_total_count")
    val listTotalCount: Int?, // 172
    @field:Json(name = "RESULT")
    val rESULT: RESULT?
)

data class RESULT(
    @field:Json(name = "CODE")
    val cODE: String?, // INFO-000
    @field:Json(name = "MESSAGE")
    val mESSAGE: String? // 정상 처리되었습니다.
)

data class Row(
    @field:Json(name = "DISTRCT_DIV_NM")
    val dISTRCTDIVNM: String?, // 국민안심병원+호흡기전담클리닉
    @field:Json(name = "EMGNCY_CENTER_TELNO")
    val eMGNCYCENTERTELNO: String?, // 031-8060-5000
    @field:Json(name = "MEDCARE_INST_NM")
    val mEDCAREINSTNM: String?, // 센트럴아동병원
    @field:Json(name = "REFINE_LOTNO_ADDR")
    val rEFINELOTNOADDR: String?, // 경기도 화성시 반송동 93-1번지 동탄위버폴리스
    @field:Json(name = "REFINE_ROADNM_ADDR")
    val rEFINEROADNMADDR: String?, // null
    @field:Json(name = "REFINE_WGS84_LAT")
    val rEFINEWGS84LAT: String?, // 37.2046139805
    @field:Json(name = "REFINE_WGS84_LOGT")
    val rEFINEWGS84LOGT: String?, // 127.0720402235
    @field:Json(name = "REPRSNT_TELNO")
    val rEPRSNTTELNO: String?, // null
    @field:Json(name = "SIGUN_CD")
    val sIGUNCD: String?, // 41590
    @field:Json(name = "SIGUN_NM")
    val sIGUNNM: String?, // 화성시
    @field:Json(name = "SUM_DE")
    val sUMDE: String? // 2021-06-09
)

아이템 디자인

Recyclerview에서 사용할 아이템의 디자인을 만들어줍니다. LinearLayout을 써서 API 응답중 Row의 내용을 표시하는 화면을 만들어줍니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/layout_viewholder"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="?attr/selectableItemBackground"
    android:gravity="center_vertical"
    android:orientation="horizontal"
    android:padding="10dp">

    <LinearLayout
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="9"
        android:orientation="vertical">

        <TextView
            android:id="@+id/tv_name"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="의료기관명"
            android:textSize="24sp"
            android:padding="3dp" />

        <TextView
            android:id="@+id/tv_type"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="구분"
            android:textSize="18sp" />

        <TextView
            android:id="@+id/tv_phone"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="전화번호"
            android:textSize="18sp" />

        <TextView
            android:id="@+id/tv_addr"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="주소"
            android:textSize="18sp" />

        <TextView
            android:id="@+id/tv_wgs84lat"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="위도"
            android:textSize="18sp" />

        <TextView
            android:id="@+id/tv_wgs84lon"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="경도"
            android:textSize="18sp" />
    </LinearLayout>

    <ImageView
        android:layout_width="30dp"
        android:layout_height="30dp"
        android:layout_gravity="center"
        android:src="@drawable/ic_baseline_reorder_24" />

</LinearLayout>

레이아웃 작성

다음은 activity_main.xml을 작성합니다. JSON 응답결과를 표시할 Recyclerview를 하나 배치하고 네트워크 연결을 시작하는데 사용할 플로팅버튼을 하나 달아줍니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recycler_view"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:scrollbars="vertical"
        tools:listitem="@layout/item_emgmed"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <com.google.android.material.floatingactionbutton.FloatingActionButton
        android:id="@+id/btn_get"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="20dp"
        android:clickable="true"
        android:focusable="true"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:srcCompat="@android:drawable/ic_menu_add" />

</androidx.constraintlayout.widget.ConstraintLayout>

Recyclerview 어댑터 작성

DTO 중 Row 클래스의 내용을 받아 ViewHolder에 표시하는 Recyclerview 어댑터를 작성합니다. 데이터의 갱신은 ListAdapter를 사용하여 수행합니다. ListAdapter의 구체적인 설명에 대해서는 DiffUtil과 ListAdapter 이해하고 RecyclerView에 적용하기 강의를 참조하시기 바랍니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class EmgMedAdapter : ListAdapter<Row, EmgMedAdapter.EmgMedViewHolder>(DiffCallback) {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): EmgMedViewHolder {
        val binding = ItemEmgmedBinding.inflate(LayoutInflater.from(parent.context), parent, false)
        return EmgMedViewHolder(binding)
    }

    override fun onBindViewHolder(holder: EmgMedViewHolder, position: Int) {
        holder.bind(getItem(position))
    }

    class EmgMedViewHolder(private val binding: ItemEmgmedBinding) :
        RecyclerView.ViewHolder(binding.root) {

        fun bind(item: Row) {
            with(binding) {
                tvName.text = item.mEDCAREINSTNM
                tvType.text = item.dISTRCTDIVNM
                tvPhone.text = item.eMGNCYCENTERTELNO
                tvAddr.text = item.rEFINELOTNOADDR
                tvWgs84lat.text = "위도: ${item.rEFINEWGS84LAT}"
                tvWgs84lon.text = "경도: ${item.rEFINEWGS84LOGT}"
            }
        }
    }

    companion object {
        private val DiffCallback = object : DiffUtil.ItemCallback<Row>() {
            override fun areItemsTheSame(oldItem: Row, newItem: Row): Boolean {
                return oldItem.hashCode() == newItem.hashCode()
            }

            override fun areContentsTheSame(oldItem: Row, newItem: Row): Boolean {
                return oldItem == newItem
            }
        }
    }
}

그리고 MainActivity의 onCreate에서 Recyclerview를 설정합니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
private val myRecyclerViewAdapter by lazy { EmgMedAdapter() }

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(binding.root)

    binding.recyclerView.apply {
        layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
        adapter = myRecyclerViewAdapter
        addItemDecoration(DividerItemDecoration(context, DividerItemDecoration.VERTICAL))
    }
}

서비스 작성

Retrofit은 HTTP API의 request를 인터페이스로 정의하여 사용합니다. 이 강의에서 사용할 선별진료소 API는 다음과 같은 인자를 받을 수 있게 만들어져 있습니다.

변수명 타입 변수 설명 설명
KEY STRING 인증키 기본값 : sample key
Type STRING 호출 문서(xml, json) 기본값 : xml
pIndex INTEGER 페이지 위치 기본값 : 1(sample key는 1 고정)
pSize INTEGER 페이지 당 요청 숫자 기본값 : 100(sample key는 5 고정)

그러면 이중 KEY와 Type 인자를 전달받아 GET 요청을 수행하는 Retrofit 서비스를 하나 만들어보겠습니다. API 요청주소가 https://openapi.gg.go.kr/EmgMedInfo 이므로 고정주소인 Base url은 https://openapi.gg.go.kr/ 이고, @GET요청시 인자는 Base url 뒤의 EmgMedInfo 입니다. KEY와 Type 인자는 @Query 어노테이션을 써서 전달해줍니다.

인터페이스의 메소드인 getEmgMedDataCall 객체를 반환하도록 정의되었습니다. Call 은 서버에 request를 보내고 그 응답을 반환하는 1회용 객체입니다.

1
2
3
4
5
interface EmgMedService {
    @GET("EmgMedInfo")
    fun getEmgMedData(@Query("KEY") KEY: String,
                      @Query("Type") Type: String): Call<EmgMedResponse>
}

코드를 해석하면 getEmgMedDataBASE_URL/EmgMedInfo?KEY="KEY"&Type="Type"이라는 GET 요청을 보내고 Call<EmgMedResponse> 객체를 반환받는 동작을 하는 함수라는 뜻이 됩니다.

Retrofit 객체 준비

RetrofitApi 작성

Retrofit을 사용하기 위해서는 우선 객체를 만들어주어야 합니다. 이 때 여러개의 객체가 만들어지면 자원도 낭비되고 통신에 혼선도 발생할 수 있으므로 object 키워드를 써서 싱글톤으로 만들어지도록 합니다. 이 때 각 변수에는 lazy를 적용함으로써 실제 사용되는 순간이 와야 비로소 만들어지게 되고, 단 하나의 인스턴스만이 만들어지도록 하였습니다. 싱글톤에 대한 구체적인 설명은 알기쉬운 Singleton Pattern 강의를 참고하시기 바랍니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
object RetrofitApi {
    private const val BASE_URL = "https://openapi.gg.go.kr/"

    private val okHttpClient: OkHttpClient by lazy {
        OkHttpClient.Builder()
            .addInterceptor(HttpLoggingInterceptor().apply {
                level = HttpLoggingInterceptor.Level.BODY
            })
            .build()
    }

    private val retrofit: Retrofit by lazy {
        Retrofit.Builder()
            .addConverterFactory(MoshiConverterFactory.create())
            .client(okHttpClient)
            .baseUrl(BASE_URL)
            .build()
    }

    val emgMedService: EmgMedService by lazy {
        retrofit.create(EmgMedService::class.java)
    }
}

빌더 패턴을 통해 retrofit 객체를 만들어줍니다. addConverterFactory에서 DTO 변환에 사용할 JSON 컨버터를 Moshi로 지정하고, baseUrl 을 전달한 뒤 build()로 객체를 생성합니다. 그리고나서 retrofit 인스턴스의 create 명령을 이용해서 emgMedService의 인스턴스를 만들어줍니다.

전송패킷 모니터링

retrofit 객체를 생성할때 client 속성에 okHttpClient를 넘겨주면 로그캣에서 패킷내용을 모니터링할 수 있습니다. okHttpClient는 다음과 같이 서버와 코어 사이에서 데이터를 가로채는 Interceptor로서 기능하는 객체입니다.

출처 : https://square.github.io/okhttp/interceptors/

Retrofit 구동

그럼 MainActivity에서 Retrofit을 사용해보겠습니다. 플로팅 버튼에 대해 다음 내용의 retrofitWork()를 실행하는 클릭리스너를 구성합니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
private fun retrofitWork() {
    val service = RetrofitApi.emgMedService

    service.getEmgMedData(getString(R.string.api_key), "json")
        .enqueue(object : Callback<EmgMedResponse> {
            override fun onResponse(
                call: Call<EmgMedResponse>,
                response: Response<EmgMedResponse>
            ) {
                if (response.isSuccessful) {
                    Log.d("TAG", response.body().toString())
                    // head를 스킵하기 위해 index 1번을 가져옴
                    val result = response.body()?.emgMedInfo?.get(1)?.row
                    myRecyclerViewAdapter.submitList(result!!)
                }
            }

            override fun onFailure(call: Call<EmgMedResponse>, t:  Throwable) {
                Log.d("TAG", t.message.toString())
            }
        })
}

싱글톤 Retrofit으로 서비스 인스턴스를 생성하고 getEmgMedData를 실행해서 Call 객체를 획득합니다. 인증키는 R.string.api_key 를 이용해 입력했고 서버로부터 json 데이터를 반환받도록 하였습니다.

Call 작업은 두가지 방법으로 실행시킬 수 있습니다. execute를 사용하면 request를 보내고 response를 받는 행위를 동기적으로 행합니다. 그리고 enqueue 작업을 실행하면 request는 비동기적으로 보내고, response는 콜백으로 받게 됩니다. 여기서는 네트워크 작업을 안정적으로 수행하기 위해 enqueue로 실행하도록 하였습니다.

enqueue의 통신결과는 성공했을 때 onResponse, 실패했을 때 onFailure 를 리스너로 받을 수 있으므로 각 상황에 따른 처리를 해 줍니다. 통신에 성공하면 response.isSuccessful을 확인하여 사용할 수 있는 응답이 돌아왔는지 확인하고, 그렇다면 body에서 값을 꺼내어 Recyclerview에 넘겨주면 됩니다.

코루틴에서 사용하기

retrofit의 사양에 따르면 enqueue는 request를 백그라운드 스레드에서, response 콜백은 메인스레드에서 처리하게 됩니다. 그런데 코루틴을 사용하면 콜백을 쓰지 않아도 동일한 처리를 할 수 있습니다. 그래서 이번엔 코루틴 안에서 retrofit 처리를 수행하도록 해 보겠습니다. 코루틴에 대한 구체적인 설명에 대해서는 알기쉬운 코루틴 이론을 참조하시기 바랍니다.

우선은 build.gradle에 플러그인을 추가합니다.

1
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.1'

다음으로 코루틴으로 작동하는 서비스를 인터페이스에 추가해 줍니다. 서비스가 코루틴 안에서 수행되어야 하므로 suspend 키워드를 붙여주고, 반환값은 Call 객체가 아니라 Response 객체로 받도록 변경하였습니다. 코루틴 자체적으로 비동기적인 처리를 수행하기 때문에, Call이 제공하는 흐름처리 기능은 더이상 필요가 없습니다.

1
2
3
4
5
@GET("EmgMedInfo")
suspend fun getDataCoroutine(
    @Query("KEY") KEY: String,
    @Query("Type") Type: String
): Response<EmgMedResponse>

다음은 retrofitWork()에서 enqueue 처리를 주석처리하고 다음 코드를 추가합니다. 네트워크 작업이므로 IO 디스패처에서 Response 반환을 획득합니다. 그리고 반환값을 RecylerView에 표시할 때만 컨텍스트를 메인 스레드로 스위치하면 됩니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
CoroutineScope(Dispatchers.IO).launch {
    val response = service.getDataCoroutine(getString(R.string.api_key), "json")

    withContext(Dispatchers.Main) {
        if (response.isSuccessful) {
            val result = response.body()?.emgMedInfo?.get(1)?.row
            result?.let {
                myRecyclerViewAdapter.submitList(it)
            }
        } else {
            Log.e("TAG", response.code().toString())
        }
    }
}

이렇게 해서 Retrofit으로 HTTP 통신을 수행하는 법에 대해 알아보았습니다.

Built with Hugo
Theme Stack designed by Jimmy