[AAC] Data Binding - 3

dwjeongΒ·2023λ…„ 11μ›” 14일
0

μ•ˆλ“œλ‘œμ΄λ“œ

λͺ©λ‘ 보기
16/28

πŸ”Ž μ•„ν‚€ν…μ²˜ κ΅¬μ„±μš”μ†Œμ— λ ˆμ΄μ•„μ›ƒ λ·° λ°”μΈλ”©ν•˜κΈ°

πŸ’‘ LiveDataλ₯Ό μ‚¬μš©ν•˜μ—¬ 데이터 변경사항을 UI에 μ•Œλ¦¬κΈ°

LiveData 개체λ₯Ό 데이터 바인딩 μ†ŒμŠ€λ‘œ μ‚¬μš©ν•˜μ—¬ 데이터 변경사항을 UI에 μžλ™μœΌλ‘œ μ•Œλ¦΄ 수 있음.

Observable ν•„λ“œμ™€ 같은 Observable을 κ΅¬ν˜„ν•˜λŠ” 객체와 달리 LiveData κ°μ²΄λŠ” 데이터 λ³€κ²½ 사항을 κ΅¬λ…ν•˜λŠ” Observer의 수λͺ…μ£ΌκΈ°λ₯Ό μ•Œκ³  있음.

바인딩 ν΄λž˜μŠ€μ™€ ν•¨κ»˜ LiveData 개체λ₯Ό μ‚¬μš©ν•˜λ €λ©΄ lifecycle ownerλ₯Ό μ§€μ •ν•˜μ—¬ 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)
    }
}



ViewModel μ»΄ν¬λ„ŒνŠΈλ₯Ό μ‚¬μš©ν•˜μ—¬ 데이터λ₯Ό λ ˆμ΄μ•„μ›ƒμ— 바인딩할 수 있음.
ViewModel μ»΄ν¬λ„ŒνŠΈμ—μ„œλŠ” LiveData 개체λ₯Ό μ‚¬μš©ν•˜μ—¬ 데이터λ₯Ό λ³€ν™˜ν•˜κ±°λ‚˜ μ—¬λŸ¬ 데이터 μ†ŒμŠ€λ₯Ό λ³‘ν•©ν•˜λŠ” 것이 κ°€λŠ₯.

class ScheduleViewModel : ViewModel() {
    val userName: LiveData

    init {
        val result = Repository.userName
        userName = Transformations.map(result) { result -> result.value }
    }
}



πŸ’‘ ViewModel을 μ‚¬μš©ν•˜μ—¬ UIκ΄€λ ¨ 데이터 관리

데이터 바인딩 λΌμ΄λΈŒλŸ¬λ¦¬λŠ” ViewModel μ»΄ν¬λ„ŒνŠΈμ™€ μ›ν™œν•˜κ²Œ λ™μž‘.
데이터 바인딩 λΌμ΄λΈŒλŸ¬λ¦¬μ™€ ν•¨κ»˜ ViewModel ꡬ성 μš”μ†Œλ₯Ό μ‚¬μš©ν•˜λ©΄ UI λ‘œμ§μ„ λ ˆμ΄μ•„μ›ƒ μ™ΈλΆ€μ˜ μ»΄ν¬λ„ŒνŠΈλ‘œ 이동할 수 μžˆμœΌλ―€λ‘œ ν…ŒμŠ€νŠΈν•˜κΈ°κ°€ μš©μ΄ν•΄μ§.

데이터 바인딩 λΌμ΄λΈŒλŸ¬λ¦¬λŠ” ν•„μš”ν•  λ•Œ λ·°κ°€ 데이터 μ†ŒμŠ€μ— λ°”μΈλ”©λ˜κ±°λ‚˜ 바인딩 ν•΄μ œλ˜λ„λ‘ 보μž₯함.

  • 데이터 바인딩 λΌμ΄λΈŒλŸ¬λ¦¬μ™€ ViewModel μ»΄ν¬λ„ŒνŠΈ μ‚¬μš©ν•˜κΈ°
  1. ViewModel ν΄λž˜μŠ€μ—μ„œ μƒμ†λ˜λŠ” μ»΄ν¬λ„ŒνŠΈλ₯Ό μΈμŠ€ν„΄μŠ€ν™” μ‹œν‚΄.
  2. 바인딩 클래슀의 μΈμŠ€ν„΄μŠ€λ₯Ό μ–»μŒ.
  3. ViewModel μ»΄ν¬λ„ŒνŠΈλ₯Ό 바인딩 클래슀의 속성에 ν• λ‹Ή
class ViewModelActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        // Obtain the ViewModel component.
        val userModel: UserModel by viewModels()

        // Inflate view and obtain an instance of the binding class.
        val binding: UserBinding = DataBindingUtil.setContentView(this, R.layout.user)

        // Assign the component to a property in the binding class.
        binding.viewmodel = userModel
    }
}
<CheckBox
    android:id="@+id/rememberMeCheckBox"
    android:checked="@{viewmodel.rememberMe}"
    android:onCheckedChanged="@{() -> viewmodel.rememberMeChanged()}" />



πŸ’‘ Observable ViewModel을 μ‚¬μš©ν•˜μ—¬ 바인딩 μ–΄λŒ‘ν„° 효과적으둜 μ œμ–΄

Observable μΈν„°νŽ˜μ΄μŠ€λ₯Ό κ΅¬ν˜„ν•˜λŠ” ViewModel μ»΄ν¬λ„ŒνŠΈλ₯Ό μ‚¬μš©ν•˜μ—¬ 데이터 λ³€κ²½ 사항을 λ‹€λ₯Έ μ•± μ»΄ν¬λ„ŒνŠΈμ— μ•Œλ¦΄ 수 있음.

Observable을 κ΅¬ν˜„ν•˜λŠ” ViewModel ꡬ성 μš”μ†Œλ₯Ό μ‚¬μš©ν•˜λ©΄ μ•±μ˜ 바인딩 μ–΄λŒ‘ν„°λ₯Ό 더 효과적으둜 μ œμ–΄ν•  수 있음.

Observable ViewModel μ»΄ν¬λ„ŒνŠΈλ₯Ό κ΅¬ν˜„ν•˜λ €λ©΄ ViewModel 클래슀λ₯Ό μƒμ†ν•˜κ³  Observable μΈν„°νŽ˜μ΄μŠ€λ₯Ό κ΅¬ν˜„ν•˜λŠ” 클래슀λ₯Ό λ§Œλ“€μ–΄μ•Ό 함.

addOnPropertyChangedCallback() 및 RemoveOnPropertyChangedCallback() λ©”μ„œλ“œλ₯Ό μ‚¬μš©ν•˜μ—¬ Observerκ°€ μ•Œλ¦Όμ„ κ΅¬λ…ν•˜κ±°λ‚˜ ꡬ독 μ·¨μ†Œν•  λ•Œ μ‚¬μš©μž μ •μ˜ λ‘œμ§μ„ μ œκ³΅ν•  수 있음.

λ˜ν•œ notifyPropertyChanged() λ©”μ„œλ“œμ—μ„œ 속성이 변경될 λ•Œ μ‹€ν–‰λ˜λŠ” μ‚¬μš©μž 지정 λ‘œμ§μ„ μ œκ³΅ν•  수 있음.

/**
 * A ViewModel that is also an Observable,
 * to be used with the Data Binding Library.
 */
open class ObservableViewModel : ViewModel(), Observable {
    private val callbacks: PropertyChangeRegistry = PropertyChangeRegistry()

    override fun addOnPropertyChangedCallback(
            callback: Observable.OnPropertyChangedCallback) {
        callbacks.add(callback)
    }

    override fun removeOnPropertyChangedCallback(
            callback: Observable.OnPropertyChangedCallback) {
        callbacks.remove(callback)
    }

    /**
     * Notifies observers that all properties of this instance have changed.
     */
    fun notifyChange() {
        callbacks.notifyCallbacks(this, 0, null)
    }

    /**
     * Notifies observers that a specific property has changed. The getter for the
     * property that changes must be marked with the @Bindable annotation to
     * generate a field in the BR class to be used as the fieldId parameter.
     *
     * @param fieldId The generated BR id for the Bindable field.
     */
    fun notifyPropertyChanged(fieldId: Int) {
        callbacks.notifyCallbacks(this, fieldId, null)
    }
}




