RxJava를 이용한 Retrofit 통신

HEETAE HEO·2022년 7월 4일
0
post-thumbnail

시작전 RxJava에 대해 간단하게 설명하고 프로젝트 설명을 하도록 하겠습니다.

  • RxJava 는 ReactiveX(Reactive Extension)를 Java로 구현한 라이브러리이고

RxJava를 안드로이드에 맞게 스케쥴러와 클래스들을 추가해 안드로이드 개발에 사용할 수 있게 한것이 RxAndroid 입니다.

위의 라이브러리를 사용해 반응형 프로그래밍을 구현할 수 있는데 반응형이라고 하면 어려울 수 있으니 예를 들어 보겠습니다.

현재 이 프로젝트에서 구현한 반응형 프로그래밍은 Edit Text에 텍스트를 입력하면 입력된 Text Data에 맞게 책 List를 출력해주는 프로젝트인데

사용자가 책 이름을 기입하고 일정시간 가만히 있으면 처음에는 모든 책에 대한 리스트가 있었지만 텍스트에 따라 결과가 바뀌게 되는 것입니다.

이렇게 사용자가 입력한 Data에 반응하여 결과를 출력하는 것이 반응형 프로그래밍인 것입니다.

이제 프로젝트 설명을 해보도록 하겠습니다.

Gradle

implementation를 추가해줍니다.

Retrofit 통신과 MVVM 패턴을 적용하기 위해 Lifecycle들도 추가해줍니다.

 	implementation 'androidx.recyclerview:recyclerview:1.2.1'
    implementation 'com.squareup.retrofit2:retrofit:2.9.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
    implementation 'com.github.bumptech.glide:glide:4.11.0'
    implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
    implementation 'com.squareup.retrofit2:adapter-rxjava2:2.9.0'

    implementation 'io.reactivex.rxjava2:rxandroid:2.1.0'
    implementation 'io.reactivex.rxjava2:rxjava:2.2.0'

또한 저는 ViewBinding을 사용하기 위해
다음과 같이 설정해줍니다.

android{
	buildFeatures{
    	viewBinding = true
    }
}

AndroidManifest

Retrofit 통신을 위해 인터넷 권한을 켜줍니다.

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

Retrofit 설정

우선 받아올 데이터들을 형식하기 위해 Json에 맞게 Data class들을 선언해줍니다.

data class BookListModel(val items : ArrayList<VolumeInfo>)
data class VolumeInfo(val volumeInfo: BookInfo)
data class BookInfo(val title:String, val publisher: String, val description : String, val imageLinks :ImageLinks)
data class ImageLinks(val smallThumbnail: String)

Instance 생성

API의 주소와 Instance를 생성해줍니다.

 companion object{
        val baseUrl = "https://www.googleapis.com/books/v1/" //volums?q=harry

        fun getRetroInstance() : Retrofit{

           return Retrofit.Builder()
                .baseUrl(baseUrl)
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .build()
        }
    }

EndPoint 설정

EndPoint를 설정해주며 책의 리스트를 받아오는 값은 "volumes"이기에 다음과 같이 선언해줍니다.

    @GET("volumes")
    fun getBookListFromApi(@Query("q") query: String): Observable<BookListModel>

Adapter

다음은 RecyclerView에 들어갈 Adapter를 작성해줍니다.

어댑터를 설정할 때 ViewHolder가 필요하기에
inner Class로 추가해줍니다.

우선 Adapter 부분에 대해서 작성하겠습니다.


class BookListAdapter : RecyclerView.Adapter<BookListAdapter.MyViewHolder>() {

    var bookListData = ArrayList<VolumeInfo>()
    
    
     override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
        val binding  = RecyclerListBinding.inflate(LayoutInflater.from(parent.context),parent,false)

        return MyViewHolder(binding)
    }

    override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
       holder.bind(bookListData[position])
    }

    override fun getItemCount(): Int {
       return  bookListData.size
    }

ViewBinding을 사용하기 때문에 view를 통해서 ViewHolder를 생성하는 것이 아닌 RecyclerListBinding을 inflate하여 ViewHolder를 생성합니다.

getItemCount의 경우 bookListData의 사이즈 만큼 생성을 합니다.

다음은 ViewHolder를 생성합니다.

inner class MyViewHolder(private val binding :RecyclerListBinding) : RecyclerView.ViewHolder(binding.root){

        val title = binding.title
        val description = binding.description
        val publisher = binding.publisher
        val imageView = binding.ImageView


        fun bind(data : VolumeInfo){
            title.text = data.volumeInfo.title
            description.text = data.volumeInfo.description
            publisher.text = data.volumeInfo.publisher

            val url = data .volumeInfo?.imageLinks?.smallThumbnail

            Glide.with(imageView)
                .load(url)
                .circleCrop()
                .into(imageView)
        }
    }

ViewHolder에서 recycler_list.xml의 id들과 연결하여 값을 bind 해줍니다.

