[Jetpack AAC] Data Binding 기본 #2 : ObservableField
https://full-stack.tistory.com/23
Android Jetpack의 아키텍처 컴포넌트(AAC) 중 하나인 데이터 바인딩의 기본기를 좀 더 학습해보고자 한다. 이번에는 이벤트 바인딩의 방법과 옵저버블 필드(ObservableField)에 대해 알아보겠다. 간단히 지난번에 한 내용을 복습하면, XML파일에 변수를 선언하고 그 변수에 kotlin 클래스의 객체를 참조하도록 한 뒤, xml 파일에서 자유롭게 그 변수를 사용하는 것이었다.
함수 참조 이벤트 바인딩
우리는 버튼 뷰를 누르면 Click 이벤트가 발생함을 알고 있다. 그래서 보통은 setOnClickListener 메서드를 이용해 리스너를 붙여 이벤트에 따른 동작을 정의하였다. 하지만 데이터 바인딩이 되어있고, 매개변수가 일치한다면 android:onClick= 속성에 함수 참조에 의한 이벤트 바인딩이 가능하다.
다음과 같이 MainActivity를 참조하고 있는 MyData라는 변수가 있다.
<data>
<variable name="MyData" type="com.rkqnt.databindingpractice.MainActivity" />
</data>
MainActivity에는 다음과 같이 데이터 바인딩과 View 하나를 받는 메서드가 정의되어있다.
(onClick 이벤트는 반환값이 없고 View 하나만 받는 메서드여야 한다.)
class MainActivity : AppCompatActivity() {
lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
binding.myData = this
}
fun onButtonClick(view: View){
Toast.makeText(this,"토스트 메시지",Toast.LENGTH_SHORT).show()
}
}
이제 직접 참조를 걸어 이벤트 바인딩이 아래와 같이 가능하다
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="43dp"
android:text="Button"
android:onClick="@{MyData::onButtonClick}"/>
onLongClick 같은 경우는 반환형이 Boolean으로 존재한다. 이런 경우 위와 같은 방식으로 진행하면 오류가 뜬다. 따라서 메서드를 정의할 때, return true를 반드시 명시해놓아야 동작한다.
fun onLongClickHandler(view: View):Boolean{
Toast.makeText(view.context,"롱클릭 이벤트 핸들러 동작",Toast.LENGTH_SHORT).show()
return true
}
<Button
android:id="@+id/button3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onLongClick="@{Handler::onLongClickHandler}"
리스너 이벤트 바인딩
만약 이벤트 등록을 하려는 함수가 onClick(view: View) 형태가 아닌, 개발자가 임의로 만든 형태의 함수를 쓰고자 할 때는 람다 형식으로 익명 함수에서 개발자의 함수를 호출하는 방식으로 사용하면 된다. 먼저 리스너 이벤트 바인딩을 연습하기 전에 별도의 클래스를 하나 정의하겠다.
class EventHandler {
fun onClickHandler(view: View){
Toast.makeText(view.context,"이벤트 핸들러 동작",Toast.LENGTH_SHORT).show()
}
fun onClickWithParameter(view: View, firstName: String){
Toast.makeText(
view.context,
"리스너 이벤트 바인딩 : 전달받은 매개변수 - $firstName",
Toast.LENGTH_SHORT).show()
}
fun onClickWithParameter2(firstName: String){
}
}
xml 파일에서는 위 클래스 객체를 참조할 변수를 선언한다.
<data>
<variable name="MyData" type="com.rkqnt.databindingpractice.MainActivity" />
<variable name="Handler" type="com.rkqnt.databindingpractice.EventHandler" />
</data>
MainActivity에서 다음과 같이 데이터 바인딩을 수행한다.
class MainActivity : AppCompatActivity() {
lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
binding.myData = this
binding.handler = EventHandler()
}
EventHandler 클래스에는 onClickWithParameter라는 메서드가 있는데 String 타입 파라미터를 별도로 받는다. 이건 다음과 같이 XML에서 람다로 수행하면 된다.
<Button
android:id="@+id/button3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="@{(view)->Handler.onClickWithParameter(view,user.firstName)}"
android:text="Listener Event Binding" />
<Button
android:id="@+id/button3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="@{()->Handler.onClickWithParameter2(user.firstName)}"
android:text="Listener Event Binding" />
ObservableField
ObservableField는 데이터 바인딩을 이용하여 변수의 값이 변화했을 때, 해당 변수를 옵저빙 하고 있는 데이터 필드는 자동으로 UI를 업데이트한다. 데이터 바인딩은 이런 기능을 만들기 위해 존재한다고 봐도 과언이 아니다. 이러한 기능을 구현하는 것으로는 LiveData가 비슷한 기능을 수행하며 장점이 더욱 많기에 ObservableField보단 LiveData가 주로 쓰인다. 비슷한 기능을 구현하는 두 기술의 차이는 LiveData에서 서술하겠다.
우선 다음과 같은 데이터 모델을 선언한다. 이중 age만 사용해 볼 것이다.
class UserObservableField() {
val firstName = ObservableField<String>()
val lastNam = ObservableField<String>()
var age = ObservableInt()
var mDataMap = ObservableArrayMap<String,Any>()
}
이제 XML에 userObserve라는 변수를 통해 UserObservableField 객체를 참조하게 하자.
<data>
<variable name="MyData" type="com.rkqnt.databindingpractice.MainActivity" />
<variable name="Handler" type="com.rkqnt.databindingpractice.EventHandler" />
<variable name="userObserve" type="com.rkqnt.databindingpractice.UserObservableField" />
</data>
데이터를 바인딩한다.
class MainActivity : AppCompatActivity() {
.. 중략
var observableUser=UserObservableField()
override fun onCreate(savedInstanceState: Bundle?) {
.. 중략
binding.userObserve = observableUser
observableUser.age = ObservableInt(5)
}
fun onButtonClick(view: View){
observableUser.age.set(observableUser.age.get()+1)
}
}
버튼은 적절히 이벤트 바인딩되어있다.
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="43dp"
android:text="Button"
android:onClick="@{MyData::onButtonClick}" />
출력되는 TextView는 다음과 같이 설정해놓는다.
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="60dp"
android:text='@{"나이 : "+userObserve.age}'/>
이제 버튼을 누를 때마다 나잇값이 자동으로 증가함을 볼 수 있다.
여기서 우리는 기존처럼 UI를 갱신하기 위해 findViewById도 필요 없었고 리스너를 붙일 필요도 없었다. 값을 증가시킨 뒤 setText를 수행할 필요도 없었다. 그저 관찰 가능한(Observable) 변수의 값을 증가시켰을 뿐이다.
별로 극적으로 보이지 않을 수 있다. 다음 예시를 보면 확실히 극적일 것이다. EditText에 쓰이는 실시간으로 변화하는 텍스트를 그대로 TextView에 띄워보자. 먼저 데이터 바인딩을 사용하지 않는 방식부터 보여주겠다.
위와 같은 UI에 아래에 EditText에 입력하는 값을 실시간으로 위의 TextView에 띄워보는 것. 기존의 View 참조 방식으로 코드를 짜면 다음과 같다. (코틀린이어서 깔끔해 보일 수도 있는데 자바는 눈뜨고 못 봐준다)
class MainActivity : AppCompatActivity() {
lateinit var textView: TextView
lateinit var editText: EditText
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
textView = findViewById(R.id.textView)
editText = findViewById(R.id.editText)
editText.addTextChangedListener(object: TextWatcher {
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
override fun afterTextChanged(s: Editable?) {
textView.text = editText.text
}
})
}
}
이제 데이터 바인딩과 ObservableField를 활용한 코드를 보자. 정확히 동일한 기능을 수행한다.
class MainActivity : AppCompatActivity() {
lateinit var binding: ActivityMainBinding
var text = ObservableField<String>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
binding.myData = this
}
}
이런 단순한 기능을 구현하는데만 해도 8줄이나 차이가 난다. 기능이 갈수록 커지고 복잡해진다면 이 두 아키텍처의 코드 길이의 차이는 기하급수적으로 날 것이다. 이 정도면 충분히 극적이지 않나 싶다.
비밀은 데이터 바인딩의 기능 중에 다음 기능을 활용하면 EditText의 값은 text="@={model.text} " 이런 식으로 ObservableField에 값을 전달할 수 있다. XML에서도 이거면 충분하다.
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{MyData.text}"/>
<EditText
android:id="@+id/editText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="64dp"
android:ems="10"
android:inputType="textPersonName"
android:text="@={MyData.text}" />
데이터 바인딩은 꽤 다양한 기능들이 있는데, 이걸 모두 다루기엔 다소 지엽적이므로 나중에 따로 정리해봐야겠다.
끝.
'Mobile : Android > Jetpack' 카테고리의 다른 글
[Jetpack AAC] 뷰모델(ViewModel) #1 : MVVM (0) | 2022.06.13 |
---|---|
[Jetpack AAC] Data Binding 기본 #1 (0) | 2022.04.12 |
[Jetpack AAC] View Binding 정리 (0) | 2021.11.15 |
[Jetpack Compose] Compose # 1 - 기본 (0) | 2021.11.06 |
댓글