안드로이드의 jetpack 라이브러리가 등장하면서 dataBinding이라는 기술이 안드로이드에도 도입되기 시작했습니다. dataBinding의 개념은 이미 예전부터 버터 나이프 라이브러리와 같은 개념으로 많이 알려져 있었습니다. 하지만 jetpack을 통해 공식적으로 도입된 느낌이 강합니다.(물론 아직까지도 버터 나이프와 같은 데이터바인딩 라이브러리를 사용할 수 있습니다.)
데이터바인딩은 XML과 기존 코틀린(자바) 코드와의 연결을 기초로 합니다. 이는 특별한 연결 설정 없이 데이터 바인딩을 통해 변수의 변경에 따라 뷰를 변경해줄 수 있다는 것을 의미합니다. 이로 인해 더욱 간단하게 view와 관련된 기능과 다른 기능들을 분리할 수 있도록 도와줍니다.(여러가지 디자인 패턴을 적용하기 더욱 좋아진다는 것을 의미하죠.)
그렇다면 jetpack의 data binding을 어떻게 사용할 지 알아보겠습니다.
먼저 데이터 바인딩을 적용하기 위해 build.gradle 내부에 두가지 방식 중 한가지를 작성합니다.
buildFeatures는 android studio 4.0 이상 버전부터 변경된 양식입니다.(4.0 이하 버전을 사용해 본 적이 없어서 실제로 어떤 경고나 에러가 뜨는지는 확인해본 적이 없지만 찾아보니 에러 메시지를 띄운다고 합니다.)
아래의 dataBinding은 기존 4.0 이하 버전에서 세팅해주시면 됩니다.(이 경우에는 경고를 뱉습니다.)
두 가지 방식중 하나를 택해 적용시켰다면 이제 데이터바인딩을 사용할 수 있습니다. 다행히 jetpack의 데이터 바인딩은 따로 library의 추가를 요구하지 않습니다.
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
</data>
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/textView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1" />
</androidx.appcompat.widget.LinearLayoutCompat>
</layout>
데이터바인딩 적용을 했다면 제일 먼저 해야할 일은 데이터바인딩 양식에 맞게 xml을 설정하는 것입니다. 위 코드를 보시면 <layout> //생략 </layout> 코드가 있습니다. 이 코드는 데이터바인딩을 위한 코드인데요. 이 layout에서 네임스페이스를 정의할 수 있습니다. 하지만 기본적인 width나 height같은 경우는 추가적인 layout들을 구현해서 제작해주어야 합니다.
기존 xml 코드를 layout으로 감싸준다고 생각하시면 되겠습니다. 기존 xml에서 layout을 감싸줄 때 가장 바깥쪽 view에 커서를 둔 후, alt + enter를 이용하시면 convert to data binding layout이라는 문구가 뜨고, 그 문구를 클릭할 경우 자동으로 안드로이드 스튜디오가 xml 코드 전체를 변경시켜 줍니다. 물론 이와 같은 기능이 보이지 않는다면 안드로이드 버전을 확인해주셔야 합니다.
(xml 맨 바깥 레이아웃) alt + enter click -> convert to data binding layout click
이렇게 xml에 적용을 끝냈다면, 이제 activity 및 fragment로 넘어가야 합니다.
class MainActivity : AppCompatActivity() {
private var _binding: ActivityMainBinding? = null
protected val binding: ActivityMainBinding get() = _binding!!
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
_binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
binding.textView.text = "이런 식으로 view를 이용할 수 있습니다."
}
override fun onDestroyView() {
_binding = null
}
}
먼저 activity에서 데이터바인딩을 적용하는 방식입니다. binding이라는 변수를 이용할 수 있고(이름은 고정되어 있지 않으니 혹시라도 다른 이름으로 쓰고 싶으시다면 얼마든지 가능합니다) 이 변수를 DataBindingUtil을 이용해 가져올 수 있습니다. setContentView는 기존 activity에서 뷰를 생성하는 것과 동일합니다.
그 후, binding 변수를 이용해 메인 액티비티 내부의 뷰들을 이용할 수 있습니다. 현재 예제에서는 textView라는 아이디를 가진 TextView를 이용하고 있습니다. 물론 이것으로 데이터바인딩은 끝이 아닙니다. 데이터를 어떻게 xml에 집어넣을 수 있는지 코드와 함께 설명해드리겠습니다.
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="textData"
type="String" />
<variable
name="classData"
type="package.Class" />
</data>
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/textView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{textData}"
<--! android:text="@{classData.text}" -->
android:layout_weight="1" />
</androidx.appcompat.widget.LinearLayoutCompat>
</layout>
일반 String, Integer, Double 등은 그냥 type에 선언해주는 정도로 사용할 수 있습니다. 그러나 개발자가 직접 만들었거나 라이브러리에서 가져오는 데이터라면 사용하려는 클래스가 있는 package 명을 입력해주어야 합니다. 또한 클래스 명까지 정확하게 적어주어야 합니다.
그 후 가져온 데이터를 사용하고 싶다면, 사용할 view의 속성에 직접 @{} 내부에 선언해주는 것으로 사용할 수 있습니다. 주석 처리된 부분은 class의 변수를 이용하는 부분이고 text="@{textData}"는 String 변수를 바로 사용하는 부분입니다.
이렇게 작성되었다면 이제 다시 코틀린 코드로 넘어가야 합니다.
class MainActivity : AppCompatActivity() {
private var _binding: ActivityMainBinding? = null
protected val binding: ActivityMainBinding get() = _binding!!
var text = "테스트입니다."
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
_binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
binding.textData = text
//binding.classData = ClassData(text = "클래스 테스트입니다.")
}
override fun onDestroyView() {
_binding = null
}
}
binding 내부에 선언된 data를 코드에서 집어넣게 되면 두 개의 연동은 끝이 납니다. 물론 이 상태로는 text가 변경된다고 한 들, 실제로 view를 업데이트 시켜주는 것도 아니라서 위 코드와의 차이점을 파악하기도 어렵습니다. 이때 필요한 것이 LiveData와 Observable 변수입니다.
class MainActivity : AppCompatActivity() {
private var _binding: ActivityMainBinding? = null
protected val binding: ActivityMainBinding get() = _binding!!
var text : MutableLiveData<String> = MutableLiveData("테스트입니다.")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
_binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
binding.textData = text.value!!
}
override fun onDestroyView() {
_binding = null
}
}
MutableLiveData는 value의 변경을 감지해서 view를 업데이트해 주는 observable한 클래스입니다. 또한 각 액티비티 및 프래그먼트의 lifecycle을 인식해서 view의 업데이트를 확인합니다. 그래서 없는 view를 참조하지 않고 백그라운드에 있는 화면을 계속 감시하고 있지도 않기 때문에 안전성과 메모리 누수면에서 장점을 가지고 있습니다. 이는 ViewModel을 사용할 때 더욱 적극적으로 사용합니다.
위와 같은 식으로만 코드를 설정해두더라도 이미 코틀린 내부에서 움직이는 변수를 xml에 연결했고, 그것을 UI를 통해 직접 확인할 수 있습니다. 물론 이대로 사용한다면 확실히 미완성 코드라고 여겨질 수 밖에 없지만 이 글에서는 ViewModel을 다룰 예정에 없어 이 정도만 작성해보겠습니다.
abstract class CommonFragment<T :ViewDataBinding>(@LayoutRes private val layoutId: Int) :Fragment() {
private var _binding: T? = null
protected val binding: T get() = _binding!!
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
_binding = DataBindingUtil.inflate(
layoutInflater,
layoutId,
null,
false
)
return binding.root
}
override fun onDestroyView() {
_binding = null
}
}
프래그먼트의 공통된 기본 작업입니다.(액티비티 역시 이렇게 공통된 클래스를 작성해두고 쓰는 것이 불가능하지는 않지만, 제가 만드는 앱들은 다 액티비티는 몇 개 없고 프래그먼트로 UI를 표현하기 때문에 그걸 가져오다 보니 이런식으로 작성되었습니다. 하하...)
실제로 데이터를 사용하는 방법은 프래그먼트나 액티비티가 동일하기 때문에 생략하도록 하겠습니다. 액티비티에서 보여주었던 예제대로 프래그먼트에서도 데이터를 묶을 수 있습니다.
T : ViewDataBinding은 DataBinding시 나오는 위의 ActivityMainBinding과 같은 녀석들을 지칭합니다.
@LayoutRes는 레이아웃의 아이디가 실제로 res 폴더 안에 있는지 검증해주는 주석입니다.
이렇게 작성하면 기본적으로 dataBinding의 기본 작업이 끝이 났습니다. 여기서 한가지 주의할 점은, dataBinding이라는 이름으로 layout에다가 코틀린 코드를 묶었다 하더라도 위의
android:text="@{textData}"
와 같이 데이터를 뷰에 묶는 코드가 따로 존재하지 않는다면 그것을 data binding이 아니라 view binding이라고 부를 수 있습니다. data binding의 기본은 항상 view와 데이터가 동일하게 움직이는 것이라는 점을 알아야 합니다. 그래서 묶을 데이터가 없는 화면의 경우, view binding을 따로 사용해서 더 효율적인 처리를 할 수 있습니다.
이러한 data binding은 jetpack 에서 AAC(Android Architecture Components) 모델에 포함되어 있습니다. 그래서 당연하게도 AAC에 포함되어 있는 여러가지 도구들과 함께 사용해야 제대로 data binding 작업을 마쳤다고 얘기할 수 있습니다.(물론 안드로이드에서는 모든 라이브러리를 조화롭게 사용하는 것이 제일 중요하다고 배웠습니다.)
점점 안드로이드가 발전하고 있는 모습(그리고 아직 미천하지만 점점 발전하는 제 실력)을 보면 제가 만들었던 옛 코드들이 지금과 얼마나 많이 다른지 확인할 수 있습니다. 이는 좋은 쪽으로 발전하기도 했지만, 더 획일적인 부분들도 분명 생겼죠. 하지만 좀 더 다른 라이브러리와 안드로이드에 대해 능숙하게 다루게 되기를 기도하면서 꾸준히 작업할 겁니다.
'안드로이드 > 코틀린' 카테고리의 다른 글
(android)greenrobot - EventBus (3) | 2021.04.06 |
---|---|
(android-jetpack)AAC에서의 뷰모델 (0) | 2021.03.30 |
(android) 스플래시 화면 만들기 (0) | 2021.03.11 |
Kotlin으로 만든 커스텀 버튼 (0) | 2021.02.09 |
Gson과 JSON의 toString (0) | 2021.01.23 |
댓글