버그 잡이

Retrofit+liveData+moshi 로 네트워크 통신하기 본문

모던 안드로이드/Udacity Android with kotlin

Retrofit+liveData+moshi 로 네트워크 통신하기

버그잡이 2020. 4. 19. 13:11

 

다음과 같은 클래스가 필요하다.

 

- data 클래스

- network api 인터페이스

- viewmodel

- fragment

 

 

 

 

1. data 클래스

- 기본적으로 Article 클래스만 있어면 된다.

- 나는 넘어오는 json이 json object가 3번 중첩되는 구조라 이를 처리하기 위해서 class를 추가로 만들었다.

 

data class ResponseData(
    @Json(name = "tistory")
    val tistory: Tistory
)

data class Tistory(
    @Json(name="item")
    val item: Item
)

data class Item(
    @field:Json(name = "posts")
    val posts: List<Article>
)

@Parcelize
data class Article(
    val id: String,
    val title: String,
    val postUrl: String,
    val visibility: String,
    val categoryId: String,
    val comment: String,
    val trackbacks: String,
    val date: String
):Parcelable{}

 

 

 

 

2. networkAPI 

 

- 여기가 핵심이다.

1) 레트로핏 객체를 생성해준다. -> moshi를 converterFactory로 넣어준다.

 

2) ApiService를 인터페이스로 구현해준다.(여기서 각종 쿼리문을 작성하면 레트로핏이 알아서 처리해준다.)

    -> getArticles() 에서 deffered를 활용했는데 이는 코루틴을 활용하기 위함이다.

 

3) BlogApi라는 object를 만들어서 어디서든지 사용할 수 있게 한다.

 

private  const val BASE_URL = "https://www.tistory.com/apis/post/"

private  val retrofit = Retrofit.Builder()
    .addConverterFactory(MoshiConverterFactory.create())
    .addCallAdapterFactory(CoroutineCallAdapterFactory())
    .baseUrl(BASE_URL)
    .build()

interface BlogApiService {
    @GET("list?access_token=[티스토리API 토큰값]&output=json&blogName=jinsangjin&page=1")
    fun getArticles(): Deferred<ResponseData>
}

object BlogApi{
    val retrofitService: BlogApiService by lazy{
        retrofit.create(BlogApiService::class.java)
    }
}

 

 

 

3. ViewModel

 

- 기본 retrofit 콜백이 아니라 코루틴 콜백을 사용하였다. 

  -> 코루틴을 활용하면 스레드를 제어할 수 있기 때문에 viewmodel이 소멸되면 레트로핏 작업을 멈출 수 있다.

 

- 코루틴 관련

1) coroutineScope를 뷰모델+Main로 했다.

- viewmodel은 뷰모델 생명주기에 맞추기 위함이고

- Main 스레드로 한 이유는 레트로핏 자체가 백그라운드에서 작업해주기 때문에 다른 새로운 스레드를 사용할 필요가 없다.

 

2) await()

- (await이 실행될때까지 해당 코루틴이 완료되지 않았다면) 해당 항목이 완료될때까지 작업을 기다리는 메서드이다.

- 즉 여기서는 listResult를 반환할때까지 기다리고 이후 작업을 진행하는 것이다.

- why?) 그래야 결과값에 따라 에러 처리를 하거나 특정 결과를 나타낼 수 있다

 

class BlogViewModel : ViewModel(){

    private val _response = MutableLiveData<List<Article>>()
    val response: MutableLiveData<List<Article>>
        get() = _response

    enum class BlogApiStatus{LOADING, ERROR, DONE}

    private val _status = MutableLiveData<BlogApiStatus>()
    val status: LiveData<BlogApiStatus>
        get() = _status

    private var viewModelJob = Job()
    private val coroutineScope = CoroutineScope(viewModelJob + Dispatchers.Main)


    init {
        getArticleList()
    }


    private fun getArticleList(){
        coroutineScope.launch {
            var getArticlesDeferred = BlogApi.retrofitService.getArticles()
            try{
                _status.value = BlogApiStatus.LOADING   //기본 로딩 상태

                var listResult = getArticlesDeferred.await()
                _response.value = listResult.tistory.item.posts

                _status.value = BlogApiStatus.DONE  //성공시 완료 상태

            }catch (t: Throwable){
                //_response.value = "Failure: " + t.message
                _status.value = BlogApiStatus.ERROR //에러시 에러 상태
            }
        }
    }
}

 

 

 

 

4. Fragment

 

- observe로 관찰하면서 데이터를 최신화 한다.

 

class BlogFragment : Fragment() {

    //lazy로 늦게 선언. 최초 사용될때 괄호 안과 같이 정의됨
    private val viewModel: BlogViewModel by lazy{
        ViewModelProviders.of(this).get(BlogViewModel::class.java)
    }

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {

        val binding = FragmentBlogBinding.inflate(inflater)
        binding.setLifecycleOwner(this)
        binding.viewModel = viewModel

        val viewAdapter = BlogListAdapter()
        binding.blogRecyclerView.adapter = viewAdapter
        viewAdapter.onItemClickListener = {

            //Toast.makeText(requireContext(), "${articleData.title}", Toast.LENGTH_SHORT).show()
        }

        viewModel.response.observe(this, Observer {
            viewAdapter.submitList(it)
        })

        viewModel.status.observe(this, Observer {
            when(it){
                BlogViewModel.BlogApiStatus.LOADING -> {
                    binding.statusText.setText("로딩중..")
                    binding.statusText.visibility = View.VISIBLE
                }
                BlogViewModel.BlogApiStatus.ERROR -> {
                    binding.statusText.setText("네트워크 에러")
                    binding.statusText.visibility = View.VISIBLE
                }
                BlogViewModel.BlogApiStatus.DONE -> {
                    binding.statusText.visibility = View.GONE
                }
            }
        })

        return binding.root
    }
}

 

 

 

 

*gradle

    //retrofit
    implementation "com.squareup.retrofit2:retrofit: $version_retrofit"
    implementation "com.squareup.retrofit2:converter-scalars:$version_retrofit"

    //moshi - json 처리해주는 라이브러리
    implementation "com.squareup.moshi:moshi:$version_moshi"
    implementation "com.squareup.moshi:moshi-kotlin:$version_moshi"
    implementation "com.squareup.retrofit2:converter-moshi:$version_retrofit"

    //retrofit & coroutine
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$version_kotlin_coroutines"
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$version_kotlin_coroutines"
    implementation "com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:$version_retrofit_coroutines_adapter"

 

 

반응형
Comments