[MVVM 패턴 공부] Retrofit+ ViewModel

LeeEunJae·2022년 9월 19일
2

Study Kotlin

목록 보기
17/20

Retrofit 을 이용해서 GET 요청을 하고, 그 결과를 RecyclerView 에 적용하는 코드를 ViewModel 을 이용해서 구현해보는 간단한 예제를 해보도록 하겠습니다.

📌 실행 결과

GET 요청 도메인

https://raw.githubusercontent.com/googlecodelabs/kotlin-coroutines/master/advanced-coroutines-codelab/sunflower/src/main/assets/plants.json

해당 링크로 들어가보면 JSON 데이터가 존재합니다.
안드로이드에서 GET 요청을 통해 이 데이터를 가져오도록 하겠습니다.

의존성 추가

이 예제를 진행하기 위해서 필요한 라이브러리를 추가해줍니다.

implementation 'com.github.bumptech.glide:glide:4.12.0' // 이미지로딩 라이브러리
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0' // viewModelScope
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9") // coroutine
implementation 'com.squareup.retrofit2:retrofit:2.9.0' // retrofit
implementation 'com.squareup.retrofit2:converter-gson:2.9.0' // GsonConverter

data class

data class Plant(
    val plantId: String,
    val name: String,
    val description: String,
    val growZoneNumber: Int,
    val wateringInterval: Int,
    val imageUrl: String
)

위에서 봤던 JSON 데이터를 가져오기 위해서 필요한 데이터 클래스 입니다.

RetrofitInstance

import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory

object RetrofitInstance{

    val BASE_URL = "https://raw.githubusercontent.com/"

    val client = Retrofit
        .Builder()
        .baseUrl(BASE_URL)
        .addConverterFactory(GsonConverterFactory.create())
        .build()

    fun getInstance(): Retrofit {
        return client
    }
}

Retrofit 객체를 가져오기 위해서 다음과 같이 코드를 작성해줍시다.
Retrofit 이 필요한 곳에서 RetrofitInstance.getInstance() 로 객체를 가져오면 됩니다.

Retrofit Interface

import com.dldmswo1209.retrofit_2.model.Plant
import retrofit2.http.GET

interface MyApi {

    @GET("googlecodelabs/kotlin-coroutines/master/advanced-coroutines-codelab/sunflower/src/main/assets/plants.json")
    suspend fun getAllPlant(): List<Plant>
}

GET 요청을 하기 위한 인터페이스를 구현해줍니다.
JSON 데이터가 리스트 형태이기 때문에 리턴 값은 Plant객체를 요소로 가지는 List 입니다.
getAllPlant()로 데이터 리스트를 가져오고, 그 리스트를 RecyclerView 의 리스트로 전달 할 것입니다.

Repository

import com.dldmswo1209.retrofit_2.api.MyApi
import com.dldmswo1209.retrofit_2.api.RetrofitInstance

class Repository {

    private val client = RetrofitInstance.getInstance().create(MyApi::class.java)

    suspend fun getAllData() = client.getAllPlant()
}

Repository 에서 Retrofit 객체를 생성하고, 서버에 데이터를 요청하는 작업을 합니다.

MainViewModel

ViewModel 에서는 Repository 에게 데이터를 가져오라고 명령하고, 그 값을 LiveData에 업데이트 해줍니다.

import android.util.Log
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.dldmswo1209.retrofit_2.model.Plant
import com.dldmswo1209.retrofit_2.repository.Repository
import kotlinx.coroutines.launch

class MainViewModel : ViewModel() {

    private val repository = Repository()

    private val _result = MutableLiveData<List<Plant>>()
    val result: LiveData<List<Plant>>
        get() = _result

    fun getAllData() = viewModelScope.launch {
        Log.d("MainViewModel", repository.getAllData().toString())
        _result.value = repository.getAllData()
    }


}

LiveData 에 대한 부분은 이전에 게시물에서 사용해본적이 있으므로 설명은 생략하겠습니다.

ViewModel 에서 Repository 를 생성하고, getAllData 라는 함수를 정의합니다.

repository.getAllData() 로 데이터를 가져오고, MutableLiveData 에 값을 저장합니다.

Activity 에서 result 라는 LiveData 를 관찰(observe)하고, 값이 변경 되었을 시 리사이클러뷰에 데이터 리스트를 전달하면 되는 것 입니다.

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/rcv"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"/>
</androidx.constraintlayout.widget.ConstraintLayout>

text_row_item.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.appcompat.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="200dp"
    android:orientation="vertical">

    <androidx.constraintlayout.utils.widget.ImageFilterView
        android:id="@+id/imageArea"
        android:layout_width="match_parent"
        android:layout_height="150dp"/>

    <TextView
        android:id="@+id/textArea"
        android:text="textArea"
        android:textSize="40sp"
        android:layout_gravity="center"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>

</androidx.appcompat.widget.LinearLayoutCompat>

Adapter

import android.content.Context
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.dldmswo1209.retrofit_2.databinding.TextRowItemBinding
import com.dldmswo1209.retrofit_2.model.Plant

class CustomAdapter(val context: Context, val dataSet: List<Plant>) : RecyclerView.Adapter<CustomAdapter.ViewHolder>() {
    inner class ViewHolder(val binding: TextRowItemBinding): RecyclerView.ViewHolder(binding.root){
        fun bind(plant: Plant){
            binding.textArea.text = plant.name
            Glide.with(context)
                .load(plant.imageUrl)
                .into(binding.imageArea)
        }
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        return ViewHolder(TextRowItemBinding.inflate(LayoutInflater.from(parent.context),parent,false))
    }

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

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

MainActivity

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import com.dldmswo1209.retrofit_2.adapter.CustomAdapter
import com.dldmswo1209.retrofit_2.databinding.ActivityMainBinding
import com.dldmswo1209.retrofit_2.viewModel.MainViewModel

class MainActivity : AppCompatActivity() {

    private val binding by lazy{
        ActivityMainBinding.inflate(layoutInflater)
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(binding.root)

        val viewModel = ViewModelProvider(this)[MainViewModel::class.java]
        viewModel.getAllData()

        viewModel.result.observe(this, Observer {
            val customAdapter = CustomAdapter(this, it)
            binding.rcv.adapter = customAdapter
        })
    }
}

viewModel 을 생성하고 getAllData로 데이터를 가져옵니다 -> 그럼, LiveData 값이 변화하겠죠?

LiveData 값이 변화되는 것을 관찰하기 위해서 viewModel.result.observe 를 구현합니다.

profile
매일 조금씩이라도 성장하자

0개의 댓글