RxJava의 가장 훌룡한 사용예에 해당하는 네트웍통신 예제로서 Retrofit만한게 없을것 같아 정리해 보겠다.
GitHub의 https://api.github.com/users/{사용자계정}/repos를 샘플로 진행하겠다.
내 계정으로 부터 위 통신을 수행하면 다음과 같은 응답이 내려온다
[
{
"id": 34425769,
"node_id": "MDEwOlJlcG9zaXRvcnkzNDQyNTc2OQ==",
"name": "JSWheelView",
"full_name": "samse/JSWheelView",
"private": false,
"owner": {
"login": "samse",
"id": 3222919,
"node_id": "MDQ6VXNlcjMyMjI5MTk=",
"avatar_url": "https://avatars.githubusercontent.com/u/3222919?v=4",
"gravatar_id": "",
"url": "https://api.github.com/users/samse",
"html_url": "https://github.com/samse",
"followers_url": "https://api.github.com/users/samse/followers",
"following_url": "https://api.github.com/users/samse/following{/other_user}",
"gists_url": "https://api.github.com/users/samse/gists{/gist_id}",
"starred_url": "https://api.github.com/users/samse/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/samse/subscriptions",
"organizations_url": "https://api.github.com/users/samse/orgs",
"repos_url": "https://api.github.com/users/samse/repos",
"events_url": "https://api.github.com/users/samse/events{/privacy}",
"received_events_url": "https://api.github.com/users/samse/received_events",
"type": "User",
"site_admin": false
},
"html_url": "https://github.com/samse/JSWheelView",
"description": "Wheel Control View",
"fork": true,
"url": "https://api.github.com/repos/samse/JSWheelView",
"forks_url": "https://api.github.com/repos/samse/JSWheelView/forks",
"keys_url": "https://api.github.com/repos/samse/JSWheelView/keys{/key_id}",
"collaborators_url": "https://api.github.com/repos/samse/JSWheelView/collaborators{/collaborator}",
"teams_url": "https://api.github.com/repos/samse/JSWheelView/teams",
"hooks_url": "https://api.github.com/repos/samse/JSWheelView/hooks",
"issue_events_url": "https://api.github.com/repos/samse/JSWheelView/issues/events{/number}",
"events_url": "https://api.github.com/repos/samse/JSWheelView/events",
"assignees_url": "https://api.github.com/repos/samse/JSWheelView/assignees{/user}",
"branches_url": "https://api.github.com/repos/samse/JSWheelView/branches{/branch}",
"tags_url": "https://api.github.com/repos/samse/JSWheelView/tags",
"blobs_url": "https://api.github.com/repos/samse/JSWheelView/git/blobs{/sha}",
"git_tags_url": "https://api.github.com/repos/samse/JSWheelView/git/tags{/sha}",
"git_refs_url": "https://api.github.com/repos/samse/JSWheelView/git/refs{/sha}",
"trees_url": "https://api.github.com/repos/samse/JSWheelView/git/trees{/sha}",
"statuses_url": "https://api.github.com/repos/samse/JSWheelView/statuses/{sha}",
"languages_url": "https://api.github.com/repos/samse/JSWheelView/languages",
"stargazers_url": "https://api.github.com/repos/samse/JSWheelView/stargazers",
"contributors_url": "https://api.github.com/repos/samse/JSWheelView/contributors",
"subscribers_url": "https://api.github.com/repos/samse/JSWheelView/subscribers",
"subscription_url": "https://api.github.com/repos/samse/JSWheelView/subscription",
"commits_url": "https://api.github.com/repos/samse/JSWheelView/commits{/sha}",
"git_commits_url": "https://api.github.com/repos/samse/JSWheelView/git/commits{/sha}",
"comments_url": "https://api.github.com/repos/samse/JSWheelView/comments{/number}",
"issue_comment_url": "https://api.github.com/repos/samse/JSWheelView/issues/comments{/number}",
"contents_url": "https://api.github.com/repos/samse/JSWheelView/contents/{+path}",
"compare_url": "https://api.github.com/repos/samse/JSWheelView/compare/{base}...{head}",
"merges_url": "https://api.github.com/repos/samse/JSWheelView/merges",
"archive_url": "https://api.github.com/repos/samse/JSWheelView/{archive_format}{/ref}",
"downloads_url": "https://api.github.com/repos/samse/JSWheelView/downloads",
"issues_url": "https://api.github.com/repos/samse/JSWheelView/issues{/number}",
"pulls_url": "https://api.github.com/repos/samse/JSWheelView/pulls{/number}",
"milestones_url": "https://api.github.com/repos/samse/JSWheelView/milestones{/number}",
"notifications_url": "https://api.github.com/repos/samse/JSWheelView/notifications{?since,all,participating}",
"labels_url": "https://api.github.com/repos/samse/JSWheelView/labels{/name}",
"releases_url": "https://api.github.com/repos/samse/JSWheelView/releases{/id}",
"deployments_url": "https://api.github.com/repos/samse/JSWheelView/deployments",
"created_at": "2015-04-23T01:15:40Z",
"updated_at": "2015-04-23T01:15:41Z",
"pushed_at": "2015-04-01T05:44:23Z",
"git_url": "git://github.com/samse/JSWheelView.git",
"ssh_url": "git@github.com:samse/JSWheelView.git",
"clone_url": "https://github.com/samse/JSWheelView.git",
"svn_url": "https://github.com/samse/JSWheelView",
"homepage": null,
"size": 244,
"stargazers_count": 0,
"watchers_count": 0,
"language": "Objective-C",
"has_issues": false,
"has_projects": true,
"has_downloads": true,
"has_wiki": true,
"has_pages": false,
"forks_count": 0,
"mirror_url": null,
"archived": false,
"disabled": false,
"open_issues_count": 0,
위 응답을 기준으로 data class를 먼저 정의한다.
data class Owner(val userId: String, val avatar_url: String, val url: String, val htmlUrl: String, val repos_url: String)
data class Repo(val name: String, val full_name: String, val private: Boolean, val owner: Owner, val htmlUrl: String,
val description: String)
인터페이스를 정의한다.
여기서는 같은 일을 하는 일반 Call<T>와 Observable<T>를 반환하는 두가지를 비교하기 위해 정의하였다.
interface GitHubService {
@GET("users/{user}/repos")
fun listRepos(@Path("user") user: String): Call<List<Repo>>
@GET("users/{user}/repos")
fun listReposRx(@Path("user") user: String): Observable<List<Repo>>
}
Retrofit 객체를 빌드한다.
RxJava2CallAdapterFactory를 추가하여 Observable를 반환할 수 있도록 한다.
var retrofit: Retrofit? = Retrofit.Builder()
.baseUrl("https://api.github.com/")
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
// .client(createClient())
.build()
일반 Call<T> 호출의 예
val service: GitHubService? = retrofit?.create(GitHubService::class.java)
service?.apply {
val call: Call<List<Repo>> = listRepos("samse")
call.enqueue(object : Callback<List<Repo>> {
override fun onResponse(call: Call<List<Repo>>, response: Response<List<Repo>>) {
println("###################################")
println("ret ${response.body()}")
println("###################################")
}
override fun onFailure(call: Call<List<Repo>>, t: Throwable) {
t.printStackTrace()
}
})
}
RxJava Observable<T> 호출의 예
val service: GitHubService? = retrofit?.create(GitHubService::class.java)
service?.apply {
listReposRx("samse")
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe ({ repos ->
println("###################################")
println("Repo count=${repos.size}")
for (v in repos) {
println(" $v")
}
println("###################################")
}, { error ->
println(error)
})
}
Retrofit이 네트웍통신에 대한 모든 방식을 지원하고 귀찮은 예외처리까지 해주는 데다가 RxJava도 가능하니 이를 통해서 좀더 깔끔하고 가독성 좋은 코드를 생산할 수 있겠다.
Retrofit은 Interceptor를 통신 중간에 추가해 넣어서 송/수신 구간에 끼어 들어서 헤더에 정보를 추가하거나 통신로깅을 수행하는 등의 작업을 가능하게 해준다.
아래는 송신패킷에 임의의 헤더값을 추가해주는 Interceptor의 예이다.
class HttpLoggingInterceptor: Interceptor {
override fun intercept(chain: Interceptor.Chain): okhttp3.Response = with(chain) {
val newRequest = request().newBuilder()
.addHeader("X-New-UA", "My custom header")
.build()
proceed(newRequest)
}
}
이렇게 정의한 Interceptor는 Retrofit를 build할때 추가해준다.
var retrofit: Retrofit? = Retrofit.Builder()
.baseUrl("https://api.github.com/")
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.client(createClient())
.build()
fun createClient(): OkHttpClient {
return OkHttpClient.Builder()
.addInterceptor(HttpLoggingInterceptor())
.build()
}