ViewBinding에 대해 알아보자 | Android Study

hoya·2021년 10월 31일
0

Android Study

목록 보기
8/19
post-thumbnail

🤔 ViewBinding?

View와 상호작용하는 코드를 쉽게 작성할 수 있게 도와주는 기능

기존에는 XML 파일의 View에 접근할 때 findViewById() 메소드를 사용했다. 그러나 이는 개발자들에게 상당한 불편함을 주었다.

  1. View에 접근할 때 하나하나 모두 일일이 메소드를 적용시켜야 하므로 코드가 길어지고 난잡해진다.
  2. 존재하지 않는 View id를 인자로 전달했을 때, Null Pointer Exception이 발생한다.
  3. 레이아웃을 일일이 순회하며 일치하는 View를 찾아가기 때문에 속도에 영향을 미친다.
  4. View의 타입을 잘못 캐스팅할시 Class Cast Exception 이 발생한다.

그래서 첫번째 대안으로 나왔던 것이 코틀린 익스텐션(Kotlin Extension)이었는데, View의 id로 직접 접근하는 방법이었다. 기존 findViewById 에 비해서는 상당히 편리했으나, 시간이 지나며 여러 문제점이 발견되기 시작했다.

  1. 코드에서 사용되는 모든 View 가 고유한 id 값을 가져야 한다.
  2. 서로 다른 xml 파일을 만들며 2개 이상의 View에 동일한 id값을 주었을 때 개발자 입장에서 혼란을 겪을 수 있다.
  3. Kotlin만 지원하며, Java에서 Kotlin으로 migration 되지 않았을 경우 불편함을 초래할 수 있다.

이런 과정 속에서 코틀린 익스텐션 역시 deprecated 되며, ViewBinding 과 DataBinding이 탄생하게 되었다. DataBinding에 대해서는 다음 포스팅에서 알아보기로 하고, 오늘은 ViewBinding에 대해 자세히 알아보도록 하자.

🤗 장점

  1. View를 직접 참조하므로 존재하지 않는 View id를 참조할 일이 없고, 그 과정에서 Null Pointer Exception 이 발생할 일이 없다.
  2. 마찬가지로 View에 잘못된 타입을 지정할 때 생기는 Class Cast Exception 역시 발생할 일이 없다.
  3. 코드의 가독성 역시 상당히 개선된다.
  4. 자바, 코틀린 모두를 지원한다.

자, 그러면 ViewBinding 을 직접 사용해보도록 하자.


🙃 실습

📌 기본 설정

    android {
        buildFeatures {
                 viewBinding = true
        }
    }

앱 수준의 그래들에서 ViewBinding을 활성화하면 모든 레이아웃 XML 파일의 Binding 클래스가 생성된다. 클래스의 이름은 해당 XML을 카멜 표기법으로 변환 후, 뒤에 Binding을 붙여 완성한다.

list_fragment.xml -> ListFragmentBinding
hoya_activity.xml -> HoyaActivityBinding

<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".ListFragment">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="50dp"
        android:id="@+id/title"
        android:text="Pcheduler"
        android:fontFamily="@font/gowundodum"
        android:textColor="@color/ft_white"
        android:textSize="40sp"
        android:layout_marginTop="15dp"
        android:layout_marginLeft="20dp"
        android:gravity="center"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
    
</androidx.constraintlayout.widget.ConstraintLayout>

XML 구성은 다음과 같다. TextView가 있는 매우 심플한 코드 구성이다.


📌 Activity 에서 사용하기

class MainActivity : AppCompatActivity() {
	private lateinit var binding : ActivityMainBinding // 뷰 바인딩
    
	override fun onCreate(savedInstanceState: Bundle?) {
           binding = ActivityMainBinding.inflate(layoutInflater)
           // inflate() 메소드 호출
           val view = binding.root
           // getRoot() 메소드 호출
           setContentView(view)
           // Root View 를 setContentView() 에 전달하여 화면상의 View로 만든다.
           binding.title.setText("velog.io/@hoyaho/")
    }
}

