Dart Const (استفاده در فلاتر)

Flutter Challenge(چالش فلاتر)

رسیدیم به بخش جذاب کار یعنی استفاده از const در Flutter. اگه مقاله مربوط به const constructor رو نخوندی میتونی از اینجا اون رو بخونی و برای بررسی همه مقالات مربوط به const هم این لینک رو ببین.

توی مقاله قبلی یه نکته در مورد Widget ها در Flutter گفتیم.گفتیم که Widget ها Immutable هستن و اون ها رو به const constructor مجهزشون کنید و هرجایی که امکانش بود ازشون object (نمونه) های const بسازید.

اگه source code فلاتر رو نگاه کنید میبینید که بالا سر کلاس ویجت @immutable قرار داره.

حالا دلیل اینکه چرا توسعه دهندگان فلاتر ویجت ها رو Immutable در نظر گرفتن خارج از بحث این مقاله هست و در آینده و مقاله های تخصصی تر بهش میپردازیم. الان هدفمون استفاده کاربردی از این ویژگی هست.

?پس طبق عکس بالا و تعریف داکیومنت از Widget باید بگیم که کلن Widget داخل فلاتر immutable هست, بعضی جاها دیدم که میگن Stateless ها Immutable هستن و Stateful ها mutable. نه اصلن این حرف رو قبول نکنید, کلن ویجت ها از هر نوعی که میخوان باشن Immutable هستن و Stateful ها یه State دارن که اون mutable هست.

?توی ورژن های قبلی فلاتر اگه یه ویجت میساختیم و داخلش فیلد هایی استفاده میکیردیم که final نبودن(یعنی قاعده ی Immutable رو رعایت نمکیردیم) بهمون Warning میداد و میگفت که ویجتی که ساختی Immutable نیست.حالا توی ورژن جدید فلاتر یعنی 2.5 به یه چیز دیگه هم گیر میده و اگه ویجتی که میسازی const constructor نداشته باشه به این هم Warning میده.

???پس یکبار دیگه تکرار میکنم که همه ویجت ها رو (همه همه همه) Immutable بسازید و واسشون const constructor بگذارید و هرجایی که امکانش بود ازشون object های const بسازید.هر جایی هم که امکانش نبود و یا نیاز داشتیم که یه object جدید بسازیم که new میکنیم.

خب بعد از این مقدمه بریم سر اصل بحثمون...

قبل از این گفتیم که دو شرط اصلی و اساسی که یک کلاس لازم داره که بتونه const constructor داشته باشه این هست که :

  • همه instance field هاش final باشن یا به عبارتی immutable باشه.
  • و constructor مورد نظر body نداشته باشه.

حالا یه تبصره به این شروط اضافه میکنیم.

?اگه یه کلاسی داشته باشیم که داره از یه کلاس دیگه ارث بری میکنه , شرط اینکه کلاس فرزند بتونه const constructor داشته باشه این هست که کلاس پدر همه شروط بالا رو داشته باشه و علاوه بر اون constructor کلاس پدر const باشه.

خب همون طور که طبق عکس بالا میبینید Base Class ویجت ها در فلاتر هم immutable هست و هم از const constructor بهره میبیره در نتیجه این امکان رو به کلاس های فرزند میده که بتونن const constructor داشته باشن.

?پ.ن: اکثر abstract class ها (base classها) که داخل فلاتر میبینیم اگه دقت کنید میبینید که یه const constructor که معمولن هیچ پارامتری هم نگرفته و خالی هست دارن(مثل همین بیس کلاس ویجت). علتش هم این هست که به کلاس های فرزند این امکان رو بده که بتونن const constructor داشته باشن.


یه مثال از چیزایی که تا الان گفتیم بزنیم:

همون طور که میبییند یه ویجت ساده ساختم و داخلش یه فیلد با نام title تعریف کردم که final نیست. و در نتیجه بهم تذکر داده که کلاست immutable نیست.

