Android App Development
Activity, Fragment, Dialog, View ve RecyclerViewHolder İçinde Android View Binding Kullanımı
Ocak 19, 2022
Bu yazıda; Activity, Fragment, Dialog, View ve RecyclerViewHolder yapılarında tek bir merkezi mantıkla ViewBinding’i nasıl kullanabileceğimizi açıklamaya çalışacağım.
Genel Bakış
View binding (Görünüm bağlama), görünümlerle etkileşime giren kodları daha kolay yazmanızı sağlayan bir özelliktir. Bir modülde view binding etkinleştirildiğinde, o modülde bulunan her XML düzen (layout) dosyası için bir bağlama sınıfı (binding class) üretir. Bir bağlama sınıfının örneği, ilgili düzende ID’si olan tüm görünümlere doğrudan referanslar içerir.
Çoğu durumda view binding, findViewById yaklaşımının yerini alır.
View binding kullanıldığında genellikle her düzen dosyası için bir bağlama sınıfı oluşturulur. Bu bağlama sınıfı, belirli görünümlere ait tüm referansları saklar.
View binding, null güvenli (null safe) ve hızlıdır. Geliştiricilerin programlama sırasında sık yapılan hatalardan kaçınmasını sağlar.
View Binding’in Avantajları
- Null güvenliğini destekler: Bu özellik, geliştiricilerin var olmayan görünümleri veya ID’leri çağırmasını engeller. Sonuç olarak uygulamanın ani çökmelerini (crash) önler.
- Kalıp kodları (boilerplate) azaltmaya yardımcı olur.
- Tip güvenliği (type safety) sağlar: Üretilen bağlama sınıfı, düzen dosyasında bildirilen görünümlerle eşleşir. Bu özellik de uygulamanın çökmesini engeller.
Kurulum Talimatları
View binding modül bazında etkinleştirilir. Bir modülde view binding’i etkinleştirmek için, aşağıdaki örnekte gösterildiği gibi modül düzeyindeki build.gradle dosyasında viewBinding derleme seçeneğini true olarak ayarlayın:
android {
...
buildFeatures {
viewBinding = true
}
}
Bağlama sınıfları oluşturulurken bir düzen dosyasının göz ardı edilmesini istiyorsanız, o düzen dosyasının kök görünümüne (root view) tools:viewBindingIgnore="true" özniteliğini ekleyin:
<ConstraintLayout
...
tools:viewBindingIgnore="true" >
...
</ConstraintLayout>
Bir proje için etkinleştirildikten sonra view binding, tüm düzenleriniz için otomatik olarak bir bağlama sınıfı üretecektir. Bağlama sınıfının adı, XML dosyasının adını Pascal case formatına dönüştürüp sonuna “Binding” kelimesi eklenerek oluşturulur.
Bu işlem tamamlandıktan sonra; Fragment, Activity, Dialog, View ve RecyclerViewHolder gibi düzenleri inflate ettiğiniz her yerde bağlama sınıfını kullanabilirsiniz.
ViewBinding İçin Genişletme Metotları (Extension Methods)
ViewBinding kullanımı için genişletme metotlarımız mevcut. Adım adım açıklamaya çalışacağım. Kodların tamamı aşağıdadır.
ViewBindingExtensions.kt
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.viewbinding.ViewBinding
import java.lang.reflect.ParameterizedTypeinternal fun <V : ViewBinding> Class<*>.getBinding(layoutInflater: LayoutInflater): V {
return try {
@Suppress("UNCHECKED_CAST")
getMethod(
"inflate",
LayoutInflater::class.java
).invoke(null, layoutInflater) as V
} catch (ex: Exception) {
throw RuntimeException("The ViewBinding inflate function has been changed.", ex)
}
}internal fun <V : ViewBinding> Class<*>.getBinding(
layoutInflater: LayoutInflater,
container: ViewGroup?
): V {
return try {
@Suppress("UNCHECKED_CAST")
getMethod(
"inflate",
LayoutInflater::class.java,
ViewGroup::class.java,
Boolean::class.java
).invoke(null, layoutInflater, container, false) as V
} catch (ex: Exception) {
throw RuntimeException("The ViewBinding inflate function has been changed.", ex)
}
}internal fun Class<*>.checkMethod(): Boolean {
return try {
getMethod(
"inflate",
LayoutInflater::class.java
)
true
} catch (ex: Exception) {
false
}
}internal fun Any.findClass(): Class<*> {
var javaClass: Class<*> = this.javaClass
var result: Class<*>? = null
while (result == null || !result.checkMethod()) {
result = (javaClass.genericSuperclass as? ParameterizedType)
?.actualTypeArguments?.firstOrNull {
if (it is Class<*>) {
it.checkMethod()
} else {
false
}
} as? Class<*>
javaClass = javaClass.superclass
}
return result
}inline fun <reified V : ViewBinding> ViewGroup.toBinding(): V {
return V::class.java.getMethod(
"inflate",
LayoutInflater::class.java,
ViewGroup::class.java,
Boolean::class.java
).invoke(null, LayoutInflater.from(context), this, false) as V
}********************************************************************
internal fun <V : ViewBinding> BindingActivity<V>.getBinding(): V {
return findClass().getBinding(layoutInflater)
}internal fun <V : ViewBinding> BindingFragment<V>.getBinding(
inflater: LayoutInflater,
container: ViewGroup?
): V {
return findClass().getBinding(inflater, container)
}internal fun <V : ViewBinding> BindingSheetDialog<V>.getBinding(
inflater: LayoutInflater,
container: ViewGroup?
): V {
return findClass().getBinding(inflater, container)
}internal fun <V : ViewBinding> BindingComponent<V>.getBinding(
inflater: LayoutInflater,
container: ViewGroup?
): V {
return findClass().getBinding(inflater, container)
}
ViewHolder İçinde View Binding Kullanımı
RecyclerView Adapter ile kullanmak üzere bağlama sınıfının bir örneğini ayarlamak için, üretilen bağlama sınıfı nesnesini holder sınıfının constructor’ına (yapıcı metoduna) geçirmeniz gerekir.
RecyclerView satır öğesi için row_character XML dosyamız var ve üretilen sınıf RowCharacterBinding şeklindedir.
row_character.xml
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView 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:id="@+id/card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardElevation="4dp"
app:cardUseCompatPadding="false"> <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"> <com.google.android.material.textview.MaterialTextView
android:id="@+id/tvName"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="8dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="Mr.Sanchez" /> </androidx.constraintlayout.widget.ConstraintLayout></com.google.android.material.card.MaterialCardView>
- ViewHolder sınıfından türeyen
BindingViewHolderadında bir sınıfımız var.
BindingViewHolder.kt
import android.content.Context
import androidx.recyclerview.widget.RecyclerView
import androidx.viewbinding.ViewBinding/**
* A Simple [BindingViewHolder] providing easier support for ViewBinding
*/
open class BindingViewHolder<VB : ViewBinding>(val binding: VB) :
RecyclerView.ViewHolder(binding.root) {
val context: Context = binding.root.context
}
CharactersViewHoldersınıfındaki örnek kullanım şu şekildedir:
CharactersViewHolder.kt
inner class CharactersViewHolder(binding: RowCharacterBinding) :
BindingViewHolder<RowCharacterBinding>(binding) { fun bind(item: Character) {
....
binding.tvName.text = item.name
....
binding.root.setOnClickListener {
....
}
}
}
ViewBindingExtensions.ktdosyasındakitoBinding()metodunu kullanın:
inline fun <reified V : ViewBinding> ViewGroup.toBinding(): V {
return V::class.java.getMethod(
"inflate",
LayoutInflater::class.java,
ViewGroup::class.java,
Boolean::class.java
).invoke(null, LayoutInflater.from(context), this, false) as V
}
- Son olarak, üretilen bağlama sınıfını ViewHolder sınıfına
parent.toBinding()şeklinde geçirmek içinonCreateViewHoldermetodunu kullanıyoruz.
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return CharactersViewHolder(parent.toBinding())
}
Activity İçinde View Binding Kullanımı
Bir Activity ile kullanmak üzere bağlama sınıfının bir örneğini ayarlamak için BindingActivity içinde aşağıdaki adımları gerçekleştirin:
AppCompatActivity() sınıfından türeyen bir BindingActivity() sınıfımız var.
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.viewbinding.ViewBindingopen class BindingActivity<VB : ViewBinding> : AppCompatActivity() {
.......
}
ViewBindingExtensions.ktdosyasındangetBinding()metodunu oluşturun ve kullanın.
internal fun <V : ViewBinding> BindingActivity<V>.getBinding(): V {
return findClass().getBinding(layoutInflater)
}
BindingActivity.ktdosyasındakisetContentView()metodunagetBinding()sonucunu geçirin.
BindingActivity.kt
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.viewbinding.ViewBindingopen class BindingActivity<VB : ViewBinding> : AppCompatActivity() { lateinit var binding: VB override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (::binding.isInitialized.not()) {
binding = getBinding()
setContentView(binding.root)
}
}
}
- Bu işlem tamamlandıktan sonra,
BindingActivity()sınıfından türeyen herhangi bir yerdeki görünümlere referans vermek ve bunları kullanmak için bağlama sınıfının örneğini kullanabilirsiniz.
MainActivity.kt
class MainActivity : BindingActivity<ActivityMainBinding>() { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) ......
binding.tvName.text = "NAME"
}
}
Fragment İçinde View Binding Kullanımı
Bir Fragment ile kullanmak üzere bağlama sınıfının bir örneğini ayarlamak için BindingFragment içinde aşağıdaki adımları gerçekleştirin:
Fragment() sınıfından türeyen bir BindingFragment() sınıfımız var.
open class BindingFragment<VB : ViewBinding> : Fragment() { .....}
ViewBindingExtensions.ktdosyasındangetBinding()metodunu oluşturun ve kullanın.
internal fun <V : ViewBinding> BindingFragment<V>.getBinding(
inflater: LayoutInflater,
container: ViewGroup?
): V {
return findClass().getBinding(inflater, container)
}
BindingFragment.ktdosyasındakionCreateView()metodunagetBinding()sonucunu geçirin.
BindingFragment.kt
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.viewbinding.ViewBindingopen class BindingFragment<VB : ViewBinding> : Fragment() { private var _binding: VB? = null val binding: VB
get() = _binding
?: throw RuntimeException("Should only use binding after onCreateView and before onDestroyView") protected fun requireBinding(): VB = requireNotNull(_binding) final override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = getBinding(inflater, container)
return binding.root
} override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
- Bu işlem tamamlandıktan sonra,
BindingFragment()sınıfından türeyen herhangi bir yerdeki görünümlere referans vermek ve bunları kullanmak için bağlama sınıfının örneğini kullanabilirsiniz.
CharactersFragment.kt
class CharactersFragment : BindingFragment<FragmentCharactersBinding>() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) ......
binding.tvName.text = "NAME"
}
}
Dialog İçinde View Binding Kullanımı
Bir BottomSheetDialogFragment ile kullanmak üzere bağlama sınıfının bir örneğini ayarlamak için BindingSheetDialog içinde aşağıdaki adımları gerçekleştirin:
BottomSheetDialogFragment() sınıfından türeyen bir BindingSheetDialog() sınıfımız var.
open class BindingSheetDialog<VB : ViewBinding> : BottomSheetDialogFragment() { .......
}
ViewBindingExtensions.ktdosyasındangetBinding()metodunu oluşturun ve kullanın.
internal fun <V : ViewBinding> BindingSheetDialog<V>.getBinding(
inflater: LayoutInflater,
container: ViewGroup?
): V {
return findClass().getBinding(inflater, container)
}
BindingSheetDialog.ktdosyasındakionCreateView()metodunagetBinding()sonucunu geçirin.
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.viewbinding.ViewBinding
import com.google.android.material.bottomsheet.BottomSheetDialogFragmentopen class BindingSheetDialog<VB : ViewBinding> : BottomSheetDialogFragment() { private var _binding: VB? = null protected val binding: VB
get() = _binding
?: throw RuntimeException("Should only use binding after onCreateView and before onDestroyView") override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = getBinding(inflater, container)
return binding.root
} override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
View/Layout İçinde View Binding Kullanımı
Bir Düzen (Layout) ile kullanmak üzere bağlama sınıfının bir örneğini ayarlamak için düzen içinde aşağıdaki adımları gerçekleştirin:
ConstraintLayout() sınıfından türeyen bir BindingComponent() sınıfımız var.
open class BindingComponent<VB : ViewBinding> @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null
) : ConstraintLayout(context, attrs) { .....
}
ViewBindingExtensions.ktdosyasındangetBinding()metodunu oluşturun ve kullanın.
internal fun <V : ViewBinding> BindingComponent<V>.getBinding(
inflater: LayoutInflater,
container: ViewGroup?
): V {
return findClass().getBinding(inflater, container)
}
getBinding()sonucunuBindingComponent.ktdosyasına geçirin.
val Context.inflater get() = LayoutInflater.from(this)********************************************************************open class BindingComponent<VB : ViewBinding> @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null
) : ConstraintLayout(context, attrs) { lateinit var binding: VB init {
initBinding()
} private fun initBinding() {
if (::binding.isInitialized.not()) {
binding = getBinding(context.inflater, container = this)
}
}
}
findViewById ile Arasındaki Farklar
View binding, findViewById kullanımına kıyasla önemli avantajlara sahiptir:
- Null güvenliği: View binding görünümlere doğrudan referanslar oluşturduğundan, geçersiz bir görünüm ID’si nedeniyle null pointer exception (boş referans istisnası) riski yoktur. Ek olarak, bir görünüm bir düzenin yalnızca bazı konfigürasyonlarında mevcut olduğunda, bağlama sınıfında onun referansını içeren alan
@Nullableile işaretlenir. - Tip güvenliği: Her bağlama sınıfındaki alanlar, XML dosyasında referans verdikleri görünümlerle eşleşen tiplere sahiptir. Bu, bir class cast exception (sınıf dönüştürme istisnası) riski olmadığı anlamına gelir.
Bu farklar, düzeniniz ile kodunuz arasındaki uyumsuzlukların uygulamanın çalışma zamanında (runtime) çökmek yerine, derleme zamanında (compile time) derleme hatasına yol açacağı anlamına gelir.
Data Binding ile Karşılaştırma
View binding ve data binding, görünümlere doğrudan referans vermek için kullanabileceğiniz bağlama sınıfları üretir. Ancak view binding daha basit kullanım senaryolarını ele almayı amaçlar ve data binding’e kıyasla aşağıdaki avantajları sağlar:
- Daha hızlı derleme: View binding herhangi bir anotasyon işleme (annotation processing) gerektirmez, bu nedenle derleme süreleri daha hızlıdır.
- Kullanım kolaylığı: View binding özel olarak etiketlenmiş XML düzen dosyaları gerektirmez, bu nedenle uygulamalarınızda benimsenmesi daha hızlıdır. Bir modülde view binding’i etkinleştirdiğinizde, o modülün tüm düzenlerine otomatik olarak uygulanır.
Buna karşılık view binding, data binding ile karşılaştırıldığında aşağıdaki sınırlamalara sahiptir:
- View binding düzen değişkenlerini (layout variables) veya düzen ifadelerini (layout expressions) desteklemez, bu nedenle doğrudan XML düzen dosyalarından dinamik UI içeriği bildirmek için kullanılamaz.
- View binding iki yönlü veri bağlamayı (two way data binding) desteklemez.
Bu hususlar nedeniyle, bazı durumlarda bir projede hem view binding hem de data binding kullanmak en iyisidir. Gelişmiş özellikler gerektiren düzenlerde data binding, gerektirmeyenlerde ise view binding kullanabilirsiniz.
Özet
Kodunuzu yeniden kullanılabilir hale getirmek için her şeyi devasa bir binding activity, fragment, view veya recyclerViewHolder (kalıtım) içine koymaya çalışmayın; bunun yerine genişletme fonksiyonlarının yardımıyla veya mantığı farklı sınıflara bölerek kompozisyon (composition) yapısını dahil edin. Çünkü:
- Yeni teknolojiye veya mimariye geçişi kolaylaştırır.
- Geliştiricilerin sizin geliştirdiğiniz activity ve fragment versiyonlarını anlamaya çalışması yerine, tüm geliştiriciler tarafından iyi bilinen yeni activity ve fragment’ları yazmaya başlamasını kolaylaştırır!
- Artı olarak, kompozisyonun kalıtıma karşı sağladığı tüm faydaları getirir!
Referanslar
- https://developer.android.com/topic/libraries/view-binding
- https://www.section.io/engineering-education/view-binding-in-android/
- https://medium.com/@samuelwahome/view-binding-in-android-f61403bd9d58
- https://medium.com/androiddevelopers/use-view-binding-to-replace-findviewbyid-c83942471fc
- https://javarevisited.blogspot.com/2013/06/why-favor-composition-over-inheritance-java-oops-design.html
- https://developer.android.com/topic/libraries/data-binding/expressions
- https://developer.android.com/topic/libraries/data-binding/two-way
Mutlu kodlamalar!
Yazar: Mesut Genç