안드로이드

[Kotlin/Android] Internet Connect (완) Complex Binding Expression, Filtering(@Query("filter")), OptionMenu와 Filtering연동

란서 2022. 1. 20. 19:22

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를 간단하게 수정하여 해당 로직을 담는 변수를 정의한다.

    data class MarsProperty(
    ...
    val type :String
    ) {
    	val isRental : Boolean = type =="rent"
    }​
    val isRental = type이 "rent" 와 같다면 true를 그렇지 않다면 false를 반환한다.
    해당 변수를 이용해 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 를 포함시킨다.

    <data>
    		...
    		<import type="android.view.View" />
    </data>
    이 <import> 태그를 통해 바인딩 표현식 내에서도 해당 클래스의 component를 사용할 수 있게 된다. 
    그렇다면 이제 '$' 이미지뷰가 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 또한 바꿔보았다.

            <ImageView
                android:id="@+id/status_image"
                ...
                android:visibility="@{viewModel.status==MarsApiStatus.DONE ? View.GONE : View.VISIBLE}"
                marsApiStatus="@{viewModel.status}"/>​
     기존에는 BindingAdapter("marsApiStatus") 내에서 View의 visibility와 setImageResource 까지 모두 처리했기 때문에 함수의 기능이 복잡해질 수 있다는 생각이 들었다.

    따라서 BindingAdpater에서는 이미지 set만 해주고, 해당 xml에서 바인딩 표현식을 통해 View의 visibility를 설정해주었다.

    이를 위해서 
            <import type="android.view.View" />
            <import type="com.example.android.marsrealestate.overview.MarsApiStatus" />​
    두 개의 <import> tag도 달아주었다.

 

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에 들어갈 변수들을  클래스화 시켜서 좀 더 가시성을 확보. 할 수 있다..? 일단 코드로 보자.

    enum class MarsApiFilter(value:String) {
    	SHOW_ALL("all"),
        SHOW_RENT("rent"),
        SHOW_BUY("buy")
    }​
    이런식으로 MarsApiFilter.SHOW_ALL를 사용하여 value로 "all" 문자열 getMarsProperties 함수에  매개변수로 넘겨줄 수 있도록 한다. 

    예를 들어  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
    }​


    구현된 어플리케이션 모습 

SHOW_ALL // SHOW_RENT // SHOW_BUY