Complex Binding Expression (복잡한 바인딩 표현식 다뤄보기)
이번엔 recyclerView의 item list의 기존 이미지뷰에 더해 해당 item의 type이 buy일 때 특정한 이미지를 만들어보려고 한다.

이미 List<MarsProperty>의 각 오브젝트에 담긴 type에 대한 정보에 따라 viewModel과 binding expression을 수정하여 $ ImageView를 간단하게 사라지게하고 나타나게 해보자.
- data calss MarsProperty 수정
우선 해당 객체의 type이 "buy" 인지, "rent" 인지 구분하는 logic이 필요하다. 따라서 data class를 간단하게 수정하여 해당 로직을 담는 변수를 정의한다.
val isRental = type이 "rent" 와 같다면 true를 그렇지 않다면 false를 반환한다.data class MarsProperty( ... val type :String ) { val isRental : Boolean = type =="rent" }
해당 변수를 이용해 binding expression을 구현해보자. - grid_list_item.xml
우선 "$" 이미지를 담을 새로운 이미지뷰를 만들어 줘야 한다. 그러면 기존의 이미지뷰와 새로 생길 이미지뷰. 두 개의 이미지뷰가 생긴다. 따라서 새로운 ViewGroup을 만들어 안에다가 쏙쏙 넣어주자
<FrameLayout android:layout_width="match_parent" android:layout_height="170dp" android:onClick="@{()->clickListener.onClick(item)}"> <ImageView android:id="@+id/mars_image" ... /> <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" /> </FrameLayout>
이 후 <data> tag 내에 <import> tag를 넣어 android.view.View 를 포함시킨다.
이 <import> 태그를 통해 바인딩 표현식 내에서도 해당 클래스의 component를 사용할 수 있게 된다.<data> ... <import type="android.view.View" /> </data>
그렇다면 이제 '$' 이미지뷰가 MarsProperty.type 이 rent일 때는 사라지고, buy일 때만 나타날 수 있도록 코드를 구현해보자
<ImageView android:id="@+id/mars_property_type" ... android:visibility="@{item.rental ? View.GONE : View.VISIBLE}" />
이처럼 android:visibility 속성에 binding 표현식과 View클래스, ?: Tenarty 를 사용하여 "@{item.rental ? View.GONE : View.VISIBLE}" 손쉽게 이미지뷰가 사라졌다 생겼다 하게 만들 수 있다.
이를 응용하여 저번 error handling 때 만들었던 recyclerView 내에 위치한 ImageView 또한 바꿔보았다.
기존에는 BindingAdapter("marsApiStatus") 내에서 View의 visibility와 setImageResource 까지 모두 처리했기 때문에 함수의 기능이 복잡해질 수 있다는 생각이 들었다.<ImageView android:id="@+id/status_image" ... android:visibility="@{viewModel.status==MarsApiStatus.DONE ? View.GONE : View.VISIBLE}" marsApiStatus="@{viewModel.status}"/>
따라서 BindingAdpater에서는 이미지 set만 해주고, 해당 xml에서 바인딩 표현식을 통해 View의 visibility를 설정해주었다.
이를 위해서
두 개의 <import> tag도 달아주었다.<import type="android.view.View" /> <import type="com.example.android.marsrealestate.overview.MarsApiStatus" />
Filtering 을 위해 요청 메서드 Query를 더하고, Option Menu와 Hook Up 하기.
현재 App화면에는 모든 MarsProperty가 type을 불문하고 리사이클러뷰를 통해 보여지고 있는 모습이다.
하지만 우리는 특정한 객체만을 보고 싶을 수 있다. (예를 들어 type이 buy 이거나 rent 인 경우)
이런 경우에는 actual data 자체에서, type이 buy인 것들만 find를 해서 새로운 List<MarsProperty> 를 만들어 submitList 할 수도 있지만, 이 방식은 받아온 데이터를 한번 더 2차 가공하는 거여서 시간이 너무 오래걸린다.
따라서 애초에 web service를 요청할 때 filter를 걸어서 조건에 맞는 데이터 응답을 받는 방식으로 진행.
- MarsApiService 수정
retrofit 의 요청 (=web service 요청) 메서드는 현재 getMarsProperties() 가 있다. 해당 메서드가 filter 기능을 갖춰 요청을 할 수 있도록 만들어보자.
interface MarsApiService { @GET("realestate") suspend fun getMarsProperties() : List<MarsProperty> } //-> interface MarsApiService { suspend fun getMarsProperties(@Query("filter") type: String) : List<MarsProperty> }
@Query("filter") = 해당 주석이 붙은 메서드의 매개변수는 실제 주소 뒤에 "?filter=type" 이 붙어서 해당 조건을 만족하는 데이터로만 응답을 받을 수 있다. 아마 매개변수의 식별자 name은 반드시 web server의 데이터 key의 name과 같아야 하고, 들어오는 인자의 문자열도 value와 같아야 데이터를 가져올 수있을 것이다. - enum class MarsApiFilter 만들기
해당 enum class는 filter에 들어갈 변수들을 클래스화 시켜서 좀 더 가시성을 확보. 할 수 있다..? 일단 코드로 보자.
이런식으로 MarsApiFilter.SHOW_ALL를 사용하여 value로 "all" 문자열 getMarsProperties 함수에 매개변수로 넘겨줄 수 있도록 한다.enum class MarsApiFilter(value:String) { SHOW_ALL("all"), SHOW_RENT("rent"), SHOW_BUY("buy") }
예를 들어 OverViewViewModel 에서는
init { getMarsRealEstateProperties(MarsApiFilter.SHOW_ALL) } private fun getMarsRealEstateProperties(filter:MarsApiFilter) { viewModelScope.launch { try{ _property.value = MarsApi.retrofitService.getProperties(filter.value) } } }
fun getMarsRealEstateProperties() 에 매개변수 filter : MarsApiFilter 를 추가하고, getProperties() 에 filter.value를 넘겨줄 수 있도록 하면 가시성을 확보하면서 손쉽게 다른 클래스에서도 사용할 수 있다. ( 이 후 OptionMenu Hook Up에 쓰일 때 보도록 하자.)
- OverViewFragment에 OptionMenu 만들기.
1. menu/overflow_menu.xml 만들기
<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>
2.OverView Fragment에 optionMenu 생성과 menu item을 선택했을 때 반응하는 메소드 override 하기.
In OverViewFragment
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { inflater.inflate(R.menu.overflow_menu, menu) super.onCreateOptionsMenu(menu, inflater) } override fun onOptionsItemSelected(item: MenuItem): Boolean { ... }
-onCreateOptionsMenu () : inflater.inflate () 인자로 생성하고 싶은 menu를 넣어주면 optionMenu로 만들어준다. -onOptionsItemSelected() : item에는 Menu의 item들이 포함되어 있다. 따라서 item.itemId를 이용하여 해당 item이 클릭 됐을 때 취하고 싶은 행동을 구현하면 된다.
OptionMenu
3.OptionMenu의 item과 MarsApiService의 getProperties 연동하기.
유저가 OptionMenu의 Show all, Rent, Buy 를 클릭했을 때 해당 type의 MarsProperty만 보게 만들고 싶다.
우선 OverviewViewModel 내에 web service 요청을 변경하여 리사이클러뷰를 갱신할 수 있는 (= filter) update함수를 정의한다.
//In OverviewModel fun updateFilter(filter: MarsApiFilter) { getMarsRealestateProperty(filter) }
그리고 onOptionItemsSelected() 의 함수 내에 viewModel.updateFilter() 메소드를 사용하여 filter값을 메뉴의 item에 따라 바뀌도록 만든다.
override fun onOptionItemsSelected(item:MenuItem) { 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 }
구현된 어플리케이션 모습


