Skip to content

Latest commit

ย 

History

History
807 lines (704 loc) ยท 29.4 KB

databinding.md

File metadata and controls

807 lines (704 loc) ยท 29.4 KB

DataBinding

๋ชฉ์ฐจ

DataBinding์ด๋ž€ ?

xml ์— ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ”์ธ๋”ฉํ•˜์—ฌ ๋ถˆํ•„์š”ํ•œ ์ฝ”๋“œ๋ฅผ ์ค„์ด๋Š” ๋ฐฉ๋ฒ•์œผ๋กœ, ๋ณดํ†ต MVVM ํŒจํ„ด์„ ๊ตฌํ˜„ ํ•  ๋•Œ ์‚ฌ์šฉ๋œ๋‹ค.
์ฆ‰, ๋ฐ์ดํ„ฐ๋ฐ”์ธ๋”ฉ์€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋กœ์ง๊ณผ ๋ ˆ์ด์•„์›ƒ์„ bindingํ•˜๋Š” ๋ฐ ํ•„์š”ํ•œ ๋ถˆํ•„์š”ํ•œ ์ฝ”๋“œ๋ฅผ ์ตœ์†Œํ™”ํ•œ๋‹ค.

์„ค์ •

// Android Studio 4.0 
android { 
    buildFeatures { 
        dataBinding = true 
    } 
}

๋ ˆ์ด์•„์›ƒ ์„ ์–ธ

ํ‘œํ˜„์‹ ์–ธ์–ด๋กœ ๋ ˆ์ด์•„์›ƒ์˜ ๋ทฐ์™€ ๋ณ€์ˆ˜๋ฅผ ์—ฐ๊ฒฐํ•˜๋Š” ํ‘œํ˜„์‹์„ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค.
DataBinding Library๋Š” ๋ ˆ์ด์•„์›ƒ์˜ ๋ทฐ๋ฅผ ๋ฐ์ดํ„ฐ ๊ฐœ์ฒด์™€ ๊ฒฐํ•ฉํ•˜๋Š” ๋ฐ ํ•„์š”ํ•œ ํด๋ž˜์Šค๋ฅผ ์ž๋™์œผ๋กœ ์ƒ์„ฑํ•œ๋‹ค.
DataBinding Library๋Š” import, variable ๋ฐ includes๊ณผ ๊ฐ™์ด ๋ ˆ์ด์•„์›ƒ์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•œ๋‹ค.

<layout xmlns:android="http://schemas.android.com/apk/res/android"
            xmlns:app="http://schemas.android.com/apk/res-auto">
        <data>
            <variable
                name="viewmodel"
                type="com.myapp.data.ViewModel" />
        </data>
        <ConstraintLayout... /> <!-- UI layout's root element -->
</layout>

๋ ˆ์ด์•„์›ƒ ๋‚ด์˜ ํ‘œํ˜„์‹์€ '@{}' ๊ตฌ๋ฌธ์„ ์‚ฌ์šฉํ•˜์—ฌ ํŠน์„ฑ ์†์„ฑ์— ์ž‘์„ฑ๋œ๋‹ค.

<TextView 
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@{viewmodel.getName()}" />

๋ฐ”์ธ๋”ฉํ•˜๊ธฐ

layout ํƒœ๊ทธ๋กœ xml์„ ๊ฐ์‹ธ๋ฉด ๊ฐ ๋ ˆ์ด์•„์›ƒ์˜ Binding Class๊ฐ€ ์ƒ์„ฑ๋œ๋‹ค.
๊ธฐ๋ณธ์ ์œผ๋กœ ํด๋ž˜์Šค ์ด๋ฆ„์€ ๋ ˆ์ด์•„์›ƒ ํŒŒ์ผ ์ด๋ฆ„์„ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•˜์—ฌ ํŒŒ์Šค์นผ ํ‘œ๊ธฐ๋ฒ•์œผ๋กœ ๋ณ€ํ™˜ํ•˜๊ณ  Binding ์ ‘๋ฏธ์‚ฌ๋ฅผ ์ถ”๊ฐ€ํ•œ๋‹ค.

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    val binding: ActivityMainBinding = DataBindingUtil.setContentView(
            this, R.layout.activity_main)
    // ์œ„์˜ ๋ ˆ์ด์•„์›ƒ ํŒŒ์ผ ์ด๋ฆ„์€ activity_main.xml์ด๋ฏ€๋กœ ์ƒ์„ฑ๋˜๋Š” ํด๋ž˜์Šค๋Š” ActivityMainBinding์ด๋‹ค

    binding.viewmodel = MainViewModel()
}

LayoutInflator๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ๋‹ค.

val binding: ActivityMainBinding = ActivityMainBinding.inflate(getLayoutInflater())

๋˜ํ•œ Fragment, RecyclerView ์–ด๋Œ‘ํ„ฐ ๋‚ด์—์„œ ๋ฐ์ดํ„ฐ ๊ฒฐํ•ฉ ํ•ญ๋ชฉ์„ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋‹ค๋ฉด ๋‹ค์Œ ์ฝ”๋“œ ์˜ˆ์—์„œ์™€ ๊ฐ™์ด ๊ฒฐํ•ฉ ํด๋ž˜์Šค ๋˜๋Š” BindingClass์˜ ํƒ€์ž…์„ ๋ฏธ๋ฆฌ ์•Œ ์ˆ˜ ์—†๋Š”๊ฒฝ์šฐ DataBindingUtil ํด๋ž˜์Šค์˜ inflate() ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ๋‹ค.

val listItemBinding = ListItemBinding.inflate(layoutInflater, viewGroup, false)
// or
val listItemBinding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false)

์ฆ‰์‹œ ๋ฐ”์ธ๋”ฉํ•˜๊ธฐ

