داشتم کد DuckDuckGo رو نگاه میکردم که دیدم نحوه پیاده سازی view binding جالبی داره و گفتم بیام این جا یه دل سیر دربارش بنویسم!
توی این لینک میتونید کل کد های view binding رو ببینید.
اول از همه چیزی که برام جالب بود این قطعه کد بود.
private val binding: ActivityAboutDuckDuckGoBinding by viewBinding()
که شبیه فرم نرمالی که ما استفاده میکنیم تا اکتیویتی ها رو bind کنیم نبود. (شاید بپرسید شکل نرمال چیه)
این شکلی هست:
private lateinit var binding: ResultProfileBinding override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ResultProfileBinding.inflate(layoutInflater) val view = binding.root setContentView(view) }
عینا کد های موجود در داکیومنت رو این جا ببینید.
همون طور که میبینید، هم کد های کمتری نوشته شده و از بازنویسی کد اجتناب کرده، وقتی شما اکتیویتی های زیادی دارید حتی همین کار های ساده هم برای شما وقت گیر هستند و بهتر هست از روشی استفاده کنیم که ساده تر بتونیم برنامه نویسی کنیم. و خودمون رو درگیر این ها نکنیم.
حالا بریم سراغ این که چجوری میتونیم این کار رو بکنیم،
inline fun <reified T : ViewBinding> AppCompatActivity.viewBinding() = ActivityViewBindingDelegate(T::class.java, this)
توی این جا ما کلمه کلیدی reified رو داریم (تو کامنت ها بنویسید آیا ازش استفاده کرده بودیدش یا نه، من که شخصا نه) این برای این هست که حتما جنریک (یعنی هر کلاس دلخواه T) باید از ViewBinding ارث بری کرده باشه، اون موقع میتونیم کلاس ActivityViewBindingDelegate رو اجرا کنیم. اما چرا inline ؟
چون حتما وقتی میخوایم از reified استفاده کنیم باید حتما تابع ما inline باشه، اگه نباشه برنامه نمیتونه بفهمه که T از نوع ViewBinding هست. چون در حالت معمولی T فقط در زمان کامپایل وجود داره اما با خطی کردن اون با inline حالا ما در زمان اجرا هم به نوع T دسترسی داریم. (امیدوارم رسونده باشم و گیج نشده باشید.)
معنی لغوی reify (که ریشه همین reified هست رو اینجا ببینید)
حالا بریم سراغ همین کلاس عزیز و دلبند :)
class ActivityViewBindingDelegate<T : ViewBinding>(
bindingClass: Class<T>,
val activity: AppCompatActivity,
) : ReadOnlyProperty<AppCompatActivity, T>
اول از همه کلاس اینجوری تعریف میشه، همون اولش میگه که T از نوع ViewBinding هست. و متغیر bindingClass بهش اشاری میکنه. و همون طور که activity که از نوع AppCompatActivity هست. (این همونی هست که تقریبا همه اکتیویتی ها ازش ارث بری میکنند.) و درنهایت از interface ی به نام ReadOnlyProperty رو implement میکنه. راستش خودم خیلی دقیق متوجه نشدم دقیقا این چیکار میکنه اما خلاصه اش این هست که با استفاده از این میتونید کلاس ViewBinding رو به طرز دلخواه خودتون برگردونید، یعنی میتونید layout inflater در این جا بهش بدیم و درگیر اضافه کردن اون در اکتیوتی نشیم.
مزایای استفاده از ReadOnlyProperty
در ادامه کد ها :
private var binding: T? = null private val bindMethod = bindingClass.getMethod("inflate", LayoutInflater::class.java)
یه متغیر binding و متغیر bindMethod رو تعریف میکنه، از bindClass که نسخه ای از AppCompatActivity هست استفاده میکنه و متد(تابع) به اسم inflate رو بگیره و برای پارامتر های ورودی تابع LayoutInflater رو بده.
در قسمت بعدی میام و تابع getValue رو که تنها تابع interface مون یعنی ReadOnlyProperty هست رو override میکنیم:
override fun getValue( thisRef: AppCompatActivity, property: KProperty<*>, ): T {
توش:
اولش چک میکنیم که اگه binding مون وجود داشت و null نبود برامون return اش کنه:
binding?.let { return it }
بعدش هم اگه null نبود باید بسازیمش binding رو:
val lifecycle = thisRef.lifecycle if (!lifecycle.currentState.isAtLeast(Lifecycle.State.INITIALIZED)) { error("Cannot access viewBinding activity lifecycle is ${lifecycle.currentState}") } binding = bindMethod.invoke(null, thisRef.layoutInflater).cast<T>() return binding!!
اولش چک میکنه که اگه حالت اکتیویتی مون از شروع شدن گذشته بود برامون ارور بنویسه که نمیتونه به چرخه حیات اکتیویتی دسترسی پیدا کنه. و در نهایت هم binding رو برامون میسازه.
اگه نمدونید invoke چیه، باید بگم میشه باهاش میتونیم نمونه یا همون instanceی که از یه کلاس میسازیم رو مثل تابع استفاده کنیم، یعنی برای همون binding که بالا تعریفش کردیم الان میتونیم layoutinflater یی که دقیقا مال همین AppCompatActivity هست رو بهش بدیم (واقعا نبوغ انگیزه، سادست ولی باحاله، میدونید ترکیبش جالبه)
و درنهایت میاد و cast رو تعریف میکنه (اگه دقت کرده باشید binding آخرش باید به همون کلاس T تبدیل بشه)
@Suppress("UNCHECKED_CAST") private fun <T> Any.cast(): T = this as T
و این طوری ما میتونیم بدون دردسر ویو هامون رو bind کنیم.
امیدوارم لذت برده باشید.
خودمم همینجوری داشتم سرچ میکردم در کنارش و یاد میگرفتم، میدونید خوبی نوشتن این هست که باید اولش چیز ها رو خودت یاد بگیری و بتونی توضیحش بدی، واقعا روش خوبیه، یعنی برای من جواب داده، نوشتن متن طول میکشه، اما میدونم که اگه خودم میخواستم بشینم و فقط یه فایل مو به مو بررسی کنم حوصلم سر میرفت :)
برای فرگمنتش هم تقریبا شبیه همین هست. میتونید خودتون ببینید.
اگه جایی اشتباه کردم یا توضیحاتم ناقص بوده از بلد نبودن من هست. و خوشحال میشم که توی کامنت ها به یادبدید :)
مخلصیم