창고

Retrofit / OkHttp

날아갔나요 2020. 5. 29. 17:27

 

 

 

Github : https://github.com/junghun9102/AndroidTemplate

Branch : library/retrofit

 

junghun9102/AndroidTemplate

Contribute to junghun9102/AndroidTemplate development by creating an account on GitHub.

github.com

 

 


Commit ea187676

 

기본 사용법

1. Service

2. OkHttpClient

3. Retrofit을 이용한 Service 구현체

 

1번과 3번만 구현하면 API통신이 가능하다.

2번은 선택적이지만 보통 구현한다.

 

GoogleNews RSS를 사용해 Retrofit 기본 사용법을 정리하려고 한다.

https://news.google.com/rss?hl=ko&gl=KR&ceid=KR:ko 

 

 

 

 

 


https://square.github.io/retrofit/

 

Retrofit

A type-safe HTTP client for Android and Java

square.github.io

dependencies

    implementation 'com.squareup.retrofit2:retrofit:2.5.0'
    implementation 'com.squareup.okhttp3:logging-interceptor:3.11.0'
    implementation 'com.squareup.okhttp3:okhttp:3.12.0'
    implementation 'com.squareup.retrofit2:converter-simplexml:2.1.0'
//    implementation 'com.squareup.retrofit2:converter-gson:2.4.0'

 

 

 

1. Service 

HTTP Method(GET/POST/PUT/DELETE)로 API 요청을 위한 interface

 

interface GoogleNewsService {

    @GET("/rss")
    fun getNews(
        @Query("hl") hl: String,
        @Query("gl") gl: String,
        @Query("ceid") ceid: String
    ): Call<RespNewsRss>

}

 

추가적인 사용 방법들은 아래 포스팅에 잘 설명되어 있다.

 

https://medium.com/@ggikko/retrofit-%EC%A0%9C%EB%8C%80%EB%A1%9C-%EC%95%8C%EA%B3%A0-%EC%93%B0%EA%B8%B0-a6ce90cf7768

 

Retrofit 홈페이지 한글로 긁어보기 RESTFUL SERVICE VERY EASY!!

원래 Volley를 쓰다가 부분적으로 적용해서 사용해 보려했는데 하다보니 홈페이지에 있는 글을 다 읽고 말았다. 그래서 영어로 읽으면 시간이 오래걸려서 한글로 작성해보려한다.. 허접하게 번역

medium.com

 

 

 

 

2. OkHttpClient

API 요청과 응답에 관한 설정을 할 수 있다.

object GoogleNewsServiceFactory

    private const val HTTP_TIMEOUT_READ = 10L
    private const val HTTP_TIMEOUT_CONNECT = 5L

    private fun makeOkHttpClient(httpLoggingInterceptor: HttpLoggingInterceptor, appendHeaderInterceptor: Interceptor) = OkHttpClient.Builder()
        .connectTimeout(HTTP_TIMEOUT_CONNECT, TimeUnit.MILLISECONDS)
        .readTimeout(HTTP_TIMEOUT_READ, TimeUnit.MILLISECONDS)
        .addInterceptor(httpLoggingInterceptor)
        .addInterceptor(appendHeaderInterceptor)
        .build()

    private fun makeLoggingInterceptor(debug: Boolean): HttpLoggingInterceptor {
        return HttpLoggingInterceptor().apply {
            level = if (debug) HttpLoggingInterceptor.Level.BODY else HttpLoggingInterceptor.Level.NONE
        }
    }

    private fun makeAppendHeaderInterceptor() = Interceptor { chain ->
            val newRequest = chain.request().newBuilder()
//            newRequest.addHeader("Version", "v1.0")
            chain.proceed(newRequest.build())
    }

 

connectTimeout / readTimeout / writeTimeout / callTimeout

타임아웃을 설정 가능

 

HttpLoggingInterceptor

요청과 응답 로그를 볼 수 있다.

 

 

makeAppendHeaderInterceptor

1번의 Service에서도 각각의 요청마다 Header를 붙일 수 있다.

하지만 디폴트 헤더 데이터가 필요한 경우 interceptor를 만들어 요청마다 같은 헤더를 추가할 수 있다.

 

 

 

 

3. Retrofit을 이용한 Service 구현체

실제 사용하기 위한 구현체 생성

