Stateful یا Stateless مسئله این است ؟؟ ??


سلام دوستان امیدوارم حالتون خوب باشه.

اول از همه چیز بگم که از هم اکنون میتوانید ویدیوهای مربوط به آموزش برنامه نویسی مخصوصا فلاتر و دارت رو در کانال یوتوب من دنبال کنید. لینکشم میزارم همین پایین :

https://www.youtube.com/c/FlutterStan

این مقاله راجع به دوتا از مهم ترین و پایه ای ترین ویدجت ها توی فلاتر هست.

پس اگه با فلاتر آشنایی دارین همراه من بیاین تا این دوتا ویدجت رو بررسی کنیم.

قبل اینکه بخوام راجع به این دوتا ویدجت صحبت کنم لازمه که اصن بدونیم خود State چی هست اصن ؟؟

State :

استیت اطلاعاتی است که هنگام ساخت یک ویدجت به طور همزمان قابل خواندن است و ممکن است در طول عمر ویدجت(ینی تا زمانی که اون ویدجت در widget tree وجود داره و حافظه بهش اختصاص داده شده) تغییر کند. به زبون خیلی ساده بخوایم بگیم استیت میشه وضعیت و حالت اون ویدجت که ممکنه این وضعیت تغییر کنه.

خب حالا که فهمیدیم استیت چی هست بریم سراغ کار اصلیه خودمون. اول با تعریف Stateless شروع میکنیم.

Stateless :

همینجور که از اسمش پیداس این ویدجت نیاز به استیت تغییر پذیر نداره. این ویدجت زمانی مفیده که اون بخش از رابط کاربری که میخوایم پیاده سازیش کنیم به چیز دیگری به جز اطلاعات پیکربندی(configuration) موجود در خود شی و BuildContext بستگی نداشته باشه. به عبارت ساده تر بخشی از رابط کاربری میشه، که به صورت ثابت هست و قرار نیس در اون بخش از رابط کاربری تغییری ایجاد بشه. برای بخش هایی که ممکنه به صورت پویا تغییر کنه از Stateful باید استفاده کنیم.

این ویدجت به صورت immutable(غیر قابل تغییر) پیاده سازی شده است ینی هیچ کدام از خواص(properties) آن قابل تغییر به خودیه خود نیست مگه اینکه یه اتفاقی خارج از اون ویدجت باعث بشه دوباره اون ویدجت ساخته بشه. ینی باعث بشه که دوباره از اون ویدجت نمونه گیری(instantiation) بشه.

این ویدجت(و همینطور Stateful) یک متدی داره به اسم build() که وظیفش نشون دادنه بخشی از رابط کاربری هست. ینی ما چیزی که قراره دیده بشه رو باید توی این متد بنویسیم.

توی ویدجت Stateless این متد build فقط در ۳ حالت صدا زده میشه :

۱) زمانی که برای اولین بار ویدجت در widget tree قرار میگیره

۲)زمانی که ویدجت والدش پیکربندی(configuration) خودش رو تغییر میده

۳)زمانی که یک InheritedWidget به تغییرات بستگی دارد که اینجور باعث میشه این ویدجت Stateless ما دوباره ساخته بشه و چون دوباره داره نمونه گیری میشه متد build() اون دوباره صدا زده میشه

خب پس با این چیزایی که گفتم تا جایی که میتونید بخش های ثابت رابط کاربریتون رو حتما توی Stateless بزارین که پرفورمنس بهتری داشته باشید.(در آخر یه مثال میزنم که این پرفورمنس رو بهتر درک کنیم)

در پایین هم یه مثال خیلی کوچیک از Stateless میزنم :

class Test extends StatelessWidget {   
    const Test({ Key key }) : super(key: key);   
    @override  
    Widget build (BuildContext context) {    
            return Container ( color: Colors.red );   
     }
 }

Stateful :

این ویدجت برعکسه Stateless هس ینی این ویدجت به صورت mutable(قابل تغییر) پیاده سازی شده و خواص آن قابل تغییره. در این ویدجت استیت به صورت mutable(قابل تغییر) هست و میتونیم اون رو عوض کنیم.(ولی چجوری ؟؟)

زمانی که فریمورک یه ویدجت Stateful میسازه در واقع یه استیت آبجکت(این استیت آبجکت مهمه چون باش کار داریم) میسازه. این آبجکت(یا همون شی) جاییه که تمام استیت تغییر پذیر اون ویدجت در اون نگه داری میشه.اگه نخوام با این تعریفا گیجتون کنم در واقع همون شی ای هست که دارای استیت هس همین.