متنش هم به این شکل هست:

This class (or a class that this class inherits from) is marked as '@immutable' , but one or more of its instance fields aren't final.

خب حالا کلاسم رو immutable میکنم (یعنی title رو final میکنم) ولی قبل از constructor از const استفاده نمکینم.

اینجا باز هم بهم Warning میده و میگه که از const constructor استفاده نکردی و این همون هشداری هست که گفتم توی ورژن جدید فلاتر بهمون میده.

Prefer declaring const constructor on @immutable classes.

اگه روی لینک مربوط به این Warning هم بزنیم توضیحات زیر رو میتونیم ببینیم.

حالا یه دونه Stateful میسازم و فیلد title رو از حالت final درمیارم:

اینجا باز هم اون Warning که میگه کلاست immutable نیست رو میبینیم.

This class (or a class that this class inherits from) is marked as '@immutable' , but one or more of its instance fields aren't final.

و اگه title رو final کنم و از const استفاده نکنم.

باز هم Warning مربوط به const constructor رو خواهیم دید.

Prefer declaring const constructor on @immutable classes.

پس به صورت عملی هم دیدیم که از این نظر یعنی immutable بودن و const constructor هیچ تفاوتی بین Sateless و Stateful نیست.


حالا ممکنه سوالاتی پیش بیاد:

❓وقتی که یه ویجت رو به const constructor مجهز کردیم , ایا همه جا موقع ساخت object ازش باید از const استفاده کنیم؟

نه... نه امکان چنین کاری هست و نه بعضی جاها منطقی.

❓چرا امکانش نیست که همه جا برای ساخت object از ویجت هامون از const استفاده کنیم؟

یه دلیل اینکه همون طور که توی مقاله های قبلی گفتیم وقتی که یه const constructor داریم و میخوایم با استفاده از اون یه object بسازیم باید تمام وروردی هایی که بهش پاس میدیم compile-time constant باشن که در عمل این امکان پذیر نیست , یعنی در عمل بعضی مواقع نمیتونیم ورودی های const برای یک constructor تامین کنیم.

مثال : مثلن شما ویجت Text رو در نظر بگیرید که قراره یه متنی رو نمایش بده و این متن قراره از سرور بیاد (مثلن عنوان کالا در یه اپ فروشگاهی یا متن پیام در یه اپ چت یا ... هست).

عملیات دریافت متن از سرور توی run-time انجام میشه در نتیجه متن مورد نظر نمیتونه یه compile-time constant باشه پس در نتیجه اینجا نمیتونیم از ویجت مورد نظرمون یه const object بسازیم.(چرا که برای ساختن یه const object باید تمام ورودی هایی که به constructor پاس میدیم compile-time constant باشن.)

علاوه بر این وقتی که موقع ساخت object از یه ویجت توی widget tree از const استفاده میکنیم موقع rebuild شدن اون قسمت از درخت, اون ویجتی که با const ساختیمش دیگه rebuild نمیشه. یعنی بار اول که ساخته شد و build شد دیگه تا آخر سرجاش هست و rebuild نمیشه.خب بعضی جاها نمیخوایم این اتفاق بیوفته و میخوایم ویجتمون rebuild شه.

حالا بریم با مثال هایی ببینیم که کجاها توی فلاتر از const استفاده کنیم و نتیجه عملی این استفاده چی هست.

مثال1)

توی کدهاتون حتمن خیلی جاها نیاز دارین که از padding یا margin استفاده کنین و برای این کار از EdgeInset استفاده میکنین.تا اینجا مشکلی نیست و خیلیم عالی.حالا فرض کنید که یه اپ داریم که از 10 تا صفحه تشکیل شده و توی هر صفحه قراره از دو تا padding با مقدار مشخص استفاده کنیم:

EdgeInsets.symmetric(horizontal: 16 , vertical: 32);
EdgeInsets.only(top: 48);

