본문 바로가기
오늘은 뭘 배울까?/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
반응형

댓글