본문 바로가기
개발/안드로이드

안드로이드 MVVM에 GraphQL 적용하기(2/2)

by 창이2 2020. 7. 19.

지난 포스팅에서는 Dependency Injection을 진행하고 GraphQL에서 사용되는 DAO 파일을 만들어 보았습니다. 

확장자이름이 graphql 로 만들면 어떻게 DAO파일이 생성되는지 궁금해 하실수 있을텐데요. 빌드시 자동으로 graphql파일명과 관련된 클래스 파일이 생성됩니다. 

프로젝트폴더에서 보시면 지정한 타입이 접미사로 붙고(query, mutation, subscription) 클래스파일이 만들어진것을

확인할 수 있습니다. 

 

이제 repository를 세팅하겠습니다. 튜토리얼에서는 크게 3가지의 통신(Get-query, Post-mutation, WebSocket-subscription) 을 보여주는데 저는 아이템 리스트를 가져오는 메소드만 설명드리겠습니다. 우선 repository 생성합니다.

interface ApiRepository {

   suspend fun getLaunchList() : Response<LaunchListQuery.Data>?

}
class DefaultRepository @Inject constructor(@ApplicationModule.ApiServerNetworkSource private val apolloClient : ApolloClient,
                                            @ApplicationModule.ApiRealTimeServerNetworkSource private val apolloRealTimeClient: ApolloClient,
                                            private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO) : ApiRepository {

    override suspend fun getLaunchList(): Response<LaunchListQuery.Data>? {
        var result :  Response<LaunchListQuery.Data> ?= null
        withContext(ioDispatcher){
            val response = apolloClient.query(LaunchListQuery()).toDeferred().await()
            val launch = response.data?.launches
            Log.d("LaunchList", launch.toString())
            result = response

        }
        return result!!
    }

}

 

이제 이 repository를 이용할 view model을 생성하겠습니다.

 

class MainViewModel @Inject constructor(
    private val repository: DefaultRepository
) : ViewModel() {

    private var _items :MutableLiveData<List<LaunchListQuery.Launch?>> = MutableLiveData(listOf())
    val items : LiveData<List<LaunchListQuery.Launch?>> = _items

    fun getItems(){
        viewModelScope.launch {
            val items = repository.getLaunchList()
          
            _items.value = items?.data?.launches!!.launches
        }
    }

}

이제 메인에서 recyclerview를 이용하여 아이템 리스트를 보여주도록 하겠습니다. recyclerview adapter 는 listadapter를 사용했고 데이터바인딩을 이용해서 각각의 아이템을 뷰모델에 바인딩 시켜주었습니다.

 

package com.example.graphql_mvvm.main

import android.util.Log
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.example.graphql_mvvm.databinding.MainItemBinding
import com.example.rocketreserver.LaunchListQuery

class MainAdapter(private val viewModel: MainViewModel,val itemClickListener: ItemClickListener)
    : ListAdapter<LaunchListQuery.Launch, RecyclerView.ViewHolder>(LaunchDiffCallback()) {


    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        val binding = MainItemBinding.inflate(LayoutInflater.from(parent.context),parent,false)
        return MainViewHolder(binding)
    }


    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int,payload:List<Any>) {
        Log.d("TAG","onBindViewHolder")
        val item = getItem(position)
        when(holder){
            is MainViewHolder ->{
               holder.bind(viewModel,item)
               holder.binding.idTextView.setOnClickListener {
                   itemClickListener.onClick(item.id)
               }
            }
        }

    }

    class MainViewHolder (val binding : MainItemBinding): RecyclerView.ViewHolder(binding.root){

        fun bind(viewModel: MainViewModel, item: LaunchListQuery.Launch){
            binding.idTextView.text = item.id
            binding.viewmodel = viewModel
            binding.launch = item
            binding.executePendingBindings()

        }
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {

    }
}

class LaunchDiffCallback : DiffUtil.ItemCallback<LaunchListQuery.Launch>() {
    override fun areItemsTheSame(oldItem: LaunchListQuery.Launch, newItem: LaunchListQuery.Launch): Boolean {
        return oldItem.id == newItem.id
    }

    override fun areContentsTheSame(oldItem: LaunchListQuery.Launch, newItem: LaunchListQuery.Launch): Boolean {
        return oldItem == newItem
    }

}

 

아이템을 바인딩 시켜주기위한 바인더

 

object AdapterBinder {

    @BindingAdapter("app:items")
    @JvmStatic
    fun setItems(view: RecyclerView, items: List<LaunchListQuery.Launch>?) {
        (view.adapter as MainAdapter).submitList(items)
    }


    @BindingAdapter("app:image")
    @JvmStatic
    fun setImage(view: ImageView, url: String?) {
        Glide.with(view.context).load(url).override(1000, 1000).into(view)
    }
}

 

 

그리고 이제 메인을 작성해보겠습니다.

 

class MainActivity : DaggerAppCompatActivity() {

    lateinit var binding : ActivityMainBinding
    private var mAdapter: MainAdapter? = null

    @Inject
    lateinit var tokenIntercepter: TokenIntercepter

    @Inject
    lateinit var viewModelFactory: ViewModelProvider.Factory
    private val viewModel by viewModels<MainViewModel> { viewModelFactory }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater).apply {
            viewmodel = viewModel
        }
        setContentView(binding.root)

        binding.lifecycleOwner = this
        val listener = object : ItemClickListener {
            override fun onClick(id: String) {

            }
        }

        mAdapter = MainAdapter(viewModel, listener)
        tokenIntercepter.token = "Y2tkcmI3MDE3QGhhaWkuaW8="
        binding.recyclerView.apply {
            adapter = mAdapter
        }

        viewModel.getItems()
        mAdapter!!.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver(){
            override fun onItemRangeChanged(positionStart: Int, itemCount: Int) {
                super.onItemRangeChanged(positionStart, itemCount)
                (binding.recyclerView.layoutManager as LinearLayoutManager).scrollToPositionWithOffset(itemCount,0)
            }
        })

    }
}

 

이제 간단하게 GraphQL을 이용해서 데이터를 가져오는 코드가 완성 되었습니다.

토큰인터셉터의 토큰은 위 프로젝트 진행할때 로그인 하는 부분이 있는데 이메일로 로그인을 하면 토큰을 발급해줍니다.

그 토큰을 인터셉터에 넣어주면 @Header 어노테이션을 이용할 필요가 없이 토큰을 넣을 수 있습니다.

 

토큰 뿐만이 아니라 다른 세팅값도 설정할 수 있는데 이전 포스팅의 토큰 인터셉터 코드를 참고해서 알아보시는 것도 좋을거 같습니다.  실행결과는 아래와 같습니다

 

 

 

 

깃허브 주소는 GRAPHQL_MVVM 프로젝트를 참고하시면 될거 같습니다.

https://github.com/ckdrb7017/BlogProject

 

ckdrb7017/BlogProject

개인 블로그 프로젝트를 올리는 곳입니다. Contribute to ckdrb7017/BlogProject development by creating an account on GitHub.

github.com

 

댓글