این ویدجت زمانی مفیده که اون بخش از رابط کاربری ما به صورت داینامیک داره تغییر میکنه. چون وقتی که رابط کاربری داره تغییر میکنه ینی داره تغییر حالت میده، و ما با ویدجت Stateful میتونیم استیت رو تغییر بدیم.

ما با استفاده از متد setState() میتونیم استیت رو عوض کنیم اما نکته مهم اینجاس که چه اتفاقی میوفته وقتی ما این متد رو صدا میزنیم ؟؟

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

خب حالا اگه یکم فک کنیم میبینیم که شاید تو نگاه اول این تغییر استیت چیز باحالی به نظر بیاد ولی اگه قرار باشه که با هر بار تغییر استیت این متد از اول فراخوانی بشه چیز زیاد باحالی هم نیس و ممکنه خیلی واسه پرفورمنس برناممون بد بشه پس چکار باید بکنیم ؟؟

  • اول اینکه از setState() در جای خودش استفاده کنین.

ببینید setState() قراره حالت برنامه رو عوض کنه که توی رابط کاربری یه اتفاقی بیوفته مثلا یه CheckBox تیک بخوره برای عوض کردن متغییر هایی که اصلا هیچ تاثیری در ظاهر برنامه نمیزارن به هیچ عنوان از setState() استفاده نکنین

  • دوم اینکه هیچ کس شما رو مجبور نکرده که همه ویدجتاتون رو توی یه کلاس بنویسین ??

ببینید شما باید ویدجت هاتون رو از هم جدا کنین این کار چندین مزیت داره : یکی اینکه خوانایی کدتون میره بالاتر بعدیش اینکه وقتی میخواین تغییری توی کدتون بدین به خدا راحت تر میتونین این کارو بکنین??

و مهم ترین مزیت اینه که فقط اون ویدجتی که نیاز به تغییر داره رو میتونین با setState() تغییر بدین(مثالش رو در آخر مقاله میزنم که ینی چی این کار)

  • سوم اینکه استیت منیجمنت ها رو در آغوش بگیرین ??

ایشاالله سعی میکنم تو یه مقاله دیگه به استیت منیجمنت ها هم بپردازم


یه مثال ساده هم از Stateful این پایین میزنم :

class Test extends StatefulWidget {   
      const Test ({ Key key }) : super(key: key);    

      @override 
       _TestState  createState() => _TestState(); 
}  
class _TestState extends State<Test> {   
       @override  
        Widget build (BuildContext context) {     
                  return Container(color: Colors.red );   
         }
 }

خب بچه هایی که از سمت android میان در جریانن که activity ها یه چرخه حیات(life cycle) داشتن لازم به ذکره که Stateful هم واسه خودش یه چرخه حیات داره که الان میگم خدمتون به چه صورته :


createState() :

این متد زمانی صدا زده میشه که فریمورک میخواد اون ویدجت استیت فول رو شروع کنه بسازه سریعا این متد صدا زده میشه.این متد واسه ما یه استیت تغییر پذیر برای اون ویدجت درست میکنه(یه استیت آبجکت).

@override         
_TestState  createState() => _TestState();




mounted == ture :

زمانی که BuildContext اختصاص داده میشه به اون استیت صدا زده میشه. اگه شما قبل از اینکه mounted = true بشه ()setState کنین به ارور میخورین. استیت آبجکت تا زمانی که فریمورک متد dispose() رو اجرا نکنه mounted میمونه.




initState() :

این اولین متدیه که بعد از اینکه ویدجت به صورت کامل ساخته شد صدا زده میشه(البته بعد از سازنده کلاس)

این متد فقط و فقط یک بار صدا زده میشه و حتما هم باید ()super.initState را صدا بزند.

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

@override
initState() {
  super.initState();
  // your code here
}




didChangeDependencies() :

زمانی که تغییری توی وابستگی(dependency) استیت آبجکت ایجاد بشه این متد صدا زده میشه.

این متد به صورت خودکار بعد از initState هم صدا زده میشه.

@protected
@mustCallSuper
void didChangeDependencies() { }




build() :

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

این متد توسط فریمورک در زمان های مختلفی صدا زده میشه :

۱) بعد از initState

۲)بعد از didUpdateWidget (که پایین تر توضیحش میدم)

۳)بعد از اینکه setState انجام شد

۴)بعد از اینکه وابستگی(dependency) استیت آبجک تغییر کرد

۵)بعد از غیر فعال کردن اون استیت آبجکت(deactivate) و دوباره درج کردن اون استیت آبجکت در درخت در مکانی دیگه

@override  
Widget build ( BuildContext context) { 
    return Container(color: const Color( 0xFFFFE306 ) );  
 }




