View bindind이 Android studio 3.6부터 정식지원 됩니다.
물론 3.6 Canary 11 버전에서부터 지원했지만, 현재 정식 3.6 버전이 나왔기에 정식버전으로 사용하시면 됩니다.
이 글은 하기 링크를 참고, 번역, 의역하였습니다.
https://medium.com/androiddevelopers/use-view-binding-to-replace-findviewbyid-c83942471fc
https://developer.android.com/topic/libraries/view-binding?hl=ko
View binding 이란?
간략하게 정리하면
- build.gradle에서 enable 시킵니다. (추가 libarary가 필요하지 않습니다.)
- 모든 layout에 대해서 binding object가 생성됩니다.
- Binding object는 id를 갖는 모든 view들을 하나의 property로 가집니다.
- type도 알아서 맞춰서 생성해 주고, findViewById에서 발생할 수 있는 null도 발생하지 않습니다. (null-safety)
- Java, kotlin 모두를 지원합니다.
build.gradle 설정
// Available in Android Gradle Plugin 3.6.0 android { viewBinding { enabled = true } }
Android Studio 4.0 부터는 이 param의 위치가 buildFeatures로 이동되었기에 아래와 같이 사용합니다.
// Android Studio 4.0 android { buildFeatures { viewBinding = true } }
일단 project에서 이 값만 enable 시켜놓는다면 view binding은 모든 layout에 대한 binding class를 자동 생성합니다.
즉, 그외 XML에 뭘 추가해야 한다던가 하는 작업이 없습니다.
Fragment나 Activity, Recyclerview의 adapter(또는 ViewHolder)등의 layout을 inflate할때 언제든지 binding class를 사용할 수 있습니다.
Use view binding in an Activity
- activity_awesome.xml
- 두개의 TextView (id가 각각 title, subtitle)
- 하나의 Button (id = button
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val binding = ActivityAwesomeBinding.inflate(layoutInflater) binding.title.text = "Hello" binding.subtext.text = "Concise, safe code" binding.button.setOnClickListener { /* ... */ } setContentView(binding.root) }
예제에서보면 xml의 이름이 카멜 대소문자 형태로 변경되어 코드에 사용됩니다.
이렇게 얻은 객체에는 각 view의 id가 property로 제공됩니다.
따라서 더이상 findViewById를 사용할 필요가 없겠죠?
특히 xml 전체를 감싸는 최상단의 부모를 root 라는 property로 제공합니다.
따라서 이 속성은 setContentView의 인자로 넘겨주어 쉽게 사용할 수 있습니다.
Safe code using binding objects
- Type-safe: view binding은 layout 내부에 정확한 view 타입을 찾아 mapping하므로 해당 view의 속성으로 접근할때 해당 view에 맞는 속성값을 노출시킵니다. 즉 예를들어 TextView라면 TextView의 속성에 맞는 properties만 노출됩니다.
- Null-safe: 여러 설정이 존재하는 layout의 경우 해당 설정에 맞는 view만 찾아 냅니다. 그리고 그렇지 않은경우 @Nullable 속성으로 만듭니다.
What code does it generate?
public final class ActivityAwesomeBinding implements ViewBinding { @NonNull private final ConstraintLayout rootView; @NonNull public final Button button; @NonNull public final TextView subtext; @NonNull public final TextView title;
위에서 예제로 들었던 XML은 view bidning을 통해 위와 같은 코드로 생성됩니다.
view binding은 View의 정확한 타입으로 view의 연결점을 제공하며, view의 id명이 변수명이 됩니다.
또한 getRoot를 통해 (kotlin은 그냥 root 겠죠?) root view를 얻어올 수 있습니다.
private ActivityAwesomeBinding(@NonNull ConstraintLayout rootView, @NonNull Button button, @NonNull TextView subtext, @NonNull TextView title) { … } @NonNull public static ActivityAwesomeBinding inflate(@NonNull LayoutInflater inflater) { /* Edited: removed call to overload inflate(inflater, parent, attachToParent) */ View root = inflater.inflate(R.layout.activity_awesome, null, false); return bind(root); }
사실 내부적으로 보면 Binding class 내부에 존재하는 inflate method가 내부적으로 view를 infllate 시킵니다.
위 예제에서의 inflate method는 parent값은 null, attachToParent 값은 false로 고정되어 있지만 이 값들을 직접 넣어줄수있는 inflate method도 존재합니다.
그리고 마지막에 보면 bind 함수가 존재합니다.
이 bind 함수는 내부적으로 findViewById를 수행합니다.
@NonNull public static ActivityAwesomeBinding bind(@NonNull View rootView) { /* Edit: Simplified code – the real generated code is an optimized version */ Button button = rootView.findViewById(R.id.button); TextView subtext = rootView.findViewById(R.id.subtext); TextView title = rootView.findViewById(R.id.title); if (button != null && subtext != null && title != null) { return new ActivityAwesomeBinding((ConstraintLayout) rootView, button, subtext, title); } throw new NullPointerException("Missing required view […]"); }
compiler가 XML으로부터 직접적으로 타입을 check하고, 잠재적인 nullability를 확인 하기 때문에 안전하게 findViewById를 호출할 수 있습니다.
(실제로 bind의 코드는 훨씬 깁니다.)
정리하면 각 binding object에는 세개의 static function이 존재합니다.
- inflate(inflater) - Activity의 onCreate에서 사용하며, parent view로 넘길게 없을 경우 사용합니다.
- inflate(inflater, parent, attachToParent) - Fragment나 RecyclerView의 Adapter(또는 ViewHolder) 처럼 parent ViewGroup를 넘겨야 하는경우 사용합니다.
- bind(rootView) - 이미 view를 inflate 한상태에서 findViewById를 피하고 싶거나, 기존 코드를 refactoring할때 유용하게 사용할 수 있습니다.
What about included layouts
<!-- activity_awesome.xml --> <androidx.constraintlayout.widget.ConstraintLayout> <include android:id="@+id/includes" layout="@layout/included_buttons" </androidx.constraintlayout.widget.ConstraintLayout> <!-- included_buttons.xml --> <androidx.constraintlayout.widget.ConstraintLayout> <Button android:id="@+id/include_me" /> </androidx.constraintlayout.widget.ConstraintLayout>
위와 같은 경우 binding object는 아래와 같이 생성됩니다.
public final class ActivityAwesomeBinding implements ViewBinding { ... @NonNull public final IncludedButtonsBinding includes;
여기서 주위해야 할 점은 반드시 <include> tag가 id를 가져야 한다는 겁니다.
그래야만 해당 id 명으로 속성이 생성됩니다.
아마도 IncludeButtonsBinding 안에 button에 연결 가능한 includeMe 속성이 있겠죠?
Using view binding and data binding
View binding and Kotlin synthetics or ButterKnife
'개발이야기 > Android' 카테고리의 다른 글
[Android, MVVM, Coroutine] 활용 #2 - Room에서 Coroutine 사용 (3) | 2020.08.05 |
---|---|
[Android, MVVM, Coroutine] 활용 #1 - Android에서 Coroutine 사용 (4) | 2020.08.04 |
[Rxbinding] RxJava를 이용한 Android의 이벤트 처리 (2) | 2020.01.08 |
Android Room & Coroutines (0) | 2019.11.25 |
Android Dev Summit 2019 - Debugging Tips n' Tricks (0) | 2019.11.20 |