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
'오늘은 뭘 배울까? > Android' 카테고리의 다른 글
안드로이드 Clean Architecture (2) | 2021.03.07 |
---|---|
코틀린 데이터 바인딩(Data binding) (0) | 2021.03.06 |
[Kotlin] 뷰페이저2 활용 예제 : tabLayout, indicator, fragment, 자동 스크롤, 무한스크롤, 배너 등 (13) | 2021.02.27 |
코틀린 viewPager2 : 사용법, 애니메이션 등 (50) | 2021.02.24 |
안드로이드 액티비티 생명주기 (Activity Life cycle) (10) | 2021.02.23 |
댓글