Unit 4: Internet (3)

quokka·2021년 11월 26일
0

Android Basics in Kotlin

목록 보기
20/25
post-thumbnail

Coil 라이브러리

Coil 라이브러리를 사용해 이미지를 다운로드, 디코드, 캐시할 수 있다.
Coil에는 이미지의 URL, 이미지를 올릴 ImageView 이 두가지가 필요하다.

1. build.gradle (Module)

dependecies에 추가한다. Coil 다큐먼트 페이지에서 최신 버전을 확인한다.

    implementation "io.coil-kt:coil:1.4.0"

2. build.gradle (Project)

Coil 라이브러리는 mavenCentral() 리포지토리를 사용한다.

repositories {
   google()
   jcenter()
   mavenCentral()
}

ViewModel 업데이트

  • MarsPhoto 객체를 저장할 MutableLiveData 유형의 _photos를 추가한다.
  • _photos.value = MarsApi.retrofitSerice.getPhotos()[0]
  • _status.value 수정

BindingAdapter

BindingAdapter는 뷰의 속성에 대한 맞춤 setter를 만드는데 사용한다.
예를 들어 일반적으로 text 속성을 수정할 때 setText 메서드를 사용하는데 이런 setter를 정의하는 것.

1. BindingAdapter 파일 생성

먼저 overview 패키지 아래 BindingAdapter.kt 파일을 생성한다. 이 파일은 앱 전반에 사용하는 Binding Adapter에 대한 내용을 갖는다.

2. BindingAdapter 코드

@BindingAdapter("imageUrl")
fun bindImage(imgView: ImageView, imgUrl: String?) {
    imgUrl?.let {
        val imgUri = imgUrl.toUri().buildUpon().scheme("https").build()
        imgView.load(imgUri)
    }
}
  • 뷰 항목에 imageUrl 속성이 있는 경우 이 결합 어댑터를 실행하도록 데이터 결합에 지시한다.
  • @BindingAdapter 속성 이름을 매개변수로 사용한다.
  • bindImage의 매개변수를 보면 첫 번째는 타겟 뷰, 두 번째는 속성에 설정되는 값.
  • let은 범위 함수로, 호출 체인의 결과에서 함수 하나 이상을 호출하는 데 사용한다.
  • ?.와 함께 사용해 객체가 null이 아닌 경우에만 실행된다.
  • imgUri 메서드로 URL을 Uri 객체로 변환한다.
  • 마지막으로 이미지 로드. 여기서 load()가 Coil 라이브러리에 속한다.

Data Binding

grid_view_item.xml을 수정한다.

1. 데이터 결합을 위한 data 요소 추가

<data>
   <variable
       name="viewModel"
       type="com.example.android.marsphotos.overview.OverviewViewModel" />
</data>

2. ImageView

ImageView의 imageUrl 속성에 binding adapter 사용

app:imageUrl="@{viewModel.photos.imgSrcUrl}"

로딩 이미지, 오류 이미지

다음과 같이 placeholder와 error에 해당 이미지를 작성해 로딩 중이나 이미지 에러 발생시 표시할 이미지를 설정한다.

@BindingAdapter("imageUrl")
fun bindImage(imgView: ImageView, imgUrl: String?){
    imgUrl?.let {
        val imgUri = imgUrl.toUri().buildUpon().scheme("https").build()
        imgView.load(imgUri){
            placeholder(R.drawable.loading_animation)
            error(R.drawable.ic_broken_image)
        }
    }
}

RecyclerView

그리드 이미지로 사진을 표시하려면 RecyclerView를 사용하도록 코드를 업데이트해야 한다.

1. ViewModel

photos를 List<MarsPhoto>로 변경해 목록을 받는다.
try catch문에서도 _photos.value = MarsApi.retrofitService.getPhotos()로 목록을 할당한다.

2. Recyclerview를 사용하도록 수정

<data> 태그로 MarsPhoto 유형의 photo변수를 추가하고, imageUrl 속성을 app:imageUrl="@{photo.imgSrcUrl}"로 수정한다.

ListAdapter

ListAdapterRecyclerView.Adapter의 서브 클래스로 백그라운드 스레드의 목록 간 차이를 계산하는 작업을 포함한다. 여기서 ListAdapterDiffUtil을 사용하는데, DiffUtil을 사용하면 리사이클러뷰의 일부 항목이 변경될 때마다 전체 목록이 새로고침되지 않는다는 점이다. 변경된 항목만 바뀐다.

1. ListAdapter

PhotoGridAdapter라는 새 Kotlin 클래스에 ListAdapter를 구현한다.

class PhotoGridAdapter : ListAdapter<MarsPhoto, PhotoGridAdapter.MarsPhotoViewHolder>(DiffCallback) {

}