ViewModel을 생성

ViewModel을 View에 API를 통해 받아온 데이터를 전달해줍니다.

MainViewModel.kt


    lateinit var bookList: MutableLiveData<BookListModel>


    init {
        bookList = MutableLiveData()
    }

BookListModel을 받아 MutableLiveData 타입을 정해주고

3가지 메서드를 만들어 줍니다.

  1. 위에 선언된 bookList를 반환해주는 메서드

  2. API에 Call을 할때 입력된 책 이름을 받아 list를 호출하고 getBookListObserverRx라는 메서드를 사용해 책 데이터를 뿌려주는 메서드를 여기서 동작하게 해준다.

  3. 2번에서 사용되는 getBookListObserverRx를 생성해줍니다.

fun getBookListObserver() : MutableLiveData<BookListModel>{
        return bookList
    }

다음과 같이 1번을 생성해줍니다.

이해하기 쉽게 3번을 먼저 설명하겠습니다.

 private fun getBookListObserverRx() : Observer<BookListModel> {
        return object : Observer<BookListModel>{
            override fun onSubscribe(d: Disposable) {
               //start showing progress indicator
            }

            override fun onNext(t: BookListModel) {
                bookList.postValue(t)
            }

            override fun onError(e: Throwable) {
                bookList.postValue(null)
            }

            override fun onComplete() {
              //hide progress indicator
            }


        }
    }

이 메서드는 Reactive X에서 사용하는 Emitter라는 인터페이스를 통해 데이터를 Push해주는데 override들에 대해 간단하게 설명하겠습니다.

  • onSubscribe은 한번만 실행이 되며 구독을 시작했음을 알리는 동작을 수행합니다.

  • onNext()는 데이터의 발행됐음을 알리는 이벤트인데 여기서는 apicall을 통해 받아오는 데이터를 post하는 동작을 수행합니다.

  • onError()는 오류가 발생했음을 알리는 이벤트 입니다. 이 프로젝트에서는 오류가 발생하면 아무 데이터도 post하지 않기 위해 null값을 넣어두었습니다.

  • onComplete()는 데이터 발행이 모두 완료되었음을 알리는 이벤트입니다.

다음은 API call 동작을 하는 메서드입니다.

fun makeApiCall(query: String) {

        val retroInstance = RetroInstance.getRetroInstance().create(RetroService::class.java)
        retroInstance.getBookListFromApi(query)
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(getBookListObserverRx())
    }
  • subscribeOn은 지정된 sheduler에서 데이터를 발행 및 처리하도록 observable을 변환해주는 동작을 수행합니다.

  • AndroiSchedulers에서 mainThread를 이용해 observe하도록 수행해주고

  • 3번 메서드에 선언된 방법으로 데이터를 발행해주기 위해 3번 메서드를 넣어줍니다.

MainActivity

MainActivity.kt

fun initSearchBox(){
        binding.inputBookName.addTextChangedListener(object :TextWatcher{
            override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {

            }

            override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
                loadAPIData(s.toString())
            }

            override fun afterTextChanged(s: Editable?) {

            }

        })
    }

사용자가 입력한 텍스트에 따라 api을 동작해야하기 때문에 onTextChanged에서 loadAPIData동작을 수행하게 선언해줍니다.

loadAPIData를 바로 살펴보자면

fun loadAPIData(input: String){
        viewModel = ViewModelProvider(this).get(MainViewModel::class.java)
        viewModel.getBookListObserver().observe(this, Observer<BookListModel>{
            if (it != null){
                // update adapter
                bookListAdapter.bookListData = it.items
                bookListAdapter.notifyDataSetChanged()
            } else{
                Toast.makeText(this,"Error",Toast.LENGTH_SHORT).show()
            }
        })
        viewModel.makeApiCall(input)
    }

viewModelProvider를 통해 ViewModel을 전달받습니다. 그리고 ViewModel에 선언되어있는 getBookListObserver를 실행해주고 데이터의 변화가 관찰이 된다면 adapter에 받아온 데이터를 넣어주고 notifyDataSetChagned를 통해 데이터으 변화를 알려줍니다.

그후 makeApiCall을 수행해 api에서 데이터를 받아와줍니다.

그리고 RecyclerView에다가 adapter를 넣어주면 됩니다.

 private fun initRecyclerView(){
        binding.recyclerView.apply {
            layoutManager = LinearLayoutManager(this@MainActivity)
            val decoration  = DividerItemDecoration(applicationContext, VERTICAL)
            addItemDecoration(decoration)
            bookListAdapter = BookListAdapter()
            adapter =bookListAdapter
        }
    }

전체 코드를 보고 싶으시다면 깃 허브 주소를 클릭해주세요 읽어주셔서 감사합니다.

https://github.com/heetaeheo/RxJava-Using-Retrofit2-Wtih-MVVM.git

profile
Android 개발 잘하고 싶어요!!!

0개의 댓글