Picasso With MVVM

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

API에서 데이터를 받아와 View에 출력해주는데 이미지 부분을 Picasso 라이브러리를 이용해서 출력해보도록 하겠습니다.

Picasso란?

Android앱을 제작하다보면 이미지를 표시해야하는 경우들이 많이 발생합니다. 그럴때는 이미지 라이브러리를 이용해 사용자들에게 보여주어야하는데 이미지 라이브러리들 중 가장 대표하는 것이 GlidePicasso입니다.

Picasso 라이브러리의 특징

  • Glide와 비교해서 기능 항목이 적은 편입니다.

  • 캐싱 방법 : 전달받은 이미지를 사이즈 변환 없이 저장합니다.

  • 메모리 사용량 : 원본 이미지를 저장함으로 상대적으로 많은 메모리를 사용합니다.

  • 로딩 시간 : 인터넷 이미지 로딩 빠릅니다.
    -> 캐시 이미지 로딩 느림

  • 이미지 품질 : Glide에 비해 비트 수가 높아 고품질 이미지를 표현할 수 있습니다.

  • GIF를 지원하지 않습니다.

Glide와 비교해서 Picasso의 가장 큰 장점은 고화질의 이미지를 출력해줄 수 있다는 것입니다.

글의 마지막에 작성하겠지만 위의 사항은 사용시 주의해야 할 부분이기도 합니다. Picasso의 경우 이미지를 원본 그대로 가지고 오기 때문에 많이 불러오다 보면 메모리 부족으로 앱이 강제종료될 수 도있습니다.

주의사항은 마지막 부분에 작성하도록 하고 사용법에 대해 설명드리겠습니다.

Gradle

우선 라이브러리를 사용하기 위해 다음과 같이 gradle에서 implementation해줍니다.

//build.gradle(Module)
// 버전의 차이는 있을 수 있습니다!!! 

	//API 통신을 위한 Retrofit2
    implementation 'com.squareup.retrofit2:retrofit:2.9.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
    
    //Picasso 라이브러리 
    implementation 'com.squareup.picasso:picasso:2.71828'
    
    // MVVM 패턴 및 코루틴(비동기)을 위한 선언
    implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.1'
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9

또한 ViewBinding 사용을 위해 ViewBinding = true를 android{} 안에 선언 해줍니다.

Retrofit

우선 통신을 위한 작업들을 먼저 해보겠습니다.

Json을 통해 데이터를 받아올텐데 해당 Json의 형태에 맞게 Data Class를 선언해줍니다.

RecyclerList

data class RecyclerList(val items: ArrayList<RecyclerData>)
data class RecyclerData(val name: String, val description: String ,val owner: Owner)
data class Owner(val avatar_url : String)

다음은 API의 Url 변수를 선언해주고 Retrofit Instance 메서드를 생성하겠습니다.

