이 Codelab은 Android Kotlin 기초 교육 과정의 일부입니다. Codelab을 순서대로 진행한다면 이 과정을 통해 최대한의 가치를 얻을 수 있을 것입니다. 모든 과정 Codelab은 Android Kotlin 기초 Codelab 방문 페이지에 나열되어 있습니다.
소개
이전 Codelab에서는 웹 서비스에서 데이터를 가져와 응답을 데이터 객체로 파싱하는 방법을 배웠습니다. 이 지식을 기반으로 이 Codelab에서는 웹 URL에서 사진을 로드하고 표시합니다. 또한 RecyclerView
를 빌드하고 이 뷰를 사용해 개요 페이지에 이미지 그리드를 표시하는 방법을 다시 확인합니다.
기본 요건
- 프래그먼트를 만들고 사용하는 방법
- 뷰 모델, 뷰 모델 팩토리, 변환,
LiveData
등의 아키텍처 구성요소를 사용하는 방법 - Retrofit 라이브러리와 Moshi 라이브러리를 사용하여 REST 웹 서비스에서 JSON을 검색하고 이 데이터를 Kotlin 객체로 파싱하는 방법
RecyclerView
로 그리드 레이아웃을 구성하는 방법Adapter
,ViewHolder
,DiffUtil
의 작동 방식
학습할 내용
- Glide 라이브러리를 사용하여 웹 URL에서 이미지를 로드하고 표시하는 방법
RecyclerView
및 그리드 어댑터를 사용하여 이미지 그리드를 표시하는 방법- 이미지를 다운로드하고 표시할 때 발생할 수 있는 오류를 처리하는 방법
실행할 작업
- 화성 속성 데이터에서 이미지 URL을 가져오도록 MarsRealEstate 앱을 수정하고 Glide를 사용하여 해당 이미지를 로드하고 표시합니다.
- 앱에 로드 애니메이션과 오류 아이콘을 추가합니다.
RecyclerView
를 사용하여 화성 속성 이미지의 그리드를 표시합니다.RecyclerView
에 상태 및 오류 처리를 추가합니다.
이 Codelab 및 관련 Codelab에서는 화성 판매의 속성을 표시하는 MarsRealEstate라는 앱을 사용합니다. 앱이 인터넷 서버에 연결하여 가격 및 부동산 매매 또는 임대 가능 여부 등의 세부정보 데이터를 검색하고 표시합니다. 각 속성을 나타내는 이미지는 NASA의 화성 탐사 로봇이 화성에서 촬영한 실제 사진입니다.
이 Codelab에서 빌드하는 앱 버전은 이미지 그리드를 표시하는 개요 페이지에 채워집니다. 이미지는 앱이 화성 부동산 웹 서비스에서 가져오는 부동산 데이터의 일부입니다. 앱은 Glide 라이브러리를 사용하여 이미지를 로드하고 표시하며 RecyclerView
는 이미지의 그리드 레이아웃을 만듭니다. 또한, 앱은 네트워크 오류를 적절히 처리합니다.
웹 URL에서 사진을 표시하는 것은 간단해 보일 수도 있지만 제대로 작동하려면 엔지니어링이 상당히 필요합니다. 이미지는 Android에서 사용할 수 있는 이미지로 다운로드, 버퍼링, 디코딩해야 합니다. 이미지는 메모리 내 캐시나 저장소 기반 캐시 또는 두 캐시 모두에 캐시해야 합니다. UI가 응답성을 유지하기 위해 이 모든 작업은 우선순위가 낮은 백그라운드 스레드에서 이루어져야 합니다. 또한 최상의 네트워크 및 CPU 성능을 위해 둘 이상의 이미지를 한 번에 가져오고 디코딩하는 것이 좋습니다. 네트워크에서 효과적으로 이미지를 로드하는 방법을 배우는 것은 그 자체로 Codelab이 될 수 있습니다.
다행히 커뮤니티에서 개발한 Glide라는 라이브러리를 사용하여 이미지를 다운로드하고 버퍼링 및 디코딩하고 캐시할 수 있습니다. Glide는 처음부터 이 모든 작업을 해야 했던 것보다 훨씬 적은 수고를 덜어줍니다.
Glide에는 기본적으로 다음 두 가지가 필요합니다.
- 로드 및 표시하려는 이미지의 URL.
- 이미지를 표시하는
ImageView
객체
이 작업에서는 Glide를 사용하여 부동산 웹 서비스의 단일 이미지를 표시하는 방법을 알아봅니다. 웹 서비스에서 반환하는 속성 목록에 첫 번째 화성 속성을 나타내는 이미지를 표시합니다. 다음은 전과 후의 스크린샷입니다.
1단계: Glide 종속 항목 추가하기
- 이전 Codelab의 MarsRealEstate 앱을 엽니다. 앱이 없는 경우 여기에서 MarsRealEstateNetwork를 다운로드할 수 있습니다.
- 앱을 실행하여 어떻게 되는지 확인합니다. (화성에서 가상으로 사용할 수 있는 속성의 텍스트 세부정보 표시)
- build.gradle (Module: app)을 엽니다.
dependencies
섹션에서 Glide 라이브러리에 다음 줄을 추가합니다.
implementation "com.github.bumptech.glide:glide:$version_glide"
버전 번호는 이미 프로젝트 Gradle 파일에 별도로 정의되어 있습니다.
- Sync Now를 클릭하여 새 종속 항목으로 프로젝트를 다시 빌드합니다.
2단계: 뷰 모델 업데이트하기
다음으로, 단일 화성 속성에 대한 실시간 데이터를 포함하도록 OverviewViewModel
클래스를 업데이트합니다.
overview/OverviewViewModel.kt
를 엽니다._response
의LiveData
바로 아래에 단일MarsProperty
객체의 내부 (변경 가능) 및 외부 (변경 불가능) 실시간 데이터를 모두 추가합니다.
요청을 받으면MarsProperty
클래스 (com.example.android.marsrealestate.network.MarsProperty
)를 가져옵니다.
private val _property = MutableLiveData<MarsProperty>()
val property: LiveData<MarsProperty>
get() = _property
getMarsRealEstateProperties()
메서드에서try/catch {}
블록 내에 있는 줄을_response.value
의 속성 수로 설정하는 줄을 찾습니다. 아래와 같이 테스트를 추가합니다.MarsProperty
객체를 사용할 수 있는 경우 이 테스트는_property
LiveData
값을listResult
의 첫 번째 속성으로 설정합니다.
if (listResult.size > 0) {
_property.value = listResult[0]
}
이제 전체 try/catch {}
블록은 다음과 같습니다.
try {
var listResult = getPropertiesDeferred.await()
_response.value = "Success: ${listResult.size} Mars properties retrieved"
if (listResult.size > 0) {
_property.value = listResult[0]
}
} catch (e: Exception) {
_response.value = "Failure: ${e.message}"
}
res/layout/fragment_overview.xml
파일을 엽니다.<TextView>
요소에서android:text
를 변경하여property
LiveData
의imgSrcUrl
구성요소에 바인딩합니다.
android:text="@{viewModel.property.imgSrcUrl}"
- 앱을 실행합니다.
TextView
는 첫 번째 화성 속성에 이미지의 URL만 표시합니다. 지금까지 이 URL의 뷰 모델과 실시간 데이터를 설정했습니다.
3단계: 결합 어댑터 만들기 및 Glide 호출
이제 표시할 이미지의 URL이 생겼으므로 Glide를 사용하여 이미지를 로드하세요. 이 단계에서는 결합 어댑터를 사용하여 ImageView
와 연결된 XML 속성에서 URL을 가져오고 Glide를 사용하여 이미지를 로드합니다. 결합 어댑터는 데이터가 변경될 때 맞춤 동작을 제공하기 위해 뷰와 결합된 데이터 사이에 있는 확장 메서드입니다. 이 경우 맞춤 동작은 Glide를 호출하여 URL에서 ImageView
로 이미지를 로드하는 것입니다.
BindingAdapters.kt
를 엽니다. 이 파일은 앱 전반에 사용하는 결합 어댑터를 보유하게 됩니다.- 매개변수로
ImageView
및String
을 사용하는bindImage()
함수를 만듭니다. 함수에@BindingAdapter
주석을 추가합니다.@BindingAdapter
주석은 XML 항목에imageUrl
속성이 있을 때 이 결합 어댑터가 실행되기를 원하는 데이터 결합을 알려 줍니다.
요청을 받으면androidx.databinding.BindingAdapter
과android.widget.ImageView
를 가져옵니다.
@BindingAdapter("imageUrl")
fun bindImage(imgView: ImageView, imgUrl: String?) {
}
bindImage()
함수 내에서imgUrl
인수의let {}
블록을 추가합니다.
imgUrl?.let {
}
let {}
블록 안에 다음 줄을 추가하여 XML에서 URL 문자열을Uri
객체로 변환합니다. 요청이 있으면androidx.core.net.toUri
를 가져옵니다.
이미지를 가져오는 서버에 이 체계가 필요하므로 최종Uri
객체가 HTTPS 스키마를 사용해야 합니다. HTTPS 스키마를 사용하려면buildUpon.scheme("https")
을toUri
빌더에 추가합니다.toUri()
메서드는 Android KTX 핵심 라이브러리의 Kotlin 확장 함수이므로 이것이String
클래스의 일부인 것처럼 보입니다.
val imgUri = imgUrl.toUri().buildUpon().scheme("https").build()
- 계속
let {}
내에서Glide.with()
를 호출하여Uri
객체의 이미지를ImageView
에 로드합니다. 요청이 있는 경우com.bumptech.glide.Glide
를 가져옵니다.
Glide.with(imgView.context)
.load(imgUri)
.into(imgView)
4단계: 레이아웃 및 프래그먼트 업데이트하기
Glide가 이미지를 로드했지만 아직 볼 것이 없습니다. 다음 단계는 이미지를 표시하도록 ImageView
으로 레이아웃과 프래그먼트를 업데이트하는 것입니다.
res/layout/gridview_item.xml
를 엽니다. 이 파일은 Codelab의 뒷부분에서RecyclerView
의 각 항목에 사용할 레이아웃 리소스 파일입니다. 여기에서 일시적으로 이 이미지를 사용하여 단일 이미지만 표시합니다.<ImageView>
요소 위에 데이터 결합의<data>
요소를 추가하고OverviewViewModel
클래스에 결합합니다.
<data>
<variable
name="viewModel"
type="com.example.android.marsrealestate.overview.OverviewViewModel" />
</data>
- 새 이미지 로드 결합 어댑터를 사용하도록
ImageView
요소에app:imageUrl
속성을 추가합니다.
app:imageUrl="@{viewModel.property.imgSrcUrl}"
overview/OverviewFragment.kt
를 엽니다.onCreateView()
메서드에서FragmentOverviewBinding
클래스를 확장하고 결합 변수에 할당하는 줄을 주석 처리합니다. 이 오류는 일시적이며 나중에 다시 확인합니다.
//val binding = FragmentOverviewBinding.inflate(inflater)
- 대신
GridViewItemBinding
클래스를 확장하는 줄을 추가합니다. 요청이 있는 경우com.example.android.marsrealestate. databinding.GridViewItemBinding
을 가져옵니다.
val binding = GridViewItemBinding.inflate(inflater)
- 앱을 실행합니다. 이제 결과 목록의 첫 번째
MarsProperty
에서 이미지 사진이 표시됩니다.
5단계: 간단한 로드 및 오류 이미지 추가
Glide는 이미지를 로드하는 동안 자리표시자 이미지를 표시하고 로드 실패 시(예: 이미지가 없거나 손상된 경우) 오류 이미지를 표시함으로써 사용자 경험을 개선할 수 있습니다. 이 단계에서는 이러한 기능을 결합 어댑터 및 레이아웃에 추가합니다.
res/drawable/ic_broken_image.xml
을 열고 오른쪽에서 Preview 탭을 클릭합니다. 오류 이미지의 경우 내장된 아이콘 라이브러리에서 사용할 수 있는 손상 이미지 아이콘을 사용합니다. 이 벡터 드로어블은android:tint
속성을 사용하여 아이콘 색상을 회색으로 지정합니다.
res/drawable/loading_animation.xml
를 엽니다. 이 드로어블은<animate-rotate>
태그로 정의된 애니메이션입니다. 애니메이션은 이미지 드로어블loading_img.xml
을 중심점을 축으로 회전시킵니다. (이 애니메이션이 미리보기에 표시되지 않습니다.)
BindingAdapters.kt
파일로 돌아갑니다.bindImage()
메서드에서Glide.with()
호출을 업데이트하여load()
및into()
사이에apply()
함수를 호출합니다. 요청이 있는 경우com.bumptech.glide.request.RequestOptions
를 가져옵니다.
이 코드는 로드하는 동안 사용할 자리표시자 로드 이미지 (loading_animation
드로어블)를 설정합니다. 또한 이 코드는 이미지가 로드되지 않는 경우 사용할 이미지 (broken_image
드로어블)를 설정합니다. 이제 전체bindImage()
메서드는 다음과 같습니다.
@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)
}
}
- 앱을 실행합니다. 네트워크 연결 속도에 따라 Glide가 속성 이미지를 다운로드하고 표시할 때 로드 이미지가 잠시 표시될 수도 있습니다. 그러나 네트워크를 사용 중지해도 손상 이미지 아이콘은 아직 표시되지 않습니다. 이 부분은 Codelab의 마지막 부분에서 수정합니다.
이제 앱이 인터넷에서 속성 정보를 로드합니다. 첫 번째 MarsProperty
목록 항목의 데이터를 사용하여 뷰 모델에 LiveData
속성을 만들고 이 속성 데이터의 이미지 URL을 사용하여 ImageView
를 채웠습니다. 하지만 앱이 이미지 그리드를 표시하는 것이 목표이므로 GridLayoutManager
와 함께 RecyclerView
를 사용하는 것이 좋습니다.
1단계: 뷰 모델 업데이트하기
이제 뷰 모델에는 웹 서비스의 응답 목록에 있는 첫 번째 객체인 MarsProperty
객체 하나를 보유하는 _property
LiveData
가 있습니다. 이 단계에서는 MarsProperty
객체의 전체 목록을 보유하도록 LiveData
를 변경합니다.
overview/OverviewViewModel.kt
를 엽니다.- 비공개
_property
변수를_properties
로 변경합니다. 유형을MarsProperty
객체 목록으로 변경합니다.
private val _properties = MutableLiveData<List<MarsProperty>>()
- 외부
property
실시간 데이터를properties
로 바꿉니다. 여기에서도LiveData
유형에 목록을 추가합니다.
val properties: LiveData<List<MarsProperty>>
get() = _properties
- 아래로 스크롤하여
getMarsRealEstateProperties()
메서드를 찾습니다.try {}
블록 내에서 이전 작업에서 추가한 전체 테스트를 아래와 같은 줄로 바꿉니다.listResult
변수는MarsProperty
객체 목록을 보유하고 있으므로 성공적인 응답을 테스트하는 대신_properties.value
에 할당할 수 있습니다.
_properties.value = listResult
이제 전체 try/catch
블록은 다음과 같습니다.
try {
var listResult = getPropertiesDeferred.await()
_response.value = "Success: ${listResult.size} Mars properties retrieved"
_properties.value = listResult
} catch (e: Exception) {
_response.value = "Failure: ${e.message}"
}
2단계: 레이아웃 및 프래그먼트 업데이트하기
다음 단계는 단일 이미지 뷰가 아닌 recycler 뷰와 그리드 레이아웃을 사용하도록 앱의 레이아웃과 프래그먼트를 변경하는 것입니다.
res/layout/gridview_item.xml
를 엽니다. 데이터 결합을OverviewViewModel
에서MarsProperty
로 변경하고 변수의 이름을"property"
로 바꿉니다.
<variable
name="property"
type="com.example.android.marsrealestate.network.MarsProperty" />
<ImageView>
에서MarsProperty
객체의 이미지 URL을 참조하도록app:imageUrl
속성을 변경합니다.
app:imageUrl="@{property.imgSrcUrl}"
overview/OverviewFragment.kt
를 엽니다.onCreateview()
에서FragmentOverviewBinding
을 확장하는 줄의 주석 처리를 삭제합니다.GridViewBinding
을 확장하는 줄을 삭제하거나 주석 처리합니다. 이렇게 변경하면 마지막 작업에서 적용한 임시 변경사항이 실행취소됩니다.
val binding = FragmentOverviewBinding.inflate(inflater)
// val binding = GridViewItemBinding.inflate(inflater)
res/layout/fragment_overview.xml
를 엽니다. 전체<TextView>
요소를 삭제합니다.- 대신 단일 항목에
GridLayoutManager
및grid_view_item
레이아웃을 사용하는 다음<RecyclerView>
요소를 추가합니다.
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/photos_grid"
android:layout_width="0dp"
android:layout_height="0dp"
android:padding="6dp"
android:clipToPadding="false"
app:layoutManager=
"androidx.recyclerview.widget.GridLayoutManager"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:spanCount="2"
tools:itemCount="16"
tools:listitem="@layout/grid_view_item" />
3단계: 사진 그리드 어댑터 추가하기
이제 fragment_overview
레이아웃에는 RecyclerView
이 있고 grid_view_item
레이아웃에 단일 ImageView
가 있습니다. 이 단계에서는 RecyclerView
어댑터를 통해 데이터를 RecyclerView
에 바인딩합니다.
overview/PhotoGridAdapter.kt
를 엽니다.- 아래와 같이 생성자 매개변수를 사용하여
PhotoGridAdapter
클래스를 만듭니다.PhotoGridAdapter
클래스는ListAdapter
를 확장합니다. 이 생성자에는 목록 항목 유형, 뷰 홀더,DiffUtil.ItemCallback
구현이 필요합니다.
요청에 따라androidx.recyclerview.widget.ListAdapter
및com.example.android.marsrealestate.network.MarsProperty
클래스를 가져옵니다. 다음 단계에서는 이 생성자에 누락되어 오류를 생성하는 다른 부분을 구현합니다.
class PhotoGridAdapter : ListAdapter<MarsProperty,
PhotoGridAdapter.MarsPropertyViewHolder>(DiffCallback) {
}
PhotoGridAdapter
클래스의 아무 곳이나 클릭하고Control+i
을 눌러onCreateViewHolder()
메서드,onBindViewHolder()
인ListAdapter
메서드를 구현합니다.
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PhotoGridAdapter.MarsPropertyViewHolder {
TODO("not implemented")
}
override fun onBindViewHolder(holder: PhotoGridAdapter.MarsPropertyViewHolder, position: Int) {
TODO("not implemented")
}
PhotoGridAdapter
클래스 정의 끝에서 방금 추가한 메서드 뒤에 아래와 같이DiffCallback
의 컴패니언 객체 정의를 추가합니다.
요청을 받으면androidx.recyclerview.widget.DiffUtil
을 가져옵니다.DiffCallback
객체는 비교하려는 객체 유형(MarsProperty
)으로DiffUtil.ItemCallback
를 확장합니다.
companion object DiffCallback : DiffUtil.ItemCallback<MarsProperty>() {
}
Control+i
를 눌러 이 객체의 비교기 메서드areItemsTheSame()
및areContentsTheSame()
을 구현합니다.
override fun areItemsTheSame(oldItem: MarsProperty, newItem: MarsProperty): Boolean {
TODO("not implemented")
}
override fun areContentsTheSame(oldItem: MarsProperty, newItem: MarsProperty): Boolean {
TODO("not implemented") }
areItemsTheSame()
메서드의 경우 TODO를 삭제합니다.oldItem
와newItem
의 객체 참조가 동일한 경우true
를 반환하는 Kotlin의 참조 동등 연산자 (===
)를 사용합니다.
override fun areItemsTheSame(oldItem: MarsProperty,
newItem: MarsProperty): Boolean {
return oldItem === newItem
}
areContentsTheSame()
의 경우oldItem
및newItem
의 ID에만 표준 동등 연산자를 사용합니다.
override fun areContentsTheSame(oldItem: MarsProperty,
newItem: MarsProperty): Boolean {
return oldItem.id == newItem.id
}
PhotoGridAdapter
클래스 내에서 컴패니언 객체 아래에RecyclerView.ViewHolder
의MarsPropertyViewHolder
을 확장하는 내부 클래스 정의를 추가합니다.
요청이 있으면androidx.recyclerview.widget.RecyclerView
와com.example.android.marsrealestate.databinding.GridViewItemBinding
를 가져옵니다.
레이아웃에MarsProperty
를 바인딩하려면GridViewItemBinding
변수가 필요하므로, 변수를MarsPropertyViewHolder
에 전달합니다. 기본ViewHolder
클래스는 생성자에 뷰가 필요하므로 결합 루트 뷰를 전달합니다.
class MarsPropertyViewHolder(private var binding:
GridViewItemBinding):
RecyclerView.ViewHolder(binding.root) {
}
MarsPropertyViewHolder
에서MarsProperty
객체를 인수로 사용하고binding.property
를 이 객체로 설정하는bind()
메서드를 만듭니다. 속성을 설정한 후executePendingBindings()
를 호출하면 업데이트가 즉시 실행됩니다.
fun bind(marsProperty: MarsProperty) {
binding.property = marsProperty
binding.executePendingBindings()
}
onCreateViewHolder()
에서 TODO를 삭제하고 아래의 줄을 추가합니다. 요청이 있는 경우android.view.LayoutInflater
을 가져옵니다.onCreateViewHolder()
메서드는GridViewItemBinding
를 확장하고 상위ViewGroup
컨텍스트의LayoutInflater
를 사용하여 생성된 새MarsPropertyViewHolder
를 반환해야 합니다.
return MarsPropertyViewHolder(GridViewItemBinding.inflate(
LayoutInflater.from(parent.context)))
onBindViewHolder()
메서드에서 TODO를 삭제하고 아래의 줄을 추가합니다. 여기서getItem()
를 호출하여 현재RecyclerView
위치와 연결된MarsProperty
객체를 가져온 다음 이 속성을MarsPropertyViewHolder
의bind()
메서드에 전달합니다.
val marsProperty = getItem(position)
holder.bind(marsProperty)
4단계: 결합 어댑터 추가 및 부분 연결하기
마지막으로, BindingAdapter
를 사용하여 MarsProperty
객체 목록으로 PhotoGridAdapter
를 초기화합니다. BindingAdapter
를 사용하여 RecyclerView
데이터를 설정하면 데이터 결합이 자동으로 MarsProperty
객체 목록의 LiveData
를 관찰합니다. 그런 다음 MarsProperty
목록이 변경되면 결합 어댑터가 자동으로 호출됩니다.
BindingAdapters.kt
를 엽니다.- 파일 끝에
RecyclerView
와MarsProperty
객체 목록을 인수로 사용하는bindRecyclerView()
메서드를 추가합니다. 이 메서드에@BindingAdapter
주석을 답니다.
요청을 받으면androidx.recyclerview.widget.RecyclerView
과com.example.android.marsrealestate.network.MarsProperty
를 가져옵니다.
@BindingAdapter("listData")
fun bindRecyclerView(recyclerView: RecyclerView,
data: List<MarsProperty>?) {
}
bindRecyclerView()
함수 내에서recyclerView.adapter
를PhotoGridAdapter
으로 변환하고 데이터를 사용하여adapter.submitList()
를 호출합니다. 그러면 새 목록을 사용할 수 있을 때RecyclerView
에 알려줍니다.
요청이 있는 경우 com.example.android.marsrealestate.overview.PhotoGridAdapter
를 가져옵니다.
val adapter = recyclerView.adapter as PhotoGridAdapter
adapter.submitList(data)
res/layout/fragment_overview.xml
를 엽니다.RecyclerView
요소에app:listData
속성을 추가하고 데이터 결합을 사용하여 이 속성을viewmodel.properties
로 설정합니다.
app:listData="@{viewModel.properties}"
overview/OverviewFragment.kt
를 엽니다.onCreateView()
의setHasOptionsMenu()
호출 직전에binding.photosGrid
에서RecyclerView
어댑터를 새PhotoGridAdapter
객체로 초기화합니다.
binding.photosGrid.adapter = PhotoGridAdapter()
- 앱을 실행합니다.
MarsProperty
이미지의 그리드가 표시됩니다. 스크롤하여 새 이미지를 볼 때 앱은 이미지 자체를 표시하기 전에 로드 진행률 아이콘을 표시합니다. 비행기 모드를 사용 설정하면 아직 로드되지 않은 이미지가 손상 이미지 아이콘으로 표시됩니다.
이미지를 가져올 수 없을 때 MarsRealEstate 앱에 손상 이미지 아이콘이 표시됩니다. 하지만 네트워크가 없으면 앱에서 빈 화면이 표시됩니다.
이는 만족스러운 사용자 경험이 아닙니다. 이 작업에서는 기본 오류 처리를 추가하여 사용자가 현재 상황을 더 잘 파악하도록 합니다. 인터넷을 사용할 수 없는 경우 앱에 연결 오류 아이콘이 표시됩니다. 앱이 MarsProperty
목록을 가져오는 동안 앱은 로드 애니메이션을 표시합니다.
1단계: 뷰 모델에 상태 추가하기
시작하려면 뷰 모델에 LiveData
를 만들어 웹 요청의 상태를 나타냅니다. 로드, 성공, 실패 등 세 가지 상태를 고려합니다. 로드 상태는 await()
호출을 위한 데이터를 기다리는 동안 발생합니다.
overview/OverviewViewModel.kt
를 엽니다. 파일 상단에서(가져오기 뒤, 클래스 정의 앞에)enum
을 추가하여 사용 가능한 모든 상태를 나타냅니다.
enum class MarsApiStatus { LOADING, ERROR, DONE }
OverviewViewModel
클래스 내부 및 외부_response
실시간 데이터 정의의 이름을_status
모두 바꿉니다. 이 Codelab 앞부분에서_properties
LiveData
지원을 추가했으므로 전체 웹 서비스 응답이 사용되지 않았습니다. 기존 변수의 이름을 바꾸려면 현재 상태를 추적하려면LiveData
이 필요합니다.
또한 유형을 String
에서 MarsApiStatus.
로 변경합니다.
private val _status = MutableLiveData<MarsApiStatus>()
val status: LiveData<MarsApiStatus>
get() = _status
getMarsRealEstateProperties()
메서드까지 아래로 스크롤하고_response
를_status
로 업데이트합니다."Success"
문자열을MarsApiStatus.DONE
상태로,"Failure"
문자열을MarsApiStatus.ERROR
로 변경합니다.await()
를 호출하기 전에try {}
블록 상단에MarsApiStatus.LOADING
상태를 추가합니다. 이 상태는 코루틴을 실행하는 동안 데이터를 기다릴 때 초기 상태입니다. 이제 전체try/catch {}
블록은 다음과 같습니다.
try {
_status.value = MarsApiStatus.LOADING
var listResult = getPropertiesDeferred.await()
_status.value = MarsApiStatus.DONE
_properties.value = listResult
} catch (e: Exception) {
_status.value = MarsApiStatus.ERROR
}
catch {}
블록에서 오류 상태 다음에_properties
LiveData
를 빈 목록으로 설정합니다. 이렇게 하면RecyclerView
가 삭제됩니다.
} catch (e: Exception) {
_status.value = MarsApiStatus.ERROR
_properties.value = ArrayList()
}
2단계: 상태 ImageView용 결합 어댑터 추가하기
이제 뷰 모델에 상태가 있지만 일련의 상태입니다. 앱 자체에 표시하려면 어떻게 해야 하나요? 이 단계에서는 데이터 결합에 연결된 ImageView
를 사용하여 로드 및 오류 상태의 아이콘을 표시합니다. 앱이 로드 상태이거나 오류 상태일 때 ImageView
가 표시됩니다. 앱에서 로드가 완료되면 ImageView
가 표시되지 않습니다.
BindingAdapters.kt
를 엽니다.ImageView
값과MarsApiStatus
값을 인수로 사용하는bindStatus()
라는 새 결합 어댑터를 추가합니다. 요청이 있는 경우com.example.android.marsrealestate.overview.MarsApiStatus
를 가져옵니다.
@BindingAdapter("marsApiStatus")
fun bindStatus(statusImageView: ImageView,
status: MarsApiStatus?) {
}
bindStatus()
메서드 내부에when {}
를 추가하여 서로 다른 상태 간에 전환합니다.
when (status) {
}
when {}
내부에 로드 상태(MarsApiStatus.LOADING
)의 사례를 추가합니다. 이 상태의 경우ImageView
를 visible로 설정하고 로드 애니메이션에 할당합니다. 이전 작업에서 Glide에 사용한 것과 동일한 애니메이션 드로어블입니다. 요청이 있는 경우android.view.View
를 가져옵니다.
when (status) {
MarsApiStatus.LOADING -> {
statusImageView.visibility = View.VISIBLE
statusImageView.setImageResource(R.drawable.loading_animation)
}
}
- 오류 상태(
MarsApiStatus.ERROR
)의 사례를 추가합니다.LOADING
상태의 경우와 유사하게 상태ImageView
를 visible로 설정하고 연결 오류 드로어블을 재사용합니다.
MarsApiStatus.ERROR -> {
statusImageView.visibility = View.VISIBLE
statusImageView.setImageResource(R.drawable.ic_connection_error)
}
- 완료 상태(
MarsApiStatus.DONE
)의 사례를 추가합니다. 여기서는 성공적인 응답이 있으므로 상태ImageView
의 공개 상태를 사용 중지하여 숨깁니다.
MarsApiStatus.DONE -> {
statusImageView.visibility = View.GONE
}
3단계: 레이아웃에 ImageView 상태 추가하기
res/layout/fragment_overview.xml
를 엽니다.RecyclerView
요소 아래ConstraintLayout
에서 아래와 같이ImageView
를 추가합니다.
이ImageView
에는RecyclerView
와 동일한 제약 조건이 있습니다. 그러나 이미지를 늘려 뷰를 채우는 대신, 너비와 높이가wrap_content
를 사용하여 이미지를 중앙에 배치합니다. 또한 뷰 모델의 상태 속성이 변경될 때 뷰가BindingAdapter
를 호출하는app:marsApiStatus
속성을 확인합니다.
<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}" />
- 에뮬레이터나 기기에서 비행기 모드를 사용 설정하여 누락된 네트워크 연결을 시뮬레이션합니다. 앱을 컴파일하고 실행하면 오류 이미지가 표시됩니다.
- Back 버튼을 탭하여 앱을 닫고 비행기 모드를 사용 중지합니다. 최근 항목 화면을 사용하여 앱을 반환합니다. 네트워크 연결 속도에 따라 앱이 웹 서비스를 쿼리할 때 이미지 로드가 시작되기 전에 로드 스피너가 아주 잠시 표시될 수도 있습니다.
Android 스튜디오 프로젝트: MarsRealEstateGrid
- 이미지 관리 프로세스를 단순화하려면 Glide 라이브러리를 사용하여 앱에서 이미지를 다운로드, 버퍼링, 디코딩, 캐시하세요.
- Glide는 인터넷에서 이미지를 로드하기 위해 이미지의 URL과 이미지를 배치할
ImageView
객체, 두 가지가 필요합니다. 이러한 옵션을 지정하려면 Glide와 함께load()
와into()
메서드를 사용합니다. - 결합 어댑터는 뷰와 이 뷰에 결합된 데이터 사이에 있는 확장 메서드입니다. 결합 어댑터는 데이터가 변경될 때(예: Glide를 호출하여 URL에서
ImageView
로 이미지 로드하기) 맞춤 동작을 제공합니다. - 결합 어댑터는
@BindingAdapter
주석이 추가된 확장 메서드입니다. - Glide 요청에 옵션을 추가하려면
apply()
메서드를 사용합니다. 예를 들어placeholder()
와 함께apply()
를 사용하여 로드 드로어블을 지정하고error()
와 함께apply()
를 사용하여 오류 드로어블을 지정합니다. - 이미지의 그리드를 생성하려면
GridLayoutManager
와 함께RecyclerView
를 사용합니다. - 변경 시 속성 목록을 업데이트하려면
RecyclerView
와 레이아웃 사이에 결합 어댑터를 사용합니다.
Udacity 과정:
Android 개발자 문서:
기타:
이 섹션에는 강사가 진행하는 과정의 일부로 이 Codelab을 통해 작업하는 학생들의 숙제 과제가 나와 있습니다. 강사는 다음을 처리합니다.
- 필요한 경우 과제를 할당합니다.
- 학생에게 과제 과제를 제출하는 방법을 알려주세요.
- 과제 과제를 채점합니다.
강사는 이러한 추천을 원하는 만큼 사용할 수 있으며 다른 적절한 숙제를 할당해도 좋습니다.
이 Codelab을 직접 학습하고 있다면 언제든지 숙제를 통해 지식을 확인해 보세요.
답변하기
질문 1
로드된 이미지가 포함될 ImageView
을 나타내는 데 사용하는 Glide 메서드는 무엇인가요?
▢ into()
▢ with()
▢ imageview()
▢ apply()
질문 2
Glide가 로드 중일 때 표시할 자리표시자 이미지를 지정하려면 어떻게 해야 하나요?
▢ 드로어블과 함께 into()
메서드를 사용합니다.
▢ RequestOptions()
를 사용하고 드로어블로 placeholder()
메서드를 호출합니다.
▢ Glide.placeholder
속성을 드로어블에 할당합니다.
▢ RequestOptions()
를 사용하고 드로어블로 loadingImage()
메서드를 호출합니다.
질문 3
메서드가 결합 어댑터임을 나타내려면 어떻게 해야 하나요?
▢ LiveData
에서 setBindingAdapter()
메서드를 호출합니다.
▢ Kotlin을 BindingAdapters.kt
라는 Kotlin 파일에 넣습니다.
▢ XML 레이아웃에서 android:adapter
속성을 사용합니다.
▢ 메서드에 @BindingAdapter
주석을 답니다.
다음 강의 시작:
이 과정의 다른 Codelab 링크는 Android Kotlin 기초 Codelab 방문 페이지를 참고하세요.