-
[Android] ViewBinding์ด๋?Android 2022. 3. 20. 03:18๋ฐ์ํ
View Binding?
Activity, Fragment์ UI๋ฅผ ๋ณ๊ฒฝํ๊ธฐ ์ํด์๋ XML ํ์ผ์ ์ธํ๋ ์ด์ ํ ๊ฐ View๋ค์ findViewById๋ฅผ ํตํด ์ฐธ์กฐ๋ฅผ ํด์ผํฉ๋๋ค. ๊ฐ๋จํ UI์ผ ๊ฒฝ์ฐ์๋ ํฌ๊ฒ ์๊ด์์ง๋ง, ๋ณต์กํ UI์ผ์๋ก ๋ณด์ผ๋ฌ ํ๋ ์ดํธ ์ฝ๋๋ฅผ ์์ฐํ๊ฒ ๋ฉ๋๋ค. ์ด๋ฅผ ๋ฐฉ์งํ๊ณ ์ฝ๊ฒ View์ ์ํธ์์ฉํ๊ธฐ ์ํด์ ๋์จ ๊ฒ์ด DataBinding๊ณผ ViewBinding์ ๋๋ค. ์ด๋ฒ ๊ธ์์๋ ViewBinding์ ๋ํด์๋ง ๋จผ์ ์ค๋ช ํ๋๋ก ํ๊ฒ ์ต๋๋ค.
View Binding ์ค์
View Binding์ ๋ชจ๋๋ณ๋ก ์ค์ ํ๊ธฐ์ build.gradle(Module) ์ ์๋์ ๊ฐ์ด ์ค์ ํด์ผ ํฉ๋๋ค.
android { ... viewBinding { enabled = true } }
๋ชจ๋์์ ์ค์ ๋ View Binding์ ๋ชจ๋ ๋ด์ ์๋ ๋ชจ๋ ๋ ์ด์์ ํ์ผ๋ค์ ํด๋นํ๋ ๋ฐ์ธ๋ฉ ํด๋์ค๋ฅผ ์์ฑํฉ๋๋ค. ๋ํ, ๋ฐ์ธ๋ฉ ํด๋์ค ๋ด์ ๋ ์ด์์์ ํฌํจ๋ ๋ทฐ๋ค์ ๋ํ ์ฐธ์กฐ๊ฐ ๊ฐ๊ฐ ์ ์๋์ด ์์ต๋๋ค.
<LinearLayout ... tools:viewBindingIgnore="true" > ... </LinearLayout>
๋ชจ๋ ๋ด์ ๋ชจ๋ ๋ ์ด์์ ํ์ผ๋ค์ ์๋์ผ๋ก ๋ฐ์ธ๋ฉ ํด๋์ค๋ฅผ ์์ฑ๋๋ ๊ฒ์ ๋ฌด์ํ๊ธฐ ์ํด์๋ ๋ค์๊ณผ ๊ฐ์ด ๋ฃจํธ๋ทฐ์ tools:viewBindingIgnore = "true" ๋ฅผ ์ถ๊ฐํฉ๋๋ค.
View Binding ์ฌ์ฉ
<?xml version="1.0" encoding="utf-8"?> <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:context=".MainActivity"> <EditText android:id="@+id/searchEditText" android:layout_width="0dp" android:layout_height="wrap_content" android:inputType="text" android:lines="1" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <androidx.recyclerview.widget.RecyclerView android:id="@+id/bookRecyclerView" android:layout_width="0dp" android:layout_height="0dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/searchEditText" /> <androidx.recyclerview.widget.RecyclerView android:id="@+id/historyRecyclerView" android:layout_width="0dp" android:layout_height="0dp" android:background="@color/white" android:visibility="gone" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/searchEditText" /> </androidx.constraintlayout.widget.ConstraintLayout>
๋ค์๊ณผ ๊ฐ์ด ๋ ์ด์์ ํ์ผ์ด ์๋ค๊ณ ๊ฐ์ ํฉ๋๋ค. ๋ฃจํธ ๋ทฐ๋ ConstraintLayout์ด๊ณ ๋ด๋ถ์ EditText 1๊ฐ์ RecyclerView๊ฐ 2๊ฐ ์กด์ฌํฉ๋๋ค. ์์์ ์ธ๊ธํ๋ฏ์ด View Binding์ด ์ค์ ๋์ด ์์ผ๋ฉด ๋ ์ด์์์ ์์ํ๋ ๋ฐ์ธ๋ฉ ํด๋์ค๋ฅผ ์์ฑํฉ๋๋ค. ๋ฐ์ธ๋ฉ ํด๋์ค์ ์ด๋ฆ์ XML ํ์ผ์ ์ด๋ฆ์ผ๋ก Camel Case์ผ๋ก ๋ณํํ๊ณ "Binding" ์ ์ถ๊ฐํ์ฌ ์์ฑํฉ๋๋ค.
์ค์ ๋ฐ์ธ๋ฉ ํด๋์ค๋ฅผ ๋ณด๊ณ ์ถ๋ค๋ฉด ์๋๋ก์ด๋ ์คํ๋์ค Android ๋ทฐ์์ Project๋ก ๋ณ๊ฒฝ ํ app > build > ... > databinding ํด๋ ์๋์ ์์ฑ๋๋ ๊ฒ์ ํ์ธํ์ค ์ ์์ต๋๋ค.
Activity์์ View Binding ์ฌ์ฉ
class MainActivity : AppCompatActivity() { private lateinit var binding: ActivityMainBinding override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) }
๋ ์ด์์ ํ์ผ์ ์ด๋ฆ์ด acitivity_main.xml์ด๊ธฐ์ ๋ฐ์ธ๋ฉ ํด๋์ค๊ฐ ActivityMainBinding์ผ๋ก ์์ฑ๋์์ต๋๋ค. ActivityMainBinding์ Static ๋ฉ์๋์ธ inflate ํจ์๋ฅผ ํธ์ถํ์ฌ Binding ํด๋์ค๋ฅผ ์์ฑํฉ๋๋ค. ์ดํ setContentView์ ๋ฃจํธ ๋ทฐ๋ฅผ ์ ๋ฌํ์ฌ ๋ ์ด์์์ ํ๋ฉด์ ๋ํ๋ ๋๋ค.
// Generated by view binder compiler. Do not edit! package ows.kotlinstudy.bookreview.databinding; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.EditText; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.constraintlayout.widget.ConstraintLayout; import androidx.recyclerview.widget.RecyclerView; import androidx.viewbinding.ViewBinding; import java.lang.NullPointerException; import java.lang.Override; import java.lang.String; import ows.kotlinstudy.bookreview.R; public final class ActivityMainBinding implements ViewBinding { @NonNull private final ConstraintLayout rootView; @NonNull public final RecyclerView bookRecyclerView; @NonNull public final RecyclerView historyRecyclerView; @NonNull public final EditText searchEditText; private ActivityMainBinding(@NonNull ConstraintLayout rootView, @NonNull RecyclerView bookRecyclerView, @NonNull RecyclerView historyRecyclerView, @NonNull EditText searchEditText) { this.rootView = rootView; this.bookRecyclerView = bookRecyclerView; this.historyRecyclerView = historyRecyclerView; this.searchEditText = searchEditText; } @Override @NonNull public ConstraintLayout getRoot() { return rootView; } @NonNull public static ActivityMainBinding inflate(@NonNull LayoutInflater inflater) { return inflate(inflater, null, false); } @NonNull public static ActivityMainBinding inflate(@NonNull LayoutInflater inflater, @Nullable ViewGroup parent, boolean attachToParent) { View root = inflater.inflate(R.layout.activity_main, parent, false); if (attachToParent) { parent.addView(root); } return bind(root); } @NonNull public static ActivityMainBinding bind(@NonNull View rootView) { // The body of this method is generated in a way you would not otherwise write. // This is done to optimize the compiled bytecode for size and performance. int id; missingId: { id = R.id.bookRecyclerView; RecyclerView bookRecyclerView = rootView.findViewById(id); if (bookRecyclerView == null) { break missingId; } id = R.id.historyRecyclerView; RecyclerView historyRecyclerView = rootView.findViewById(id); if (historyRecyclerView == null) { break missingId; } id = R.id.searchEditText; EditText searchEditText = rootView.findViewById(id); if (searchEditText == null) { break missingId; } return new ActivityMainBinding((ConstraintLayout) rootView, bookRecyclerView, historyRecyclerView, searchEditText); } String missingId = rootView.getResources().getResourceName(id); throw new NullPointerException("Missing required view with ID: ".concat(missingId)); } }
์์ ์ฝ๋๋ ์ค์ ๋ก ์์ฑ๋ ActivityMainBinding ํด๋์ค๋ก ์์ฑ์ rootView ์ด์ธ์๋ rootView์ ํฌํจ๋ ๋ชจ๋ ๋ทฐ๋ค์ ๋ํ ์ฐธ์กฐ๊ฐ ์กด์ฌํฉ๋๋ค. ๋ด๋ถ์ ์ผ๋ก ActivityMainBinding ํด๋์ค๊ฐ ์์ฑ๋๋ ํ๋ฆ์ ์ดํด๋ณด๋ ๊ฒ๋ ์ข์ต๋๋ค.
- inflate(LayoutInflater)
- inflate(LayoutInflater, ViewGroup, boolean)
- bind(View)
Activity ๊ฐ์ ๊ฒฝ์ฐ๋ parent๊ฐ ์กด์ฌํ์ง ์๊ธฐ์ inflate(LayoutInflater)๊ฐ ํธ์ถ๋์ง๋ง, Fragment ๋ RecyclerView์ Item์ ํด๋นํ๋ View๋ค์ inflate(LayoutInflater, ViewGroup, boolean)์ด ๋ฐ๋ก ํธ์ถ๋ ์ ์์ต๋๋ค. ๊ทธ๋ฆฌ๊ณ ์ธํ๋ ์ด์ ์ ํตํด rootView๋ฅผ ์ป๋๋ค๋ฉด bind ๋ฉ์๋์ rootView๋ฅผ ๋์ ํ์ฌ ๋ชจ๋ ๋ทฐ์ ๋ํ ์ฐธ์กฐ๋ฅผ findViewById๋ฅผ ํตํด ์ป์ด์ค๊ฒ ๋ฉ๋๋ค. ๋ฆฌํด ๊ฐ์ผ๋ก ActivityMainBinding์ ๋ฐํํ๊ฒ ๋๊ณ ๋งค๊ฐ๋ณ์๋ก ์ฐธ์กฐํ ๋ทฐ๋ค์ ๋ชจ๋ ๋์ ํ์ฌ ์์ฑ์ผ๋ก ์กด์ฌํ๋ ๋ชจ๋ ๋ทฐ๋ค์ ์ธํ ๊น์ง ๋ง๋ฌด๋ฆฌํ๊ฒ ๋ฉ๋๋ค. ์ด๋ฐ ๋ก์ง์ ํตํด์ ActivityMainBinding.inflate ๋ฉ์๋๋ง ํธ์ถํ๋๋ผ๋ ๋ชจ๋ ๋ทฐ๋ค์ ๋ฐ์ธ๋ฉ ํด๋์ค๋ก ์ฐธ์กฐ๋ฅผ ํ ์ ์์ต๋๋ค. ์ฐธ ์ฝ์ฃ ?~
Fragment View Binding ์ฌ์ฉ
Activity์์๋ View Binding์ ์ฌ์ฉํ ๋ ํฐ ์ฃผ์์ฌํญ์ ์์ง๋ง Fragment์์ View Binding์ ์ฌ์ฉํ ๋ ์ฃผ์ํ ์ ์ด ์์ต๋๋ค.
private var _binding: ResultProfileBinding? = 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 = ResultProfileBinding.inflate(inflater, container, false) val view = binding.root return view } override fun onDestroyView() { super.onDestroyView() _binding = null }
onCreateView์์ View Binding ํด๋์ค๋ฅผ ์์ฑํ๊ณ onDestroyView์์๋ ๊ผญ View Binding์ null ์ฒ๋ฆฌ ํด์ฃผ์ด์ผ ํฉ๋๋ค. Fragment์ ์๋ช ์ฃผ๊ธฐ๋ก ๋ณด๋ฉด onDestory ๋ฉ์๋๊ฐ onDestoryView ๋ค์์ ํธ์ถ๋๊ธฐ์ View๋ณด๋ค ์ค๋ ์ง์๋๋ค๊ณ ๋ณผ ์ ์์ต๋๋ค. ๊ทธ๋์ Fragment๊ฐ ์ข ๋ฃ๋๊ธฐ ์ ์ View๋ฅผ null์ฒ๋ฆฌ ํด์ GC๊ฐ ์์งํด๊ฐ ์ ์๋๋ก ํด์ผํฉ๋๋ค. ๋ง์ผ null์ฒ๋ฆฌ๋ฅผ ํ์ง ์์ ๊ฒฝ์ฐ์ Fragment๋ ๋ฐ์ธ๋ฉ ํด๋์ค์ ๊ฐํ ์ฐธ์กฐ ๋๊ณ ์์ด์ Fragment๊ฐ ์ข ๋ฃ๋๋๋ผ๋ ๋ฐ์ธ๋ฉ ํด๋์ค๋ก ์ธํด GC๊ฐ ์์งํ์ง ์๋ ํ์์ด ๋ํ๋ ์ ์์ต๋๋ค. ์ฆ ๋ฉ๋ชจ๋ฆฌ ๋์๋ก ์ด์ด์ง๊ฒ ๋ฉ๋๋ค. ์ด ๋ถ๋ถ์ด ์ดํด๊ฐ ๊ฐ์ง ์๋๋ค๋ฉด Stronge Reference, Week Reference์ ๋ํด์ ๊ณต๋ถํด๋ณด์ ๋ ์ข์ ๊ฒ ๊ฐ์ต๋๋ค.
๋ฐ์ํfindViewById์์ ์ฐจ์ด์
Null ์์ ์ฑ
๋ฐ์ธ๋ฉ ํด๋์ค๊ฐ ์ง์ ๋ทฐ๋ค์ ์ฐธ์กฐํ๊ธฐ์ ์๋ชป๋ id๋ก ์ธํ NullPointerException๊ณผ ํด๋จผ์๋ฌ๋ฅผ ๋ฐฉ์งํฉ๋๋ค.
Type ์์ ์ฑ
์์ ์ฅ์ ๊ณผ ๋ง์ฐฌ๊ฐ์ง๋ก ๋ฐ์ธ๋ฉ ํด๋์ค๊ฐ XML ํ์ผ์ ์ ์๋ ๋ทฐ๋ค์ ๊ฐ์ง๊ณ ์ฐธ์กฐ๋ฅผ ํ๊ธฐ์ ํด๋์ค ๋ณํ๊ณผ ๊ฐ์ ์๋ฌ๋ฅผ ๋ฐฉ์งํฉ๋๋ค.
DataBinding๊ณผ์ ์ฐจ์ด์
๋ ๋น ๋ฅธ ์ปดํ์ผ
@BindingAdapter์ ๊ฐ์ ์ด๋ ธํ ์ด์ ํ๋ก์ธ์ฑ์ ์ฌ์ฉํ์ง ์๊ธฐ์ ์ปดํ์ผ ์๊ฐ์ด ์งง์ต๋๋ค.
ํธ๋ฆฌํ ์ฌ์ฉ์ฑ
DataBinding์ XML ํ์ผ์ <layout> ํ๊ทธ๋ฅผ ์ถ๊ฐํด์ผ๋ง ๋ฐ์ธ๋ฉ ํด๋์ค๋ฅผ ์์ฑํ๋๋ฐ ๋ฐํด ViewBinding์ ๋ชจ๋ ๋ชจ๋ ๋ด์ ์กด์ฌํ๋ ๋ ์ด์์์ ์๋์ผ๋ก ์์ฑํ์ฌ ํธ๋ฆฌํฉ๋๋ค.
๋ ์ด์์ ํํ์
DataBinding์ ๋ ์ด์์ ๋ด์ ์ด๋ฒคํธ๋ฅผ ์ค์ ํ๊ฑฐ๋ ๊ฐ์ ๋์ ํ ์ ์์ด ์ ์ธํ ํ๋ก๊ทธ๋๋ฐ์ด ๊ฐ๋ฅํฉ๋๋ค. ํ์ง๋ง ViewBinding์ ์ง์ํ์ง ์์ต๋๋ค.
์๋ฐฉํฅ ๋ฐ์ดํฐ ๊ฒฐํฉ
ViewBinding์ ์ฝ๋ ๋ด์์ ๋ ์ด์์์ ์ฐธ์กฐํ๋ค๋ฉด, DataBinding์ ๋ ์ด์์์์ ์ฝ๋ ๋ด์ ๋ณ์๋ฅผ ์ฐธ์กฐํ์ฌ ๊ฐ์ ๋ณ๊ฒฝํ๊ฑฐ๋ UI๋ฅผ ๋ณ๊ฒฝํ ์ ์์ด์ ์๋ฐฉํฅ์ผ๋ก ์ํธ์์ฉ์ด ๊ฐ๋ฅํฉ๋๋ค.
์ฐธ๊ณ
๋ฐ์ํ'Android' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[Android] RecyclerView ViewHolder ํจํด, DiffUtil ํด๋์ค (1) 2022.03.22 [Android] Storage Access Framework(SAF) ์ด๋? (0) 2022.03.13 [Android] findViewById ์๋ฆฌ (0) 2022.03.13 [Android] Native Application(C/C++), NDK build ๋ฐ CMake ๊ตฌ์ฑ (0) 2022.02.14 [Android] ContentProvider ๊ตฌํ ๋ฐ ์ฌ์ฉ๋ฒ (0) 2022.02.05