코드랩 Retrofit으로 데이터 가져오기2

4 minute read

지난번에 Retrofit과 Moshi라이브러리로 데이터 통신 성공까지 만들었다. 이번엔 통신한 데이터를 RecyclerView(+LiveData, ViewModel, DataBinding, DiffUtil 전부 사용해서 !)로 띄워주는것 까지 구현!

ezgif com-gif-maker (11)

리싸이클러뷰에 띄울 item 레이아웃 만들기

일단 데이터를 가져와 img_src를 이미지뷰로 띄워주기만 하기때문에 item 레이아웃에는 imageView만 있으면 된다.

그리고 아이템하나는 MarsProperty객체 하나를 띄워주는거기 때문에 MarsProperty DataBinding을 해준다.

grid_view_item.xml

<?xml version="1.0" encoding="utf-8"?>  
  
<!--  
 ~ Copyright 2019, The Android Open Source Project ~ ~ Licensed under the Apache License, Version 2.0 (the "License"); ~ you may not use this file except in compliance with the License. ~ You may obtain a copy of the License at ~ ~     http://www.apache.org/licenses/LICENSE-2.0 ~ ~ Unless required by applicable law or agreed to in writing, software ~ distributed under the License is distributed on an "AS IS" BASIS, ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ~ See the License for the specific language governing permissions and ~ limitations under the License. ~ -->  
<layout 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">  
 <data> <variable  name="property"  
  type="com.example.android.marsrealestate.network.MarsProperty" />  
  
 </data> <ImageView  android:id="@+id/mars_image"  
  android:layout_width="match_parent"  
  android:layout_height="170dp"  
  android:scaleType="centerCrop"  
  android:adjustViewBounds="true"  
  imageUrl="@{property.imgSrcUrl}"  
  android:padding="2dp"  
  tools:src="@tools:sample/backgrounds/scenic"/>  
</layout>

리싸이클러뷰를 띄울 화면에 리싸이클러뷰를 넣어준다.

전체화면에 리싸이클러뷰를 보여줄거기때문에 match_parent로 해주고, 한 행에 2개씩 보여줄거기때문에 spanCount=2, GridLayoutManager를 설정해준다.

그리고 여기엔 서버통신으로 받아온 MarsProperty 리스트에 접근해야하기때문에 viewModel을 바인딩 해준다.

밑의 imageView는 데이터 통신 상태에따라 보여줄 이미지 !

fragment_overview.xml

<?xml version="1.0" encoding="utf-8"?>  
  
<!--  
 ~ Copyright 2019, The Android Open Source Project ~ ~ Licensed under the Apache License, Version 2.0 (the "License"); ~ you may not use this file except in compliance with the License. ~ You may obtain a copy of the License at ~ ~     http://www.apache.org/licenses/LICENSE-2.0 ~ ~ Unless required by applicable law or agreed to in writing, software ~ distributed under the License is distributed on an "AS IS" BASIS, ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ~ See the License for the specific language governing permissions and ~ limitations under the License. ~ -->  
<layout 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">  
  
 <data> <variable  name="viewModel"  
  type="com.example.android.marsrealestate.overview.OverviewViewModel" />  
  
 </data>  
 <androidx.constraintlayout.widget.ConstraintLayout  android:layout_width="match_parent"  
  android:layout_height="match_parent"  
  tools:context="com.example.android.marsrealestate.MainActivity">  
  
 <androidx.recyclerview.widget.RecyclerView  android:id="@+id/rv_photo_list"  
  android:layout_width="match_parent"  
  android:layout_height="match_parent"  
  app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"  
  android:padding="10dp"  
  app:layout_constraintBottom_toBottomOf="parent"  
  app:layout_constraintEnd_toEndOf="parent"  
  app:layout_constraintStart_toStartOf="parent"  
  app:layout_constraintTop_toTopOf="parent"  
  app:listData="@{viewModel.properties}"  
  app:spanCount="2"  
  tools:itemCount="16"  
  tools:listitem="@layout/grid_view_item" />  
  
 <ImageView  android:id="@+id/status_image"  
  android:layout_width="wrap_content"  
  android:layout_height="wrap_content"  
  app:layout_constraintBottom_toBottomOf="parent"  
  app:layout_constraintLeft_toLeftOf="parent"  
  app:layout_constraintRight_toRightOf="parent"  
  app:layout_constraintTop_toTopOf="parent"  
  app:marsApiStatus="@{viewModel.status}" />  
 </androidx.constraintlayout.widget.ConstraintLayout></layout>

리싸이클러뷰를 만들었으니 Adapter와 ViewHolder를 만들어준다.

먼저 DiffUtil을 상속받는 DiffCallback을 정의해준다.

companion object DiffCallback : DiffUtil.ItemCallback<MarsProperty>() {  
  override fun areItemsTheSame(oldItem: MarsProperty, newItem: MarsProperty): Boolean {  
  return oldItem === newItem  
  }  
  
  override fun areContentsTheSame(oldItem: MarsProperty, newItem: MarsProperty): Boolean {  
  return oldItem.id == newItem.id  
    }  
}

데이터바인딩과 DiffUtil을 사용한다.

ListAdapter<객체타입, 뷰홀더>(DiffCallback)상속받는 어댑터를 생성하고, 필요한 메소드를 오버라이드 해준다.

class PhotoGridAdapter : ListAdapter<MarsProperty, PhotoGridAdapter.MarsPropertyViewHolder>(DiffCallback){
...
}

