مهدی معروف به سقراط مناطق محروم :)
مهدی معروف به سقراط مناطق محروم :)
خواندن ۴ دقیقه·۵ ماه پیش

نحوه پیاده سازی view binding توی DuckDuckGo چجوریه؟

داشتم کد DuckDuckGo رو نگاه میکردم که دیدم نحوه پیاده سازی view binding جالبی داره و گفتم بیام این جا یه دل سیر دربارش بنویسم!



https://github.com/duckduckgo/Android/tree/develop/common/common-ui/src/main/java/com/duckduckgo/common/ui/viewbinding

توی این لینک میتونید کل کد های 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

  • پیاده سازی بصورت lazy انجام میشه و هر وقت لازم بود نمونه گیری انجام میشه
  • تضمین میکنه که مقدار همیشه ثابت هست و دوباره نمونه گیری نمیکنه و فقط همون یکبار نمونه میگیره
  • کد مون تمیز تر و قشنگ تر میشه



در ادامه کد ها :

private var binding: T? = null private val bindMethod = bindingClass.getMethod(&quotinflate&quot, 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(&quotCannot access viewBinding activity lifecycle is ${lifecycle.currentState}&quot) } binding = bindMethod.invoke(null, thisRef.layoutInflater).cast<T>() return binding!!

اولش چک میکنه که اگه حالت اکتیویتی مون از شروع شدن گذشته بود برامون ارور بنویسه که نمیتونه به چرخه حیات اکتیویتی دسترسی پیدا کنه. و در نهایت هم binding رو برامون میسازه.

اگه نمدونید invoke چیه، باید بگم میشه باهاش میتونیم نمونه یا همون instanceی که از یه کلاس میسازیم رو مثل تابع استفاده کنیم، یعنی برای همون binding که بالا تعریفش کردیم الان میتونیم layoutinflater یی که دقیقا مال همین AppCompatActivity هست رو بهش بدیم (واقعا نبوغ انگیزه، سادست ولی باحاله، میدونید ترکیبش جالبه)



و درنهایت میاد و cast رو تعریف میکنه (اگه دقت کرده باشید binding آخرش باید به همون کلاس T تبدیل بشه)

@Suppress(&quotUNCHECKED_CAST&quot) private fun <T> Any.cast(): T = this as T

و این طوری ما میتونیم بدون دردسر ویو هامون رو bind کنیم.


امیدوارم لذت برده باشید.

خودمم همینجوری داشتم سرچ میکردم در کنارش و یاد میگرفتم، میدونید خوبی نوشتن این هست که باید اولش چیز ها رو خودت یاد بگیری و بتونی توضیحش بدی، واقعا روش خوبیه، یعنی برای من جواب داده، نوشتن متن طول میکشه، اما میدونم که اگه خودم میخواستم بشینم و فقط یه فایل مو به مو بررسی کنم حوصلم سر میرفت :)
برای فرگمنتش هم تقریبا شبیه همین هست. میتونید خودتون ببینید.

اگه جایی اشتباه کردم یا توضیحاتم ناقص بوده از بلد نبودن من هست. و خوشحال میشم که توی کامنت ها به یادبدید :)


مخلصیم


view bindingپیاده سازیبرنامه نویسینوشتن متنandroid
سوال های احمقانه ایست مرا که جوابی بر آن نیست، پس بنواز ای ساز گیتی تا ز آن خرد آموزم
شاید از این پست‌ها خوشتان بیاید