setState() — متد کلیدی State در Flutter است که فریمورک را از تغییر دادهها مطلع کرده و بازسازی رابط کاربری را راهاندازی میکند. طبق مستندات رسمی Flutter (Flutter.dev, 2026)، setState مکانیزم اصلی واکنشگرایی در StatefulWidget است: بدون فراخوانی آن، UI از تغییرات فیلدهای State مطلع نمیشود و در وضعیت قبلی باقی میماند. این متد یک callback از نوع VoidCallback دریافت میکند که درون آن توسعهدهنده فیلدهای قابل تغییر را اصلاح میکند و پس از آن Flutter به طور خودکار build را برای بازسازی ویجت فراخوانی میکند.
نکات اصلی
setState() — متد داخلی کلاس State در Flutter است که برای اطلاعرسانی به فریمورک درباره تغییر وضعیت داخلی ویجت و نیاز به بازسازی UI طراحی شده است. بدون فراخوانی setState، Flutter از تغییرات مطلع نمیشود — حتی اگر فیلدهای State اصلاح شده باشند، رابط کاربری تا下一次 بازسازی اجباری توسط والد بدون تغییر میماند.
امضای متد: void setState(VoidCallback fn). callback به صورت همزمان درون setState اجرا میشود و فقط پس از اتمام آن، State به عنوان dirty علامتگذاری میشود. این تضمین میکند که تمام تغییرات قبل از بازسازی به صورت اتمی اعمال شوند. طبق Dart Language Specification (Dart Team, 2026)، اتمی بودن setState از شرایط مسابقه جلوگیری میکند که در آن build میتوانست وضعیت نیمهبهروز را ببیند.
setState آرگومان نمیگیرد، مقداری برنمیگرداند و نمیتواند بازنویسی شود. این یک متد نهایی (sealed) از کلاس State است. توسعهدهنده نمیتواند رفتار آن را تغییر دهد — فقط میتواند طبق هدف از آن استفاده کند. تلاش برای فراخوانی setState خارج از State (مثلاً از کلاس دیگر) غیرممکن است، زیرا متد در کلاس State اعلام شده است.
یک باور غلط رایج — این که setState خودش وضعیت را تغییر میدهد. این درست نیست. setState فقط callback ارسال شده را فراخوانی میکند (که در آن توسعهدهنده فیلدها را تغییر میدهد) و سپس به فریمورک درباره نیاز به build سیگنال میدهد. callback اجباری است — ارسال null یا callback خالی باعث خطا میشود.
مکانیزم کار setState() را میتوان به چهار مرحله تقسیم کرد. مرحله اول — فراخوانی متد با callback. مرحله دوم — اجرای همزمان callback که درون آن فیلدهای State تغییر میکنند. مرحله سوم — State در فیلد ویژه _dirty به عنوان dirty (کثیف) علامتگذاری میشود. مرحله چهارم — در پایان میکروتسک (microtask) جاری، Flutter تمام عناصر dirty را پیمایش کرده و build آنها را به ترتیب ظهور در درخت فراخوانی میکند.
جزئیات مهم: setState بلافاصله build را فراخوانی نمیکند. Flutter از استراتژی بهروزرسانی دستهای استفاده میکند: تمام عناصر dirty جمعآوری شده و در یک فریم بازسازی میشوند. این بدان معناست که اگر setState چندین بار در یک بلوک همزمان فراخوانی شود، build فقط یک بار — پس از اتمام تمام تغییرات — اجرا میشود. چنین بهینهسازیای از بازسازیهای متعدد در یک فریم جلوگیری میکند.
طبق گفته Flutter Engine Team (Google, 2025)، مکانیزم پرچمهای dirty بر اساس عبور از BuildOwner._dirtyElements است. هر StatefulElement dirty به لیست اضافه شده و در مرحله بهروزرسانی فریم پردازش میشود. اگر ویجت قبل از پردازش از درخت حذف شده باشد، به طور خودکار از لیست عناصر dirty حذف میشود.
نمونه پایه setState() با افزایش شمارنده. استفاده صحیح را نشان میدهد: تغییر فیلد درون callback:
class _CounterState extends State<CounterWidget> {
int _count = 0;
void _increment() {
setState(() {
_count++; // جهش فیلد درون callback
});
}
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: _increment,
child: Text('$_count'),
);
}
}
نمونه با فیلد متنی و کنترلر — setState() برای مدیریت visibility رمز عبور:
class _PasswordFieldState extends State<PasswordField> {
bool _obscured = true;
final _controller = TextEditingController();
void _toggleVisibility() {
setState(() {
_obscured = !_obscured;
});
}
@override
Widget build(BuildContext context) {
return TextField(
controller: _controller,
obscureText: _obscured,
decoration: InputDecoration(
suffixIcon: IconButton(
icon: Icon(_obscured ? Icons.visibility : Icons.visibility_off),
onPressed: _toggleVisibility,
),
),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
}
در این مثال setState() فقط فیلد بولی _obscured را تغییر میدهد که باعث بازسازی TextField با آیکون جدید و حالت نمایش میشود. کنترلر متنی بازسازی نمیشود — یک بار در initState مقداردهی شده و در dispose آزاد میشود.
اگر نیاز به تغییر چند فیلد دارید، تمام تغییرات درون یک setState انجام میشوند. این تضمین میکند که build وضعیت سازگاری را میبیند:
setState(() {
_isLoading = false;
_items = newItems;
_error = null;
});
سه فیلد در یک callback تغییر میکنند — build یک بار اجرا شده و تمام تغییرات را همزمان میبیند. اگر هر فراخوانی یک setState جداگانه بود، build همچنان یک بار به دلیل پردازش دستهای عناصر dirty اجرا میشد.
یکی از مهمترین نکات setState() — رفتار آن با عملیات ناهمزمان. callback setState به صورت همزمان اجرا میشود، اما اگر درون آن await فراخوانی شود، کد بعد از await پس از اتمام کار setState اجرا خواهد شد. این بدان معناست که تغییرات فیلدها بعد از await توسط setState جاری گرفته نمیشوند.
رویکرد صحیح: عملیات ناهمزمان خارج از setState انجام میشود و setState پس از اتمام آن فراخوانی میشود. تمام کد بین دریافت نتیجه و فراخوانی setState در زمینه همزمان پس از await اجرا میشود:
// درست: await خارج از setState
Future<void> _loadData() async {
final result = await ApiService.fetchData();
setState(() {
_data = result;
_isLoading = false;
});
}
// غلط: await درون setState — تضمین بهروزرسانی وجود ندارد
void _loadDataWrong() {
setState(() async {
_data = await ApiService.fetchData(); // setState قبل از اتمام await برمیگردد
_isLoading = false; // این کد توسط setState گرفته نمیشود
});
}
طبق مستندات Flutter (Dart async patterns, 2026)، ارسال async-callback به setState یک ضدالگو است، زیرا setState منتظر VoidCallback (تابع همزمان) است، در حالی که تابع async یک Future برمیگرداند که نادیده گرفته میشود. تغییرات پس از اولین await در چنین callbackی به درستی توسط فریمورک پردازش نخواهند شد.
قبل از فراخوانی setState() پس از عملیات ناهمزمان همیشه mounted را بررسی کنید:
if (mounted) {
setState(() => _data = data);
}
اگر ویجت در حین اجرای عملیات ناهمزمان از درخت حذف شده باشد، mounted false میشود و setState فراخوانی نمیشود. این از استثنا و نشت منابع جلوگیری میکند.
setState() — مکانیزم راحت اما بالقوه پرهزینه، اگر بدون فکر استفاده شود. هر بار فراخوانی setState کل ویجت و تمام فرزندان آن (اگر const نباشند) را بازسازی میکند. در درختهای عمیق یا با فراخوانیهای مکرر، این میتواند منجر به افت FPS شود.
استراتژیهای اصلی بهینهسازی: به حداقل رساندن محدوده بازسازی (انتقال بخشهای متغیر UI به StatefulWidgetهای جداگانه)، استفاده از const برای فرزندان غیرقابل تغییر و اجتناب از فراخوانی setState در ویجتهای والد اگر فقط یک جزئیات کوچک UX تغییر کرده است. اگر وضعیت با فرکانس بالا بهروز میشود (انیمیشن، جریان داده)، AnimatedBuilder یا ValueListenableBuilder را در نظر بگیرید.
طبق Flutter Performance Best Practices (Flutter.dev, فوریه 2026)، پروفایلگیری برنامههای واقعی نشان میدهد که تا 40٪ از تمام فراخوانیهای setState را میتوان با ویجتهای const فرزند یا builderهای واکنشگرا (StreamBuilder, FutureBuilder) جایگزین کرد. این میانگین زمان ساخت فریم را 15–25٪ کاهش میدهد.
| سناریو | جایگزین | مزیت |
|---|---|---|
| انیمیشن | AnimatedBuilder | فقط ویجت متحرک را بازسازی میکند |
| جریان داده | StreamBuilder | به هر عنصر جریان واکنش نشان میدهد |
| نتیجه آینده | FutureBuilder | وضعیتهای بارگذاری/خطا را مدیریت میکند |
| مقدار محلی | ValueListenableBuilder | به تغییر یک مقدار واکنش نشان میدهد |
با وجود تطبیقپذیری setState()، در پروژههای بزرگ عمدتاً برای وضعیت محلی استفاده میشود. برای وضعیت سراسری یا اشتراکی از راهحلهای تخصصی استفاده میشود که هر کدام setState را جایگزین یا میپوشانند.
Provider از ChangeNotifier + notifyListeners به عنوان معادل setState استفاده میکند، اما با امکان اشتراک چند ویجت. Bloc از Streams استفاده میکند — وضعیت با افزودن رویدادها به StreamController تغییر میکند. Riverpod رویکردها را ترکیب میکند و مدیریت محلی (StateProvider) و ناهمزمان (AsyncNotifier) را بدون وابستگی به StatefulWidget ارائه میدهد. هر سه رویکرد نیاز به فراخوانی دستی setState را از بین میبرند — بهروزرسانی UI با تغییر دادهها به طور خودکار رخ میدهد.
طبق Flutter Community Survey 2025 (Flutter Foundation, دسامبر 2025)، 74٪ از توسعهدهندگان حداقل از یک ابزار مدیریت وضعیت علاوه بر setState استفاده میکنند. در عین حال، 92٪ همچنان از setState برای دادههای محلی فیلد متنی، چکباکس یا شمارنده ساده استفاده میکنند — این بهترین روش (best practice) محسوب میشود.
اولین و خطرناکترین اشتباه — فراخوانی setState پس از dispose. عملیات ناهمزمان در initState شروع شد، کاربر صفحه را ترک کرد، ویجت حذف شد و callback عملیات ناهمزمان setState را فراخوانی میکند — برنامه با استثنا crash میکند. راهحل — همیشه قبل از فراخوانی mounted را بررسی کنید.
دومین اشتباه — فراخوانی setState درون build. این منجر به حلقه بینهایت میشود: build → setState → dirty → build → setState → ... Flutter چنین فراخوانی را مسدود نمیکند (شما StackOverflowError دریافت خواهید کرد). setState فقط در پاسخ به رویداد (فشار دکمه، اتمام Future، دریافت داده از جریان) قابل فراخوانی است.
سومین اشتباه — تغییر فیلدهای State بدون فراخوانی setState. توسعهدهنده _count++ مینویسد و انتظار دارد UI بهروز شود. Flutter نمیتواند تغییرات فیلدها را به طور خودکار ردیابی کند — به سیگنال صریح از طریق setState نیاز دارد. این تفاوت اساسی با فریمورکهای واکنشگرا مانند Vue.js است، where تغییر دادهها به طور خودکار بهروزرسانی را راهاندازی میکند.
چهارمین اشتباه — فراخوانی setState با callback ناهمزمان (async-lambda). همانطور که در بخش ناهمزمانی توضیح داده شد، تغییرات بعد از await گرفته نمیشوند که منجر به باگهایی میشود که بازتولید آنها دشوار است. از callback همزمان استفاده کنید و setState را بعد از await فراخوانی کنید.
mounted را در callbackهای ناهمزمان بررسی کنیدسوالات متداول
setState() به Flutter اطلاع میدهد که دادههای داخلی StatefulWidget تغییر کرده و UI نیاز به بازسازی دارد. متد یک callback دریافت میکند، آن را به صورت همزمان اجرا میکند، ویجت را به عنوان dirty علامتگذاری کرده و فراخوانی build را در فریم بعدی برنامهریزی میکند.
UI بهروز نمیشود. Flutter تغییرات فیلدها را به طور خودکار ردیابی نمیکند. مقدار فیلد در حافظه تغییر میکند، اما ویجت تا下一次 بازسازی اجباری توسط والد در وضعیت قبلی باقی میماند.
نمیتوان. این منجر به حلقه بینهایت میشود: build setState را فراخوانی میکند، که ویجت را به عنوان dirty علامتگذاری کرده و دوباره build را فراخوانی میکند. Flutter چنین وضعیتی را مسدود نمیکند — برنامه با StackOverflowError crash میکند.
Build یک بار اجرا میشود. Flutter تمام عناصر dirty را جمعآوری کرده و در پایان فریم به صورت دستهای بازسازی میکند. setState دوم قبل از پردازش اولی، عنصر را به همان لیست عناصر dirty اضافه میکند — build تکراری رخ نخواهد داد.
mounted — یک پرچم بولی که نشان میدهد ویجت هنوز در درخت وجود دارد. اگر پس از عملیات ناهمزمان setState را بدون بررسی mounted فراخوانی کنید و ویجت حذف شده باشد — برنامه با استثنای „setState called after dispose” crash میکند.
خلاصه
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید