Android Studio

안드로이드 스튜디오 Retrofit

끝까지 처음처럼 2023. 8. 10. 21:17
728x90

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)
        }

    }


}