이 Codelab은 Android Kotlin 기초 과정의 일부입니다. Codelab을 순서대로 진행하면 이 과정의 학습 효과를 극대화할 수 있습니다. 모든 과정 Codelab은 Android Kotlin 기본사항 Codelab 방문 페이지에 나열되어 있습니다.
소개
이 과정의 이전 Codelab에서는 웹 서비스에서 화성 부동산에 관한 데이터를 가져오는 방법과 그리드 레이아웃으로 RecyclerView를 만들어 해당 데이터에서 이미지를 로드하고 표시하는 방법을 알아봤습니다. 이 Codelab에서는 대여 또는 구매 가능 여부에 따라 화성 속성을 필터링하는 기능을 구현하여 MarsRealEstate 앱을 완성합니다. 또한 사용자가 개요에서 숙소 사진을 탭하면 해당 숙소에 관한 세부정보가 포함된 세부정보 보기가 표시되도록 세부정보 보기를 만듭니다.
기본 요건
- 프래그먼트를 만들고 사용하는 방법
- 프래그먼트 간 이동 방법과 Safe Args (Gradle 플러그인)를 사용하여 프래그먼트 간에 데이터를 전달하는 방법
- 뷰 모델, 뷰 모델 팩토리, 변환,
LiveData를 비롯한 아키텍처 구성요소를 사용하는 방법 - Retrofit 및 Moshi 라이브러리를 사용하여 REST 웹 서비스에서 JSON 인코딩 데이터를 검색하고 이 데이터를 Kotlin 객체로 파싱하는 방법
학습할 내용
- 레이아웃 파일에서 복잡한 바인딩 표현식을 사용하는 방법
- 쿼리 옵션으로 웹 서비스에 Retrofit 요청을 하는 방법
실행할 작업
- 판매용 화성 속성 (임대용 속성과 구분)을 달러 기호 아이콘으로 표시하도록 MarsRealEstate 앱을 수정합니다.
- 개요 페이지의 옵션 메뉴를 사용하여 유형별로 화성 속성을 필터링하는 웹 서비스 요청을 만듭니다.
- 화성 속성의 세부정보 프래그먼트를 만들고, 탐색을 사용하여 해당 프래그먼트를 개요 그리드에 연결하고, 속성 데이터를 해당 프래그먼트에 전달합니다.
이 Codelab (및 관련 Codelab)에서는 화성에서 판매 중인 부동산을 표시하는 MarsRealEstate라는 앱을 사용합니다. 이 앱은 인터넷 서버에 연결하여 가격, 매매 또는 임대 가능 여부와 같은 세부정보를 포함한 부동산 데이터를 검색하고 표시합니다. 각 속성을 나타내는 이미지는 NASA의 화성 탐사 로봇이 화성에서 촬영한 실제 사진입니다. 이전 Codelab에서는 모든 숙박 시설 사진에 그리드 레이아웃을 사용하여 RecyclerView를 만들었습니다.

이 버전의 앱에서는 속성 유형 (임대 대 구매)을 사용하고 판매용 속성을 표시하기 위해 그리드 레이아웃에 아이콘을 추가합니다.

앱의 옵션 메뉴를 수정하여 그리드를 필터링하여 대여 또는 판매용 속성만 표시합니다.

마지막으로 개별 속성의 세부정보 뷰를 만들고 개요 그리드의 아이콘을 탐색을 사용하여 해당 세부정보 프래그먼트에 연결합니다.