πŸ”Ž μ–‘λ°©ν–₯ 데이터 바인딩

단방ν–₯ 데이터 바인딩을 μ‚¬μš©ν•  경우 속성에 값을 μ„€μ •ν•˜κ³  ν•΄λ‹Ή μ†μ„±μ˜ 변경에 λ°˜μ‘ν•˜λŠ” λ¦¬μŠ€λ„ˆλ₯Ό μ„€μ •ν•  수 있음.

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

μ–‘λ°©ν–₯ 바인딩은 λ‹€μŒκ³Ό 같이 μž‘μ„±ν•  수 있음.
@={} ν‘œκΈ°λ₯Ό 톡해 속성에 λŒ€ν•œ 데이터 변경을 μˆ˜μ‹ ν•˜λŠ” λ™μ‹œμ— μ‚¬μš©μž μ—…λ°μ΄νŠΈλ₯Ό μˆ˜μ‹ ν•¨.

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



λ°±μ—… λ°μ΄ν„°μ˜ λ³€κ²½ 사항에 λ°˜μ‘ν•˜κΈ° μœ„ν•΄ λ ˆμ΄μ•„μ›ƒ λ³€μˆ˜λ₯Ό Observable(보톡 BaseObservable)둜 κ΅¬ν˜„ν•˜κ³  @Bindable μ–΄λ…Έν…Œμ΄μ…˜μ„ μ‚¬μš©.

class LoginViewModel : BaseObservable {
    // val data = ...

    @Bindable
    fun getRememberMe(): Boolean {
        return data.rememberMe
    }

    fun setRememberMe(value: Boolean) {
        // Avoids infinite loops.
        if (data.rememberMe != value) {
            data.rememberMe = value

            // React to the change.
            saveData()

            // Notify observers of a new value.
            notifyPropertyChanged(BR.remember_me)
        }
    }
}



πŸ’‘ μ‚¬μš©μž μ •μ˜ 속성을 μ‚¬μš©ν•˜λŠ” μ–‘λ°©ν–₯ 데이터 바인딩

μ‚¬μš©μž μ •μ˜ 속성과 ν•¨κ»˜ μ–‘λ°©ν–₯ 데이터 바인딩을 μ‚¬μš©ν•˜λ €λ©΄ @InverseBindingAdapter 및 @InverseBindingMethod μ–΄λ…Έν…Œμ΄μ…˜μ„ μ‚¬μš©ν•΄μ•Όν•¨.


  1. @BindingAdapterλ₯Ό μ‚¬μš©ν•˜μ—¬ μ΄ˆκΈ°κ°’μ„ μ„€μ •ν•˜κ³  값이 λ³€κ²½λ˜λ©΄ μ—…λ°μ΄νŠΈν•˜λŠ” λ©”μ„œλ“œμ— μ–΄λ…Έν…Œμ΄μ…˜μ„ λ‹¬μ•„μ€Œ.
@BindingAdapter("time")
@JvmStatic fun setTime(view: MyView, newValue: Time) {
    // Important to break potential infinite loops.
    if (view.time != newValue) {
        view.time = newValue
    }
}

  1. @InverseBindingAdapterλ₯Ό μ‚¬μš©ν•˜μ—¬ λ·°μ—μ„œ 값을 μ½λŠ” λ©”μ„œλ“œμ— μ–΄λ…Έν…Œμ΄μ…˜μ„ λ‹¬μ•„μ€Œ.
@InverseBindingAdapter("time")
@JvmStatic fun getTime(view: MyView) : Time {
    return view.getTime()
}

μ—¬κΈ°μ—μ„œ 데이터 바인딩은 데이터가 λ³€κ²½λ λ•Œ μˆ˜ν–‰ν•  μž‘μ—…(@BindingAdapter μ–΄λ…Έν…Œμ΄μ…˜μ΄ 달린 λ©”μ„œλ“œ)κ³Ό λ·° 속성이 변경될 λ•Œ ν˜ΈμΆœν•  μž‘μ—…(@InverseBindingListener 호좜)을 μ•Œκ³  μžˆμ§€λ§Œ,

