코틀린 프래그먼트 뷰바인딩 - koteullin peulaegeumeonteu byubainding

안녕하세요 골드입니다. 

 이번 글은 안드로이드에서 사용하도록 권장하는 방법 중 하나인 뷰 결합에 대한 글입니다. 뷰 결합을 사용함으로써 생산성이 다소 높아질 것이라고 생각됩니다. 뷰 결합은 기존의 개발 방식을 더 편리하게 만들어주는 하나의 도구입니다. 특히 Java로 안드로이드 개발을 하던 시절에 View에 대한 참조를 위해 findViewById를 상당히 많이 사용했었습니다. findViewById로 참조하고 형변환을 해서 사용하던 기존 방식에 대한 불편함을 View Binding이 상당 부분 해결해줍니다. 그렇기 때문에 구글에서 사용을 권장하는 방법입니다.

요약 : 

 구글 : 너네 findViewById 쓰니까 에러도 많고 귀찮았지? 그래서 우리가 준비했다. View Binding이라는 걸 한 번 사용해봐!


1. 사용하기

코틀린 프래그먼트 뷰바인딩 - koteullin peulaegeumeonteu byubainding

뷰 결합을 사용하기 위해 앱 모듈 build.gradle에 ViewBinding을 사용한다고 알려줍니다. View Binding은 모듈 별로 사용 설정됩니다. 사용 설정된 View Binding은 모듈에 있는 각 xml 파일에 대한 Binding 클래스를 생성합니다. 우리는 이 Binding 클래스를 사용하여 레이아웃 ID가 있는 뷰에 직접 참조 할 수 있게 됩니다.


  • 만약 Binding을 사용하지 않을 xml 파일이 있다면 루트 뷰에 viewBindingIgnore 속성을 추가합니다.
코틀린 프래그먼트 뷰바인딩 - koteullin peulaegeumeonteu byubainding

예제를 위해 HomeFragment.kt와 fragment_home.xml 파일을 생성하도록 하겠습니다.

코틀린 프래그먼트 뷰바인딩 - koteullin peulaegeumeonteu byubainding

 fragment_home.xml 파일입니다. ConstraintLayout을 루트로 감싸고 FragmentContainerView와 BottomNavigationView를 하나씩 선언하였습니다. 참조를 위해서는 반드시 id 속성을 선언해야 합니다. 만일 참조가 필요하지 않다면 id속성을 선언하지 않아도 됩니다.

 HomeFragment.kt로 이동해서 생성된 Binding 클래스를 참조하는 변수를 선언합니다.

코틀린 프래그먼트 뷰바인딩 - koteullin peulaegeumeonteu byubainding

 Binding 클래스는 카멜 표기법으로 작성한 xml 파일 이름 뒤에 Binding이 붙은 이름으로 생성됩니다. 접근할 때 사용하는 getter 변수도 하나 선언합니다(Optional). Fragment에서 사용하기 위해 onCreateView 안에서 Binding클래스 참조 변수에 inflate()를 하고 getter 변수로 접근하여 루트 뷰의 참조를 return 합니다. 가상 머신을 실행시켜보면 반환된 루트 뷰가 화면의 활성 뷰로 나타나고 있음을 확인할 수 있습니다. 

코틀린 프래그먼트 뷰바인딩 - koteullin peulaegeumeonteu byubainding

 이제 getter 변수로 xml에서 ID 속성을 선언한 View들을 참조할 수 있음을 확인할 수 있습니다. 참조 뷰들은 카멜 표기법으로 선언되었음을 알 수 있습니다. xml에 선언되어 있는 어떠한 View도 ID속성만 갖고 있다면 Binding 클래스를 활용하여 참조할 수 있습니다.

2. findViewById와의 차이점

 앞서 View Binding은 findViewById를 대체할 수 있다고 말씀드렸습니다. 대체가 가능하다는건 위의 예제를 보면 알 수 있습니다. 그럼 구체적으로 어떤 차이점이 있는지 보겠습니다.

  • Null 안전 : ID속성값에 따른 뷰의 직접 참조를 생성하기 때문에 유효하지 않은 ID접근으로 인한 예외의 위험이 없습니다.

  • 유형 안전 : Binding 클래스에 정의된 필드의 유형과 XML 파일에서 참조하는 View의 유형이 같습니다. 그렇기 때문에 불필요한 형변환을 하지 않아도 됩니다.


결과적으로 코드의 생산성을 높일 수 있다는 점이 View Binding에 가장 큰 매력으로 생각됩니다.

여기까지 골드였습니다.

감사합니다.

참고자료 : developer.android.com/topic/libraries/view-binding?hl=ko

뷰 결합  |  Android 개발자  |  Android Developers

뷰 결합 기능을 사용하면 뷰와 상호작용하는 코드를 쉽게 작성할 수 있습니다. 모듈에서 사용 설정된 뷰 결합은 모듈에 있는 각 XML 레이아웃 파일의 결합 클래스를 생성합니다. 바인딩 클래스의

