تبدیل نماهای کاتلین به تابع حالت – از صفر تا صد


یکی از جالبترین تغییرهایی که در چند سال اخیر رخ داده است، ظهور کتابخانههای مدیریت حالت مانند Redux ،Flux یا MobX است. بدین ترتیب مباحث خوبی در میان توسعهدهندگان در مورد شیوه نظمبخشی به روند رو به رشد پیچیدگی در نرمافزار ایجاد شده است. در این مقاله به بررسی روش تبدیل نماهای کاتلین به تابع حالت میپردازیم.
ریداکس و کتابخانههای مشابه دیگر تلاش میکنند از پیچیدگی نرمافزار که در پی وجود حالت و تغییر حالت رخ میدهد، بکاهند. به طور خاص ریداکس این کار را از طریق الزام چند قاعده خاص انجام میدهد:
- تنها یک «منبع واقعیت» وجود دارد که «حالت» (State) است.
- حالت، «تغییرناپذیر» (immutable) است.
- حالت جدید میتواند به وسیله تابع خاصی به نام reducer و در نتیجه نوعی اکشن قبلی ایجاد شود.
در سیستمهای موبایل، حالتهای زیادی در لایه UI قرار دارند که در برخی موارد صریح و در برخی موارد به صورت ضمنی هستند. چیزهایی مانند مقدار متنی یک برچسب، حالت فعال با غیر فعالشده یک دکمه یا وضعیتهای پنهان و نمایان یک تصویر، همگی اساساً بروز نوعی حالت در سیستم محسوب میشوند.
متأسفانه در اغلب موارد شاهد کدهایی مانند زیر در UI هستیم:
این کد فینفسه کد بدی نیست. کاری که مورد نظر کدنویس بوده را اجرا میکند و ممکن است استدلال کنید که این کار را به قدر کافی خوب انجام میدهد. البته در مورد یک نمای ساده، این کد عملکرد چندان بدی نخواهد داشت، اما یک مشکل ظریف وجود دارد: این کد لایه بیزینس را با حالت لایه نما مخلوط کرده است.
هر بار که کدی مانند این مینویسیم، موجب میشویم که درک کارکرد سیستم دشوارتر شود. در مثال فوق، حالت انتخابشده یا نشده یک دکمه منفرد به عوامل مختلفی بستگی دارد که این پیچیدگی در سراسر کد پخش میشود. بدیهی است که وقتی نماها پیچیدهتر شوند، این اوضاع از این هم بغرنجتر میشود و در نقطهای دیگر امکان مقیاسبندی وجود نخواهد داشت. زمانی که این اتفاق بیفتد، میبایست تلاش و زمانی زیادی صرف درک همه مسیرهای کد شود.
در این مقاله قصد داریم روشی برای تفکر در مورد حالت UI نشان دهیم. در واقع بیدلیل نبود که در مقدمه مطلب به Redux اشاره کردیم، چون قصد داریم بهترین رویهها و آموختهها را برای ساخت یک نما به عنوان تابعی از حالت معرفی کنیم. به این ترتیب درک کد آسانتر خواهد بود و قطعیت کد افزایش یافته و تست آن سادهتر میشود.
برای شروع یک ایده بصری از نمایی که میخواهیم طراحی کنیم ارائه میکنیم:
چنان که میبینید قرار است یک تصویر و نام پروفایل کاربر و نمایش یابد. همچنین شماره تلفن و در ادامه یک دکمه برای مشخص ساختن کاربران مورد علاقه ارائه میشود. اگر روی دکمه بزنید، میتوانید یک کاربر را به لیست افراد مورد علاقه، وارد یا از آن خارج کنید. اگر روی شماره تلفن ضربه بزنید، میتوانید یک تماس تلفنی با مخاطب مورد نظر شروع کنید.
پیش از ادامه توضیحات، ابتدا باید کد نحوه نمایش این نما را بنویسیم:
چنان که پیشتر گفتیم، یکی از ارکان اساسی Redux این است که حالت باید تغییرناپذیر باشد و تنها میتواند به وسیله «تابعهای خالص» (Pure Function) به نام reducer تولید شود. شیوه کار به صورت زیر است:
آیا این به آن معنی است که ساخت نماها به صورت تابعی از حالت، متضمن این است که یک وهله جدید در هر زمان که چیزی تغییر مییابد، ایجاد شود؟ روی سیستمهای موبایل این کار میتواند بسیار پرهزینه باشد. بهجای آن میتوانیم حالت کامل نما را در نظر بگیریم و حالتهای مختلف را بر مبنای آن بازترسیم کنیم. بنابراین اگر به لیآوت فوق نگاه کنید، آیا میتوانید تشخیص دهید چه حالتی داریم؟ در سادهترین شکل به صورت زیر است:
- یک منبع URI برای تصویر پروفایل مخاطب.
- یک رشته برای نام کاربر.
- یک رشته برای شماره تلفن.
- یک منبع تصویر برای دکمه مخاطبین محبوب.
کد آن به صورت زیر است:
در مثال فوق، نما با استفاده از «واژگونی کنترل» (Inversion of Control) به دنیای بیرون اعلام میکند که فکر میکند نما باید چگونه باشد. همچنین حالت به طور خاص برای نما ساخته شده است. حالت تنها شامل اطلاعاتی است که نما به طور مستقیم برای رندر مجدد خود نیاز دارد و نه چیز دیگر. در این وضعیت، کافی است یک متد برای رسم مجدد نما با استفاده از ViewState مفروض بنویسیم:
میبینید که در نهایت یک فهرست ساده و قطعی از انتسابها داریم. هیچ کد دیگری در نما وجود ندارد که موارد نمایش یافته روی صفحه را تغییر دهد. تست کردن این کد با استفاده از Espresso یا هر فریمورک دیگر تست بسیار آسان خواهد بود.
در واقع، بخش بزرگی از منطق تجاری نیز که قبلاً در نما قرار داشت، از آن جدا شده است. این کار از سوی ViewState و به وسیله تبدیل برخی لایههای شیء داده به قالبی که برای نما مناسب باشد، صورت گرفته است. این مورد نیز را به نیز به صورت جداگانه تست یونیت میکنیم.
با این حال، در مثال اول میتوانستیم بر اساس تغییرات متناظر در UI مخاطب را به فهرست افراد محبوب نیز اضافه کرده و یا از آن حذف کنیم. برای فعالسازی مجدد این کارکرد و حفظ سازوکار قطعی رسم مجدد، میتوانیم یک اینترفیس جدید به نام Interactor اضافه کنیم:
این سازوکار اساساً به نما امکان میدهد که به دنیای خارج (یعنی اکتیویتیها، فرگمانها، ویومدلها و غیره) اعلام کند که باید نوعی عملیات اجرا شود. در عمل کد به صورت زیر خواهد بود:
از این جا به بعد، نما این مسئولیتها را به هر کلاسی که اینترفیس Interactor را پیادهسازی کند، واگذار خواهد کرد. همانند قبل، تست کردن Interactor با استفاده از هر دو فریمورک Espresso و Mockito بسیار آسان خواهد بود:
در نهایت یک ContactView ایجاد کردهایم که به صورت تابعی از یک حالت رسم مجدد میشود. این کار از طریق گروهبندی همه عملیات رسم مجدد در یک متد و انتقال همه منطق به خارج از نما و به عنوان بخشی از یک Interactor میسر شده است.
کد کامل نما اینک به صورت زیر در آمده است:
سخن پایانی
اینک که تصویر کاملی از همه تغییرها در مثال اولیه داریم، میتوانیم ببینیم که یک اثر ناخواسته جانبی مطلوب دیگر این تغییرها آن است که با رشد نما و افزایش تغییرها، موقعیت بهتری برای مدیریت پیچیدگی در اختیار داریم.
اگر لازم باشد نماهای فرعی مانند فیلدهای آدرس، مخاطبین کاری و غیره اضافه کنیم، تنها کافی است چند خط کد را در متد رسم مجدد تغیر دهیم. به طور مشابه اگر لازم باشد قابلیتهای نما را بسط دهیم، تنها باید چند متد دیگر به Interactor اضافه شود.
اگر مطلب برای شما مفید بوده است، آموزشهای زیر نیز به شما پیشنهاد میشوند:
- مجموعه آموزشهای برنامهنویسی اندروید
- مجموعه آموزشهای برنامهنویسی
- آموزش مقدماتی زبان برنامه نویسی کاتلین (Kotlin) برای توسعه اندروید (Android)
- زبان برنامه نویسی کاتلین (Kotlin) — راهنمای کاربردی
- برنامه نویسی Kotlin — مقدمهای بر برنامهنویسی اندروید با زبان کاتلین
==