RetroInstance.kt

    companion object{
        val baseUrl = "https://api.github.com/search/"

        fun getRetroInstance() : Retrofit{

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

다음은 비동기 작업을 통해 데이터를 API에서 받아오는 메서드를 생성해보겠습니다.

RetroService(Interface)

    @GET("repositories")
    suspend fun getDataFromApi(@Query("q")query : String): RecyclerList

해당 API에서 Data를 받아오는 EndPoint는 repositories이기 때문에 GET 어노테이션 Value로 넣어주고 Query를 통해 위치가 바뀌어도 값을 동적으로 받아옵니다

미리 형태를 선언해 놓은 RecyclerList 사용해 쉽게 파싱하도록 해줍니다.

Adpater

RecyclerView에 사용될 Adpater 및 ViewHolder를 선언해줍니다

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



    init {

    }
        var items = ArrayList<RecyclerData>()

// 데이터를 받아오고 난뒤 notifyDataSetChanged를 
통해 RecycelrView를 새로 업데이트 해줍니다. 

    fun setUpdateData(items : ArrayList<RecyclerData>){
        this.items = items
        notifyDataSetChanged()
    }
    
        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(items.get(position))
    }

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

MyViewHolder에서 Picasso 라이브러리를 사용하게됩니다.

class MyViewHolder(private val binding: RecyclerListBinding) : RecyclerView.ViewHolder(binding.root){
        val imageView = binding.iamgeVIew
        val textTitle =  binding.tvTitle
        val textContent =  binding.tvContent

        fun bind(data : RecyclerData){
            textTitle.text = data.name
            textContent.text = data.description

            val url = data.owner.avatar_url

            Picasso.get()
                .load(url)
                .into(imageView)
        }

    }
  • load() : 이미지의 경로

  • into() : 받아온 이미지를 받을 공간

위에서는 사용하지 않았지만 다음과 같은 함수도 있습니다.

  • error() : 에러시, 대체할 이미지

  • resize() : 이미지 크기 조정

  • placeholder(): 다운로드 중 표시할 이미지

  • transform() : 이미지 후 처리

등과 같은 기능들이 있습니다.

ViewModel

 lateinit var recyclerListLiveData : MutableLiveData<RecyclerList>

    init {
        recyclerListLiveData = MutableLiveData()
    }

    fun getRecyclerListObserver() : MutableLiveData<RecyclerList>{
        return recyclerListLiveData
    }

    fun makeApiCall() {
        viewModelScope.launch(Dispatchers.IO){
            val retroInstance = RetroInstance.getRetroInstance().create(RetroService::class.java)
            val response = retroInstance.getDataFromApi("ny")
            recyclerListLiveData.postValue(response)
        }
    }

apicall을 만들어주고 받아온 데이터를 MutableLiveData() 형태로 선언해줍니다.

ViewModel에 선언된 call기능을 Fragment에서 사용할 수 있도록 ViewModelProvider를 통해 Fragment에서 사용할 수 있도록 합니다.

Activity/Fragment

Acitivty에서는 SupportFragmentManager를 통해 Activity에서 Fragment로 화면전환을 하고

MainActivity.kt

  private fun setUpFragment(){

        supportFragmentManager.beginTransaction().replace(R.id.fragmentContainer,RecyclerListFragment.newInstance()).commit()
    }
}

Framgnet에서는

위에서 말한것 처럼 API CALL을 사용할 수있게 가져와줍니다.

  private fun viewModelProvider(){
        val viewModel = ViewModelProvider(this).get(MainActivityViewModel::class.java)
        viewModel.getRecyclerListObserver().observe(viewLifecycleOwner, Observer<RecyclerList> {
            if(it != null) {
                recyclerAdapter.setUpdateData(it.items)
            } else {
                Toast.makeText(activity, "Error in getting data", Toast.LENGTH_SHORT).show()
            }
        })
        viewModel.makeApiCall()
    }

다음은 RecyclerView에 어댑터 및 표시 방식을 선언해줍니다.

   private fun initViewModel() = with(binding){
        recyclerview.layoutManager = LinearLayoutManager(activity)
        val decoration = DividerItemDecoration(activity, DividerItemDecoration.VERTICAL)
        recyclerview.addItemDecoration(decoration)

        recyclerAdapter = RecyclerAdapter()
        recyclerview.adapter = recyclerAdapter
    }

Kotlin의 with을 사용해서 binding을 앞에 붙이지 않더라도 값을 받아올 수 있게 해줍니다.

이렇게 하면 모든 준비가 끝났습니다.

API 통신을 해야하기에 AndroidManifest에 INTERNET 권한 설정도 해줘야합니다.

전체 코드는 깃 허브 주소를 남겨두도록 하겠습니다.

https://github.com/heetaeheo/MVVM-Using-Coroutine-Retrofit-Picasso.git

Picasso 라이브러리 사용시 주의점

  1. Fit 또는 Resize를 사용하지 못하는 경우

피카소에서는 into를 통해서 이미지를 할당하는데 Bitmap의 경우 fit이나 centerCrop과 같은 기능을 사용할 수 없기에 후처리하는 커스텀 함수를 생성하고 transform을 통해 후처리를 해줘야합니다.

  1. 메모리 증가 문제

onBitmapLoaded를 통해 넘어온 bitmap을 가공해서 사용할 경우 가공된 bitmap을 캐시할 수 없기 때문에 메모리 누수가 발생합니다.

그렇기에 onBitmapLoaded로 넘어오기 이전에 이미지 처리를 해야 가공된 이미지까지 캐시를 해 메모리 누수를 막을 수 있습니다.

references

https://jasonbla.tistory.com/15
https://square.github.io/picasso
https://github.com/square/picasso

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

0개의 댓글