마스터Q&A 안드로이드는 안드로이드 개발자들의 질문과 답변을 위한 지식 커뮤니티 사이트입니다. 안드로이드펍에서 운영하고 있습니다. [사용법, 운영진]

데이터바인딩이라는게 xml상에서 꼭 변수로 지정할 필요가 없나요?

0 추천

MVVM패턴을 적용하고자 영상보고 따라하면서 적용중인데요..

처음에 observe에서 뷰모델을 관찰한 데이터가 뷰에 업데이트가 안되었습니다.

몰랐다가 xml 에서 데이터바인딩 코드가 문제였다는 것을 알았습니다..

<TextView
   android:id="@+id/title"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:layout_gravity="center"
   android:text="@{data.titleArg}"
   android:textColor="@color/white"
android:textAppearance="@style/TextAppearance.AppCompat.Widget.ActionBar.Title"
   android:layout_centerHorizontal="true" />

여기서 처음에 data.titleArg라는 데이터바인딩 코드를 사용했는데.. 이게 알고보니 필요가 없었네요

더 정확히는 <data></data> 태그도 필요가 없구요.. 그냥 코드상에서 binding 객체를 이용해서 id를 통해서

접근하면 되는것이었습니다.. 맞나요..?

 

그리고 추가적으로 코드를 좀 더 가공?하고싶은데 어떤 점을 수정해야할까요?

개인적으로 두~세군데 정도 건드리고 싶은데,

1.newInstance는 프래그먼트를 생성함과동시에 데이터를 넣는 코드(arguments)라 꼭 필요한 코드 같고..
더 좋은 방법 혹은 깔끔한 방법이 있는지 모르겠습니다. 아마 메인액티비티에도 MVVM 패턴을 적용하면서

뷰모델을 만들지 싶은데 그때 ViewModel을 통한 데이터 공유방법도 있다고 들어 그걸 이용할까하는데

어떨까요?

 

2.넣은 인자를 onCreate(0에서 받아 데이터를 가공하는 코드도 MVVM 패턴에서 보면 좋지 않아보이는데

뷰모델 내부에서 메소드를 정의하고 arguments.apply에서 메소드를 호출하면서 인자를 전달해 메소드

내부에서 가공시켜줘야할까요?
 

3.observe가 데이터 변경을 알 수있도록 setTitle이라는 함수를 호출하는데 이것의 위치가 어디가 좋을까요..

 

WriteRoutineFragment.kt

class WritingRoutineFragment : Fragment() {
    var titleArg: String? = null
    private var _binding: FragmentWritingRoutineBinding? = null
    private val binding get() = _binding!!
    private val viewModel = WriteRoutineViewModel()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        arguments?.apply {
            titleArg = getStringArrayList("title")?.joinToString(" / ")
        }
    }

    override fun onCreateView(inflater: LayoutInflater,
                              container: ViewGroup?,
                              savedInstanceState: Bundle?): View? {
        _binding = FragmentWritingRoutineBinding.inflate(inflater, container, false)

        viewModel.title.observe(viewLifecycleOwner) { titleData ->
            // UI 업데이트
            binding.title.text = titleData // 업데이트 안됨
        }
        return binding.root
    }
    companion object {
        @JvmStatic
        fun newInstance(data: ArrayList<String>) =
            WritingRoutineFragment().apply {
                arguments = Bundle().apply {
                    putStringArrayList("title", data)
                }
            }
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        viewModel.setTitle(titleArg)
    }
}

WriteRoutineViewModel.kt

class WriteRoutineViewModel : ViewModel() {
    val title: MutableLiveData<String> = MutableLiveData()
    init {
        title.value = "제 목" // observe가 데이터변경을 알 수있도록 초기값 설정
    }

    fun setTitle(data: String?) { // observe가 관찰
        title.value = data
    }
}

 

codeslave (3,940 포인트) 님이 2021년 5월 29일 질문

2개의 답변

0 추천

여기서 처음에 data.titleArg라는 데이터바인딩 코드를 사용했는데.. 이게 알고보니 필요가 없었네요 더 정확히는 <data></data> 태그도 필요가 없구요.. 그냥 코드상에서 binding 객체를 이용해서 id를 통해서접근하면 되는것이었습니다.. 맞나요..?

=> 이 부분이 좀 이상하네요. 안드로이드 데이터바인딩은 Angular, React, ViewJS같은 웹프레임웍이 아니라서 자동으로 업데이터 되지 않습니다. 명료하게 어떤 데이터를 사용한다고 XML에 정의해주고 이걸 바인딩 시켜주셔야 합니다. 따라서 XML에 data 태그 부분에 어떤 데이터를 가져다 쓰는지 정의해주서야 하는 걸로 압니다. 안그러면 데이터바인딩이 data가 어떤 걸 가리기는지 알 방법이 없어요. 빌드 자체가 안될 걸로 생각합니다. 이렇게 하시면 

viewModel.title.observe(viewLifecycleOwner) { titleData ->
            // UI 업데이트
            binding.title.text = titleData // 업데이트 안됨
        }