onCreateViewHolder로 바인딩.inflate~ 로 뷰홀더에 넘겨준다.

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MarsPropertyViewHolder {  
  return MarsPropertyViewHolder(GridViewItemBinding.inflate(LayoutInflater.from(parent.context)))  
}

onBindViewHolder 에서는 ListAdapter의 getItem을 사용해서 holder.bind로 해당 포지션의 아이템을 넘겨준다.

override fun onBindViewHolder(holder: MarsPropertyViewHolder, position: Int) {  
  val marsProperty = getItem(position)  
  holder.bind(marsProperty)  
}

아까 onCreateViewHolder에서 binding 을 인수로 넘겼기 때문에 변수로 받아주고, onBindViewHolder에서 bind함수의 인수로 MarsProperty 객체를 넘겼기 때문에 받아서 grid_view_item.xml에 써줬던 property에 값을 넣어준다.

class MarsPropertyViewHolder(private var binding:GridViewItemBinding):RecyclerView.ViewHolder(binding.root){  
  fun bind(marsProperty: MarsProperty) {  
  binding.property = marsProperty  
  binding.executePendingBindings()  
 }}

PhotoGridAdapter

/*  
 * Copyright 2019, The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * *     http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */  
package com.example.android.marsrealestate.overview  
  
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.android.marsrealestate.databinding.GridViewItemBinding  
import com.example.android.marsrealestate.network.MarsProperty  
  
class PhotoGridAdapter : ListAdapter<MarsProperty, PhotoGridAdapter.MarsPropertyViewHolder>(DiffCallback){  
  
  override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MarsPropertyViewHolder {  
  return MarsPropertyViewHolder(GridViewItemBinding.inflate(LayoutInflater.from(parent.context)))  
 }  
  override fun onBindViewHolder(holder: MarsPropertyViewHolder, position: Int) {  
  val marsProperty = getItem(position)  
  holder.bind(marsProperty)  
 }  
  class MarsPropertyViewHolder(private var binding:GridViewItemBinding):RecyclerView.ViewHolder(binding.root){  
  fun bind(marsProperty: MarsProperty) {  
  binding.property = marsProperty  
  binding.executePendingBindings()  
 } }  
  companion object DiffCallback : DiffUtil.ItemCallback<MarsProperty>() {  
  override fun areItemsTheSame(oldItem: MarsProperty, newItem: MarsProperty): Boolean {  
  return oldItem === newItem  
  }  
  
  override fun areContentsTheSame(oldItem: MarsProperty, newItem: MarsProperty): Boolean {  
  return oldItem.id == newItem.id  
        }  
 }}

만든 어댑터 리싸이클러뷰에 적용

OverviewFragment.kt

binding.rvPhotoList.adapter = PhotoGridAdapter()

BindingAdapter 사용하기

마지막으로 서버통신으로 받아온 데이터를 adapter 에 바인딩해주면 된다.

@BindingAdpater(“xml에서 사용할 이름”)

fun 함수이름(적용할 view, view에 적용할 값)

{ 어떻게 적용할지! }

이렇게 이해하면 편한것 같다.

BindingAdpaters.kt

/*  
 * Copyright 2019, The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * *     http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */  
package com.example.android.marsrealestate  
  
import android.view.View  
import android.widget.ImageView  
import androidx.core.net.toUri  
import androidx.databinding.BindingAdapter  
import androidx.recyclerview.widget.RecyclerView  
import com.bumptech.glide.Glide  
import com.bumptech.glide.request.RequestOptions  
import com.example.android.marsrealestate.network.MarsApi  
import com.example.android.marsrealestate.network.MarsProperty  
import com.example.android.marsrealestate.overview.MarsApiStatus  
import com.example.android.marsrealestate.overview.PhotoGridAdapter  
  
//String 이미지 주소 Glide로 띄워주기 //placeholder -> 통신 길어질때 보여질 모습  
//error -> 네트워크 끊겼을때 보여질 모습 @BindingAdapter("imageUrl")  
fun bindImage(imgView : ImageView, imgUrl : String? ){  
  imgUrl?.let{  
  val imgUri  = imgUrl.toUri().buildUpon().scheme("https").build()  
  Glide.with(imgView.context)  
  .load(imgUri)  
  .apply(RequestOptions()  
  .placeholder(R.drawable.loading_animation)  
  .error(R.drawable.ic_broken_image))  
  .into(imgView)  
  }  
}  
  
//서버통신으로 받아온 객체리스트 어댑터에 값 설정해주기 @BindingAdapter("listData")  
fun bindRecyclerView(recyclerView: RecyclerView, data: List<MarsProperty>?) {  
  val adapter = recyclerView.adapter as PhotoGridAdapter  
  adapter.submitList(data)  
}  
  
//OverviewFragment에 적용해줬던 이미지 처리 -> 네트워크에따라 보이게할지 여부 판단 @BindingAdapter("marsApiStatus")  
fun bindStatus(statusImageView: ImageView,status:MarsApiStatus?){  
  when(status){  
  MarsApiStatus.LOADING -> {  
  statusImageView.visibility = View.VISIBLE  
  statusImageView.setImageResource(R.drawable.loading_animation)  
 }  MarsApiStatus.DONE -> {  
  statusImageView.visibility = View.GONE  
  }  
  MarsApiStatus.ERROR -> {  
  statusImageView.visibility = View.VISIBLE  
  statusImageView.setImageResource(R.drawable.ic_connection_error)  
 } }}

참고

https://developer.android.com/codelabs/kotlin-android-training-internet-images?index=..%2F..android-kotlin-fundamentals#0

Comments