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 BindingViewHolder adı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
}
  • CharactersViewHolder sı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.kt dosyasındaki toBinding() 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çin onCreateViewHolder metodunu 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.kt dosyasından getBinding() metodunu oluşturun ve kullanın.
internal fun <V : ViewBinding> BindingActivity<V>.getBinding(): V {
return findClass().getBinding(layoutInflater)
}
  • BindingActivity.kt dosyasındaki setContentView() metoduna getBinding() 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.kt dosyasından getBinding() metodunu oluşturun ve kullanın.
internal fun <V : ViewBinding> BindingFragment<V>.getBinding(
inflater: LayoutInflater,
container: ViewGroup?
): V {
return findClass().getBinding(inflater, container)
}
  • BindingFragment.kt dosyasındaki onCreateView() metoduna getBinding() 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.kt dosyasından getBinding() metodunu oluşturun ve kullanın.
internal fun <V : ViewBinding> BindingSheetDialog<V>.getBinding(
inflater: LayoutInflater,
container: ViewGroup?
): V {
return findClass().getBinding(inflater, container)
}
  • BindingSheetDialog.kt dosyasındaki onCreateView() metoduna getBinding() 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.kt dosyasından getBinding() metodunu oluşturun ve kullanın.
internal fun <V : ViewBinding> BindingComponent<V>.getBinding(
inflater: LayoutInflater,
container: ViewGroup?
): V {
return findClass().getBinding(inflater, container)
}
  • getBinding() sonucunu BindingComponent.kt dosyası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 @Nullable ile 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:

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

Mutlu kodlamalar!

Yazar: Mesut Genç

Tags

Android Android App Development View Binding View Binding Delegate