فرض کنید از این دو مقدار توی 10 صفحه اپ و هر صفحه هم یکبار قراره استفاده بشه.

حالا دو حالت در نظر میگیریم:

  • حالت اول توی همه جاهایی که میخوایم از اینها استفاده کنیم قبلش new میگذاریم (یا هیچی نمیگذاریم)
  • حالت دوم قبلشون const میگذاریم.

قبل از اینکه این دو حالت رو با هم بررسی کنیم یه سری به کلاس EdgeInset بزنیم و ببینیم که چی داخلش داره.

این کلاس یه کلاس immutable هست که 4 تا instance field داره (left , top , right , bottom) , همه constructor هاش هم const هستن و همه constructor ها در نهایت این 4 فیلد رو مقدار دهی میکنن.

خب حالا حالت اول رو بررسی کنیم:

توی این حالت هر جایی که به یه EdgeInsets.symmetric(horizontal: 16 , vertical: 32); نیاز داریم برامون یه دونه object جدید از EdgeInset با مقادیر (left = 16 , right = 16 , top = 32 , bottom = 32) میسازه .هر بار هم که به (EdgeInsets.only(top: 48 یه object جدید با مقادیر (left= right=bottom=0 , top=48)میسازه.

پس اگه توی 10 تا صفحه و هر صفحه یک بار بهشون نیاز داشته باشیم در مجموع 20 تا object از کلاس EdgeInset میسازه و توی رم قرار میده.

داستان به اینجا ختم نمیشه و اگه شما یک صفحه رو ببینید و دوباره باز کنید (pop , push) دوباره یه object جدید ساخته میشه و از قبلیا استفاده نمیکنه.(البته اینجا زباله جمع کن دارت میاد و object های بلااستفاده رو جمع میکنه ولی خب باز هم همین طوری الکی داره object جدید ساخته میشه.)

حالا ببینیم توی حالت دوم چی میشه:

اینجا چون از const استفاده کردیم.

آقای کامپایلر میاد وسط میدون.

وقتی میبینه که ما توی کدمون const EdgeInsets.only(top: 48) رو درخواست دادیم میره و یه object از این کلاس با مقادیر (left=right=bottom= 0 , top=48)میسازه و توی حافظه ذخیره میکنه و بعد از اون توی برنامه هر جایی که نیاز به یه object از این کلاس با این مقادیر بود(که قبلش هم const اومده بود) از همین چیزی که قبلن ساخته استفاده میکنه.پس در نتیجه از ساخته شدن object های بیشمار و مصرف بیهوده فضای رم جلوگیری میشه.


مثال2)

توی کدهاتون خیلی جاها نیاز دارین که از ویجت SizedBox استفاده کنید, مثلن خیلی جاها توی یه column یا row نیازه که بین دو تا ویجت یه فاصله مشخص ایجاد کنیم. خب SizedBox هم که یه ویجته و مثل همه ویجت های دیگه const constructor داره. پس اینجور مواقع هم از const استفاده کنید.

توی کد بالا چون که برای Text هام از String literal استفاده کردم که خودشون compile-time constant هستن میتونم Text ها رو هم const کنم.

حالا چون همه ویجت هایی که توی این لیست (لیست چیلدرن) قرار دارن const هستن میتونیم پشت لیست یه const بذاریم و همه const ها رو از پشت ویجت ها برداریم.

و در این حالت هم همه ویجت هایی که توی لیست هستن از حالت const استفاده میکنن.

مثلن کد زیر رو ببینید:

وقتی که قبل از Center از const استفاده کردیم دیگه نیاز نیست که برای Text و TextStyle هم از const استفاده کنیم و خودش این کار رو میکنه (یعنی الان Text و TextStyle هم const میشن)

یعنی کد بالا با کد زیر فرقی نداره.

میدونید چرا این اتفاق میوفته؟

گفتیم که وقتی میخایم از یه کلاس یه const object بسازیم باید تمام ورودی هایی که به constructor پاس میدیم compile-time constant باشه. خب اینجا قبل Center از const استفاده کردیم. ورودی Constructorاش هم که Text هست که باید const باشه (که بدون اینکه ما بگذاریم خودش اینکار رو میکنه) و ورودی Text هم که یه متن و همین طور TextStyle هستن, پس اگه قبل TextStyle هم const نذاریم خودش اینکار رو میکنه.

حالا فرض کنید قبل Center از const استفاده کردیم ولی Text قابلیت const بودن نداره مثل کد زیر:

میبینید که در این حالت بهمون ارور میده. چون که Text دیگه نمیتونه const باشه(متن Text داره از widget گرفته میشه که توی run-time مشخص میشه و دیگه compile-time constant نیست) و در نتیجه Center هم نمیتونه const باشه (چون که ورودیش const نیست), در این حالت فقط همون TextStyle رو const میکنیم.

مثال3)

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

