본문 바로가기

안드로이드/안드로이드 개인공부

[안드로이드 개인공부] Retrofit에 대해서 단계별로 공부해보자!

프로젝트 진행하면서 무작정 Retrofit 라이브러리를 사용했었다.

 

그래서인지 단계별로 왜 이렇게 구현하는 것인지, 왜 이것을 사용하는지 아무것도 모른 상태로 사용했다.

그렇기에 여기에 Retrofit의 기본 단계들을 공부하여 정리하려고한다.

 

일단, Retrofit안드로이드 및 자바를 위한 안전한 REST 클라이언트라고 말할 수 있다.

 

2010년 당시에는 네트워크 Request 보내기 위해서는 low-level java connection을 통해서

스레딩, json 파싱 등을 스스로 구현하여 사용해야했다.

이것들은 너무 복잡했다.

 

하지만 Retrofit은 이러한 복잡한 구현들을 모두 숨긴다.

즉, API와 Interact 하는 것을 더 쉽게 도와준다.

 

그렇기에 개발자들은 더 쉬운 네트워크 통신을 하기 위해서 Retrofit을 사용하게 되었다.

 

Retrofit을 왜 사용하게 되었는지에 설명은 여기까지하고,

예제를 하나 구현해보면서 Retrofit을 어떻게 구현하는지 알아보도록하자.

 

예제를 간략하게 설명하자면,

Github API를 사용하여 사용자들의 repositories 리스트를 보여줄 것이다.

 

그리고, 구현 과정

1. Retrofit dependency를 추가한다.

2. API EndPoint를 설명한다.

3. JSON Mapping을 설계한다.

그 이유는 Github에서 사용하는 데이터 포맷이 JSON이기 때문이다.

4. 마지막으로 Retrofit Client를 만들고, 그것을 통해서 Request를 실행할 것이다.

 

자 그럼 시작해보자.

 

먼저, APP의 build.gradle에 2개의 dependencies를 추가할 것이다.

 

[build.gradle (app)]

implementation 'com.squareup.retrofit2:retrofit:2.6.0'
implementation 'com.squareup.retrofit2:converter-gson:2.6.0'

Retrofit은 그것 자체로 어떤 네트워킹도 하지 않는다.

개발자들이 편하게 사용하게 하기 위해서 Top Layer에 Build 되어 있다.

그렇기에 우리는 레트로핏을 첫 번째 dependency로 추가해줬다.

 

참고로, Retrofit은 Network Layout로서 OkHttp를 사용한다.

만약에 OkHttp의 특정버전을 쓰고 싶다면 따로 dependency에 특정버전을 implementation 하면된다.

 

Github API의 Data Format이 JSON이라고 했다.

JSON으로 부터 Java Object로 변환해줄 Converter(컨버터)가 필요하다.

그렇기에 우리는 JSON을 Java Object로 변환해줄 컨버터로 GSON을 사용하기 위해 두번째 dependency로 추가한다.

 

그다음 앱에서 Network Request를 할 수 있는 권한 여부를 체크를 해야한다.

AndroidManifests.xml에서 네트워크 사용할 수 있는 권한을 주자!

 

[AndroidManifest.xml]

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

 

이 예제의 목표는 앱에 사용자들의 Github Repositories의 리스트를 보여주는 것이기에

Endpoint사용자의 Repositories를 가져올 메소드가 필요하다.

 

[GitHubClient.kt]

 

1단계) Endpoint인 GitHubClient 라는 인터페이스를 만든다.

그리고, Repositories를 가져올 메소드를 선언한다.

사용자에 따라서 Repositories를 가져와야하기에, String 타입의 user 파라미터를 전달받고 있다.

그리고 반환값으로는 List<GitHubRepo>를 반환하는데, Repositories의 정보들을 리스트를 담아서 반환한다. 

package com.example.demoretrofit

import retrofit2.Call
import retrofit2.http.GET
import retrofit2.http.Path

interface GitHubClient {
    fun reposForUser(user:String): List<GitHubRepo>
}

여기서 우리는 이 메소드를 통해서 Request를 요청해야한다.

우리는 Get Request를 사용할 것인데, 이미 Annotation으로 만들어져있다.

 

2단계) @Get Annotation을 이용하여 Get Request를 사용할 수 있게 만든다.

package com.example.demoretrofit

import retrofit2.Call
import retrofit2.http.GET
import retrofit2.http.Path

interface GitHubClient {

    @GET("/users/pjs3065/repos")
    fun reposForUser(user:String): List<GitHubRepo>
}

현재 상대적인 주소를 pjs3065라는 user이름을 고정해서 넣었다.

하지만 우리는 동적 파라미터를 Request로 보내기를 원한다.

즉, username이 동적으로 대체되기를 원하는 것이다.

 

3단계) @Path() 어노테이션을 이용하여 username을 동적으로 대체하기

package com.example.demoretrofit

import retrofit2.Call
import retrofit2.http.GET
import retrofit2.http.Path

interface GitHubClient {

    @GET("/users/{user}/repos")
    fun reposForUser(@Path("user") user:String):List<GitHubRepo>
}

 @Path("user")는 Path Parmeter의 값을 런타임하는 동안 대체할 수 있다.

즉 reposForUser메소드의 파라미터로 넘어온 user의 값이 Path Parameter의 {user}에 대체되는 것이다.

자, 여기까지가 동기식으로 만든 메소드이다.

네트워크 통신은 네트워크를 연결하고, request해서 서버로 전달하고, 서버가 값을 찾아서 response해서 가져오게 된다.

이러한 과정이 동기식으로 진행된다면, 앱 상에서 UI는 Freeze될 것이고, Crashing이 빈번히 일어날 것이다.

 