developer.android.com

코틀린 프래그먼트 뷰바인딩 - koteullin peulaegeumeonteu byubainding

ViewBinding 이란?

뷰바인딩은 안드로이드의 뷰를 xml 파일이 아닌 액티비니나 프래그먼트 상에서 바로 작업하기 위해 사용하는 기능이다.

기존에는 kotlin-android-extensions을 사용하거나 findViewById를 사용했지만
kotlin-android-extensions는 deprecated 되었고, findViewById는 뷰의 갯수가 많아지면 코드가 복잡해진다.

이러한 상황 속에서 뷰바인딩이 등장하게 되었고 간단한 설정만 하면 뷰에 쉽게 접근하여 작업이 가능해진다.

build.gradle 설정 (Module 수준)

뷰바인딩을 사용하려면 모듈 수준build.gradle에서 android 블럭 안에 아래 내용을 추가한다.

android {
	... (생략) ...
    
    // 아래 부분 추가
    buildFeatures {
        viewBinding true
    }
}

추가하면 우측 상단의 Sync Now를 클릭한다.

액티비티(Activty)에서 뷰바인딩 사용하기

뷰바인딩은 액티비티와 프래그먼트에서 초기화 방법이 다르다.

이유는 액티비티와 프래그먼트의 생명주기(Lifecycle)가 다르기 때문이다.

우선 액티비티에서는 아래와 같이 사용한다.

class MainActivity : AppCompatActivity() {

	// binding 변수를 lateinit var로 생성한다
    lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        // inflate(layoutInflater)로 초기화 한다
        binding = ActivityMainBinding.inflate(layoutInflater)
        
        // binding.root 뷰를 화면에 표시하도록 설정
        setContentView(binding.root)
}

여기서 binding 변수의 자료형은 아래의 규칙을 가지고 만들어진다.

각각의 액티비티나 프래그먼트에 연결된 xml 파일을 이름을 카멜 표기법으로 변경하고 이름 끝에 Binding을 추가하여 만들어진다.

예를 들어, 메인 액티비티의 경우 activity_main.xml와 연결되는데 이를 카멜 표기법으로 바꾸면 ActivityMain이 되고 끝에 Binding이 추가되어 ActivityMainBinding이 되는 것이다.

같은 방법으로 my_custom_activity.xml와 연결된 액티비티의 경우 MyCustomActivityBinding을 inflate한 값으로 binding 변수를 초기화해주면 된다.

프래그먼트(Fragment)에서 뷰바인딩 사용하기

일반적으로 프래그먼트는 뷰보다 생명주기가 더 길다. 때문에 뷰가 Destroy 되었을 때 binding 값을 초기화하는 방법으로 메모리 누수를 방지해야 한다.

때문에 프래그먼트에서는 뷰바인딩을 아래와 같이 사용한다.

class MainFragment : Fragment() {

	// 메모리 관리를 위해 _binding을 nullable 타입으로 선언하고 null로 초기화한다
    private var _binding: FragmentMainBinding? = null
    // binding은 _binding을 not null 처리하고 getter를 통해 값을 받아온다
    private val binding get() = _binding!!
    
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?,
    ): View {
    
    	// binding이 아닌 _binding을 inflate를 통해 초기화해준다
    	_binding = FragmentMainBinding.inflate(inflater, container, false)
        
        ... (생략) ...
        
        // binding.root 뷰를 return 한다
        return binding.root
     }
   
	override fun onDestroyView() {
        super.onDestroyView()
        // 뷰가 Destroy될 때 _binding을 null로 초기화한다
        _binding = null
    }
}

추가로, bindingbinding = _binding!!으로 바로 값을 가져오지 않고 binding get() = _binding!!으로 가져오는지 궁금하신분은 이 블로그 글에 정리가 매우 잘되어 있으니 읽어보시면 좋을거 같습니다.

뷰바인딩을 통해 뷰 작업하기

뷰바인딩 설정이 끝나면 뷰를 뷰바인딩을 통해 작업할 수 있게된다.

이때 액티비티나 프래그먼트와 결합하는 뷰(xml파일)의 이름이 자동 생성되었던 것처럼 결합된 뷰 안에 있는 TextView나 ImageView 같은 뷰의 참조값도 자동 생성된다.

예를 들어 @+id/text_view라는 아이디를 가진 TextView는 binding.textView를 통해 사용할 수 있다.

마찬가지로 @+id/add_btn 아이디의 Button은 binding.addBtn을 통해 사용할 수 있다.

// 아래와 같이 버튼을 누르면 토스트 메세지가 나타나도록 하는 등으로 사용할 수 있다
binding.addBtn.setOnClickListener {
	Toast.makeText(this, "추가버튼 눌림!", Toast.LENGTH_SHORT).show()
}