๊ตฌ์ฒด์ ์ธ ๊ฒฐํ•ฉ ํด๋ž˜์Šค๋ฅผ ์•Œ ์ˆ˜ ์—†๋Š” ๋•Œ๋„ ์žˆ๋‹ค.
์˜ˆ๋ฅผ ๋“ค์–ด ์ž„์˜์˜ ๋ ˆ์ด์•„์›ƒ์— ์ž‘๋™ํ•˜๋Š” RecyclerView.Adapter๋Š” ํŠน์ • ๊ฒฐํ•ฉ ํด๋ž˜์Šค๋ฅผ ์ธ์‹ํ•˜์ง€ ๋ชปํ•œ๋‹ค.
๋”ฐ๋ผ์„œ ์ด ์–ด๋Œ‘ํ„ฐ๋Š” onBindViewHolder() ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ๋™์•ˆ์—๋„ ๊ณ„์†ํ•ด์„œ ๊ฒฐํ•ฉ ๊ฐ’์„ ํ• ๋‹นํ•ด์•ผ ํ•œ๋‹ค.

override fun onBindViewHolder(holder: BindingHolder, position: Int) {
    item: T = items.get(position)
    holder.binding.setVariable(BR.item, item);
    holder.binding.executePendingBindings();
}

ํ‘œํ˜„์‹ ์–ธ์–ด

xml ๋ ˆ์ด์•„์›ƒ ํ‘œํ˜„์‹์–ธ์–ด

  • ์‚ฐ์ˆ  + - / * %
  • ๋ฌธ์ž์—ด ์—ฐ๊ฒฐ +
  • ๋…ผ๋ฆฌ && ||
  • ๋ฐ”์ด๋„ˆ๋ฆฌ & | ^
  • ๋‹จํ•ญ + - ! ~
  • ์ „ํ™˜ >> >>> <<
  • ๋น„๊ต == > < >= <=(<๋Š” &lt;์œผ๋กœ ์ด์Šค์ผ€์ดํ”„ ์ฒ˜๋ฆฌํ•ด์•ผ ํ•จ)
  • instanceof
  • ๋ฆฌํ„ฐ๋Ÿด - ๋ฌธ์ž, ๋ฌธ์ž์—ด, ์ˆซ์ž, null
  • ๋ณ€ํ™˜
  • ๋ฉ”์„œ๋“œ ํ˜ธ์ถœ
  • ํ•„๋“œ ์•ก์„ธ์Šค
  • ๋ฐฐ์—ด ์•ก์„ธ์Šค []
  • ์‚ผํ•ญ ์—ฐ์‚ฐ์ž ?:
android:text="@{String.valueOf(index + 1)}"
android:visibility="@{age > 13 ? View.GONE : View.VISIBLE}"
android:transitionName='@{"image_" + id}'

์‚ฌ์šฉํ•  ์ˆ˜ ์—†๋Š” ์—ฐ์‚ฐ์ž

  • this
  • super
  • new
  • ๋ช…์‹œ์  ์ œ๋„ค๋ฆญ(Generic) ํ˜ธ์ถœ

Null ๋ณ‘ํ•ฉ ์—ฐ์‚ฐ์ž

null ๋ณ‘ํ•ฉ ์—ฐ์‚ฐ์ž(??)๋Š” ์™ผ์ชฝ ํ”ผ์—ฐ์‚ฐ์ž๊ฐ€ null์ด ์•„๋‹ˆ๋ฉด ์™ผ์ชฝ ํ”ผ์—ฐ์‚ฐ์ž๋ฅผ ์„ ํƒํ•˜๊ณ  null์ด๋ฉด ์˜ค๋ฅธ์ชฝ ํ”ผ์—ฐ์‚ฐ์ž๋ฅผ ์„ ํƒํ•œ๋‹ค.

android:text="@{user.displayName ?? user.lastName}"

null ํฌ์ธํ„ฐ ์˜ˆ์™ธ ๋ฐฉ์ง€

์ƒ์„ฑ๋œ ๋ฐ์ดํ„ฐ ๊ฒฐํ•ฉ ์ฝ”๋“œ๋Š” ์ž๋™์œผ๋กœ null ๊ฐ’์„ ํ™•์ธํ•˜๊ณ  null ํฌ์ธํ„ฐ ์˜ˆ์™ธ๋ฅผ ๋ฐฉ์ง€ํ•œ๋‹ค.
์˜ˆ๋ฅผ ๋“ค์–ด @{user.name}ํ‘œํ˜„์‹์—์„œ user๊ฐ€ null์ด๋ฉด user.name์— null์ด ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ ํ• ๋‹น๋ฉ๋‹ˆ๋‹ค. age์˜ ์œ ํ˜•์ด int์ธ user.age๋ฅผ ์ฐธ์กฐํ•˜๋ฉด ๋ฐ์ดํ„ฐ ๊ฒฐํ•ฉ์€ 0์˜ ๊ธฐ๋ณธ๊ฐ’์„ ์‚ฌ์šฉํ•œ๋‹ค.

์ปฌ๋ ‰์…˜

Array, List, Map๊ณผ ๊ฐ™์€ ์ผ๋ฐ˜ ์ปฌ๋ ‰์…˜์—๋Š” ํŽธ์˜์ƒ [] ์—ฐ์‚ฐ์ž๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์•ก์„ธ์Šคํ•  ์ˆ˜ ์žˆ๋‹ค.

<data>
    <import type="android.util.SparseArray"/>
    <import type="java.util.Map"/>
    <import type="java.util.List"/>
    <variable name="list" type="List&lt;String>"/>
    <variable name="sparse" type="SparseArray&lt;String>"/>
    <variable name="map" type="Map&lt;String, String>"/>
    <variable name="index" type="int"/>
    <variable name="key" type="String"/>
    </data>
    โ€ฆ
    android:text="@{list[index]}"
    โ€ฆ
    android:text="@{sparse[index]}"
    โ€ฆ
    android:text="@{map[key]}"

๋ฌธ์ž์—ด ๋ฆฌํ„ฐ๋Ÿด