그렇기에, 우리는 비동기식으로 만들어야한다.

 

4단계) Call<> object를 이용하여 반환값을 비동기식으로 구현한다. 

package com.example.demoretrofit

import retrofit2.Call
import retrofit2.http.GET
import retrofit2.http.Path

interface GitHubClient {

    @GET("/users/{user}/repos")
    fun reposForUser(@Path("user") user:String): Call<List<GitHubRepo>>
}

Call<List<GitHubRepo>>로 변환하여 비동기식으로 통신할 수 있게 작성했다.

 

여기서 GitHubRepo가 궁금할것이다.

GitHubRepo는 API를 통해서 어떤 데이터가 넘어오는지 Java Object에 정의하는 것이다.

간단한 예제이기에 name값만 선언해보자.

 

[GitHubRepo.kt]

package com.example.demoretrofit

import com.google.gson.annotations.SerializedName

data class GitHubRepo (@SerializedName("name") val name: String)

자 여기까지 우리는 레트로핏을 사용하기 위한 3번째 단계까지 완료했다.

 

* 참고

1. Retrofit dependency를 추가한다.

2. API EndPoint를 설명한다.

3. JSON Mapping을 설계한다.

 

이제, 4번째 단계인 Retrofit 객체를 만들어서 request를 보내고 response 받아보자!

Retrofit을 만들기 위한 효율적인 코드들이 존재하지만,

여기서 설명한 예제는 기본적이기 때문에,

 

MainActivity에서 만들고, request를 보내고, response를 받을 것이다.

 

Retrofit을 사용하기 위해서는 Retrofit.Builder를 통해 객체를 만들 수가 있다.

 

[MainActivity.kt]

 

1단계) Retrofit.Builder를 통해서 baseUrl을 설정하고, converter를 GSON Converter로 설정한다.

package com.example.demoretrofit

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import kotlinx.android.synthetic.main.activity_main.*
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val builder: Retrofit.Builder = Retrofit.Builder()
            .baseUrl("https://api.github.com/")
            .addConverterFactory(GsonConverterFactory.create())
	}
}

* baseUrl은 우리가 원할 때마다 기본 URL을 간단하게 변경이 가능하고, 모든 endpoint를 변경할 필요가 없는 이점으로 사용된다.

 

2단계) 빌더를 이용해서 Retrofit의 객체를 만든다.

package com.example.demoretrofit

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import kotlinx.android.synthetic.main.activity_main.*
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val builder: Retrofit.Builder = Retrofit.Builder()
            .baseUrl("https://api.github.com/")
            .addConverterFactory(GsonConverterFactory.create())

        val retrofit: Retrofit = builder.build()
    }
}

만들어진 builder 객체를 이용하여 build() 메소드를 호출하면 retrofit 객체가 만들어지게 된다.

 

3단계) 이제 Request를 할것이다.

package com.example.demoretrofit

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import kotlinx.android.synthetic.main.activity_main.*
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val builder: Retrofit.Builder = Retrofit.Builder()
            .baseUrl("https://api.github.com/")
            .addConverterFactory(GsonConverterFactory.create())

        val retrofit: Retrofit = builder.build()

        val client: GitHubClient = retrofit.create(GitHubClient::class.java)

        val call: Call<List<GitHubRepo>> = client.reposForUser("pjs3065")
    }
}

 일단 request를 하기 위해서는 client가 필요하다.

retrofit.create(클라이언트 클래스(인터페이스))를 통해서 클라이언트를 만들 수 있다.

그리고 그 클라이언트의 메소드인 reposForUser(username)을 통해서 request를 호출할 수 있다.

 

자, 이제 request를 완료했으니, response를 받아야한다.

reponse를 받기 위해서는 두가지 옵션이 있다.

동기/ 비동기

 

우리는 UI 스레드에서 진행해야하기에 Asynchronously로 구현하는 것이 필요하다.

이때 사용하는 것이 enqueue라는 메소드이다.

 

enqueue라는 메소드callback 을 기대하게 되는데, callback은 서버로 부터 response 될때마다 실행되게 된다.

 

onFailure() 는 네트워크 failure, No Internet 등 오류가 나면 호출된다.

onResponse() 는 실제 서버로부터 response를 받을 수 있다.

 

4단계) Response를 받는다.

package com.example.demoretrofit

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import kotlinx.android.synthetic.main.activity_main.*
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val builder: Retrofit.Builder = Retrofit.Builder()
            .baseUrl("https://api.github.com/")
            .addConverterFactory(GsonConverterFactory.create())

        val retrofit: Retrofit = builder.build()

        val client: GitHubClient = retrofit.create(GitHubClient::class.java)

        val call: Call<List<GitHubRepo>> = client.reposForUser("pjs3065")

        call.enqueue(object : Callback<List<GitHubRepo>> {
            override fun onFailure(call: Call<List<GitHubRepo>>, t: Throwable) {
                Log.e("debugTest","error:(${t.message})")
            }

            override fun onResponse(
                call: Call<List<GitHubRepo>>,
                response: Response<List<GitHubRepo>>
            ) {
                val repos:List<GitHubRepo>? = response.body()
                var reposStr = ""

                repos?.forEach { it ->
                    reposStr += "$it\n"
                }
                textView.text = reposStr
            }
        })
    }
}

 나는 이렇게 받아온 response의 body()를 가져와서 텍스트뷰에 텍스트값으로 넣어서 출력해보았다.

 

결과는 아래 그림과 같이 Repotories name이 나열되게 된다.

Github User의 Repotories name 값 출력

 

Retrofit 간단 예제 전체 코드 보러가기