안드로이드 스튜디오 Retrofit
Retrofit 이란?
통신 라이브러리 중 가장 많이 사용되는 대표적인 라이브러이 입니다.
REST API 통신을 위해 구현되었으며, Squareup사의 OkHttp 라이브러리의 상위 구현체 입니다.
Retrofit은 OkHttp를 네트워크 계층으로 활용하고 그 위에 구축되어 있습니다.
AsyncTask 없이 Background Thread 실행 -> Callback을 통해 Main Thread에서 UI 업데이트
Retrofit 장점
1. 빠른 성능 : Asynctask를 사용하지 않고 자체적인 비동기 실행과 스레드 관리를 통해 속도가 많이 개선
2. 간단한 구현 : Retrofit에서는 Request, Response 설정 등 반복적인 작업을 라이브러리에서 넘겨서 처리하므로 작업량이 줄어들고 사용하기 굉장히 편리합니다. 또한 동기 / 비동기에 대해서 쉽게 구현을 할 수 있습니다.
3. 가독성 : 애노테이션 사용으로 코드의 가독성이 뛰어나며, 직관적이 설계가 가능합니다.
안드로이드 스튜디오에서 사용하기 위한 셋팅으로는 하기와 같습니다.
1. Manifest 에 인터넷 권한을 허용하는 코드를 작성합니다.
<uses-permission android:name="android.permission.INTERNET" />
2. Gradle 의존성을 추가 합니다.
// Retrofit 라이브러리
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
// Gson 변환기 라이브러리
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
// Scalars 변환기 라이브러리
implementation 'com.squareup.retrofit2:converter-scalars:2.9.0'
Gson Converter : JSON 타입의 응답 결과를 객체로 변환해주는 Converter
Scalars Converter - 응답결과를 String자체로 받아서 보여주는 Converter
Moshi Converter Jackson Converter등 이 외의 여러 Converter가 있습니다.
3. REST API로 받아올 데이터를 변환하여 매핑할 데이터 클래스 선언
class Repository : ArrayList<RepositoryItem>()
data class License(
val key: String,
val name: String,
val node_id: String,
val spdx_id: String,
val url: String
)
data class Owner(
val avatar_url: String,
val events_url: String,
val followers_url: String,
val following_url: String,
val gists_url: String,
val gravatar_id: String,
val html_url: String,
val id: Int,
val login: String,
val node_id: String,
val organizations_url: String,
val received_events_url: String,
val repos_url: String,
val site_admin: Boolean,
val starred_url: String,
val subscriptions_url: String,
val type: String,
val url: String
)
package com.test.retrofitstudy
data class RepositoryItem(
val allow_forking: Boolean,
val archive_url: String,
val archived: Boolean,
val assignees_url: String,
val blobs_url: String,
val branches_url: String,
val clone_url: String,
val collaborators_url: String,
val comments_url: String,
val commits_url: String,
val compare_url: String,
val contents_url: String,
val contributors_url: String,
val created_at: String,
val default_branch: String,
val deployments_url: String,
val description: String,
val disabled: Boolean,
val downloads_url: String,
val events_url: String,
val fork: Boolean,
val forks: Int,
val forks_count: Int,
val forks_url: String,
val full_name: String,
val git_commits_url: String,
val git_refs_url: String,
val git_tags_url: String,
val git_url: String,
val has_discussions: Boolean,
val has_downloads: Boolean,
val has_issues: Boolean,
val has_pages: Boolean,
val has_projects: Boolean,
val has_wiki: Boolean,
val homepage: String,
val hooks_url: String,
val html_url: String,
val id: Int,
val is_template: Boolean,
val issue_comment_url: String,
val issue_events_url: String,
val issues_url: String,
val keys_url: String,
val labels_url: String,
val language: String,
val languages_url: String,
val license: License,
val merges_url: String,
val milestones_url: String,
val mirror_url: Any,
val name: String,
val node_id: String,
val notifications_url: String,
val open_issues: Int,
val open_issues_count: Int,
val owner: Owner,
val `private`: Boolean,
val pulls_url: String,
val pushed_at: String,
val releases_url: String,
val size: Int,
val ssh_url: String,
val stargazers_count: Int,
val stargazers_url: String,
val statuses_url: String,
val subscribers_url: String,
val subscription_url: String,
val svn_url: String,
val tags_url: String,
val teams_url: String,
val topics: List<String>,
val trees_url: String,
val updated_at: String,
val url: String,
val visibility: String,
val watchers: Int,
val watchers_count: Int,
val web_commit_signoff_required: Boolean
)
interface GithubService {
@GET("users/Kotlin/repos")
fun users(): Call<Repository>
}
@GET("users/Kotlin/repos") - 요청메소드 GET, baseUrl에 연결될 EndPoint 'posts/{post}
반환타입 Call<Repository> - Call은 응답이 왔을때 Callback으로 불려질 타입
PostResult - 요청GET에 대한 응답데이터를 받아서 DTO 객체화할 클래스 타입 지정
메소드명 "users" - 자유롭게 설정, 통신에 영향 x
Retrofit.Build를 통해 Retrofit 인스턴스 생성
- baseUrl, Converter, Client 설정 부분 (baseUrl는 꼭 / 로 끝나야 함, 아니면 예외 발생)
- Converter는 여러개 등록 가능, 등록 순서대로 변환 가능여부 판단, 변환 불가능하면
다음 컨버터 확인
GsonConverter를 마지막에 넣는걸 추천 (Gson은 변환이 불가능해도 가능하다고 반응함)
Interface 객체 구현
- retrofit을 통한 객체 구현, 추상 메소드 중 사용할 메소드 Call 객체에 등록
동기 / 비동기 통신작업 실행
- 비동기 enqueue 작업으로 실행, 통신종료 후 이벤트 처리를 위해 Callback 등록
- onResponse 성공 / onFailure 실패 구분하여 메인스레드에서 처리할 작업 등록
(onResponse가 무조건 성공 X, 실패코드(3xx & 4xx 등)에도 호출 - isSuccesful() 확인이 필요
package com.test.retrofitstudy
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.test.retrofitstudy.databinding.ActivityMainBinding
import com.test.retrofitstudy.databinding.RvItemBinding
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import retrofit2.http.GET
interface GithubService {
@GET("users/Kotlin/repos")
fun users(): Call<Repository>
}
class MainActivity : AppCompatActivity() {
lateinit var activityMainBinding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
activityMainBinding = ActivityMainBinding.inflate(layoutInflater)
setContentView(activityMainBinding.root)
val retrofit = Retrofit.Builder()
.baseUrl("https://api.github.com") // baseUrl 등록( 반드시 '/'로 마무리
.addConverterFactory(GsonConverterFactory.create())
.build()
val adapter = CustomAdapter()
activityMainBinding.run{
recyclerView.adapter = adapter
recyclerView.layoutManager = LinearLayoutManager(this@MainActivity)
}
activityMainBinding.buttonRequest.setOnClickListener{
val githubService = retrofit.create(GithubService::class.java)
githubService.users().enqueue(object: Callback<Repository> { // enqueue 를 사용하여 비동기 통신 실행 + 통신 완료 후 이벤트 처리 위한 Callback 리스너 등록
override fun onResponse(call: Call<Repository>, response: Response<Repository>) {
if(response.isSuccessful){ // 정상적으로 통신이 된 경우 여기 부분은 메인 스레드에서 작업하는 부분이다.
activityMainBinding.textView.text = "통신 성공"
adapter.userList = response.body() as Repository
adapter.notifyDataSetChanged()
} else {
// 통신이 실패한 경우
// onRespons가 무조건 성공 응답이 아니기에 확인이 필요하다.
activityMainBinding.textView.text = "통신 실패"
adapter.userList?.clear()
adapter.notifyDataSetChanged()
}
}
override fun onFailure(call: Call<Repository>, t: Throwable) {
// 인터넷 끊김, 예외 발생 등 시스템적인 이유
activityMainBinding.textView.text = "통신 오류"
}
})
}
}
inner class CustomAdapter: RecyclerView.Adapter<CustomAdapter.Holder>() {
var userList: Repository? = null
inner class Holder(val binding: RvItemBinding) : RecyclerView.ViewHolder(binding.root) {
fun setUser(user: RepositoryItem?){
user?.let{
binding.textName.text = user.name
binding.textId.text = user.git_url
Glide.with(binding.imageAvater).load(user.owner.avatar_url).into(binding.imageAvater)
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder {
val binding = RvItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return Holder(binding)
}
override fun getItemCount(): Int {
return userList?.size?: 0
}
override fun onBindViewHolder(holder: Holder, position: Int) {
val user = userList?.get(position)
return holder.setUser(user)
}
}
}