🤔 binding.root?

이는 Root View 를 참조한다는 의미인데, Root View는 레이아웃에서 가장 바깥쪽의 View Container 이다. 즉, 호출 시 XML에 있는 ConstraintLayout의 Root View를 반환한다는 의미이다.

이 과정을 거침으로서, 해당 레이아웃에 있는 id만 사용할 수 있게 되어 다른 레이아웃에 있는 동일한 이름의 id와 헷갈릴 일이 없어지는 것이다.


📌 Fragment 에서 사용하기

class ListFragment : Fragment() {
    private var _binding : FragmentListBinding? = null // 뷰 바인딩
    private val binding get() = _binding!!
    
    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {

        _binding = FragmentListBinding.inflate(inflater, container, false)
        val view = binding.root

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

        binding.title.text = "https://velog.io/@hoyaho"
    }

    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null // 메모리 누수 방지를 위해 null 처리
    }
}

코드를 보면 알겠지만, 프래그먼트에서 View Binding 사용이 액티비티와 다른 점을 확인할 수 있다. 이유는, 프래그먼트와 액티비티의 생명주기가 다르기 때문이다.

기본적으로 프래그먼트에서는 onDestroyView() 메소드가 호출될 때 프래그먼트 객체 자체가 사라지지 않고 메모리에 남아있는데, 이로 인해 메모리 누수가 발생할 수 있다.

고로, onDestroyView() 메소드에서 직접 binding 객체를 null 로 만들어 가비지 컬렉터에서 수집해가도록, 즉 사라지게 해야 메모리 누수를 방지할 수 있는 것이다.

또한, binding 변수를 NULL able로 선언 후 다시 한번 NON-NULL 타입으로 포장하는 것을 확인할 수 있는데, NULL able로 사용하면 코드를 작성할 때마다 ?. 를 붙여야하는 불편함이 있어 편의를 위해 포장하는 것이다. 다만, 이런 경우 NULL Safe 가 보장되지 않기 때문에 유의하며 코드를 작성해야 한다.


참고로, _binding 에서 _ 은 private 한 변수를 사용할 때 관례적으로 붙이는 특수기호이니 알아두도록 하자.


📌 RecyclerView 에서 사용하기

class ListAdapter(
    val context: Context,
    var list: List<TaskEntity>
) : RecyclerView.Adapter<ListAdapter.MainViewHolder>() {


    override fun getItemCount(): Int {
        return list.size
    }


    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MainViewHolder {
//     val itemView = LayoutInflater.from(context).inflate(R.layout.item_list, parent, false)
//     return MainViewHolder(itemView)

        val binding = ItemListBinding.inflate(LayoutInflater.from(parent.context), parent, false)
        return MainViewHolder(binding)
        // binding access
    }

    override fun onBindViewHolder(holder: MainViewHolder, position: Int) {
        val task = list[position]

        holder.taskText.text = task.content
    }


	// binding의 root view 형태로 지정한다.
    inner class MainViewHolder(private val binding : ItemListBinding) : RecyclerView.ViewHolder(binding.root) {
        var taskText = binding.listEdt
        var taskImage = binding.listImg
        // 생성자에서 val 로 binding 을 받았기 때문에 Holder 전역에서 사용이 가능하다.
    }
    
    // inner class MainViewHolder(val view : View) : RecyclerView.ViewHolder(view)

}

세부적인 사항은 주석을 확인하면 되고, 큰 틀로 보았을 때 View를 생성자로 사용하던 것을 Binding으로 교체된 것을 중점적으로 보면 이해하기 쉬울 것이다.


참고 및 출처

ViewBinding을 프레그먼트에서 사용할 때 Memory Leak 방지하기
안드로이드 공식 문서
뷰 바인딩(View Binding) in Activity, Fragment

profile
즐겁게 하자 🤭

0개의 댓글