추석 연휴가 지나고, 첫 포스팅이다.
그만큼 개발을 쉬었다는 얘기니 좀더 열심히 코딩을 해야겠다고 다짐한다.
DroidNights 2021년도 봐야하는데 8시간동안 시간이 나려나..
오늘 해줄 얘기를 들어가보자.
오늘의 얘기는 복잡하다. Flow를 도입하였고, stateFlow를 쓴다.
Livedata는 Domain Layer에선 어울리지 않는다나 뭐라나..
<Spinner
android:id="@+id/spinner"
android:layout_width="168dp"
android:layout_height="48dp"
android:layout_marginTop="64dp"
android:layout_marginEnd="48dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
/>
스피너는 entries(이곳에List를 넣으면 선택항목으로 된다)
와 Listner를 구현하여 현재의 데이터를 받아올수있다.
하지만 databinding을 생각한다면 한번더 생각해봐야 한다.
물론 저렇게 구현하는게 가장 편한것같다..
private val _spinnerEntry = MutableStateFlow(
emptyList<Int>()
)
val spinnerEntry: StateFlow<List<Int>?> = _spinnerEntry
MutableStateFlow의 값은 다른곳에서 바뀌면 안되기때문에
getter의 의미로 아래에 StateFlow를 사용하여 ReadOnly하게 하였다.
@BindingAdapter("entries")
fun Spinner.setEntries(entries: List<Int>?) {
entries?.run {
val arrayAdapter = ArrayAdapter(context, R.layout.simple_spinner_item, entries)
arrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
adapter = arrayAdapter
}
}
해당 스피너의 어뎁터를 만들어줬다.
Spinner의 모양은 해당어뎁터로 수정하면 될것같다.
이제 layout에서 사용하자.
tools:entries="@{viewmodel.spinnerEntry}"
하지만 이렇게 하면 데이터가 안넣어질것이다.
왜냐면 spinnerEntry에 값을 넣지 않았기때문이다.
StateFlow이기 때문에 값을 방출시키자.
fun setSpinnerEntry(Entry: List<Int>) {
viewModelScope.launch {
_spinnerEntry.emit(Entry)
}
이제 데이터가 제대로 들어간것을 확인할수있다.
이제 선택 데이터를 가져오려고 한다. DataBinding를 이용하자.
val spinnerDATE = MutableStateFlow<Int>(0)
자 똑같이 ViewModel에 StateFlow를 작성한뒤, BindingAdapter를 사용해야하는데
결국 양방향으로 데이터 바인딩을 해야하므로 @= 가 필요한 시점이다.
하지만 TwoWayBinding은 기본적으로 제공하는 몇개의 component를 제외하곤 InverseBindingAdpater를 사용해야하는데 Spinner도 그 예중 하나이다.
InverseBindingAdapter는 나중에 설명하겠지만, 일단 코드로 이해해보자.
// attribute는 바인딩 어뎁터처럼 value을 의미한다. event는 반응할 bindingapdater의 value를 의미한다.
@InverseBindingAdapter(attribute = "selectedValue", event = "selectedValueAttrChanged")
@JvmStatic
fun Spinner.getSelectedValue(): Any? {
return selectedItem
}
@JvmStatic
// 위에것이 실행되고 아래의 bindingapdater가 실행된다.
@BindingAdapter("selectedValueAttrChanged")
fun Spinner.setInverseBindingListener(inverseBindingListener: InverseBindingListener?) {
inverseBindingListener?.run {
onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(
parent: AdapterView<*>,
view: View,
position: Int,
id: Long
) {
if (tag != position) {
inverseBindingListener.onChange()
}
}
override fun onNothingSelected(parent: AdapterView<*>) {}
}
}
}
@JvmStatic
@BindingAdapter("selectedValue")
fun Spinner.setSelectedValue(selectedValue: Any?) {
adapter?.run {
val position =
(adapter as ArrayAdapter<Any>).getPosition(selectedValue)
setSelection(position, false)
tag = position
}
}
코드를 분석해보자.
InverseBindingAdapter에는 attribute와 event가 들어간다.
양방향을 하기위해선 getter와 setter , 그리고 변하는걸 감지해주는
3가지의 어댑터가 필수적이다.
일단 InverseBindingApdater 구현체는 get의 역할을 한다.
그리고 event에 해당하는 adapter의 구현체는 change 역할을 한다.
그리고 attribute에 해당하는 adapter의 구현체는 setter의 역할을 한다.
한번 순서가 어떻게 되는지 확인해보자.
- 값 변경 시 InverseBindingListener.onChange() 호출한다 (값변경 X).
- 해당 event를 갖는 @InverseBindingAdapter (Getter) 메서드 실행
- @BindingAdapter (Setter) 메서드 실행
이걸로 조금더 나은 개발력이 생겼길 바라며 이만 물러가겠다.