이렇게 Fragment가 ViewModel을  observe할 필요가 없습니다. DataBinding을 사용할 때 님과 방법으로 사용하지는 않아요. 그냥 binding.data = ViewModel 이런 식으로만 설정해주면 끝입니다. DataBinding이 자동으로 observe를 하게 되어있어요.
님의 사용하신 코드는 이미 data binding이 내부적으로 처리하고 있는 부분을 중복으로 코딩하시는 것이므로 binding.data로 필요한 데이터를 설정하시고 제거하시는 게 맞다고 보여요. 이 부분은 onViewCreated를 이용하시는면 될 듯합니다.

 

그리고 ViewModel에서,

val title: MutableLiveData<String> = MutableLiveData()

 

MutableLiveData를 public으로 만드는 것보다는

private var _title: MutableLiveData<String> = MutableLiveData()
val title: LiveData<String> get = _title

MutableLIveData는 값 변경이 가능하기 때문에 Readonly인 LiveData를 public으로 해주는 게 더 좋습니다.

 

1.newInstance는 프래그먼트를 생성함과동시에 데이터를 넣는 코드(arguments)라 꼭 필요한 코드 같고..
더 좋은 방법 혹은 깔끔한 방법이 있는지 모르겠습니다. 아마 메인액티비티에도 MVVM 패턴을 적용하면서

뷰모델을 만들지 싶은데 그때 ViewModel을 통한 데이터 공유방법도 있다고 들어 그걸 이용할까하는데

어떨까요? 
님의 경우 VIew 레이어가 DataBinding(XML)이기 때문에 Activity는 MVVM과는 직접 관련이 없습니다. 
프레그먼트 생성은 newInstance든, ViewModel 공유 든간에 님의 요구에 맞게 선택하시면 됩니다.

 

2.넣은 인자를 onCreate(0에서 받아 데이터를 가공하는 코드도 MVVM 패턴에서 보면 좋지 않아보이는데

뷰모델 내부에서 메소드를 정의하고 arguments.apply에서 메소드를 호출하면서 인자를 전달해 메소드

내부에서 가공시켜줘야할까요?

setArguments를 사용하신다면 ViewModel에 arguments로 받은 데이터를 ViewModel로 전달하는 부분이 필요할 수 있을 겁니다. 이건 안드로이드 시스템 구조상 그런 부분이고 ViewModel을 공유하는 것도 안드로이드에 존재하는 개념이구요.
 

3.observe가 데이터 변경을 알 수있도록 setTitle이라는 함수를 호출하는데 이것의 위치가 어디가 좋을까요.

위에서 말씀드린 데로 onViewCreate에서 binding.data 를 호출하시면 데이터바인딩에 필요한 데이터를 세팅해 주시면 됩니다. Fragment가 ViewModel에 있는 LiveData를 observe하실 필요가 없어요. Databinding을 쓰기 때문에 Fragment는 이제 더이상 MVVM에 말하는  View가 아니예요.

참고로 님이 하신 방법대로라면 DataBinding이 굳이 필요없구요. 그냥 ViewBinding만 쓰시고 XML은 layout, data없이 정상적으로 사용하시면 됩니다. 제가 보기엔 님께서는 Databinding과 ViewBinding을 헷갈리고 계시는 것 같아요.

spark (226,420 포인트) 님이 2021년 5월 30일 답변
흠 코드

<data> 태그 없이 접근가능한게 보니까 binding.title 이 코드에서 xml 상의 뷰의 아이디로 접근을 하는것같습니다..그래서 data 태그 그러니까 xml의 ㅣ바인딩 변수없이 접근이 가능한것같아요
->이게 보니까말씀하신 뷰바인딩이라는것같네요..

선생님 그런데 binding.data = ViewModel 하라는게 잘이해가가지 않습니다.
뷰모델 객체를 생성해서 data라는 뷰바인딩 변수에 할당해주라는것인지..
사실 이렇게하면 타입이 안맞아서 에러가나는데.. 이해가 잘가지않습니다
0 추천

databinding을 data tag없이 사용하시면 더이상 data binding이 아니구요. 수동으로 작성해주어야 될 코드를 자동으로 생성해주기 때문에 편의상 사용하는게 주된 이유입니다.

binding.data = ViewModel은 그냥 예시이구요. 실제로는 ViewModel에 있는 LiveData를 참조하도록 해야겠죠.

<data
    name="viewModel"
    type="PersonViewModel" />

<TextView
     android:id="@+id/nameText"
     android:text="@{viewModel.person.name}" 
... />

<TextView
     android:id="@+id/ageText"
     android:text="@{viewModel.person.age}" 
... />

data class Person(
   val name: String,
   val age: Int
)

class PersonViewModel: ViewModel() {
   val person: LiveData<Person> = ...
}

binding.viewModel = viewModel

안드로이드 개발문서에 나와 있는 자료들만 읽어보셔도 충분히 사용법을 익히실 수 있을 거라고 생각합니다. ViewModel에 LiveData들을 정의해 놓고 XML에 binding해서 사용하시면 됩니다.

 

추가로 ViewBinding이나 DataBinding을 Fragment에서 사용할 때는 Memory leak(메모리 누수) 현상이 있습니다.  바인딩 객체를 클래스 멤버변수로 사용하고 있다면 onDestoryView 에서 null에 만들어주어야 합니다. 구글의 버그라고 할 수 있습입니다. autoCleared 나 Propety delegation을 활용하면 깔끔한 처리가 가능합니다. 관심있으면 찾아보시길...

spark (226,420 포인트) 님이 2021년 5월 31일 답변
spark님이 2021년 5월 31일 수정
...