본문 바로가기
Android(Kotlin)

[Kotlin Android] View 객체 가져오기(3) - DataBinding(구현)

by 클리마 2024. 3. 12.
728x90

이번 포스팅은 이전 포스팅에 이어서 실제로 DataBinding을 사용하는 방법에 대해서 다루어보려고 합니다. 개념에 대해 보고 싶으신 분은 이전 포스팅을 참고해주세요.

build.gradle


android {
    buildFeature {
        dataBinding true
    }
}

Layout


Data Binding을 사용할 때는 전체 레이아웃과 <data>태그를 <layout> 태그로 감싸주어야 합니다.

여기서 <data> 태그는 레이아웃 변수를 설정하는 태그입니다. Data Binding은 레이아웃에서 사용하는 변수를 별도로 설정할 수 있습니다.

<layout ... >
    <data>
        <variable
            name = "user"
            type = "com.example.appName.User" />
        <variable
            name = "mainViewmodel"
            type = "com.example.appName.MainViewmodel" />
    </data>

    ...

</layout>

Class file


> 액티비티

class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding
    private val viewmodel: MainViewmodel by viewModels()

    override fun onCreate(Bundle savedInstanceState) {
        super.onCreate()
        binding = DataBindingUtil.setContent(this, R.layout.activity_main)

        val user = User("first", "last")
        binding.setUser(user)
        binding.mainViewmodel = viewmodel
    }
}

> 프래그먼트

class MainFragment : Fragment() {

    private lateinit var binding: FragmentMainBinding
    private val viewmodel: MainViewModel by viewModels()

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        binding = DataBindingUtil.inflate(inflater, R.layout.game_fragment, container, false)
        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        // Set viewModel, lifecycleOwner and UI interations
        binding.mainViewmodel = viewmodel
        binding.lifecycleOwner = viewLifecycleOwner
        ...
    }
}

변수 값 참조


> 레이아웃

<layout>
    <data>
        <variable
            name = "user"
            type = "com.example.appName.User" />
    </data>

    <LinearLayout>
        <TextView
            android:text="@{user.firstname}"
    </LinearLayout>
</layout>

이벤트 처리


이벤트 처리에 사용할 메소드의 리턴타입은 해당 이벤트의 반환값과 일치시켜주어야 합니다.
Ex> Boolean onLongClick() 이벤트일 경우 메소드도 Boolean을 리턴해야 함.

1. 메서드 참조

  • Class file

    • 레이아웃 파일에서 onClick 속성을 설정하여 사용합니다.
    • 메서드를 public 처리해주어야 xml에서 사용이 가능합니다.
    class MainActivity : ... {
    
    private val viewmodel: MainViewmodel by viewModels()
    
    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        binding.setActivity(this)
        binding.viewmodel = viewmodel
    }
    
    public fun showToast(View view) {
        ...
    }
    }
  • 레이아웃

    • 1)

      <layout>
      <data>
        <variable
            name = "activity"
            type = "com.example.appName.MainActivity" />
      </data>
      <TextView
        android:onClick="@{activity::showToast}" />
      </layout>
    • 2)

      <layout>
      <data>
        <variable
            name = "viewmodel"
            type = "com.example.appName.MainViewmodel" />
      </data>
      <TextView
        android:onClick = "@{() -> viewmodel.showToast}" />
      </layout>

    2번 같은 경우는 android:onClick="showToast" 라고 선언하는 것에 비해 좀 더 안전한 방법이라고 할 수 있습니다. 메서드 이름만 작성하는 것은 메서드의 명확한 서명이 없으면 앱 충돌이 발생할 수 있기 때문 입니다. 하지만 2번의 경우 컴파일 타임에 체크가 되고 viewmodel의 메서드를 호출하는 람다표현식을 사용하기 때문에 더 안전합니다.

2. 리스너 바인딩

리스너 바인딩을 하게될 경우 파라미터를 통해 임의의 데이터를 전달할 수 있습니다. 바인딩할 메소드는 위의 메소드를 사용하겠습니다. 리스너를 바인딩할 때는 람다 형식으로 지정하면 됩니다.

<layout>
    <data>
        ...
    </data>

    <TextView
        ...
        android:onClick="@{(view) -> activity.showToast(view)}"
</layout>