지금까지 사용한 화성 속성 데이터는 속성 이미지의 URL뿐입니다. 하지만 MarsProperty 클래스에서 정의한 속성 데이터에는 ID, 가격, 유형 (대여 또는 판매)도 포함됩니다. 기억을 되살리기 위해 웹 서비스에서 가져오는 JSON 데이터의 스니펫을 소개합니다.
{
"price":8000000,
"id":"424908",
"type":"rent",
"img_src": "http://mars.jpl.nasa.gov/msl-raw-images/msss/01000/mcam/1000ML0044631290305226E03_DXXX.jpg"
},이 작업에서는 화성 속성 유형을 사용하여 개요 페이지에서 판매 중인 속성에 달러 기호 이미지를 추가합니다.
1단계: 유형을 포함하도록 MarsProperty 업데이트
MarsProperty 클래스는 웹 서비스에서 제공하는 각 속성의 데이터 구조를 정의합니다. 이전 Codelab에서는 Moshi 라이브러리를 사용하여 Mars 웹 서비스의 원시 JSON 응답을 개별 MarsProperty 데이터 객체로 파싱했습니다.
이 단계에서는 속성이 임대용인지 여부 (즉, 유형이 문자열 "rent"인지 "buy"인지)를 나타내는 로직을 MarsProperty 클래스에 추가합니다. 이 로직은 두 개 이상의 위치에서 사용되므로 복제하는 것보다 데이터 클래스에 있는 것이 좋습니다.
- 이전 Codelab에서 MarsRealEstate 앱을 엽니다. (앱이 없는 경우 MarsRealEstateGrid를 다운로드하면 됩니다.)
network/MarsProperty.kt를 엽니다.MarsProperty클래스 정의에 본문을 추가하고 객체가"rent"유형인 경우true를 반환하는isRental의 맞춤 getter를 추가합니다.
data class MarsProperty(
val id: String,
@Json(name = "img_src") val imgSrcUrl: String,
val type: String,
val price: Double) {
val isRental
get() = type == "rent"
}2단계: 그리드 항목 레이아웃 업데이트하기
이제 판매 중인 속성 이미지에만 달러 기호 드로어블이 표시되도록 이미지 그리드의 항목 레이아웃을 업데이트합니다.

데이터 바인딩 표현식을 사용하면 그리드 항목의 XML 레이아웃에서 이 테스트를 완전히 실행할 수 있습니다.
res/layout/grid_view_item.xml를 엽니다.RecyclerView의 그리드 레이아웃에 있는 각 개별 셀의 레이아웃 파일입니다. 현재 파일에는 숙박 시설 이미지의<ImageView>요소만 포함되어 있습니다.<data>요소 내에View클래스의<import>요소를 추가합니다. 레이아웃 파일의 데이터 결합 표현식 내에서 클래스의 구성요소를 사용하려면 가져오기를 사용합니다. 이 경우View.GONE및View.VISIBLE상수를 사용하므로View클래스에 액세스해야 합니다.
<import type="android.view.View"/>- 달러 기호 드로어블이 속성 이미지 위에 쌓이도록 전체 이미지 뷰를
FrameLayout로 둘러쌉니다.
<FrameLayout
android:layout_width="match_parent"
android:layout_height="170dp">
<ImageView
android:id="@+id/mars_image"
...
</FrameLayout>ImageView의 경우android:layout_height속성을match_parent로 변경하여 새 상위FrameLayout을 채웁니다.
android:layout_height="match_parent"FrameLayout내에서 첫 번째<ImageView>요소 바로 아래에 두 번째<ImageView>요소를 추가합니다. 아래에 표시된 정의를 사용하세요. 이 이미지는 그리드 항목의 오른쪽 하단에 Mars 이미지 위에 표시되며, 달러 기호 아이콘에res/drawable/ic_for_sale_outline.xml에 정의된 드로어블을 사용합니다.
<ImageView
android:id="@+id/mars_property_type"
android:layout_width="wrap_content"
android:layout_height="45dp"
android:layout_gravity="bottom|end"
android:adjustViewBounds="true"
android:padding="5dp"
android:scaleType="fitCenter"
android:src="@drawable/ic_for_sale_outline"
tools:src="@drawable/ic_for_sale_outline"/>mars_property_type이미지 뷰에android:visibility속성을 추가합니다. 바인딩 표현식을 사용하여 속성 유형을 테스트하고 공개 상태를View.GONE(대여의 경우) 또는View.VISIBLE(구매의 경우)에 할당합니다.
android:visibility="@{property.rental ? View.GONE : View.VISIBLE}"지금까지는 <data> 요소에 정의된 개별 변수를 사용하는 레이아웃에서만 바인딩 표현식을 보았습니다. 바인딩 표현식은 매우 강력하며 XML 레이아웃 내에서 테스트 및 수학 계산과 같은 작업을 완전히 실행할 수 있습니다. 이 경우 삼항 연산자 (?:)를 사용하여 테스트 (이 객체가 대여인가?)를 실행합니다. true인 경우 (View.GONE로 달러 기호 아이콘 숨기기) 하나의 결과를 제공하고 false인 경우 (View.VISIBLE로 해당 아이콘 표시) 다른 결과를 제공합니다.
새로운 전체 grid_view_item.xml 파일은 다음과 같습니다.
<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>
<import type="android.view.View"/>
<variable
name="property"
type="com.example.android.marsrealestate.network.MarsProperty" />
</data>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="170dp">
<ImageView
android:id="@+id/mars_image"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:adjustViewBounds="true"
android:padding="2dp"
app:imageUrl="@{property.imgSrcUrl}"
tools:src="@tools:sample/backgrounds/scenic"/>
<ImageView
android:id="@+id/mars_property_type"
android:layout_width="wrap_content"
android:layout_height="45dp"
android:layout_gravity="bottom|end"
android:adjustViewBounds="true"
android:padding="5dp"
android:scaleType="fitCenter"
android:src="@drawable/ic_for_sale_outline"
android:visibility="@{property.rental ? View.GONE : View.VISIBLE}"
tools:src="@drawable/ic_for_sale_outline"/>
</FrameLayout>
</layout>- 앱을 컴파일하고 실행하면 대여가 아닌 속성에는 달러 기호 아이콘이 표시됩니다.