๋‹ค์Œ ์˜ˆ์—์„œ์™€ ๊ฐ™์ด ์ž‘์€๋”ฐ์˜ดํ‘œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์†์„ฑ ๊ฐ’์„ ๋ฌถ์–ด ํ‘œํ˜„์‹์— ํฐ๋”ฐ์˜ดํ‘œ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

android:text='@{map["firstName"]}'

๋˜ํ•œ ํฐ๋”ฐ์˜ดํ‘œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์†์„ฑ ๊ฐ’์„ ๋ฌถ์„ ์ˆ˜๋„ ์žˆ๋‹ค.

android:text="@{map[`firstName`]}"

๋ฆฌ์†Œ์Šค

ํ‘œํ˜„์‹์€ ๋‹ค์Œ ๊ตฌ๋ฌธ์„ ์‚ฌ์šฉํ•˜์—ฌ ์•ฑ ๋ฆฌ์†Œ์Šค๋ฅผ ์ฐธ์กฐํ•  ์ˆ˜ ์žˆ๋‹ค.

android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"

๋ณต์ˆ˜ํ˜• ๋งค๊ฐœ๋ณ€์ˆ˜๋„ ์ „๋‹ฌํ•˜๋ ค ๋ฆฌ์†Œ์Šค๋ฅผ ์ฐธ์กฐ ๊ฐ€๋Šฅํ•˜๋‹ค.

android:text="@{@string/nameFormat(firstName, lastName)}"
android:text="@{@plurals/banana(bananaCount)}"

๋ฆฌ์Šค๋„ˆ ๋ฐ”์ธ๋”ฉ

๋ฆฌ์Šค๋„ˆ๋„ ๋ฐ”์ธ๋”ฉ ๊ฐ€๋Šฅํ•˜๋‹ค. ์ด๋ฒคํŠธ ์†์„ฑ ์ด๋ฆ„์€ ๋ช‡ ๊ฐ€์ง€ ์˜ˆ์™ธ๋ฅผ ์ œ์™ธํ•˜๊ณ  ๋ฆฌ์Šค๋„ˆ ๋ฉ”์„œ๋“œ์˜ ์ด๋ฆ„์— ๋”ฐ๋ผ ๊ฒฐ์ •๋œ๋‹ค.
setOnXXXListener์—์„œ set์„ ๋นผ๊ณ  O๋ฅผ ์†Œ๋ฌธ์ž o๋กœ ๋ฐ”๊ฟ”์“ด๋‹ค.

<!-- setOnNavigationSelectListener-->
app:NavigationItemSelectedListener="@{viewModel::NavigationItemSelectedListener}"

<!-- onRangeSelectListener -->
app:onRangeSelectedListener="@{calendarDialogViewModel::onRangeSelectedListener}"


<!-- setOnRefreshListener -->
 app:onRefreshListener="@{() -> homeVM.onRefresh()}"

๋ฉ”์†Œ๋“œ ์ฐธ์กฐ

class MyHandlers {
    fun onClickFriend(view: View) { ... }
}
<?xml version="1.0" encoding="utf-8"?>
    <layout xmlns:android="http://schemas.android.com/apk/res/android">
       <data>
           <variable name="handlers" type="com.example.MyHandlers"/>
           <variable name="user" type="com.example.User"/>
       </data>
       <LinearLayout
           android:orientation="vertical"
           android:layout_width="match_parent"
           android:layout_height="match_parent">
           <TextView android:layout_width="wrap_content"
               android:layout_height="wrap_content"
               android:text="@{user.firstName}"
               android:onClick="@{handlers::onClickFriend}"/>
       </LinearLayout>
    </layout>

๋งค๊ฐœ๋ณ€์ˆ˜๋„ ์ „๋‹ฌ ๊ฐ€๋Šฅํ•˜๋‹ค

class Presenter {
    fun onSaveClick(task: Task){}
}
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <variable name="task" type="com.android.example.Task" />
        <variable name="presenter" type="com.android.example.Presenter" />
    </data>

    <LinearLayout 
        android:layout_width="match_parent" 
        android:layout_height="match_parent">
            <Button 
                android:layout_width="wrap_content" 
                android:layout_height="wrap_content"
                android:onClick="@{() -> presenter.onSaveClick(task)}" />
        </LinearLayout>
</layout>

๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋‘˜ ์ด์ƒ์˜ ๋งค๊ฐœ๋ณ€์ˆ˜์™€ ํ•จ๊ป˜ ๋žŒ๋‹ค ํ‘œํ˜„์‹์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

class Presenter {
    fun onCompletedChanged(task: Task, completed: Boolean){}
}
<CheckBox 
    android:layout_width="wrap_content" 
    android:layout_height="wrap_content"
    android:onCheckedChanged="@{(cb, isChecked) -> presenter.completeChanged(task, isChecked)"/>

import

import๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋ ˆ์ด์•„์›ƒ ํŒŒ์ผ ๋‚ด์—์„œ ํด๋ž˜์Šค๋ฅผ ์‰ฝ๊ฒŒ ์ฐธ์กฐํ•  ์ˆ˜ ์žˆ๋‹ค.

<data>
    <import type="android.view.View"/>
</data>

...

<TextView
       android:text="@{user.lastName}"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:visibility="@{user.isAdult ? View.VISIBLE : View.GONE}"/>

alias

ํด๋ž˜์Šค ์ด๋ฆ„ ์ถฉ๋Œ์ด ๋ฐœ์ƒํ•˜๋ฉด ํด๋ž˜์Šค ์ค‘ ํ•˜๋‚˜์˜ ์ด๋ฆ„์„ ๋ณ„์นญ์œผ๋กœ ๋ฐ”๊ฟ€ ์ˆ˜ ์žˆ๋‹ค.
๋‹ค์Œ ์˜ˆ๋Š” com.example.real.estate ํŒจํ‚ค์ง€์˜ View ํด๋ž˜์Šค ์ด๋ฆ„์„ Vista๋กœ ๋ฐ”๊พผ๋‹ค.

