본문 바로가기
공부/Android

Android - findViewById vs kotlinx-synthetic vs viewBinding

by hhhello 2024. 6. 19.

findViewById와 kotlin-synthetic의 문제점은 오래전부터 알려져 왔고 요새는 Compose를 쓰는 게 아니라면 거의 모든 프로젝트에서 viewBinding아니면 dataBinding을 쓴다.

오늘은 findViewById와 kotlin-synthetic의 장단점과 왜 이걸 안 쓰고 viewBinding을 사용하는지 알아보자

findViewById

textView = findViewById(R.id.textview)

일단 코드가 길다. ‘R.id’라는 부분이 중복된다.

하지만 더 치명적인 단점이 있는데 바로 null과 type 안정성이 없다는 것이다.

null-safety 하지 않다

R.id.OOO으로 view의 id에 접근을 하는 경우 해당 activity혹은 fragment에 대응되는 view 말고 다른 view의 id에도 접근이 가능하다. 이렇게 잘못된 접근을 하면 당연히 원래 view에는 다른 view의 것이 없기 때문에 null이 들어온다. kotlin은 분명 null-safety한 언어인데 android가 이걸 null-safety하지 않게 만들어주니 참 아이러니하다.

type-safety 하지 않다

또, id는 똑같은데 type이 다른 경우 컴파일에 시점에 에러가 나야하는데 이걸 못 잡아서 런타임에서 에러가 난다.

findViewById는 느리다

findViewById의 동작 원리는 쉽게 말해 해당 view를 DFS하는 것이다. 그래서 findViewById를 호출할때마다 view안에 있는 모든 view에서 해당 id가 나올때 까지 순회한다.

아래 블로그에서 findViewById의 속도를 측정한 부분을 봐보자. 느리다..

https://holika.tistory.com/entry/내-맘대로-정리한-안드로이드-findViewById의-사용을-최소화해야-하는-이유와-대체-방법ViewBinding

그래서 findViewById는 중복 id를 최대한 피해야 하고 type을 개발자가 신경써줘야 해서 매우 매우 불편하다. 심지어 코드도 안 이쁘고 느리다.

kotlin-synthetic

textview.text = "이야"

Android 4.1버전에서부터 새 프로젝트를 생성하면 ‘kotlin-android-extensions’를 더 이상 찾아볼 수 없다.

사실 ‘kotlin-android-extensions’은 좋은 플러그인은 아니다. findViewById의 반복적인 작업을 제거하려고 만들어졌고, 내부적인 캐시를 통해 재사용성을 높였을 뿐이다.

하지만 모든 곳에서 재사용성을 지켜주지 않는다. RecyclerView의 ViewHolder에서는 지켜주지 않는다.

참고: https://www.androidhuman.com/2017-11-26-kotlin_android_extensions_on_viewholder

import 실수

같은 이름의 id가 두 view에 있다면 import할 때 매우 주의해야 한다.

import실수를 한 경우 컴파일 시점에는 에러가 나지 않지만 런타임에서 에러가 난다.

ViewHolder에서 캐싱되지 않는다

분명 ViewHolder의 목적은 재사용성을 높이는 것인데 kotlin-android-extensions를 사용하면 캐싱을 안 해준다…

ViewHolder를 캐싱하기 위해서는 각 view의 인스턴스를 담는 프로퍼티를 수동으로 추가해야하고 그것 마저 귀찮다면 LayoutContainer와 같은 것을 사용할 수 있다.

viewBinding

viewBinding을 사용하면 null-safety하고 type-safety하며 속도도 빠르고 코드도 이쁘다!

와우!

kotlin-android-extensions를 사용하고 있다면 관련된 거 다 지우고 아래 buildFeatures어쩌구 한 줄만 추가해주면 된다.

android {
		// ...
    buildFeatures {
        viewBinding = true
    }
}

MainActivity의 경우 ActivityMainBinding이라는 클래스가 자동으로 추가되고 아래 코드처럼 바인딩 설정만 해주면 view를 id로 참조할 수 있다.

import com.bestswlkh0310.viewbinding.databinding.ActivityMainBinding

class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // ...
        
        binding = ActivityMainBinding.inflate(layoutInflater)
        
        setContentView(binding.root)

        binding.textview.text = "이야"
    }
}

viewBinding의 경우 id를 global하게 참조하는 게 아니라 해당 activity 혹은 fragment에 대응되는 view만 사용할 수 있다.

binding클래스를 만들기 싫다면 XML의 루트 태그에 tools:viewBindingIgnore=”true”라는 속성을 추가하면 된다.

viewBinding은 빠르기도 하다.

id를 참조하는 위의 두 방식과 비교하여 viewBinding의 단점이라고는 딱히 없다. Layout과 양방향으로 데이터 흐름을 만들고 싶다면 dataBinding을 채택하면 된다.