Hasan
Hasan
خواندن ۶ دقیقه·۹ ماه پیش

نیم نگاهی به Context Receiverها در کاتلین و اندروید

مقدمه

به بسم الله می خوانم خدا را ...... زمشتی خاک آدم ساخت ما را

سلام خیلی خوش اومدین، توی این مقاله قراره خیلی کاربردی در مورد Context Receiver در اندروید و کاتلین صحبت کنیم. کانتکس رسیورها در کاتلین، اجازه تعریف فانکشن‌هایی با چند رسیور رو فراهم می‌کنن. فانکشن‌هایی که فقط در کانتکس همون رسیور قابل دسترسی یا صدا زدن هستن! در ادامه یاد می‌گیریم چطوری به پروژه‌مون اضافه‌ش کنیم، فرقش با اکستنشن فانکشن‌ها چیه، چند مثال کاربردی ازش می‌بینیم و در نهایت به کدهای جاواییش هم یه نیم نگاهی میندازیم.

نحوه اضافه کردن به پروژه

برای فعال‌سازی کانتکس رسیور باید ورژن کاتلین 1.6.2 و یا بالاتر باشه. و چون هنوز در مرحله آزمایشی (یا همون Experimental) هست، باید Xcontext-receivers- رو به شکل زیر به گریدل پروژه اضافه کنیم:

آشنایی با Context Receiver

با استفاده از کانتکس رسیورها، فانکشن مورد نظرمون فقط در صورتی که در زمینه یا کانتکس اون رسیور باشه میتونه صدا زده بشه. از طرفی داخل همون فانکشن هم به خود اون رسیور و اجزای پابلیکش دسترسی داریم. یه جورایی شبیه اکستنشن فانکشن‌ها هست، اما تفاوت‌هایی هم داره که جلوتر میگم.

سینتکس استفاده از اون هم ساده‌س، فقط کافیه قبل از فانکشن مورد نظرمون کلیدواژه context رو بنویسیم و بعد رسیورهای ورودی رو براش تعریف کنیم.

مثلا اینجا فانکشن a را با رسیور A تعریف کردیم. این بدین معنی هست که داخل فانکشن a به همه اجزای پابلیک A دسترسی داریم. البته که هر جایی هم بخوایم این فانکشن رو استفاده کنیم باید در کانتکس A باشیم که اینو در ادامه با هم می‌بینیم.

چگونه در کانتکسِ رسیور باشیم؟

فراخوانی فانکشنی که با کانتکس رسیور تعریف شده، کمی متفاوت هست. یعنی نمی‌تونیم مستقیما از طریق آبجکت اون رسیور با زدن دات بهش دسترسی داشته باشیم بلکه باید در کانتکس اون آبجکت باشیم. خب چجوری میشه تو کانتکس یه آبجکت بود؟ درسته راه‌های متفاوتی هست، مثل استفاده از اسکوپ فانکشن‌ها و یا ارث‌بری.

  • استفاده از اسکوپ فانکشن‌ها: بیاید کلاس A رو در نظر بگیریم، یه ممبر فانکشن معمولی که خودش داره، یدونه هم با اکستنشن و یدونه هم با کانتکس رسیور بهش اضافه شده. حالا موقع صدا زدن با استفاده از with در اسکوپ آبجکت A رفتیم، اینطوری می‌تونیم همه فانکشن‌هاشو صدا بزنیم، اما خارج از این اسکوپ فانکشن b رو نمیشه صدا زد و خطای کامپایل می‌گیریم در حالی که بقیه فانکشن ها مشکلی ندارن (این یکی از تفاوت‌هاش با سایر فانکشن‌هاست):
  • ارث‌بری: وقتی کلاسی از یک کلاس دیگه ارث‌بری میکنه یا یه interface رو پیاده (implement) می‌کنه، پس ما در کانتکس اون کلاس یا interface هستیم و می‌تونیم به همه اجزای پابلیک اون دسترسی داشته باشیم. برای مثال در اینجا ما داخل کلاس E به فانکشنی که روی اینترفیس D تعریف شده بود دسترسی داریم، چرا که کلاس E اون اینترفیس رو پیاده کرده:

فراخوانی رسیورها

خُب قبلا هم گفتیم داخل فانکشن‌هایی که با کانتکس رسیور تعریف شدند، به همه اجزای پابلیک اون رسیور مستقیما دسترسی داریم، اما اگه بخوایم خود اون آبجکتِ رسیور رو صدا بزنیم باید از this به همراه لیبل استفاده کنیم. برای مثال، تو اکستنشن فانکشن پایین برای اینکه خود آبجکت کانتکس رو به Toast پاس بدیم از this@Context استفاده کردیم در حالی که خود this اشاره به String داره:

نحوه استفاده‌ش هم توی اکتیویتی به صورت زیر هست:

حواسمون باشه اکتیویتی در نهایت از کانتکس ارث بری کرده، برای همین ما یجورایی داخل کانتکس هم هستیم و میتونیم فانکشن toast رو صدا بزنیم.

کانتکس رسیور با چند ورودی

یکی از مزیت‌های کانتکس رسیورها، همین عدم محدودیت در تعداد رسیورها هست. چیزی که در اکستنشن فانکشن‌ها ممکن نبود:

نکته‌ای که هست، رابطه بین رسیورها and هست نه or. یعنی موقع فراخوانی، این فانکشن باید همزمان در کانتکس هر دو رسیور باشه، نه اینکه هر کدوم از دوتا رسیور بود، بتونیم از این فانکشن استفاده کنیم:

خب پس درسته قطار کردن رسیورها ممکنه کار ما رو توی اون فانکشن راحت کنه، اما موقع استفاده ممکنه خوانایی کدمون رو کاهش بده و یا حتی در مواردی بلاک‌ کدهای تو در تو ایجاد کنه!

مثال‌های کاربردی

ترکیب اکستنشن فانکشن با کانتکس رسیور میتونه خیلی کاربردی و جذاب باشه مثل همون فانکشن toast که بالاتر نوشتیم. حالا بیاید دو مثال کاربردی دیگه رو بررسی کنیم. البته که بسته به ذوق و خلاقیت‌تون می‌تونید موارد کاربردی دیگه‌ای رو برای خودتون بنویسید.

مثال اول: Message

معمولا توی پروژه، به جای اینکه پیام‌ها یا خطاها رو به صورت هارد کد بنویسیم میایم آی‌دی اونها در فایل strings.xml (یا همون resource id) رو برمی‌گردونیم. اینطوری اپ میتونه از چند زبان پشتیبانی کنه. و بعد در لایه ویو (view) با کمک متد getString اون متن رو نمایش بدیم. اما مواردی هم هست که ممکنه متن پیام یا خطا مستقیما از سرور بیاد و یا به نحوی هارد کد شده باشه. در اینجور موارد بهتره برای هر دو حالت یک کلاس برای هندل کردن خطا یا پیام بنویسیم. خب حالا می‌تونیم با کمک کانتکس رسیور یک فانکشنی مثل asText رو بنویسیم تا متن خطا رو بدون هیچ پارامتر ورودی اضافی به استرینگ برگردونه!

در فرگمنت هم خیلی ساده میتونیم اینجوری پیاممون رو نشون بدیم:

مثال دوم: Flow

توی مقاله A safer way to collect flows from Android Uis آقای Manuel Vivo میاد و نحوه صحیح کالکت کردن فلو رو در فرگمنت و اکتیویتی توضیح میده. اما خودش هم میگه این در مقایسه با LiveData که خیلی راحت آبزرو (observe) میشد، طولانی هست ولی خب بهترین و امن‌ترین راهه:

البته همین آقا Manuel دوباره تو یه مقاله دیگه به اسم repeatOnLifecycle API design story میاد و یه سری توضیحات از نحوه طراحی این api میگه و چند تا راهکار مثل flowWithLifecycle رو پیشنهاد میده که مراحل کار رو کمی ساده‌تر میکنه اما باز برای کالکت شدن به کوروتین نیاز هست. در نهایت هم میگه اگه میخواید به صورت یک خطی تقریبا چیزی شبیه لایو دیتا فلو رو کالکت کنید، میتونید یک اکستنشن شبیه این برای خودتون بنویسید:

و اینطوری هم استفاده میشه:

خب حالا این همه داستان برای اینه که بگیم به لطف کانتکس رسیور اینو ساده‌تر هم میشه کرد:

توی فرگمنت هم اینطوری کالکت می‌کنیم:

فقط یه نکته که خارج از موضوع اصلی مقاله هست رو بگم، اونم اینکه حواسمون باشه که رسیور رو حتما روی Fragment تعریف کنیم، نه روی LifecycleOwner، دلیلش هم همون بحث‌هایی هست که آقای Manuel توی اون دوتا مقاله کرده بود.

در واقع اگه روی LifecycleOwner اکستنشن رو بنویسیم، دیگه فلوی ما در لایف سایکل ویو کالکت نمیشه بلکه تو لایف سایکل خود فرگمنت کالکت میشه و این یعنی همه‌ی اون دو تا مقاله‌ی آقا Manuel کشک!

به نظرم توضیح بیشترش دیگه خیلی مقاله رو از موضوعش منحرف میکنه برای همین اگه دوست داشتید تو بخش نظرات بگید که این دو تا مقاله‌ی آقای Manuel رو جدا با هم موشکافی کنیم.

نگاهی به درون!

اگه از ابزار Kotlin Bytecode داخل IDE استفاده کنیم، می‌بینیم که کد جاوایی کانتکس رسیورها مشابه اکستنشن‌هاست. یعنی یک متد static که رسیور رو در آرگومان‌های ورودیش گرفته. تو مثال پایین فانکشن a رو با کانتکس رسیور روی آبجکت A تعریف کردیم:

در نهایت کد جاواییش به صورت یک متد static شده که آبجکت A رو تو ورودی گرفته:

روش دسترسی به Kotlin Bytecode:

Tools -> Kotlin -> Show Kotlin Bytecode

و بعد با Decompile کردن می‌تونیم کد جاوایی رو ببینیم.

کلام آخر

خب امیدوارم این مقاله برات مفید بوده باشه، اگه حس و حالشو داشتی لایک و کامنت بزار، هر جا هم که هستی ان‌شاألله موفق باشی.

context receiverkotlinandroidextension functionکانتکس رسیور
برنامه نویس
شاید از این پست‌ها خوشتان بیاید