<import type="android.view.View"/>
<import type="com.example.real.estate.View" alias="Vista"/>

๋‹ค๋ฅธ ํด๋ž˜์Šค import

<data>
    <import type="com.example.User"/>
    <import type="java.util.List"/>
    <variable name="user" type="User"/>
    <variable name="userList" type="List&lt;User>"/>
</data>

๋˜ํ•œ ๊ฐ€์ ธ์˜จ ์œ ํ˜•์„ ์‚ฌ์šฉํ•˜์—ฌ ํ‘œํ˜„์‹์˜ ์ผ๋ถ€๋ฅผ ๋ณ€ํ™˜ํ•  ์ˆ˜๋„ ์žˆ๋‹ค. ๋‹ค์Œ ์˜ˆ๋Š” connection ์†์„ฑ์„ User ์œ ํ˜•์œผ๋กœ ๋ณ€ํ™˜

<TextView
    android:text="@{((User)(user.connection)).lastName}"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"/>

ํ‘œํ˜„์‹์—์„œ ์ •์  ํ•„๋“œ ๋ฐ ๋ฉ”์„œ๋“œ๋ฅผ ์ฐธ์กฐํ•  ๋•Œ ๊ฐ€์ ธ์˜จ ์œ ํ˜•์„ ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ๋‹ค.
๋‹ค์Œ ์ฝ”๋“œ๋Š” MyStringUtils ํด๋ž˜์Šค๋ฅผ ๊ฐ€์ ธ์™€์„œ capitalize ๋ฉ”์„œ๋“œ๋ฅผ ์ฐธ์กฐํ•œ๋‹ค.

<data>
    <import type="com.example.MyStringUtils"/>
    <variable name="user" type="com.example.User"/>
</data>
    โ€ฆ
    <TextView
       android:text="@{MyStringUtils.capitalize(user.lastName)}"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"/>

include

์†์„ฑ์— ์•ฑ ๋„ค์ž„์ŠคํŽ˜์ด์Šค ๋ฐ ๋ณ€์ˆ˜ ์ด๋ฆ„์„ ์‚ฌ์šฉํ•จ์œผ๋กœ์จ ํฌํ•จํ•˜๋Š” ๋ ˆ์ด์•„์›ƒ์—์„œ ํฌํ•จ๋œ ๋ ˆ์ด์•„์›ƒ์˜ ๊ฒฐํ•ฉ์œผ๋กœ ๋ณ€์ˆ˜๋ฅผ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ๋‹ค.

<?xml version="1.0" encoding="utf-8"?>
    <layout xmlns:android="http://schemas.android.com/apk/res/android"
            xmlns:bind="http://schemas.android.com/apk/res-auto">
       <data>
           <variable name="user" type="com.example.User"/>
       </data>

       <LinearLayout
           android:orientation="vertical"
           android:layout_width="match_parent"
           android:layout_height="match_parent">
           <include layout="@layout/name" bind:user="@{user}"/>
           <include layout="@layout/contact" bind:user="@{user}"/>
       </LinearLayout>
</layout>

BindingAdapter

BindingAdapter๋Š” ์ ์ ˆํ•œ ํ”„๋ ˆ์ž„์›Œํฌ๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ๊ฐ’์„ ์„ค์ •ํ•˜๋Š” ์ž‘์—…์„ ๋‹ด๋‹นํ•œ๋‹ค.
์ผ๋ถ€ ์†์„ฑ์—๋Š” CustomBindingLogic์ด ํ•„์š”ํ•˜๋‹ค
์˜ˆ๋ฅผ ๋“ค์–ด, android:paddingLeft ์†์„ฑ์—๋Š” ์—ฐ๊ฒฐ๋œ setter๊ฐ€ ์—†๋‹ค. ๋Œ€์‹  setPadding(left, top, right, bottom) ๋ฉ”์„œ๋“œ๊ฐ€ ์ œ๊ณต๋˜๋‹ˆ BindingAdapter ์–ด๋…ธํ…Œ์ด์…˜์ด ์žˆ๋Š” static BindingAdapter ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์†์„ฑ์˜ setter๊ฐ€ ํ˜ธ์ถœ๋˜๋Š” ๋ฐฉ์‹์„ ๋งž์ถค์„ค์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.

@BindingAdapter("android:paddingLeft")
fun setPaddingLeft(view: View, padding: Int) {
    view.setPadding(padding,
                view.getPaddingTop(),
                view.getPaddingRight(),
                view.getPaddingBottom())
}

@BindingAdapter("app:goneUnless")
fun goneUnless(view: View, visible: Boolean) {
    view.visibility = if (visible) View.VISIBLE else View.GONE
}

@BindingAdapter("loadImage")
fun bindLoadImageFromUri(view: ImageView, uri: Uri?) {
    uri?.let {
        Glide.with(view.context)
            .load(it.toString())
            .into(view)
    }
}

๋งค๊ฐœ๋ณ€์ˆ˜ ์œ ํ˜•์€ ์ค‘์š”ํ•˜๋‹ค.
์ฒซ ๋ฒˆ์งธ ๋งค๊ฐœ๋ณ€์ˆ˜๋Š” ์†์„ฑ๊ณผ ์—ฐ๊ฒฐ๋œ ๋ทฐ์˜ ์œ ํ˜•์„ ๊ฒฐ์ •ํ•œ๋‹ค.
๋‘ ๋ฒˆ์งธ ๋งค๊ฐœ๋ณ€์ˆ˜๋Š” ์ง€์ •๋œ ์†์„ฑ์˜ ๊ฒฐํ•ฉ ํ‘œํ˜„์‹์—์„œ ํ—ˆ์šฉ๋˜๋Š” ์œ ํ˜•์„ ๊ฒฐ์ •ํ•œ๋‹ค.