همین طور که میبینید این فرم داخل یه State قرار گرفته و به دلایل مختلف ممکنه که rebuild بشه و با هر بار rebuild شدن تمام ویجت هایی که داخل متد build قرار گرفتن یه بار دیگه از اول ساخته میشن و rebuild میشن.

حواستون باشه با هر بار rebuild شدن متد build تمام ویجت ها دوباره ساخته میشن و متد build اشون صدا زده میشه.

خب اینجا یه سری از ویجت ها مثل ویجت Text که داره عنوان رو نشون میده نیاز نیست هر بار که State مورد نظر rebuild میشه دوباره از اول ساخته بشه. پس میشه با قرار دادن یه const قبل ویجت Text که داره متن Sign In رو نشون میده از این مورد جلوگیری کرد.

ولی نه صبر کنید!!!

قبل Text یه const گذاشتم ولی داره بهم ارور میده.

❓میبینید؟ چرا اینطوری شد؟

مربوط به style هست , style ای که بهش دادیم یه compile-time constant نیست.

❓خب پس چکار کنیم؟ از یه طرف نمیخوایم با هر بار rebuild شدن این State ویجت Text یه بار از اول ساخته بشه(چون که یه عنوان هست و قرار نیست تغییر کنه) و از طرفی هم نمیتونیم قبلش const بگذاریم.

❓نظر شما چیه؟

من میگم میتونیم این ویجت رو کلن اکسترکت کنیم و ی ویجت جدید با نام SignInFormTitle بسازیم و اون رو const کنیم.

الان یه ویجت ساختیم که وظیفش اینه که عنوان فرم رو نشون بده (SignInFormTitle) و قبلش هم از const استفاده کردیم در نتیجه هر بار که State مورد نظر rebuild بشه این ویجت دوباره از اول ساخته نمیشه و در نتیجه rebuild هم نمیشه.

❓دقیقن چه اتفاقی میوفته؟

یه ویجت به اسم SignInFormTitle داریم که برای استفاده ازش از const استفاده کردیم.پس کامپایلر یه object ازش میساره و هر جا که خواستیم بهمون میده. حالا بار اول اول که این کد ها اجرا میشن متد build این ویجت اجرا میشه و اون Text که const هم نیست ساخته میشه. ولی دفعه های بعدی که درخت rebuild میشه دیگ این ویجت rebuild نمیشه و متد build اش صدا زده نمیشه.(این موضوع که بار اول اول که کدها اجرا میشن متد build ویجت SigninFormTitle اجرا میشه و بعد از rebuild شدن درخت دیگه اجرا نمیشه به دلیل کاری هست که فریمورک فلاتر با ویجت های const میکنه که توضیحش از بحث این مقاله خارج هست.)


??سعی میکنیم در آینده با مثال های بیشتر این مقاله رو تکمیل تر کنیم.??

??امیدوارم از مجموعه مقاله Dart Const لذت برده باشید و بتونید توی پروژه هاتون به خوبی و با درک عمیق ازش استفاده کنید.??