2. onCreateViewHolder(), onBindViewHolder()

두 개 메서드를 implement한다.

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

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

3. ViewHolder()

onCreateViewHolder()onBindViewHolder()에서 ViewHolder 클래스가 필요하다.
executePendingBindings()를 호출하면 바로 업데이트가 된다.

    class MarsPhotoViewHolder(
        private var binding: GridViewItemBinding
    ) : RecyclerView.ViewHolder(binding.root) {
        fun bind(marsPhoto: MarsPhoto) {
            binding.photo = marsPhoto
            // This is important, because it forces the data binding to execute immediately,
            // which allows the RecyclerView to make the correct view size measurements
            binding.executePendingBindings()
        }
    }

4. DiffCallback

DiffCallback companion object가 필요하다. 여기서 두 화성 사진 객체를 비교한다.

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

        override fun areContentsTheSame(oldItem: MarsPhoto, newItem: MarsPhoto): Boolean {
            return oldItem.imgSrcUrl == newItem.imgSrcUrl
        }
    }

BindingAdapter

BindingAdapter를 사용하여 MarsPhoto 객체 목록으로 PhotoGridAdapter를 초기화한다.

BindingAdapter를 사용하여 RecyclerView 데이터를 설정하면 데이터 결합이 자동으로 MarsPhoto 객체 목록의 LiveData를 관찰한다. 그래서 MarsPhoto 목록이 변경되면 결합 어댑터가 자동으로 호출된다.

@BindingAdapter("listData")
fun bindRecyclerView(recyclerView: RecyclerView, data: List<MarsPhoto>?) {
    val adapter = recyclerView.adapter as PhotoGridAdapter
    adapter.submitList(data)
}

listData

fragment_overview.xml의 리사이클러뷰에 listData 속성을 추가하고, viewModel.photos로 설정한다.
app:listData="@{viewModel.photos}"

adapter 변경

OverviewFragment의 리사이클러뷰 어댑터를 PhotoGridAdapter로 변경한다. binding.photosGrid.adapter = PhotoGridAdapter()

오류 처리 추가

1. ViewModel에 상태 추가 - enum

웹 요청의 상태를 표시하는 속성을 만든다. 로드, 성공, 실패 등

enum은 상수 집합을 저장하는 데이터 타입이다. 다음과 같이 세 상태를 정의하고,

enum class MarsApiStatus { LOADING, ERROR, DONE }

getMarsPhotos()에서 각 상태를 설정한다. ERROR인 경우 _photos도 빈 배열로 설정한다.

private fun getMarsPhotos() {
   viewModelScope.launch {
        _status.value = MarsApiStatus.LOADING
        try {
           _photos.value = MarsApi.retrofitService.getPhotos()
           _status.value = MarsApiStatus.DONE
        } catch (e: Exception) {
           _status.value = MarsApiStatus.ERROR
           _photos.value = listOf()
        }
    }
}

2. BindingAdapter에 상태 imageView 추가

ImageView 값과 MarsApiStatus 값을 인수로 사용하는 bindStatus()라는 새 결합 어댑터를 추가한다.
when문을 사용해 각 상태에 따라 동작을 설정한다.

@BindingAdapter("marsApiStatus")
fun bindStatus(statusImageView: ImageView, status: MarsApiStatus?) {
    when (status) {
        MarsApiStatus.LOADING -> {
            statusImageView.visibility = View.VISIBLE
            statusImageView.setImageResource(R.drawable.loading_animation)
        }
        MarsApiStatus.ERROR -> {
            statusImageView.visibility = View.VISIBLE
            statusImageView.setImageResource(R.drawable.ic_connection_error)
        }
        MarsApiStatus.DONE -> {
            statusImageView.visibility = View.GONE
        }
    }
}

중단점 디버그

로그를 출력해서 디버깅 하는 방법도 있지만 중단점을 찍어서 순서대로 실행되는 과정을 보는 것도 디버깅에 효과적이다.

중단점 조건 설정

빨간색 중단점을 찍고, 중단점 위에서 오른쪽 마우스를 클릭하면 중단 조건을 설정할 수 있다. 반복문에서 i번째 반복문을 디버깅 하고싶을 때 i번째까지 단계별로 실행하지 않고도 조건이 충족되는 시점에 멈추도록 할 수 있다.

Watches

디버깅하는 동안 특정 값을 모니터링하려는 경우 Variables 탭에서 검색하지 않고 Watches에 항목을 추가해서 특적 변수를 모니터링할 수 있다. 디버깅 뷰의 변수 창에 Watches라는 빈 창이 있는데 여기에서 New Watch를 추가할 수 있다.

0개의 댓글