속성이 μ–Έμ œ μ–΄λ–»κ²Œ λ³€κ²½λ˜λŠ”μ§€ μ•Œ 수 μ—†μœΌλ―€λ‘œ 뷰에 λ¦¬μŠ€λ„ˆλ₯Ό 섀정해야함. λ¦¬μŠ€λ„ˆλ₯Ό μ„€μ •ν•˜λŠ” λ©”μ„œλ“œμ— @BindingAdapter μ–΄λ…Έν…Œμ΄μ…˜μ„ μΆ”κ°€.

@BindingAdapter("app:timeAttrChanged")
@JvmStatic fun setListeners(
        view: MyView,
        attrChange: InverseBindingListener //InverseBindingListener λ§€κ°œλ³€μˆ˜λ‘œ μ‚¬μš©.
        //데이터 바인딩 μ‹œμŠ€ν…œμ— 속성이 λ³€κ²½λ˜μ—ˆμŒμ„ μ•Œλ €μ€€ ν›„ @InverseBindingAdapter 등을 μ‚¬μš©ν•œ μ–΄λ…Έν…Œμ΄μ…˜μ΄ 달린 λ©”μ„œλ“œ ν˜ΈμΆœμ„ μ‹œμž‘ν•  수 있음.
) {
    // Set a listener for click, focus, touch, etc.
}






πŸ’‘ 컨버터

View 객체에 λ°”μΈλ”©λœ λ³€μˆ˜λ₯Ό ν‘œμ‹œν•˜κΈ° 전에 λ³€ν™˜ν•΄μ•Όν•˜λŠ”κ²½μš° Converter 개체 μ‚¬μš© κ°€λŠ₯.

<EditText
    android:id="@+id/birth_date"
    android:text="@={Converter.dateToString(viewmodel.birthDate)}"
/>

μ–‘λ°©ν–₯ 데이터 바인딩이 μ œκ³΅λ˜λ―€λ‘œ μ—­λ³€ν™˜μ„ ν•˜λŠ” κΈ°λŠ₯도 ν•„μš”ν•¨.

컨버터에 @InverseMethod μ–΄λ…Έν…Œμ΄μ…˜μ„ μΆ”κ°€ν•œ ν›„ 이 μ–΄λ…Έν…Œμ΄μ…˜μ΄ 역컨버터λ₯Ό μ°Έμ‘°ν•˜λ„λ‘ 함.

object Converter {
    @InverseMethod("stringToDate")
    @JvmStatic fun dateToString(
        view: EditText, oldValue: Long,
        value: Long
    ): String {
        // Converts long to String.
    }

    @JvmStatic fun stringToDate(
        view: EditText, oldValue: String,
        value: String
    ): Long {
        // Converts String to long.
    }
}



πŸ’‘ μ–‘λ°©ν–₯ 데이터 바인딩을 μ‚¬μš©ν•œ λ¬΄ν•œ 루프

μ–‘λ°©ν–₯ 데이터 바인딩을 μ‚¬μš©ν•  λ•Œ λ¬΄ν•œ 루프가 λ°œμƒν•˜μ§€ μ•Šλ„λ‘ μ£Όμ˜ν•΄μ•Ό 함

μœ μ €κ°€ 속성 λ³€κ²½ -> @InverseBindingAdapterκ°€ μ‚¬μš©λœ λ©”μ„œλ“œκ°€ 호좜 -> @BindingAdapterκ°€ μ‚¬μš©λœ λ©”μ„œλ“œ 호좜 -> @InverseBindingAdapterκ°€ μ‚¬μš©λœ λ©”μ„œλ“œμ— λŒ€ν•œ 또 λ‹€λ₯Έ 호좜 트리거

λ”°λΌμ„œ @BindingAdapterκ°€ μ‚¬μš©λœ λ©”μ„œλ“œμ˜ μƒˆ κ°’κ³Ό 이전 값을 λΉ„κ΅ν•˜μ—¬ λ¬΄ν•œ 루프λ₯Ό λ°©μ§€ν•˜λŠ” 것이 μ€‘μš”.




+) @BindingAdapter @InverseBindingAdapter λ‚΄μš© μ •λ¦¬ν•˜κΈ°

0개의 λŒ“κΈ€