Android 데이터 바인딩

timothy jeong·2021년 12월 4일
0

Android with Kotlin

목록 보기
61/69

뷰 모델과 라이브 데이터를 적용하여 각 코드영역의 관심사의 분리를 만들어냈다. 그럼에도 불구하고 view 가 프레그먼트에서 코드의 복잡성은 증가하게 된다. 만약 뷰가 스스로 정보를 업데이트 하도록 만들면 어떨까? 프레그먼트의 코드가 훨씬 단순해질 것이다.

이러한 방식을 데이터 바인딩이라고 부른다. 데이터 바인딩을 쓰면 뷰가 뷰모델로 부터 데이터를 직접 가져올 수 있다.

데이터 바인딩 사용

계속해서 만들던 앱에 데이터바인딩을 적용해보자.


    buildFeatures {
        viewBinding true
        dataBinding true
    }

result_fragment 에 적용

fragment_result 에 적용할 것이므로, 해당 프레그먼트를 layout 으로 감싸고, data 에 variable 을 선언해준다. 그리고 가져다쓸 객체를 매핑하고, 해당 객체의 프로퍼티를 어디에 적용할지 입력한다.

<?xml version="1.0" encoding="utf-8"?>
<layout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    tools:context=".ResultFragment">

    <data>
        <variable
            name="resultViewModel"
            type="com.coliv.guessinggame.ResultViewModel" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:gravity="center">

        <TextView
            android:id="@+id/won_lost"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:textSize="28sp"
            android:layout_margin="18dp"
            android:text="@{resultViewModel.result}"/>
      <!-- android:text="@{resultViewModel.result}" 텍스트에 적용 -->

        <Button
            android:id="@+id/new_game_button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:text="Start new game"/>

    </LinearLayout>
</layout>

프레그먼트 코드에서는 인스턴스화된 객체를 매핑해줘야한다.

        viewModelFactory = ResultViewModelFactory(result)
        viewModel = ViewModelProvider(this, viewModelFactory)
            .get(ResultViewModel::class.java)
        binding.resultViewModel = viewModel

game_fragment 에 적용

게임 프레그먼트의 경우 문제가 있다.

      viewModel.incorrectGuess.observe(viewLifecycleOwner, { newValue ->
            binding.incorrectGuesses.text = "Incorrect guesses: $newValue"
        })

        viewModel.livesLeft.observe(viewLifecycleOwner, { newValue ->
            binding.lives.text = "You have $newValue lives left"
        })

이 두 부분이 프로퍼티값만 출력하는게 아니라는것이다. 이럴때는 String 리소스를 이용해서 해결할 수 있다. String 리소스는 argument 를 가질 수 있는데, 이를 이용하면 된다.

<resources>
    <string name="lives_left">You have %d lives left</string>
    <string name="incorrect_guesses">Incorrect guesses: %s</string>
</resources>

그리고 argument 를 넘겨주면 된다.

<TextView
        android:id="@+id/lives"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="16sp"
        android:text="@{@string/lives_left(gameViewModel.livesLeft)}"/>

    <TextView
        android:id="@+id/incorrect_guesses"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="16sp"
        android:text="@{@string/incorrect_guesses(gameViewModel.incorrectGuess)}"/>

프레그먼트의 코드도 훨씬 단순해졌다. 데이터 바인딩으로 매핑된 뷰는 굳이 observe 함수를 구현하지 않아도, binding.lifecycleOwner = viewLifecycleOwner 만으로도 해결된다.

class GameFragment: Fragment() {
    private var _binding: FragmentGameBinding? = null
    private val binding get() = _binding!!
    private lateinit var viewModel: GameViewModel

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        _binding = FragmentGameBinding.inflate(inflater, container, false)
        val view = binding.root
        viewModel = ViewModelProvider(this).get(GameViewModel::class.java)
        binding.gameViewModel = viewModel

        binding.lifecycleOwner = viewLifecycleOwner

        viewModel.gameOver.observe(viewLifecycleOwner, {newValue ->
            if (newValue) {
                val action = GameFragmentDirections
                    .toResultFragment(viewModel.wonListMessage())
                view.findNavController().navigate(action)
            }
        })

        binding.guessButton.setOnClickListener {
            viewModel.makeGuess(binding.guess.text.toString().uppercase())
            binding.guess.text = null
        }
        return view
    }

    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
    }
}
profile
개발자

0개의 댓글