여기서 주의할 점은 파라미터가 없는 메소드를 리스너 바인딩 형식으로 사용한다던지, view만 파라미터로 받는 메소드를 호출할 때 view를 명시하지 않고 리스너 바인딩 형식으로 사용하게 되면 다음과 같은 에러가 발생합니다.


=> ⇒ 리스너 바인딩 형식은 확실히 파라미터를 사용하는 메소드만 사용해야 에러없이 동작합니다.


static 메서드, 필드 사용


안드로이드 개발을 하다보면 안드로이드 기본 라이브러리의 static필드를 사용하거나, 커스텀한 static 필드 혹은 메소드를 사용할 때가 있습니다. DataBinding에서는 static필드와 메소드를 사용할 때 <import>를 사용합니다.

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

    <Button
        ...
        android.:visibility="@{memo.isComplete ? View.VISIBLE : View.GONE}" />
</layout>

커스텀 레이아웃 재활용


데이터 바인딩을 사용하여 만든 레이아웃을 재활용할 때 <include> 를 사용합니다. 아래 코드는 list_memo.xml을 include한 예제입니다.

list_memo.xml

<layout>
    <data>
    <variable
        name="memo"
      type="wony.quickmemo.MemoData" />
  </data>

  <TextView
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:text="@{memo.title}" />
  <TextView
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:text="@{memo.content}" />
</layout>

activity_main.xml

<layout>
    <data>
        ...
    </data>

    <include layout="@layout/list_memo"
        bind:memo="@{memo}" />    <!-- 현재 레이아웃에 있는 변수 전달 -->

</layout>

데이터 옵저빙


Data Binding에서는 양방향 바인딩을 지원하기 때문에 UI의 값을 앱 데이터로 처리할 수 있고, 앱 데이터를 UI 값에 반영할 수 있습니다. 이를 위해 Android에서는 한쪽에서 값이 변경되었을 경우 자동으로 다른 쪽의 데이터를 변경해주는 기능을 지원합니다.

  • Observable field
  • Observable class
  • LiveData

이렇게 3가지를 통해 자동으로 데이터를 연결하는 것을 지원하지만 요즘 개발 트렌드에서 많이 사용하고 있는 LiveData 를 알아보겠습니다.

> LiveData

LiveData는 Observable field를 대체할 수 있다. 예시로 LiveData를 이용하여 사용자가 LIKE 버튼을 누르면 Likes의 숫자가 올라가는 동작을 구현해보겠습니다.

  • 먼저 ViewModel에 Observable field를 적용한 코드
    val name = "Grace"
    val lastName = "Hopper"
    var likes = 0
      private set // This is to prevent external modification of the variable.
    이 코드를 LiveData로 대체하면 다음과 같이 대체할 수 있습니다.
    private val _name = MutableLiveData("Ada")
    private val _lastName = MutableLiveData("Lovelace")
    private val _likes =  MutableLiveData(0)
    

val name: LiveData = _name
val lastName: LiveData = _lastName
val likes: LiveData = _likes


LiveData는 수명주기를 인식하는 observable이기 때문에 사용하기 위해서는 `lifecycleOwner`를 명시해주어야 합니다. 바인딩 객체에 작업을 수행합니다.
```kotlin
binding.lifecycleOwner = this

LIKE 버튼을 누르면 숫자가 증가하는 메서드는 다음과 같습니다.

fun onLike() {
  _likes.value = (_likes.value ?: 0) + 1
}

레이아웃에는 버튼의 onClick과 TextView의 text를 data binding해줍니다.

<TextView
    android:text="@{Integer.toString(viewModel.likes)}" />

<Button
    android:onClick="@{() -> viewModel.onLike()}" />

BindingAdapter


바인딩 어댑터는 속성 값을 커스텀할 수 있는 방법을 제공합니다. 기본 UI의 속성을 재할당하여 원하는대로 속성을 사용할 수 있습니다. 바인딩 어댑터를 사용하여 속성을 변경할 때는 주로 메서드에 View 객체를 매개변수로 넣어 databinding을 손쉽게 하도록 합니다. 예를 들어 app:hideIfZero 속성을 다음과 같이 변경할 수 있습니다.

@BindingAdapter({"hideIfZero"})
fun hideIfZero(view: View, number: Int) {
    view.visibility = if (number == 0) View.GONE else View.VISIBLE
}

더 많은 내용은 공식사이트 문서를 참고하시기 바랍니다.




728x90