본문 바로가기
개발 이야기/안드로이드 개발

ViewBinding으로 Binding 처리하기. (feat. DataBinding)

by 정선한 2024. 3. 23.
728x90
반응형

이번 포스팅에서는 ViewBinding을 좀 더 뜯어보고 여러 뷰에 적용도 시켜보는 내용을 다루어 보려고 합니다.

  • 사실 현업에서나 학생때나 ViewBinding은 익히 써왔고 익숙하고 당연히 View의 내용을 가져오기 위해서 사용하는 방법이기 때문에 기술적으로 탐구를 해볼 생각은 없었는데요.
  • 이번에 여러 이슈들을 겪으면서 경험에 의존하는 코딩이 굉장히 위험하고 실제적인 업무 처리 능력을 저하시키는 것을 경험하면서 다시 원론적인 문제에 집중하는 시간을 가져보게 되었습니다.
    그래서 여러 문서들을 보고 코드에도 녹여보면서 이 글을 작성하고 있네요. 역시 공부는 미리미리 꼼꼼하게,,,

gradle(:app) 파일에서 viewBinding 활성화

android {
    buildFeatures {
        viewBinding = true
    }
}

xml : result_profile.xml
xml 파일 명에서 생성된 binding 클래스의 이름 : ResultProfileBinding
또한 모든 Binding 클래스에는 상응하는 레이아웃 파일의 루트 뷰의 직접 참조를 제공하는 getRoot() (kotlin : root) 메서드도 포함입니다.

<LinearLayout ... >
    <TextView android:id="@+id/name" />
    <ImageView android:cropToPadding="true" />
    <Button android:id="@+id/button"
        android:background="@drawable/rounded_button" />
</LinearLayout>

Activity에서 View의 결합을 사용하는 방법
onCreate() 메서드에 ViewBinding의 인스턴스를 설정합니다.

  1. 생성된 binding 클래스에 포함된 정적 inflate() 메서드를 호출 -> Activity에서 사용할 클래스 인스턴스 생성
  2. getRoot() or Kotlin 속성 문법 .root 를 통해 RootView를 참조한다.
  3. RootView를 setContentView()에 전달하여 화면의 활성 뷰로 선언한다.
private lateinit var binding: ResultProfileBinding

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    binding = ResultProfileBinding.inflate(layoutInflater)
    val view = binding.root

    setContentView(view)
}

AppCompatActivity.java 내부의 setContentView() 메서드

@Override
public void setContentView(View view) {
    initViewTreeOwners();
    getDelegate().setContentView(view);
}

Fragment 에서 View 결합을 사용하는 방법
onCreateView() 메서드에 ViewBinding의 인스턴스를 설정합니다.

  1. 생성된 binding 클래스에 포함된 정적 inflate() 메서드를 호출 -> Fragment에서 사용할 클래스 인스턴스 생성
  2. getRoot() or Kotlin 속성 문법 .root를 통해 RootView를 참조한다.
  3. RootView를 onCreateView()에 전달하여 화면의 활성 뷰로 선언한다.
private var _binding: ResultProfileBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!

override fun onCreateView(
    inflater: LayoutInflater,
    container: ViewGroup?,
    savedInstanceState: Bundle?
): View? {
    _binding = ResultProfileBinding.inflate(inflater, container, false)
    val view = binding.root
    return view
}

override fun onDestroyView() {
    super.onDestroyView()
    _binding = null
}

여기까지가 기본적으로 Android Developer에서 제공하는 Guide의 코드들입니다. 이런 것처럼 ViewBinding을 구성할 수 있는데, 그러면 Activity, Fragment 마다마다 각각의 ViewBinding 코드가 생겨나는 상황을 마주하게 됩니다.
그래서 보통은 이를 code base 에서 다룰 수 있도록 부모 클래스를 하나 만들어서 사용하곤 합니다. BaseActivity.kt , BaseFragment.kt 이런 식으로 베이스 클래스를 만들고 이 안에서 binding 및 기타 부모 클래스의 역할로 가져갈 수 있는 것들을 분리합니다.
이렇게 아래의 코드처럼 BaseActivity를 구현하고 이를 상속받는 특정 Activity 코드에서 Binding 처리를 해주고 있습니다.
이때, setContentView()DataBindingUtil클래스의 메서드를 사용하게 됩니다.
DataBindingUtil 클래스는 Binding 타입을 사전에 알 수 없을 때, DataBindingUtil을 사용해서 Binding 클래스를 생성할 수 있는 클래스 입니다.
BaseActivity

abstract class BaseActivity<Binding : ViewDataBinding>(private val layout: Int) : AppCompatActivity() {
    protected lateinit var binding: Binding
    protected lateinit var callback: OnBackPressedCallback

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        binding = DataBindingUtil.setContentView(this, layout)
        binding.lifecycleOwner = this
    }
}
@SuppressLint("CustomSplashScreen")
@AndroidEntryPoint
class SplashActivity : BaseActivity<ActivitySplashBinding>(R.layout.activity_splash) {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
    }

    ...
}

BaseFragment

abstract class BaseFragment<Binding : ViewDataBinding>(private val layout: Int) : Fragment() {
    protected lateinit var binding: Binding

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        binding = DataBindingUtil.inflate(inflater, layout, container, false)
        binding.lifecycleOwner = viewLifecycleOwner
        return binding.root
    }
    ....
}
@AndroidEntryPoint
class MainFragment : BaseFragment<FragmentMainBinding>(R.layout.fragment_main) {

    ....

}

근데 사실 이렇게 되면 ViewBinding 보다는 DataBinding으로 구현하는 방법이 되겠네요. 이렇게 DataBinding을 사용할 때에는 xml 코드 내 최상위 태그를 <layout> </layout>으로 지정해 주어야 합니다. 그러면 ViewBinding과 DataBinding이 다른 거냐,,라고 하기에는 viewBinding이 DataBinding의 부분집합이라 이걸 따지는 의미가 있을까 싶기도 합니다.
약간 View 객체화 방법 중 findViewById()를 쓰지 않기 위해서는 ViewBinding을 권장하는 듯하고 좀 더 동적으로 사용하고 단순히 View 뿐만이 아닌 기능적 사용을 위해서는 DataBinding을 이런 느낌으로 권장하는 것 같습니다.
저는 실무 프로젝트에서 DataBinding, ViewBinding을 혼용해서 사용하고 있는데요. 혼용 자체에는 문제가 없지만 좀 더 나은 방법이 있다면 개선을 해봄직하기도 한 것 같네요. 흠😕

728x90
반응형