๊ฐœ๋ฐœ์ž๊ฐ€ ์ •์˜ํ•˜๋Š” BindingAdapter๋Š” ์ถฉ๋Œ์ด ๋ฐœ์ƒํ•˜๋ฉด Android ํ”„๋ ˆ์ž„์›Œํฌ์—์„œ ์ œ๊ณตํ•˜๋Š” ๊ธฐ๋ณธ ์–ด๋Œ‘ํ„ฐ๋ณด๋‹ค ์šฐ์„  ์ ์šฉ๋œ๋‹ค.

๋˜ํ•œ ์—ฌ๋Ÿฌ๊ฐ€์ง€ ์†์„ฑ์„ ๋ฐ›๋Š” ์–ด๋Žํ„ฐ๋„ ์ •์˜ํ•  ์ˆ˜ ์žˆ๋‹ค.

@BindingAdapter("imageUrl", "error")
fun loadImage(view: ImageView, url: String, error: Drawable) {
    Picasso.get().load(url).error(error).into(view)
}
<ImageView 
    app:imageUrl="@{venue.imageUrl}" 
    app:error="@{@drawable/venueError}"/>

ํ•˜๋‚˜๋ผ๋„ ์„ค์ •๋  ๋•Œ ์–ด๋Œ‘ํ„ฐ๊ฐ€ ํ˜ธ์ถœ๋˜๋„๋ก ํ•˜๋ ค๋ฉด ๋‹ค์Œ ์˜ˆ์‹œ์™€ ๊ฐ™์ด ์–ด๋Œ‘ํ„ฐ์˜ ์„ ํƒ์  requireAll ํ”Œ๋ž˜๊ทธ๋ฅผ false๋กœ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.

@BindingAdapter(value = ["imageUrl", "placeholder"], requireAll = false)
fun setImageUrl(imageView: ImageView, url: String?, placeHolder: Drawable?) {
    if (url == null) {
        imageView.setImageDrawable(placeholder);
    } else {
        MyImageLoader.loadInto(imageView, url, placeholder);
    }
}

BindingAdapter ๋ฉ”์„œ๋“œ๋Š” ์„ ํƒ์ ์œผ๋กœ ํ•ธ๋“ค๋Ÿฌ์˜ ์ด์ „ ๊ฐ’์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.
์ด์ „ ๊ฐ’๊ณผ ์ƒˆ ๊ฐ’์„ ์‚ฌ์šฉํ•˜๋Š” ๋ฉ”์„œ๋“œ๋Š” ์•„๋ž˜ ์˜ˆ์™€ ๊ฐ™์ด ์†์„ฑ์˜ ๋ชจ๋“  ์ด์ „ ๊ฐ’์„ ๋จผ์ € ์„ ์–ธํ•œ ํ›„ ์ƒˆ ๊ฐ’์„ ์„ ์–ธํ•ด์•ผ ํ•œ๋‹ค.

@BindingAdapter("android:paddingLeft")
fun setPaddingLeft(view: View, oldPadding: Int, newPadding: Int) {
    if (oldPadding != newPadding) {
        view.setPadding(padding,
                    view.getPaddingTop(),
                    view.getPaddingRight(),
                    view.getPaddingBottom())
    }
}

Observable data object

๊ฐ„๋‹จํ•œ ๊ธฐ์กด ๊ฐ์ฒด๋ฅผ ๋ฐ์ดํ„ฐ ๊ฒฐํ•ฉ์— ์‚ฌ์šฉํ•  ์ˆ˜๋Š” ์žˆ์ง€๋งŒ ๊ฐ์ฒด๋ฅผ ์ˆ˜์ •ํ•ด๋„ UI๊ฐ€ ์ž๋™์œผ๋กœ ์—…๋ฐ์ดํŠธ๋˜์ง€๋Š” ์•Š๋Š”๋‹ค.
Observable data๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋ฐ์ดํ„ฐ ๋ณ€๊ฒฝ ์‹œ ๋ฆฌ์Šค๋„ˆ๋ผ๋Š” ๋‹ค๋ฅธ ๊ฐ์ฒด์— ์•Œ๋ฆฌ๋Š” ๊ธฐ๋Šฅ์„ ๋ฐ์ดํ„ฐ ๊ฐ์ฒด์— ์ œ๊ณตํ•  ์ˆ˜ ์žˆ๋‹ค.
Observable data object์—๋Š” ์„ธ ๊ฐ€์ง€ ์œ ํ˜•, ์ฆ‰ ๊ฐ์ฒด(object), ํ•„๋“œ(field) ๋ฐ ์ปฌ๋ ‰์…˜(collection), LiveData, Flow๋“ฑ์ด ์žˆ๋‹ค.

ObservableField

Observable ํด๋ž˜์Šค ๋ฐ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ํ”„๋ฆฌ๋ฏธํ‹ฐ๋ธŒ(Primitive) ๊ด€๋ จ ํด๋ž˜์Šค๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ•„๋“œ๋ฅผ ์‹๋ณ„ ๊ฐ€๋Šฅํ•˜๊ฒŒ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค.

val firstName = ObservableField<String>()
val lastName = ObservableField<String>()
val age = ObservableInt()

๋‹ค์Œ๊ณผ ๊ฐ™์ด field ๊ฐ’์— ์•ก์„ธ์Šคํ•œ๋‹ค.

user.firstName = "Google"
val age = user.age

ObservableCollection