object GoogleNewsServiceFactory

    fun makeNewsService(debug: Boolean, baseUrl: String): GoogleNewsService {
        val okHttpClient = makeOkHttpClient(
            makeLoggingInterceptor(debug),
            makeAppendHeaderInterceptor()
        )
        return makeNewsService(
            baseUrl,
            okHttpClient
        )
    }

    private fun makeNewsService(baseUrl: String, okHttpClient: OkHttpClient): GoogleNewsService {
        val retrofit = Retrofit.Builder()
            .baseUrl(baseUrl)
            .client(okHttpClient)
            .addConverterFactory(SimpleXmlConverterFactory.create())
            .build()

        return retrofit.create(GoogleNewsService::class.java)

 

baseUrl(baseUrl)

기본 URL을 설정

 

client(okHttpClient)

2번에서 만들었던 okHttpClient 설정

 

addConverterFactory(SimpleXmlConverterFactory.create())

응답 데이터를 파싱해 객체로 받기 위한 설정

 

 

 

 

XML

 

dependecies

implementation 'com.squareup.retrofit2:converter-simplexml:2.1.0'

 

Retrofit Builder 

addConverterFactory(SimpleXmlConverterFactory.create())

 

<rss xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
  <channel>
  <generator>NFE/5.0</generator>
  <title>주요 뉴스 - Google 뉴스</title>
  <link>https://news.google.com/?hl=ko&gl=KR&ceid=KR:ko</link>
  <language>ko</language>
  <webMaster>news-webmaster@google.com</webMaster>
  <copyright>2020 Google Inc.</copyright>
  <lastBuildDate>Fri, 29 May 2020 03:09:37 GMT</lastBuildDate>
  <description>Google 뉴스</description>
  <item>
    <title>[속보] 코로나19 확진 58명 늘어…신규 확진자 전원 수도권 - 한겨레</title>
    <link>https://news.google.com/__i/rss/rd/articles/CBMiNWh0dHA6Ly93d3cuaGFuaS5jby5rci9hcnRpL3NvY2lldHkvaGVhbHRoLzk0NzA1Ny5odG1s0gEA?oc=5</link>
    <guid isPermaLink="false">52782408608000</guid>
    <pubDate>Fri, 29 May 2020 01:23:03 GMT</pubDate>
    <description><ol><li><a href="https://news.google.com/__i/rss/rd/articles/CBMiNWh0dHA6Ly93d3cuaGFuaS5jby5rci9hcnRpL3NvY2lldHkvaGVhbHRoLzk0NzA1Ny5odG1s0gEA?oc=5" target="_blank">[속보] 코로나19 확진 58명 늘어…신규 확진자 전원 수도권</a>&nbsp;&nbsp;<font color="#6f6f6f">한겨레</font></li><li><a href="https://news.google.com/__i/rss/rd/articles/CBMiK2h0dHBzOi8vd3d3LnlvdXR1YmUuY29tL3dhdGNoP3Y9UEFEOE1UVXBoMmfSAQA?oc=5" target="_blank">쿠팡발 감염 확산에 수도권 '비상'…모자·신발서 코로나 나와</a>&nbsp;&nbsp;<font color="#6f6f6f">뉴스TVCHOSUN</font></li><li><a href="https://news.google.com/__i/rss/rd/articles/CBMiMWh0dHBzOi8vd3d3Lnl0bi5jby5rci9fbG4vMDEwM18yMDIwMDUyOTEwNTI0NDc0NDbSAUNodHRwczovL20ueXRuLmNvLmtyL25ld3Nfdmlldy5hbXAucGhwP3BhcmFtPTAxMDNfMjAyMDA1MjkxMDUyNDQ3NDQ2?oc=5" target="_blank">신규 발생 58명...지역 발생 55명 모두 수도권에 집중</a>&nbsp;&nbsp;<font color="#6f6f6f">YTN</font></li><li><a href="https://news.google.com/__i/rss/rd/articles/CBMiJ2h0dHBzOi8vbmV3cy5qb2lucy5jb20vYXJ0aWNsZS8yMzc4ODg4MNIBK2h0dHBzOi8vbW5ld3Muam9pbnMuY29tL2FtcGFydGljbGUvMjM3ODg4ODA?oc=5" target="_blank">코로나 신규환자 58명, 지역감염 55명···모두 수도권서 터졌다 - 중앙일보</a>&nbsp;&nbsp;<font color="#6f6f6f">중앙일보 모바일</font></li><li><a href="https://news.google.com/__i/rss/rd/articles/CBMiK2h0dHBzOi8vd3d3LnlvdXR1YmUuY29tL3dhdGNoP3Y9cnVjU2VzMVB0R2_SAQA?oc=5" target="_blank">수도권 덮친 '쿠팡 물류센터 집단감염'…"2주가 고비" / JTBC 뉴스룸</a>&nbsp;&nbsp;<font color="#6f6f6f">JTBC News</font></li><li><strong><a href="https://news.google.com/stories/CAAqOQgKIjNDQklTSURvSmMzUnZjbmt0TXpZd1NoTUtFUWlBMHR6bGxZQU1FVmdIQWUwRXpZRzRLQUFQAQ?oc=5" target="_blank">Google 뉴스에서 전체 콘텐츠 보기</a></strong></li></ol></description>
    <source url="http://www.hani.co.kr">한겨레</source>
  </item>

 

@Root(name = "rss", strict = false)
data class RespNewsRss(
    @field:Element(name = "channel") var channel: RespNewsRssChannel? = null
)

@Root(name = "channel", strict = false)
class RespNewsRssChannel(
    @field:ElementList(entry = "item", inline = true) var newsList: List<RespNews>? = null
)

@Element(name = "item")
data class RespNews(
    @field:Element(name = "title") var title: String? = null,
    @field:Element(name = "link") var link: String? = null,
    @field:Element(name = "guid") var guid: String? = null,
    @field:Element(name = "pubDate") var pubDate: String? = null,
    @field:Element(name = "description") var description: String? = null,
    @field:Element(name = "source") var source: String? = null
)

 

 

Json

 

depenencies

implementation 'com.squareup.retrofit2:converter-gson:2.4.0'

 

Retrofit Builder

.addConverterFactory(GsonConverterFactory.create())

 

{
  "rss" : {
    "channel" : {
    	"items" : [{
          "title" : "인천시 “쿠팡물류센터 확진자 딸 등 2명 추가 확진” - 한겨레",
          "link" : "https://news.google.com/__i/rss/rd/articles/CBMiM2h0dHA6Ly93d3cuaGFuaS5jby5rci9hcnRpL2FyZWEvY2FwaXRhbC85NDcyMjIuaHRtbNIBAA?oc=5",
          "guid" : "52782408608000",
          "pubDate" : "Sun, 31 May 2020 05:56:12 GMT",
          "description" : "<ol><li><a href="https://news.google.com/__i/rss/rd/articles/CBMiM2h0dHA6Ly93d3cuaGFuaS5jby5rci9hcnRpL2FyZWEvY2FwaXRhbC85NDcyMjIuaHRtbNIBAA?oc=5" target="_blank">인천시 “쿠팡물류센터 확진자 딸 등 2명 추가 확진”</a>&nbsp;&nbsp;<font color="#6f6f6f">한겨레</font></li><li><a href="https://news.google.com/__i/rss/rd/articles/CBMiOGh0dHBzOi8vd3d3Lmhhbmtvb2tpbGJvLmNvbS9OZXdzL1JlYWQvMjAyMDA1MzAxNjc3MDQ3MzMy0gEA?oc=5" target="_blank">이태원 클럽→쿠팡 물류센터→여의도 학원 전파?</a>&nbsp;&nbsp;<font color="#6f6f6f">한국일보</font></li><li><a href="https://news.google.com/__i/rss/rd/articles/CBMiNWh0dHA6Ly93d3cuaGFuaS5jby5rci9hcnRpL3NvY2lldHkvaGVhbHRoLzk0NzE4OS5odG1s0gEA?oc=5" target="_blank">부천 쿠팡물류센터 관련 코로나19 확진자 최소 109명</a>&nbsp;&nbsp;<font color="#6f6f6f">한겨레</font></li><li><a href="https://news.google.com/__i/rss/rd/articles/CBMiJ2h0dHBzOi8vbmV3cy5qb2lucy5jb20vYXJ0aWNsZS8yMzc4OTYxONIBK2h0dHBzOi8vbW5ld3Muam9pbnMuY29tL2FtcGFydGljbGUvMjM3ODk2MTg?oc=5" target="_blank">쿠팡 물류센터 총 108명 확진…"이태원클럽 학원강사발 추정" - 중앙일보</a>&nbsp;&nbsp;<font color="#6f6f6f">중앙일보 모바일</font></li><li><a href="https://news.google.com/__i/rss/rd/articles/CBMiK2h0dHBzOi8vd3d3LnlvdXR1YmUuY29tL3dhdGNoP3Y9R1RwQ3ZuQ2I2U1nSAQA?oc=5" target="_blank">코로나19 신규 확진 39명…수도권 이어 지역서도 산발 감염</a>&nbsp;&nbsp;<font color="#6f6f6f">뉴스TVCHOSUN</font></li><li><strong><a href="https://news.google.com/stories/CAAqOQgKIjNDQklTSURvSmMzUnZjbmt0TXpZd1NoTUtFUWlBMHR6bGxZQU1FV1BYSElmd2VCR2RLQUFQAQ?oc=5" target="_blank">Google 뉴스에서 전체 콘텐츠 보기</a></strong></li></ol>",
          "soruce" : "한겨레"
        }]
    }
  }
}

 

data class RespNewsRss(
    @field:SerializedName("channel") var channel: RespNewsRssChannel? = null
)

class RespNewsRssChannel(
    @field:SerializedName("items") var newsList: List<RespNews>? = null
)

data class RespNews(
    @field:SerializedName("title") var title: String? = null,
    @field:SerializedName("link") var link: String? = null,
    @field:SerializedName("guid") var guid: String? = null,
    @field:SerializedName("pubDate") var pubDate: String? = null,
    @field:SerializedName("description") var description: String? = null,
    @field:SerializedName("source") var source: String? = null
)

 

 

 

 

실제 사용

MainActivity

    private val newsService = GoogleNewsServiceFactory.makeNewsService(BuildConfig.DEBUG, "https://news.google.com")

    private fun initViews() {
        btn_main.setOnClickListener {
            newsService.getNews("ko", "KR", "KR:ko")
                .enqueue(object : Callback<RespNewsRss> {
                    override fun onResponse(
                        call: Call<RespNewsRss>,
                        response: Response<RespNewsRss>
                    ) {
                        tv_main.text = response.body()?.channel?.newsList?.toString()
                    }

                    override fun onFailure(call: Call<RespNewsRss>, t: Throwable) {
                        Toast.makeText(this@MainActivity, t.message, Toast.LENGTH_SHORT).show()
                    }
                })
        }
    }

 

 

 

 


Commit ad3ddc26

 

 

+Rx

1. Service

2. OkHttpClient

3. Retrofit을 이용한 Service 구현체

 

 

dependecies 추가

implementation "com.jakewharton.retrofit:retrofit2-rxjava2-adapter:1.0.0"

 

 

1. Service

interface GoogleNewsService {

    @GET("/rss")
    fun getNews(
        @Query("hl") hl: String,
        @Query("gl") gl: String,
        @Query("ceid") ceid: String
    ): Single<RespNewsRss>

}

리턴 타입만 바꼈다.

 

 

2. 동일함

 

 

3. Retrofit을 이용한 Service 구현체

    private fun makeNewsService(baseUrl: String, okHttpClient: OkHttpClient): GoogleNewsService {
        val retrofit = Retrofit.Builder()
            .baseUrl(baseUrl)
            .client(okHttpClient)
            .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
            .addConverterFactory(SimpleXmlConverterFactory.create())
            .build()

        return retrofit.create(GoogleNewsService::class.java)
    }

 

addCallAdapterFactory(RxJava2CallAdapterFactory.create())

기존의 리턴받던 Call에서 Rx객체로 변경하기 위한 한줄이 추가되었다.

 

 

실제사용

    private val newsService = GoogleNewsServiceFactory.makeNewsService(BuildConfig.DEBUG, "https://news.google.com")
    private val compositeDisposable = CompositeDisposable()

    private fun initViews() {
        btn_main.setOnClickListener {
            newsService.getNews("ko", "KR", "KR:ko")
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe { resp ->
                    val newsList = resp.channel?.newsList
                    tv_main.text = newsList.toString()
                }.let {
                    compositeDisposable.add(it)
                }
        }
    }