didUpdateWidget()

زمانی که پیکربندی(configuration) اون ویدجت تغییر کنه صدا زده میشه.

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

میتونین این متد رو override کنین تا هر وقت ویدجت تغییر کرد بتونین خبر دار بشین و یه کاری انجام بدین.

فریمورک همیشه بعد از این صدا زدن این متد به صورت خودکار متد build هم صدا میزنه پس نیاز نیس توی این متد از setState() استفاده کنید.

@mustCallSuper
@protected
void didUpdateWidget(covariant T oldWidget) { }




setState() :

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

فراخوانی setState به فریمورک اعلام می کند که وضعیت داخلی این شی به گونه ای تغییر کرده که ممکنه رابط کاربری در این زیر درخت را تحت تأثیر قرار بده ، که باعث می شه فریمورک اون استیت آبجکت رو دوباره build کنه.

اگر فقط وضعیت را مستقیماً بدون فراخوانی setState تغییر بدهید ، ممکنه این فریمورک برنامه ریزی برای build نداشته باشد و ممکنه رابط کاربری این زیرشاخه به روز نشه تا وضعیت جدید را نشون بده.

setState(() { _myState = newValue });




deactivate() :

زمانی که استیت از درخت پاک شه صدا زده میشه. اما ممکنه قبل از پایان تغییر فریم فعلی مجدداً درج شه.

فریمورک این متد را هر زمان که این استیت آبجکت را از درخت خارج کند فراخوانی می کنه. در بعضی موارد ، فریمورک مجدداً استیت آبجکت را در قسمت دیگری از درخت قرار می دهد. اگر چنین اتفاقی بیفتد ، این فریمورک تضمین می کند که build را فراخوانی می کند تا به استیت آبجکت فرصتی برای سازگاری با مکان جدید خود در درخت بدهد.

@protected
@mustCallSuper
void deactivate() { }




dispose() :

زمانی که آبجکت از درخت به صورت دائم پاک شه و حافظه اختصاص داده به اون آزاد شه صدا زده میشه.

فریمورک این متد را زمانی فراخوانی می کنه که این استیت آبجکت هرگز دوباره ساخته نشه. پس از اینکه فریمورک dispose رو فراخوانی کرد ، استیت آبجکت unmounted در نظر گرفته می شود و ویژگی mounted برابر با false میشود. در این مرحله فراخوانی setState با خطا مواجه میشه. هیچ راهی برای بازپس گیری یک استیت آبجکت که dispose شده است وجود ندارد.

زیر کلاس ها باید این متد را override کنند تا منابع ذخیره شده توسط این شی آزاد شود (به عنوان مثال ، جلوی انیمیشن های فعال را بگیرید).

@protected
@mustCallSuper
void dispose() {}


خب اینم از چرخه حیات Stateful حالا از هر کدوم از این ویدجتا یه سری مثال میزنم که با استفاده از یه کدوم از این دوتا ویدجت پیاده سازی شدن :

Stateless Widgets :

AlertDialog()

Card()

Container()

Icon()

FloatingActionButton()

Text() , ......


Stateful Widgets :

AppBar()

BottomNavigationBar()

BottomSheet()

CheckBox()

Slider()

TextField() , ....


و اما اون نکته ای که راجع به پرفورمنس میخواستم بگم :

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

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

این اسلایدر رو خودم کاستوم درست کرده بودم و هر بار که صفحش عوض میشد من setState میکردم و همه ویدجت هامم تو یه کلاس و یه متد build بود ینی هر بار که صفحه اسلایدر عوض میشد من یه بار کلا همه ویدجتا رو بیلد میکردم. بعد دیدم که اپم لگ میزنه حتی تو حالت ریلیز تا اینکه فهمیدم قضیه چیه.

اوومدم تنها چیزی که تو صفحم داینامیک بود ینی همون اسلاید رو بردم تو یه کلاس Stateful جدا و اون کلاس اصلیم که همه ویدجتام توش بود رو Stateless کردم و setState رو فقط تو کلاس اسلایدر صدا میزدم که اینجور باعث میشد فقط متد build اون کلاس اسلایدر صدا زده بشه و فقط اون ویدجت دوباره ساخته شه نه همه ویدجت هام و مشکل لگ خوردن حل شد.

پس ویدجت هاتون رو از هم جدا کنید و اون ویدجتی که قرار داینامیک باشه رو فقط Stateful کنید.??


سخن آخر :

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

و از دوستانی هم که مشوق این مقاله بودن تشکر میکنم.❤️❤️

لینک کانال یوتوب من لطفا سر بزنید : https://www.youtube.com/c/FlutterStan