์ผ๋ถ€ ์•ฑ์€ ๋™์  ๊ตฌ์กฐ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณด์œ ํ•ฉ๋‹ˆ๋‹ค.
ObservableCollection ํ†ตํ•ด ํ‚ค๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ด๋Ÿฌํ•œ ๊ตฌ์กฐ์— ์•ก์„ธ์Šคํ•  ์ˆ˜ ์žˆ๋‹ค.
๋‹ค์Œ ์˜ˆ์™€ ๊ฐ™์ด ํ‚ค๊ฐ€ String๊ณผ ๊ฐ™์€ ์ฐธ์กฐ ์œ ํ˜•์ผ ๋•Œ๋Š” ObservableArrayMap ํด๋ž˜์Šค๊ฐ€ ์œ ์šฉํ•˜๋‹ค.

 ObservableArrayMap<String, Any>().apply {
        put("firstName", "Google")
        put("lastName", "Inc.")
        put("age", 17)
    }
<data>
    <import type="android.databinding.ObservableMap"/>
    <variable name="user" type="ObservableMap<String, Object>"/>
</data>
    โ€ฆ
<TextView
    android:text="@{user.lastName}"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"/>
<TextView
    android:text="@{String.valueOf(1 + (Integer)user.age)}"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"/>

๋˜ ๋‹ค๋ฅธ ์˜ˆ์‹œ

 ObservableArrayList<Any>().apply {
       add("Google")
       add("Inc.")
       add(17)
   }
<data>
    <import type="android.databinding.ObservableList"/>
    <import type="com.example.my.app.Fields"/>
    <variable name="user" type="ObservableList<Object>"/>
</data>
    โ€ฆ
<TextView
    android:text='@{user[Fields.LAST_NAME]}'
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"/>
<TextView
    android:text='@{String.valueOf(1 + (Integer)user[Fields.AGE])}'
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"/>

ObservableObject

Observable ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ํด๋ž˜์Šค๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์‹๋ณ„ ๊ฐ€๋Šฅํ•œ ๊ฐ์ฒด์˜ ์†์„ฑ ๋ณ€๊ฒฝ์— ๊ด€ํ•ด ์•Œ๋ฆผ์„ ๋ฐ›์œผ๋ ค๋Š” ๋ฆฌ์Šค๋„ˆ๋ฅผ ๋“ฑ๋กํ•  ์ˆ˜ ์žˆ๋‹ค.

Observable ์ธํ„ฐํŽ˜์ด์Šค์— ๋ฆฌ์Šค๋„ˆ๋ฅผ ์ถ”๊ฐ€ ๋ฐ ์‚ญ์ œํ•˜๋Š” ๋ฉ”์ปค๋‹ˆ์ฆ˜์ด ์žˆ์ง€๋งŒ ์•Œ๋ฆผ์ด ์ „์†ก๋˜๋Š” ์‹œ์ ์€ ๊ฐœ๋ฐœ์ž๊ฐ€ ์ง์ ‘ ๊ฒฐ์ •ํ•ด์•ผ ํ•œ๋‹ค.
๋” ์‰ฝ๊ฒŒ ๊ฐœ๋ฐœํ•  ์ˆ˜ ์žˆ๋„๋ก ๋ฐ์ดํ„ฐ ๊ฒฐํ•ฉ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋Š” ๋ฆฌ์Šค๋„ˆ ๋“ฑ๋ก ๋ฉ”์ปค๋‹ˆ์ฆ˜์„ ๊ตฌํ˜„ํ•˜๋Š” BaseObservable ํด๋ž˜์Šค๋ฅผ ์ œ๊ณต.
BaseObservable์„ ๊ตฌํ˜„ํ•˜๋Š” ๋ฐ์ดํ„ฐ ํด๋ž˜์Šค๋Š” ์†์„ฑ์ด ๋ณ€๊ฒฝ๋  ๋•Œ ์•Œ๋ฆฌ๋Š” ์—ญํ• ์„ ํ•œ๋‹ค.
๋‹ค์Œ ์˜ˆ์™€ ๊ฐ™์ด Bindable ์ฃผ์„์„ getter์— ํ• ๋‹นํ•˜๊ณ  setter์˜ notifyPropertyChanged() ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•จ์œผ๋กœ์จ ์ด ์ž‘์—…์„ ์™„๋ฃŒํ•œ๋‹ค.

class User : BaseObservable() {

    @get:Bindable
    var firstName: String = ""
        set(value) {
            field = value
            notifyPropertyChanged(BR.firstName)
        }

    @get:Bindable
    var lastName: String = ""
        set(value) {
            field = value
            notifyPropertyChanged(BR.lastName)
        }
    }

LiveData

ObservableField์™€ ๊ฐ™์ด Observable์„ ๊ตฌํ˜„ํ•˜๋Š” ๊ฐ์ฒด์™€ ๋‹ฌ๋ฆฌ LiveData ๊ฐ์ฒด๋Š” ๋ฐ์ดํ„ฐ ๋ณ€๊ฒฝ์„ ๊ตฌ๋…ํ•˜๋Š” ๊ด€์ฐฐ์ž์˜ ์ˆ˜๋ช… ์ฃผ๊ธฐ๋ฅผ ์•Œ๊ณ  ์žˆ๋‹ค.
์ด ์ˆ˜๋ช… ์ฃผ๊ธฐ๋ฅผ ์•Œ๋ฉด LiveData ์‚ฌ์šฉ์˜ ์ด์ ์— ์„ค๋ช…๋œ ๋งŽ์€ ์ด์ ์„ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

LiveCyclerOwner ๋“ฑ๋กํ•˜๊ธฐ

LiveData ๊ฐ์ฒด๋ฅผ ์‚ฌ์šฉํ•˜๋ ค๋ฉด ์ˆ˜๋ช… ์ฃผ๊ธฐ ์†Œ์œ ์ž๋ฅผ ์ง€์ •ํ•˜์—ฌ LiveData ๊ฐ์ฒด์˜ ๋ฒ”์œ„๋ฅผ ์ •์˜ํ•ด์•ผ ํ•œ๋‹ค.
๋‹ค์Œ ์˜ˆ์—์„œ๋Š” ๊ฒฐํ•ฉ ํด๋ž˜์Šค๋ฅผ ์ธ์Šคํ„ด์Šคํ™”ํ•œ ํ›„ ํ™œ๋™์„ ์ˆ˜๋ช… ์ฃผ๊ธฐ ์†Œ์œ ์ž๋กœ ์ง€์ •ํ•œ๋‹ค.

class ViewModelActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        // Inflate view and obtain an instance of the binding class.
        val binding: UserBinding = DataBindingUtil.setContentView(this, R.layout.user)

        // Specify the current activity as the lifecycle owner.
        binding.setLifecycleOwner(this)
    }
}

LiveData ๋ฐ”์ธ๋”ฉ

private val _homeCategories = MutableLiveData<List<HomeCategory>>()
val homeCategories: LiveData<List<HomeCategory>>
    get() = _homeCategories
<layout>
    <data>
        <variable
            name="viewmodel"
            type="com.example.main.MainViewModel"/>
        <variable
            name="adapter"
            type="com.example.main.MainAdapter" /> 
    </data>

    ...
     <androidx.recyclerview.widget.RecyclerView
                android:id="@+id/recyclerview"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                app:adapter="@{adapter}"
                app:adapterHomeCategories="@{viewmodel.homeCategories}"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="parent"
                app:spanCount="2"
                tools:listitem="@layout/item_home_category" />
    ...
</layout>
object RecyclerViewBindingAdapter {
    @JvmStatic
    @BindingAdapter("adapterHomeCategories")
    fun bindAdapterHomeItemList(recyclerView: RecyclerView, homeCategories: List<HomeCategory>?) {
        homeCategories?.let { (recyclerView.adapter as MainAdapter).addItems(homeCategories) }
    }
}

๋‹จ๋ฐฉํ–ฅ, ์–‘๋ฐฉํ–ฅ ๋ฐ”์ธ๋”ฉ

๋‹จ๋ฐฉํ–ฅ DataBinding์„ ์‚ฌ์šฉํ•˜๋ฉด ์•„๋ž˜์™€๊ฐ™์ด ์‚ฌ์šฉํ•˜๊ฒŒ๋œ๋‹ค.
์†์„ฑ์— ๊ฐ’์„ ์„ค์ •ํ•˜๊ณ  ์ด ์†์„ฑ์˜ ๋ณ€๊ฒฝ์— ๋ฐ˜์‘ํ•˜๋Š” ๋ฆฌ์Šค๋„ˆ๋ฅผ ์„ค์ •ํ•œ๋‹ค.

 <CheckBox
    android:id="@+id/rememberMeCheckBox"
    android:checked="@{viewmodel.rememberMe}"
    android:onCheckedChanged="@{viewmodel.rememberMeChanged}"/>

์ด๊ฒƒ์„ ์–‘๋ฐฉํ–ฅ DataBinding์œผ๋กœ ๋ณ€๊ฒฝํ•˜๋ฉด ์•„๋ž˜์™€ ๊ฐ™๋‹ค

<CheckBox
    android:id="@+id/rememberMeCheckBox"
    android:checked="@={viewmodel.rememberMe}"/>

'=' ๊ธฐํ˜ธ๊ฐ€ ํฌํ•จ๋œ @={} ํ‘œ๊ธฐ๋ฒ•์€ ์†์„ฑ๊ณผ ๊ด€๋ จ๋œ ๋ฐ์ดํ„ฐ ๋ณ€๊ฒฝ์‚ฌํ•ญ์„ ๋ฐ›๋Š” ๋™์‹œ์— ์‚ฌ์šฉ์ž ์—…๋ฐ์ดํŠธ๋ฅผ ์ˆ˜์‹  ํ•œ๋‹ค.

์ปค์Šคํ…€ ์†์„ฑ์„ ํ†ตํ•œ ์–‘๋ฐฉํ–ฅ ๋ฐ”์ธ๋”ฉ

์ดˆ๊ธฐ ๊ฐ’์„ ์„ค์ •ํ•˜๊ณ  ๊ฐ’์ด ๋ณ€๊ฒฝ๋  ๋•Œ ์—…๋ฐ์ดํŠธํ•˜๋Š” ๋ฉ”์„œ๋“œ์— @BindingAdapter๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์–ด๋…ธํ…Œ์ด์…˜์„ ์ถ”๊ฐ€

@BindingAdapter("time")
@JvmStatic fun setTime(view: MyView, newValue: Time) {
    // Important to break potential infinite loops.
    if (view.time != newValue) {
        view.time = newValue
    }
}

๋ทฐ์—์„œ ๊ฐ’์„ ์ฝ๋Š” ๋ฉ”์„œ๋“œ์— @InverseBindingAdapter๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์–ด๋…ธํ…Œ์ด์…˜์„ ์ถ”๊ฐ€

@InverseBindingAdapter("time")
@JvmStatic fun getTime(view: MyView) : Time {
    return view.getTime()
} 

์ด๋•Œ ๋ฐ์ดํ„ฐ ๊ฒฐํ•ฉ์€ ๋ฐ์ดํ„ฐ ๋ณ€๊ฒฝ ์‹œ ์ง„ํ–‰ํ•  ์ž‘์—…(@BindingAdapter๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์–ด๋…ธํ…Œ์ด์…˜์„ ์ถ”๊ฐ€ํ•œ ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•จ) ๊ณผ ๋ทฐ ์†์„ฑ ๋ณ€๊ฒฝ ์‹œ ํ˜ธ์ถœํ•  ํ•ญ๋ชฉ(InverseBindingListener๋ฅผ ํ˜ธ์ถœํ•จ)์„ ํŒŒ์•…ํ•˜๊ฒŒ ๋ค๋‹ค. ํ•˜์ง€๋งŒ ์†์„ฑ์ด ์–ธ์ œ ๋˜๋Š” ์–ด๋–ป๊ฒŒ ๋ณ€๊ฒฝ๋˜๋Š”์ง€๋Š” ์ธ์‹ํ•˜์ง€ ๋ชปํ•œ๋‹ค.

