본문 바로가기
오늘은 뭘 배울까?/Android

안드로이드 뷰 바인딩(view binding)

by Kim Juhwan 2021. 3. 5.

1. 뷰 바인딩
   1-1. 라떼는 말이야...
   1-2. 변천사
   1-3. findViewById와의 차이점
2. 사용법
   2-1. gradle 추가
   2-2. 액티비티
   2-3. 프래그먼트
   2-4. viewBindingIgnore

 

 


 

1. 뷰 바인딩

1-1. 라떼는 말이야...

예를 들어, xml에서 이렇게 텍스트뷰를 3개 쓰고 있다고 치자

 

public class MainActivity extends AppCompatActivity {

    private TextView nameView, phoneView, addressView; // 변수 선언하고
	
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        nameView = findViewById(R.id.name); // findViewById로 id를 찾아서 넣어준다.
        phoneView = findViewById(R.id.phone);
        addressView = findViewById(R.id.address);
    }
}

예전에는 액티비티에서 텍스트뷰의 값을 변경하거나 뭔가 작업을 하려면

이렇게 변수를 선언하고, findViewById를 이용해서 xml의 뷰와 변수를 연결시켜주는 그런 작업을 해야 했다.

이게 한 두 개 정도는 상관없다. 근데 한 페이지에 들어가는 뷰가 좀 많아야지...

덕분에 코드가 항상 길어지고 더러워졌다.

(빙고 게임 만들기 위해서 버튼 25개 추가하는 상상을 해보자)

 

view binding은 이 findViewById를 대체할 수 있는 기능이다.

 

1-2. 변천사

안드로이드 스튜디오 버전에 따른 변천사

3.5 버전까지는 개발자들이 findViewById를 사용했었다.

Butter knife라는 라이브러리나 extension을 이용해서 불편함을 해결했다고 한다.

 

그러다가 3.6 버전에서 이를 대체할 수 있는 view binding이 나오게 된다.

코틀린에서는 이 뷰 바인딩 작업조차 안 하고

id를 바로 변수처럼 사용할 수 있는 Kotlin Synthetic이 생겼다.

 

하지만 다음과 같은 문제가 있어 4.1 버전에서 Kotlin Synthetic은 deprecated 되었다.

  • 전역 네임 스페이스가 오염된다.
  • 개발자가 실수로 다른 레이아웃의 동일한 id를 가진 뷰를 가져오면서 NullPointException이 발생할 가능성이 있다.
  • Kotlin만 지원이 가능하다.

현재는 자바와 코틀린 둘 다 view binding을 사용한다.

findViewById도 사용할 수 있지만, 속도도 느리고 안전하지 못하다.

 

Kotlin Synthetic이 deprecated 된 이유는 이 해외 블로그를 참고했다.

 

1-3. findViewById와의 차이점

앱을 디버깅하면 이런 파일이 만들어진다.

뷰 바인딩을 사용하면 직접 id를 적고 타입을 정하고 이런 작업을 하지 않아도 된다.

자동으로 클래스 파일을 생성해주기 때문이다.

그에 비해 findViewById의 문제점은 다음과 같다.

 

  • Null 안정성: 개발자가 실수로 유효하지 않은 id를 사용하면 null 오류가 발생할 수 있다.
  • Type 안정성: textView의 타입을 imageView라고 잘못 적어서 캐스팅하면 cast exception이 발생할 수 있다.
  • 속도가 상대적으로 느리다.

 

 

2. 사용법

아래 내용들은 Android Developer 공식문서를 참고하였다.

2-1. gradle 추가

// 안드로이드 스튜디오 4.0 이상
android {
    ...
    buildFeatures {
        viewBinding = true
    }
}

사용하고 있는 안드로이드 스튜디오 버전이 4.0 이상이라면 이렇게

 

// 안드로이드 스튜디오 3.6 ~ 4.0
android {
    ...
    viewBinding {
        enabled true
    }
}

이하라면 이렇게 적어주면 된다.

(3.6 보다 낮으면 안 됨)

 

2-2. 액티비티

public class MainActivity extends AppCompatActivity {

    private ActivityMainBinding binding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding = ActivityMainBinding.inflate(getLayoutInflater()); // 1
        setContentView(binding.getRoot()); // 2
    }

    private void updateUI(UserProfile userProfile){
        binding.name.setText("아이유"); // 3
        binding.phone.setText("010-1111-2222");
        binding.address.setText("사랑시 고백구 행복동");
    }
}

1. [목차 1-2]에서 보여주었던 클래스를 만들기 위하여 위와 같이 적어준다. inflate는 xml에 있는 뷰를 객체화해준다고 생각하면 된다.

2. 원래는 R.layout.activity_main을 넘겨주지만 이번에는 우리가 생성한 루트 뷰를 넘겨준다.

3. 바인딩된 객체 안에 있는 name, phone, address에 접근하여 사용하면 끝!

사용하기도 간단하고 코드도 깔끔해진 것을 볼 수 있다.

 

Activity 이름 Binding Class 이름
MainActivity ActivityMainBinding
HelloActivity ActivityHelloBinding
XXXActivity ActivityXXXBinding

바인딩 클래스 이름은 규칙이 정해져 있다.