현재 앱은 개요 그리드에 모든 화성 속성을 표시합니다. 사용자가 화성에서 임대 주택을 쇼핑하는 경우 판매 중인 주택을 나타내는 아이콘이 유용하지만 페이지에서 스크롤해야 하는 주택이 여전히 많습니다. 이 작업에서는 사용자가 임대 매물만 표시하거나, 판매 매물만 표시하거나, 모든 매물을 표시할 수 있는 옵션 메뉴를 개요 프래그먼트에 추가합니다.

이 작업을 수행하는 한 가지 방법은 개요 그리드에서 각 MarsProperty의 유형을 테스트하고 일치하는 속성만 표시하는 것입니다. 하지만 실제 Mars 웹 서비스에는 rent 유형 또는 buy 유형의 속성만 가져올 수 있는 쿼리 매개변수 또는 옵션 (filter)이 있습니다. 브라우저에서 다음과 같이 realestate 웹 서비스 URL과 함께 이 필터 쿼리를 사용할 수 있습니다.
https://android-kotlin-fun-mars-server.appspot.com/realestate?filter=buy이 작업에서는 MarsApiService 클래스를 수정하여 Retrofit으로 웹 서비스 요청에 쿼리 옵션을 추가합니다. 그런 다음 옵션 메뉴를 연결하여 해당 쿼리 옵션을 사용하여 모든 화성 속성 데이터를 다시 다운로드합니다. 웹 서비스에서 가져오는 응답에는 관심 있는 속성만 포함되므로 개요 그리드의 뷰 표시 로직을 전혀 변경하지 않아도 됩니다.
1단계: Mars API 서비스 업데이트
요청을 변경하려면 이 시리즈의 첫 번째 Codelab에서 구현한 MarsApiService 클래스를 다시 방문해야 합니다. 클래스를 수정하여 필터링 API를 제공합니다.
network/MarsApiService.kt를 엽니다. 가져오기 바로 아래에MarsApiFilter이라는enum을 만들어 웹 서비스에서 예상하는 쿼리 값과 일치하는 상수를 정의합니다.
enum class MarsApiFilter(val value: String) {
SHOW_RENT("rent"),
SHOW_BUY("buy"),
SHOW_ALL("all") }- 필터 쿼리의 문자열 입력을 가져오도록
getProperties()메서드를 수정하고 아래와 같이@Query("filter")로 입력을 주석 처리합니다.
메시지가 표시되면retrofit2.http.Query를 가져옵니다.@Query주석은getProperties()메서드 (따라서 Retrofit)에 필터 옵션으로 웹 서비스 요청을 하도록 지시합니다.getProperties()이 호출될 때마다 요청 URL에는?filter=type부분이 포함되며, 이 부분은 웹 서비스가 해당 쿼리와 일치하는 결과로 응답하도록 지시합니다.
fun getProperties(@Query("filter") type: String): 2단계: 개요 뷰 모델 업데이트
OverviewViewModel의 getMarsRealEstateProperties() 메서드에서 MarsApiService의 데이터를 요청합니다. 이제 필터 인수를 사용하도록 요청을 업데이트해야 합니다.
overview/OverviewViewModel.kt를 엽니다. 이전 단계에서 변경한 내용으로 인해 Android 스튜디오에 오류가 표시됩니다. 가능한 필터 값의 enum인MarsApiFilter을getMarsRealEstateProperties()호출의 매개변수로 추가합니다.
요청이 있는 경우com.example.android.marsrealestate.network.MarsApiFilter를 가져옵니다.
private fun getMarsRealEstateProperties(filter: MarsApiFilter) {- Retrofit 서비스에서
getProperties()호출을 수정하여 해당 필터 쿼리를 문자열로 전달합니다.
var getPropertiesDeferred = MarsApi.retrofitService.getProperties(filter.value)init {}블록에서MarsApiFilter.SHOW_ALL을getMarsRealEstateProperties()에 인수로 전달하여 앱이 처음 로드될 때 모든 속성을 표시합니다.
init {
getMarsRealEstateProperties(MarsApiFilter.SHOW_ALL)
}- 클래스 끝에
MarsApiFilter인수를 사용하고 해당 인수로getMarsRealEstateProperties()를 호출하는updateFilter()메서드를 추가합니다.
fun updateFilter(filter: MarsApiFilter) {
getMarsRealEstateProperties(filter)
}3단계: 프래그먼트를 옵션 메뉴에 연결
마지막 단계는 사용자가 메뉴 옵션을 선택할 때 뷰 모델에서 updateFilter()를 호출하도록 오버플로 메뉴를 프래그먼트에 연결하는 것입니다.
res/menu/overflow_menu.xml를 엽니다. MarsRealEstate 앱에는 사용 가능한 세 가지 옵션(모든 숙박 시설 표시, 임대 숙박 시설만 표시, 판매 숙박 시설만 표시)을 제공하는 기존 오버플로 메뉴가 있습니다.
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/show_all_menu"
android:title="@string/show_all" />
<item
android:id="@+id/show_rent_menu"
android:title="@string/show_rent" />
<item
android:id="@+id/show_buy_menu"
android:title="@string/show_buy" />
</menu>overview/OverviewFragment.kt를 엽니다. 클래스 끝에서onOptionsItemSelected()메서드를 구현하여 메뉴 항목 선택을 처리합니다.
override fun onOptionsItemSelected(item: MenuItem): Boolean {
} onOptionsItemSelected()에서 적절한 필터를 사용하여 뷰 모델의updateFilter()메서드를 호출합니다. Kotlinwhen {}블록을 사용하여 옵션 간에 전환합니다. 기본 필터 값에는MarsApiFilter.SHOW_ALL를 사용합니다. 메뉴 항목을 처리했으므로true를 반환합니다. 요청이 있는 경우MarsApiFilter(com.example.android.marsrealestate.network.MarsApiFilter)를 가져옵니다. 전체onOptionsItemSelected()메서드는 다음과 같습니다.
override fun onOptionsItemSelected(item: MenuItem): Boolean {
viewModel.updateFilter(
when (item.itemId) {
R.id.show_rent_menu -> MarsApiFilter.SHOW_RENT
R.id.show_buy_menu -> MarsApiFilter.SHOW_BUY
else -> MarsApiFilter.SHOW_ALL
}
)
return true
}- 앱을 컴파일하고 실행합니다. 앱은 모든 속성 유형과 판매용 속성이 달러 아이콘으로 표시된 첫 번째 개요 그리드를 실행합니다.
- 옵션 메뉴에서 대여를 선택합니다. 속성이 새로고침되고 달러 아이콘이 표시되지 않습니다. (임대 숙박 시설만 표시됩니다.) 필터링된 속성만 표시되도록 디스플레이가 새로고쳐질 때까지 잠시 기다려야 할 수 있습니다.
- 옵션 메뉴에서 구매를 선택합니다. 속성이 다시 로드되고 모든 속성에 달러 아이콘이 표시됩니다. (판매용 숙박 시설만 표시됩니다.)
이제 화성 속성의 스크롤 그리드 아이콘이 표시되지만, 자세한 내용을 살펴볼 차례입니다. 이 작업에서는 특정 속성의 세부정보를 표시하는 세부정보 프래그먼트를 추가합니다. 세부정보 프래그먼트에는 더 큰 이미지, 가격, 숙박 시설 유형(대여 또는 판매)이 표시됩니다.

이 프래그먼트는 사용자가 개요 그리드에서 이미지를 탭할 때 실행됩니다. 이를 위해 RecyclerView 그리드 항목에 onClick 리스너를 추가한 다음 새 프래그먼트로 이동해야 합니다. 이 수업 전반에서 했던 것처럼 ViewModel에서 LiveData 변경을 트리거하여 탐색합니다. 또한 탐색 구성요소의 Safe Args 플러그인을 사용하여 선택한 MarsProperty 정보를 개요 프래그먼트에서 세부정보 프래그먼트로 전달합니다.
1단계: 세부정보 뷰 모델 생성 및 세부정보 레이아웃 업데이트
개요 뷰 모델 및 프래그먼트에 사용한 프로세스와 마찬가지로 이제 세부정보 프래그먼트의 뷰 모델과 레이아웃 파일을 구현해야 합니다.
detail/DetailViewModel.kt를 엽니다. 네트워크 관련 Kotlin 파일이network폴더에 포함되고 개요 파일이overview에 포함되는 것처럼detail폴더에는 세부정보 뷰와 연결된 파일이 포함됩니다.DetailViewModel클래스 (현재는 비어 있음)는 생성자에서marsProperty를 매개변수로 사용합니다.
class DetailViewModel( marsProperty: MarsProperty,
app: Application) : AndroidViewModel(app) {
}- 클래스 정의 내에서 선택한 Mars 속성에
LiveData를 추가하여 세부정보 뷰에 해당 정보를 노출합니다.MarsProperty자체를 보유하는MutableLiveData를 만들고 변경 불가능한 공개LiveData속성을 노출하는 일반적인 패턴을 따릅니다.
요청이 있는 경우androidx.lifecycle.LiveData를 가져오고androidx.lifecycle.MutableLiveData를 가져옵니다.
private val _selectedProperty = MutableLiveData<MarsProperty>()
val selectedProperty: LiveData<MarsProperty>
get() = _selectedPropertyinit {}블록을 만들고 생성자의MarsProperty객체로 선택한 Mars 속성의 값을 설정합니다.
init {
_selectedProperty.value = marsProperty
}res/layout/fragment_detail.xml을 열고 디자인 뷰에서 확인합니다.
세부정보 프래그먼트의 레이아웃 파일입니다. 큰 사진의ImageView, 숙박 시설 유형 (임대 또는 판매)의TextView, 가격의TextView이 포함되어 있습니다. 사용자가 가로 모드로 볼 때와 같이 뷰가 디스플레이에 비해 너무 커지면 자동으로 스크롤되도록 제약 조건 레이아웃이ScrollView로 래핑됩니다.- 레이아웃의 텍스트 탭으로 이동합니다. 레이아웃 상단에서
<ScrollView>요소 바로 앞에<data>요소를 추가하여 세부정보 뷰 모델을 레이아웃과 연결합니다.
<data>
<variable
name="viewModel"
type="com.example.android.marsrealestate.detail.DetailViewModel" />
</data>ImageView요소에app:imageUrl속성을 추가합니다. 뷰 모델의 선택된 속성에서imgSrcUrl로 설정합니다.
Glide를 사용하여 이미지를 로드하는 바인딩 어댑터는 모든app:imageUrl속성을 감시하므로 여기에서도 자동으로 사용됩니다.
app:imageUrl="@{viewModel.selectedProperty.imgSrcUrl}"2단계: 개요 뷰 모델에서 탐색 정의하기
사용자가 개요 모델에서 사진을 탭하면 클릭한 항목에 관한 세부정보를 표시하는 프래그먼트로의 탐색이 트리거되어야 합니다.
overview/OverviewViewModel.kt를 엽니다._navigateToSelectedPropertyMutableLiveData속성을 추가하고 불변LiveData로 노출합니다.
이LiveData이 null이 아닌 값으로 변경되면 탐색이 트리거됩니다. (곧 이 변수를 관찰하고 탐색을 트리거하는 코드를 추가할 예정입니다.)
private val _navigateToSelectedProperty = MutableLiveData<MarsProperty>()
val navigateToSelectedProperty: LiveData<MarsProperty>
get() = _navigateToSelectedProperty- 클래스 끝에 선택한 Mars 속성으로 _
navigateToSelectedProperty을 설정하는displayPropertyDetails()메서드를 추가합니다.
fun displayPropertyDetails(marsProperty: MarsProperty) {
_navigateToSelectedProperty.value = marsProperty
}_navigateToSelectedProperty값을 null로 설정하는displayPropertyDetailsComplete()메서드를 추가합니다. 탐색 상태를 완료로 표시하고 사용자가 세부정보 뷰에서 돌아올 때 탐색이 다시 트리거되지 않도록 하려면 이 작업이 필요합니다.
fun displayPropertyDetailsComplete() {
_navigateToSelectedProperty.value = null
}3단계: 그리드 어댑터 및 프래그먼트에서 클릭 리스너 설정
overview/PhotoGridAdapter.kt를 엽니다. 클래스 끝에marsProperty매개변수가 있는 람다를 사용하는 맞춤OnClickListener클래스를 만듭니다. 클래스 내에서 람다 매개변수로 설정된onClick()함수를 정의합니다.
class OnClickListener(val clickListener: (marsProperty:MarsProperty) -> Unit) {
fun onClick(marsProperty:MarsProperty) = clickListener(marsProperty)
}PhotoGridAdapter의 클래스 정의까지 위로 스크롤하고 생성자에 비공개OnClickListener속성을 추가합니다.
class PhotoGridAdapter( private val onClickListener: OnClickListener ) :
ListAdapter<MarsProperty,
PhotoGridAdapter.MarsPropertyViewHolder>(DiffCallback) {onBindviewHolder()메서드의 그리드 항목에onClickListener를 추가하여 사진을 클릭 가능하게 만듭니다.getItem() and bind()호출 사이에 클릭 리스너를 정의합니다.
override fun onBindViewHolder(holder: MarsPropertyViewHolder, position: Int) {
val marsProperty = getItem(position)
holder.itemView.setOnClickListener {
onClickListener.onClick(marsProperty)
}
holder.bind(marsProperty)
}overview/OverviewFragment.kt를 엽니다.onCreateView()메서드에서binding.photosGrid.adapter속성을 초기화하는 줄을 아래에 표시된 줄로 바꿉니다.
이 코드는PhotoGridAdapter.onClickListener객체를PhotoGridAdapter생성자에 추가하고 전달된MarsProperty객체로viewModel.displayPropertyDetails()를 호출합니다. 이렇게 하면 탐색 뷰 모델에서LiveData가 트리거됩니다.
binding.photosGrid.adapter = PhotoGridAdapter(PhotoGridAdapter.OnClickListener {
viewModel.displayPropertyDetails(it)
})4단계: 탐색 그래프 수정 및 MarsProperty를 Parcelable로 만들기
사용자가 개요 그리드에서 사진을 탭하면 앱이 세부정보 프래그먼트로 이동하고 선택한 Mars 속성의 세부정보를 전달하여 세부정보 뷰에 해당 정보가 표시되도록 해야 합니다.

현재 탭을 처리하는 PhotoGridAdapter의 클릭 리스너와 뷰 모델에서 탐색을 트리거하는 방법이 있습니다. 하지만 아직 세부정보 프래그먼트에 전달되는 MarsProperty 객체가 없습니다. 이를 위해 탐색 구성요소의 Safe Args를 사용합니다.
res/navigation/nav_graph.xml를 엽니다. Text 탭을 클릭하여 탐색 그래프의 XML 코드를 확인합니다.- 세부정보 프래그먼트의
<fragment>요소 내부에 아래와 같이<argument>요소를 추가합니다.selectedProperty이라는 이 인수의 유형은MarsProperty입니다.
<argument
android:name="selectedProperty"
app:argType="com.example.android.marsrealestate.network.MarsProperty"
/>- 앱을 컴파일합니다.
MarsProperty가 parcelable이 아니므로 탐색에서 오류가 발생합니다.Parcelable인터페이스를 사용하면 객체를 직렬화하여 객체의 데이터를 프래그먼트 또는 활동 간에 전달할 수 있습니다. 이 경우MarsProperty객체 내부의 데이터가 Safe Args를 통해 세부정보 프래그먼트로 전달되려면MarsProperty가Parcelable인터페이스를 구현해야 합니다. 다행히 Kotlin은 이 인터페이스를 구현하기 위한 쉬운 단축키를 제공합니다. network/MarsProperty.kt를 엽니다. 클래스 정의에@Parcelize주석을 추가합니다.
요청이 있는 경우kotlinx.android.parcel.Parcelize를 가져옵니다.@Parcelize주석은 Kotlin Android 확장 프로그램을 사용하여 이 클래스의Parcelable인터페이스에 있는 메서드를 자동으로 구현합니다. 다른 조치를 취하지 않아도 됩니다.
@Parcelize
data class MarsProperty (MarsProperty의 클래스 정의를 변경하여Parcelable를 확장합니다.
요청이 있는 경우android.os.Parcelable를 가져옵니다.
이제MarsProperty클래스 정의는 다음과 같습니다.
@Parcelize
data class MarsProperty (
val id: String,
@Json(name = "img_src") val imgSrcUrl: String,
val type: String,
val price: Double) : Parcelable {5단계: 프래그먼트 연결하기
아직 탐색이 이루어지지 않습니다. 실제 탐색은 프래그먼트에서 이루어집니다. 이 단계에서는 개요와 세부정보 프래그먼트 간 탐색을 구현하기 위한 마지막 부분을 추가합니다.
overview/OverviewFragment.kt를 엽니다.onCreateView()에서 사진 그리드 어댑터를 초기화하는 줄 아래에 아래와 같은 줄을 추가하여 개요 뷰 모델에서navigatedToSelectedProperty를 관찰합니다.
요청이 있는 경우androidx.lifecycle.Observer를 가져오고androidx.navigation.fragment.findNavController를 가져옵니다.
관찰자는 람다의it인MarsProperty이 null이 아닌지 테스트하고, null이 아닌 경우findNavController()이 있는 프래그먼트에서 탐색 컨트롤러를 가져옵니다. 앱이OverviewFragment로 돌아갈 때 실수로 탐색이 다시 트리거되지 않도록displayPropertyDetailsComplete()를 호출하여 뷰 모델에LiveData을 null 상태로 재설정하도록 지시합니다.
viewModel.navigateToSelectedProperty.observe(this, Observer {
if ( null != it ) {
this.findNavController().navigate(
OverviewFragmentDirections.actionShowDetail(it))
viewModel.displayPropertyDetailsComplete()
}
})detail/DetailFragment.kt를 엽니다.onCreateView()메서드에서setLifecycleOwner()호출 바로 아래에 다음 줄을 추가합니다. 이 줄은 Safe Args에서 선택한MarsProperty객체를 가져옵니다.
Kotlin의 null이 아닌 어설션 연산자 (!!)가 사용되었습니다.selectedProperty가 없으면 심각한 문제가 발생한 것이므로 코드가 null 포인터를 발생시키도록 해야 합니다. (프로덕션 코드에서는 이 오류를 어떤 식으로든 처리해야 합니다.)
val marsProperty = DetailFragmentArgs.fromBundle(arguments!!).selectedProperty- 다음 줄을 추가하여 새
DetailViewModelFactory를 가져옵니다.DetailViewModelFactory를 사용하여DetailViewModel의 인스턴스를 가져옵니다. 시작 앱에는DetailViewModelFactory구현이 포함되어 있으므로 여기서는 초기화만 하면 됩니다.
val viewModelFactory = DetailViewModelFactory(marsProperty, application)- 마지막으로 이 줄을 추가하여 공장에서
DetailViewModel를 가져오고 모든 부분을 연결합니다.
binding.viewModel = ViewModelProviders.of(
this, viewModelFactory).get(DetailViewModel::class.java)- 앱을 컴파일하고 실행한 다음 화성 속성 사진을 탭합니다. 해당 속성의 세부정보에 대한 세부정보 프래그먼트가 표시됩니다. 뒤로 버튼을 탭하여 개요 페이지로 돌아가면 세부정보 화면이 여전히 다소 비어 있습니다. 다음 작업에서 세부정보 페이지에 속성 데이터를 추가하는 작업을 완료합니다.
현재 세부정보 페이지에는 개요 페이지에서 보던 것과 동일한 화성 사진만 표시됩니다. MarsProperty 클래스에는 속성 유형 (임대 또는 구매)과 속성 가격도 있습니다. 세부정보 화면에 이 두 값이 모두 포함되어야 하며, 임대 숙박 시설에서 가격이 월별 값임을 표시하는 것이 좋습니다. 뷰 모델에서 LiveData 변환을 사용하여 두 가지를 모두 구현합니다.
res/values/strings.xml를 엽니다. 시작 코드에는 아래와 같이 세부정보 뷰의 문자열을 빌드하는 데 도움이 되는 문자열 리소스가 포함되어 있습니다. 가격의 경우 속성 유형에 따라display_price_monthly_rental리소스 또는display_price리소스를 사용합니다.
<string name="type_rent">Rent</string>
<string name="type_sale">Sale</string>
<string name="display_type">For %s</string>
<string name="display_price_monthly_rental">$%,.0f/month</string>
<string name="display_price">$%,.0f</string>detail/DetailViewModel.kt를 엽니다. 클래스 하단에 아래 코드를 추가합니다.
요청이 있는 경우androidx.lifecycle.Transformations를 가져옵니다.
이 변환은 첫 번째 작업의 동일한 테스트를 사용하여 선택한 속성이 임대인지 테스트합니다. 숙박 시설이 임대인 경우 변환은 Kotlinwhen {}스위치가 있는 리소스에서 적절한 문자열을 선택합니다. 이 두 문자열 모두 끝에 숫자가 필요하므로 나중에property.price를 연결합니다.
val displayPropertyPrice = Transformations.map(selectedProperty) {
app.applicationContext.getString(
when (it.isRental) {
true -> R.string.display_price_monthly_rental
false -> R.string.display_price
}, it.price)
}- 생성된
R클래스를 가져와 프로젝트의 문자열 리소스에 액세스합니다.
import com.example.android.marsrealestate.RdisplayPropertyPrice변환 후 아래에 표시된 코드를 추가합니다. 이 변환은 속성 유형이 렌탈인지에 따라 여러 문자열 리소스를 연결합니다.
val displayPropertyType = Transformations.map(selectedProperty) {
app.applicationContext.getString(R.string.display_type,
app.applicationContext.getString(
when (it.isRental) {
true -> R.string.type_rent
false -> R.string.type_sale
}))
}res/layout/fragment_detail.xml를 엽니다. 이제 한 가지 작업만 남았습니다.LiveData변환으로 만든 새 문자열을 세부정보 뷰에 바인딩하는 것입니다. 이렇게 하려면 속성 유형 텍스트의 텍스트 필드 값을viewModel.displayPropertyType로 설정하고 가격 값 텍스트의 텍스트 필드 값을viewModel.displayPropertyPrice로 설정합니다.
<TextView
android:id="@+id/property_type_text"
...
android:text="@{viewModel.displayPropertyType}"
...
tools:text="To Rent" />
<TextView
android:id="@+id/price_value_text"
...
android:text="@{viewModel.displayPropertyPrice}"
...
tools:text="$100,000" />- 앱을 컴파일하고 실행합니다. 이제 모든 속성 데이터가 세부정보 페이지에 보기 좋게 형식화되어 표시됩니다.

Android 스튜디오 프로젝트: MarsRealEstateFinal
결합 표현식
- XML 레이아웃 파일에서 결합 표현식을 사용하여 결합된 데이터에 수학 또는 조건부 테스트와 같은 간단한 프로그래매틱 작업을 실행합니다.
- 레이아웃 파일 내에서 클래스를 참조하려면
<data>태그 내에<import>태그를 사용합니다.
웹 서비스 쿼리 옵션
- 웹 서비스에 대한 요청에는 선택적 매개변수가 포함될 수 있습니다.
- 요청에서 쿼리 매개변수를 지정하려면 Retrofit에서
@Query주석을 사용합니다.
Udacity 과정:
Android 개발자 문서:
- ViewModel 개요
- LiveData 개요
- 결합 어댑터
- 레이아웃 및 바인딩 수식
- 탐색
- 탐색 구성요소 시작하기
- 대상 간 데이터 전달 (Safe Args도 설명)
Transformations클래스ViewModelProvider클래스ViewModelProvider.Factory클래스
기타:
이 섹션에는 강사가 진행하는 과정의 일부로 이 Codelab을 진행하는 학생에게 출제할 수 있는 과제가 나열되어 있습니다. 다음 작업은 강사가 결정합니다.
- 필요한 경우 과제를 할당합니다.
- 과제 제출 방법을 학생에게 알립니다.
- 과제를 채점합니다.
강사는 이러한 추천을 원하는 만큼 사용할 수 있으며 적절하다고 생각되는 다른 과제를 출제해도 됩니다.
이 Codelab을 직접 진행하는 경우 이러한 과제를 자유롭게 사용하여 배운 내용을 테스트해 보세요.
질문에 답하세요
질문 1
XML 레이아웃 파일의 <import> 태그는 어떤 기능을 하나요?
▢ 하나의 레이아웃 파일을 다른 파일에 포함합니다.
▢ 레이아웃 파일 내에 Kotlin 코드를 삽입합니다.
▢ 데이터 결합 속성에 대한 액세스를 제공합니다.
▢ 결합 표현식에서 클래스 및 클래스 멤버를 참조할 수 있게 합니다.
질문 2
Retrofit에서 REST 웹 서비스 호출에 쿼리 옵션을 추가하려면 어떻게 해야 하나요?
▢ 쿼리를 요청 URL 끝에 추가합니다.
▢ 요청하는 함수에 쿼리의 매개변수를 추가하고 @Query로 매개변수를 주석 처리합니다.
▢ Query 클래스를 사용하여 요청을 빌드합니다.
▢ Retrofit 빌더에서 addQuery() 메서드를 사용합니다.
다음 강의 시작:
이 과정의 다른 Codelab 링크는 Android Kotlin 기초 Codelab 방문 페이지를 참고하세요.