์†์„ฑ์˜ ๋ณ€๊ฒฝ ์‹œ๊ธฐ ๋˜๋Š” ๋ฐฉ์‹์„ ์•Œ๊ธฐ ์œ„ํ•ด์„œ๋Š” ๋ทฐ์— ๋ฆฌ์Šค๋„ˆ๋ฅผ ์„ค์ •ํ•ด์•ผ ํ•œ๋‹ค.
๋ฆฌ์Šค๋„ˆ๋Š” ๋งž์ถค ๋ทฐ์™€ ์—ฐ๊ฒฐ๋œ ๋งž์ถค ๋ฆฌ์Šค๋„ˆ์ด๊ฑฐ๋‚˜ ํฌ์ปค์Šค ์ƒ์‹ค ๋˜๋Š” ํ…์ŠคํŠธ ๋ณ€๊ฒฝ๊ณผ ๊ฐ™์€ ์ผ๋ฐ˜ ์ด๋ฒคํŠธ์ผ ์ˆ˜ ์žˆ๋‹ค.

@BindingAdapter("app:timeAttrChanged")
@JvmStatic fun setListeners(
        view: MyView,
        attrChange: InverseBindingListener
) {
    // Set a listener for click, focus, touch, etc.
}

๋ฆฌ์Šค๋„ˆ์—๋Š” InverseBindingListener๊ฐ€ ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ํฌํ•จํ•œ๋‹ค.
InverseBindingListener๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋ฐ์ดํ„ฐ ๊ฒฐํ•ฉ ์‹œ์Šคํ…œ์— ์†์„ฑ์ด ๋ณ€๊ฒฝ๋˜์—ˆ์Œ์„ ์•Œ๋ฆด ์ˆ˜ ์žˆ๋‹ค.
๊ทธ๋Ÿฌ๋ฉด ์‹œ์Šคํ…œ์€ @InverseBindingAdapter๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์–ด๋…ธํ…Œ์ด์…˜์ด ์ถ”๊ฐ€๋œ ๋ฉ”์„œ๋“œ ํ˜ธ์ถœ์„ ์‹œ์ž‘ํ•  ์ˆ˜ ์žˆ๋‹ค.

๋ฆฌ์‹ธ์ดํด๋Ÿฌ๋ทฐ ๋ฐ”์ธ๋”ฉ

๋ฆฌ์‹ธ์ดํด๋Ÿฌ๋ทฐ ๊ฐ ์•„์ดํ…œ์—๋„ DataBinding์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

// HomeAdapter.kt
class HomeAdapter(
    private val clickListener: HomeItemViewHolder.OnClickListener
) : BindingRecyclerViewAdapter<HomeAdapter.HomeItemViewHolder>() {

    private val items = mutableListOf<HomeCategory>()

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HomeItemViewHolder {
        val binding = ItemHomeCategoryBinding.inflate(LayoutInflater.from(parent.context), parent, false)
        return HomeItemViewHolder(binding)
    }

    override fun onBindViewHolder(holder: HomeItemViewHolder, position: Int) {
        holder.bind(items[position], clickListener)
        holder.binding.executePendingBindings()
    }

    override fun getItemCount(): Int = items.size

    fun addItems(items: List<HomeCategory>) {
        this.items.clear()
        this.items.addAll(items)
        notifyDataSetChanged()
    }

    class HomeItemViewHolder(val binding: ItemHomeCategoryBinding) : RecyclerView.ViewHolder(binding.root) {

        interface OnClickListener {
            fun onItemClick(item: HomeCategory)
        }

        fun bind(item: HomeCategory, clickListener: OnClickListener) {
            binding.item = item
            binding.clickListener = clickListener
        }
    }
}
// HomFragment.kt
override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        super.onCreateView(inflater, container, savedInstanceState)
        return binding {
            homeVM = homeViewModel
            adapter = HomeAdapter(object : HomeAdapter.HomeItemViewHolder.OnClickListener {
                override fun onItemClick(item: HomeCategory) {
                    startActivity(Intent(context, CategoryActivity::class.java))
                }
            })
        }.root
    }
<!-- home_fragment.xml-->
<com.beomjo.whitenoise.ui.view.GridRecyclerView
            android:id="@+id/recyclerview"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:adapter="@{adapter}"
            app:adapterHomeCategories="@{homeVM.homeCategories}"
            app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:spanCount="2"
            tools:listitem="@layout/item_home_category" />
<!-- item_home_category.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>

        <variable
            name="item"
            type="com.beomjo.whitenoise.model.HomeCategory" />

        <variable
            name="clickListener"
            type="com.beomjo.whitenoise.ui.adapters.HomeAdapter.HomeItemViewHolder.OnClickListener" />
    </data>

    <androidx.cardview.widget.CardView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:cardCornerRadius="24dp"
        app:cardElevation="0dp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

        <androidx.constraintlayout.widget.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="180dp"
            android:background="@{item.getPrimaryColor()}"
            android:onClick="@{()->clickListener.onItemClick(item)}"
            android:padding="30dp"
            tools:background="@android:color/holo_blue_bright">

            <ImageView
                android:layout_width="36dp"
                android:layout_height="36dp"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintTop_toTopOf="parent"
                app:loadImage="@{item.iconUrl}" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:gravity="center"
                android:text="@{item.name}"
                android:textColor="@color/white"
                android:textSize="24sp"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                tools:text="afaf" />
        </androidx.constraintlayout.widget.ConstraintLayout>
    </androidx.cardview.widget.CardView>
</layout>
object RecyclerViewBindingAdapter {
    @JvmStatic
    @BindingAdapter("adapterHomeCategories")
    fun bindAdapterHomeItemList(recyclerView: RecyclerView, homeCategories: List<HomeCategory>?) {
        homeCategories?.let { (recyclerView.adapter as HomeAdapter).addItems(homeCategories) }
    }
}