마치 자바 파일 이름에 맞는 xml 파일이 생성되듯이..

규칙에 맞게 사용해주자.

 

private lateinit var binding: ActivityMainBinding

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

        binding.textView.text = "안녕"
    }
}

코틀린은 이런 식으로 작성해주면 된다.

 

2-3. 프래그먼트

class BlankFragment : Fragment() {
    private FragmentBlankBinding binding;

    @Override
    public View onCreateView (LayoutInflater inflater,
                              ViewGroup container,
                              Bundle savedInstanceState) {
        binding = FragmentBlankBinding.inflate(inflater, container, false);
        View view = binding.getRoot();
        return view;
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        binding = null;
    }
}

프래그먼트는 위와 같이 하면 된다.

다른 건 다 똑같은데 다른 점이 onDestroyView에서 binding에 null을 집어넣어 준다는 것이다.

액티비티에서는 안 하던 짓을 왜 하지? 싶었는데 공식문서에 답이 적혀있었다.

 

Fragments outlive their views. Make sure you clean up any references to the binding class instance in the fragment's onDestroyView() method.

프래그먼트는 뷰보다 더 오래 살아남는다고 한다. (오..? 처음 알았다. 프래그먼트 생명 주기도 다시 공부해야지)

바인딩 클래스는 뷰에 대한 참조를 가지고 있는데

뷰가 제거될 때(=onDestroyView) 이 바인딩 클래스의 인스턴스도 같이 정리해주는 것이다.

 

class BlankFragment : Fragment() {

    private var _binding: FragmentBlankBinding? = 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 = FragmentBlankBinding.inflate(inflater, container, false)
        val view = binding.root
        return view
    }
    
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        binding.textView.text = "안녕"
    }

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

코틀린의 경우 이렇게 작성해주면 된다.

 

2-4. viewBindingIgnore

<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:viewBindingIgnore="true" // 뷰 바인딩 클래스 생성을 안하고 싶을 때
    tools:context=".HelloActivity">

</androidx.constraintlayout.widget.ConstraintLayout>

공식문서에서 viewBindingIgnore 속성을 사용하면 바인딩 클래스를 생성 안 한다길래

액티비티에서 바인딩 클래스를 사용해야 생성되는 거 아닌가?

사용 안 하면 되는 거 아녀? 싶었는데

바인딩 클래스는 레이아웃 별로 무조건 생성되는 거고

만약 이 레이아웃은 바인딩 클래스가 필요 없다! 싶으면 저 속성을 루트 뷰에 추가해주면 된다.

 

 


그렇다면 데이터 바인딩은 무엇일까? 공부한 내용을 이곳에 적어두었다.
그 외 참고한 김초희님 블로그, 알면 쓸모있는 개발지식님 블로그. 감사합니다 :D
반응형

댓글6

  • ㅇㅇ 2022.03.24 17:53

    좋은 글 너무너무 감사합니다.
    한 가지 궁금한게 있는데, Fragment Binding 클래스의 이름은
    프래그먼트가 BlankFragment일 때 FragmentBlankBinding이 아니라 다른 이름으로 사용하는 건가요?
    프래그먼트의 전역에 ResultProfileBinding이라고 되어있어서 여쭤봅니다.
    답글

  • ㅇㅇㅇ 2022.05.10 15:51

    안녕하세요 글 보고 열심히 배우고 있는 코딩초보입니다
    질문 좀 받아주실 수 있나요?
    두 가지인데 oncreate 밖에서 binding을 쓰려고 하니까 오류가 생겨요 제가 잘못 쓴 건가요?
    다른 하나는 본문이랑 좀 상관없는데 코드 그대로 따라해보고 있는데 updateUI가 회색으로 표시되는데 쓰려면 따로 포함해야 되는게 있나요?
    답글

    • Kim Juhwan 2022.05.10 16:26 신고

      넵 물론이죠! 혹시 코드나 에러를 보여주실 수 있나요? 말씀해주신 것 만으로는 어떤 문제인지 알수가없네요 ㅠㅠ
      예상을 해보자면 혹시 변수 선언을 onCreate안에서 하신게 아니신지요?? onCreate 밖에다가 선언을 하고 onCreate안에서 값을 넣어줘야 onCreate 밖에서도 binding을 사용하실 수 있습니다.

      updateUI가 회색으로 표시된다는 건 메소드가 사용되는 곳이 없다는 뜻입니다. 만들어 놓았지만 사용을 안하고 있다고 개발자에게 알려주고 있는거죠!
      버튼을 클릭하면 updateUI를 호출한다든지 식으로 구현을 하시면 회색으로 표시되던게 검은색으로 바뀔겁니다.

    • ㅇㅇㅇ 2022.05.10 19:19

      함수를 oncreate 안에서 선언했더니 됐어요! updateUI는 안드로이드 스튜디오에서 정해 놓은 함수인가요? 아니면 직접 작성하신건가요?

    • Kim Juhwan 2022.06.13 00:59 신고

      죄송합니다 이제서야 답변을 드리네요 ㅠㅠ
      우선 함수는 onCreate 밖에 선언을 하시는 게 맞습니다.
      updateUI는 제가 작성한 함수입니다.