StreamBuilder: این چیست، اصل کار و کاربرد در Flutter

نویسنده: IT Sectr منتشر شده: 2026-07-03 زمان مطالعه: 8 دقیقه

StreamBuilder — ویجت Flutter که هنگام دریافت داده‌های جدید از جریان ناهم‌زمان، به طور خودکار رابط کاربری را بازسازی می‌کند. برخلاف FutureBuilder که با نتیجه یک‌بار مصرف کار می‌کند، StreamBuilder به‌روزرسانی مداوم UI را در طول چرخه حیات Stream پشتیبانی می‌کند. بر اساس مستندات رسمی Flutter (2026)، StreamBuilder در برنامه‌های زمان‌واقعی استفاده می‌شود: چت‌ها، فیدهای خبری، پایش سنسورها و تیکرهای مالی. این ابزار کلیدی برنامه‌نویسی واکنش‌گرا است که در آن UI وضعیت داده‌ها را بدون فراخوانی دستی setState منعکس می‌کند.

نکات اصلی

  • StreamBuilder — ویجتی که Stream و snapshot داده را برای رندرینگ واکنش‌گرای UI دریافت می‌کند
  • Snapshot شامل connectionState، data و error است که وضعیت فعلی جریان را تعیین می‌کند
  • ConnectionState چهار فاز را طی می‌کند: none، waiting، active، done
  • AsyncSnapshot — یک شیء تغییرناپذیر که سازگاری داده‌ها را در هر فریم تضمین می‌کند
  • StreamController جریان را مدیریت می‌کند: داده اضافه می‌کند، خطاها را پردازش می‌کند و Stream را می‌بندد

StreamBuilder چیست

StreamBuilder — ویجتی از بسته Flutter SDK است که در Stream مشترک می‌شود و در هر رویداد جدید جریان، عنصر فرزند خود را بازسازی می‌کند. StreamBuilder یک شیء Stream دریافت می‌کند و بر اساس آخرین snapshot به‌دست‌آمده از جریان، یک ویجت برمی‌گرداند.

در معماری Flutter، StreamBuilder به گروه ویجت‌های Builder تعلق دارد که ساخت UI را از وضعیت داده جدا می‌کند. برخلاف StatefulWidget که تغییر وضعیت نیاز به فراخوانی صریح setState دارد، StreamBuilder به رویدادهای ناهم‌زمان به طور خودکار واکنش نشان می‌دهد که کد را ساده‌تر کرده و خطر خطاهای همگام‌سازی را کاهش می‌دهد.

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

StreamBuilder در تمام سناریوهایی که داده‌ها به طور مداوم وارد می‌شوند استفاده می‌شود: اتصالات WebSocket، فراخوانی‌های سنسور، اعلان‌های Firebase، صف‌های رویداد Bluetooth و پخش وضعیت برنامه از طریق BLoC. بر اساس تحلیل پروژه‌های Flutter در GitHub (2025)، StreamBuilder در کنار FutureBuilder و LayoutBuilder در سه ویجت Builder پراستفاده قرار دارد.

نتیجه: در هر جایی که UI باید داده‌های مداوم در حال تغییر را منعکس کند و از مدیریت دستی وضعیت از طریق StatefulWidget اجتناب کنید، از StreamBuilder استفاده کنید.

StreamBuilder چگونه کار می‌کند

StreamBuilder در لحظه ساخت در Stream مشترک می‌شود و هنگام نابودی ویجت اشتراک را لغو می‌کند. هر بار که Stream رویدادی ارسال می‌کند، StreamBuilder یک AsyncSnapshot جدید دریافت کرده و برای بازسازی UI تابع builder را فراخوانی می‌کند.

فرآیند از سه مرحله تشکیل شده است. اول: StreamBuilder از طریق متد stream.listen یک اشتراک در Stream ایجاد می‌کند. دوم: در هر رویداد، StreamBuilder AsyncSnapshot داخلی را به‌روز می‌کند و ویجت را برای بازسازی به عنوان «کثیف» علامت‌گذاری می‌کند. سوم: فریم‌ورک تابع builder را با snapshot جدید فراخوانی می‌کند و UI داده‌های فعلی را نمایش می‌دهد.

مهم: StreamBuilder در داخل از StreamSubscription استفاده می‌کند. اگر Stream مستقیماً ارسال شود، StreamBuilder یک بار در مقداردهی اولیه مشترک می‌شود. اگر Stream تغییر کند (مثلاً در بازسازی والد)، StreamBuilder از جریان قدیمی لغو اشتراک کرده و در جریان جدید مشترک می‌شود. این رفتار توسط پارامترهای initialData و buildWhen کنترل می‌شود که بهینه‌سازی تعداد بازسازی‌ها را امکان‌پذیر می‌کند.

نتیجه: درک چرخه حیات اشتراک، اساس استفاده صحیح از StreamBuilder است. مدیریت نادرست جریان‌ها منجر به نشت حافظه یا داده‌های قدیمی در UI می‌شود.

ConnectionState: چهار وضعیت جریان

ویژگی connectionState شیء AsyncSnapshot تعیین می‌کند که StreamBuilder در کدام مرحله از کار با جریان قرار دارد. چهار وضعیت متمایز می‌شوند: none، waiting، active، done.

ConnectionState.none

None — وضعیت اولیه زمانی که Stream هنوز شروع به ارسال داده نکرده است. در این وضعیت snapshot.connectionState برابر ConnectionState.none و snapshot.data برابر null است. معمولاً در این وضعیت یک placeholder یا انتظار برای اولین رویداد نمایش داده می‌شود. اگر Stream داده اولیه ارائه ندهد، StreamBuilder از این وضعیت شروع می‌کند.

ConnectionState.waiting

Waiting — وضعیت انتظار برای داده از جریان ناهم‌زمان. Stream فعال است اما داده‌ها هنوز وارد نشده‌اند. این وضعیت مثلاً هنگام بارگذاری داده از شبکه یا باز کردن یک اتصال طولانی‌مدت رخ می‌دهد. در این وضعیت معمولاً CircularProgressIndicator یا اسکلت بارگذاری نمایش داده می‌شود.

ConnectionState.active

Active — جریان داده ارسال می‌کند و UI اطلاعات فعلی را نمایش می‌دهد. در این وضعیت snapshot.hasData برابر true و snapshot.data حاوی آخرین مقدار از جریان است. اگر Stream از نوع Broadcast Stream باشد، وضعیت active می‌تواند هم‌زمان با انتظار برای داده‌های جدید وجود داشته باشد.

ConnectionState.done

Done — جریان کامل شده و داده جدیدی نخواهد بود. Snapshot.data حاوی آخرین مقدار ارسال‌شده قبل از بسته شدن جریان است. اگر جریان با موفقیت کامل شده باشد، snapshot.hasError برابر false است. این وضعیت برای نمایش نتیجه نهایی استفاده می‌شود: پیام «بارگذاری کامل شد» یا انتقال به صفحه بعدی.

نتیجه: هنگام ساخت UI از طریق StreamBuilder باید هر چهار وضعیت را پردازش کرد تا رابط کاربری به‌درستی بارگذاری، داده، خطا و تکمیل را نمایش دهد.

استفاده از StreamController برای مدیریت جریان

StreamController — کلاسی از بسته dart:async است که Stream را ایجاد و مدیریت می‌کند. StreamController امکان اضافه کردن داده، پردازش خطاها و بستن جریان را با کنترل چرخه حیات آن فراهم می‌کند.

StreamController دو نوع دارد: single-subscription (یک مشترک) و broadcast (چندین مشترک). کنترل‌کننده single-subscription فقط یک شنونده را در یک زمان می‌پذیرد — اشتراک مجدد باعث استثنا می‌شود. کنترل‌کننده broadcast به چند StreamBuilder اجازه می‌دهد هم‌زمان به یک جریان گوش دهند که برای BLoC و وضعیت مشترک برنامه مفید است.

هنگام ایجاد StreamController از طریق StreamController<T>.broadcast()، داده‌های اضافه‌شده قبل از اولین اشتراک برای مشترک جدید بازپخش نمی‌شوند. اگر نیاز به دریافت آخرین مقدار هنگام اتصال دارید، از BehaviourSubject از بسته rxdart استفاده کنید که آخرین رویداد را کش می‌کند.

پس از اتمام کار با کنترل‌کننده باید controller.close() فراخوانی شود. عدم فراخوانی close منجر به نشت منابع می‌شود: جریان باز می‌ماند، مشترکان در حافظه باقی می‌مانند و GC اشیاء مرتبط را آزاد نمی‌کند.

نتیجه: از StreamController با مدیریت صریح چرخه حیات استفاده کنید. برای جریان‌های single-subscription از کنترل‌کننده استاندارد و برای وضعیت اشتراکی از کنترل‌کننده broadcast یا BehaviourSubject استفاده کنید.

نمونه کد با StreamBuilder

نمونه 1 تایمر شمارش معکوس را با استفاده از StreamController و StreamBuilder نشان می‌دهد.

dart
import 'dart:async';

class TimerWidget extends StatefulWidget {
  const TimerWidget({super.key});

  final StreamController<int> controller = StreamController<int>();

  void startTimer() {
    int count = 0;
    Timer.periodic(Duration(seconds: 1), (timer) {
      controller.sink.add(count++);
      if (count > 10) {
        controller.close();
        timer.cancel();
      }
    });
  }
}

در نمونه، کنترل‌کننده‌ای برای تولید اعداد 0 تا 10 با فاصله 1 ثانیه ایجاد می‌شود. پس از رسیدن به 10، close فراخوانی می‌شود و جریان پایان می‌یابد. StreamBuilder مشترک در stream این کنترل‌کننده هر مقدار جدید را نمایش می‌دهد.

نمونه 2 — استفاده از StreamBuilder با Broadcast Stream برای نمایش داده از چندین منبع.

dart
final StreamController<String> broadcastController =
    StreamController<String>.broadcast();

StreamBuilder<String>(
  stream: broadcastController.stream,
  initialData: 'Waiting for data...',
  builder: (context, AsyncSnapshot<String> snapshot) {
    if (snapshot.connectionState == ConnectionState.waiting) {
      return const Center(
        child: CircularProgressIndicator(),
      );
    }
    if (snapshot.hasError) {
      return Text('Error: ${snapshot.error}');
    }
    return Text('Data: ${snapshot.data}');
  },
)

نمونه دوم پردازش همه وضعیت‌ها را نشان می‌دهد: initialData برای نمایش اولیه، waiting برای نشانگر بارگذاری، hasError برای خطاها و data برای نتیجه موفق. این الگو استاندارد کد تولیدی با StreamBuilder است.

نتیجه: برای جلوگیری از صفحه خالی در لحظه اول از initialData استفاده کنید و همیشه hasError را برای نمایش صحیح خطاها به کاربر پردازش کنید.

خطاهای رایج هنگام کار با StreamBuilder

خطای 1: ایجاد Stream جدید در هر بازسازی والد. اگر Stream از طریق عبارتی ارسال شود که در هر ساخت یک شیء جدید می‌سازد، StreamBuilder از جریان قدیمی لغو اشتراک کرده و در جریان جدید مشترک می‌شود و باعث حلقه بی‌پایان بازسازی می‌شود. راه‌حل: از متغیر remembered یا StatefulWidget با Stream ثابت استفاده کنید.

خطای 2: عدم پردازش خطاها. Stream می‌تواند از طریق controller.sink.addError خطا ارسال کند و اگر builder snapshot.hasError را بررسی نکند، کاربر صفحه خالی یا بارگذاری بی‌پایان می‌بیند. راه‌حل: همیشه hasError را بررسی کنید و پیام قابل فهم نمایش دهید.

خطای 3: نشت حافظه به دلیل StreamController بسته نشده. اگر کنترل‌کننده در dispose بسته نشود، جریان به وجود خود ادامه می‌دهد و GC حافظه را آزاد نمی‌کند. راه‌حل: در dispose controller.close() را فراخوانی کنید و برای اقدامات پایانی رویداد done را گوش دهید.

خطای 4: استفاده از StreamBuilder با تابع builder کند. از آنجا که builder در هر رویداد جریان فراخوانی می‌شود، محاسبات سنگین داخل آن باعث افت فریم می‌شود. راه‌حل: محاسبات را به ایزوله جداگانه منتقل کنید یا از Stream.map برای تبدیل داده استفاده کنید.

نتیجه: StreamBuilder ابزاری قدرتمند اما پرتوقع است. مراقب چرخه حیات Stream باشید، خطاها را پردازش کنید و از عملیات سنگین در builder اجتناب کنید.

سوالات متداول

StreamBuilder چه تفاوتی با FutureBuilder دارد؟

FutureBuilder برای نتیجه ناهم‌زمان یک‌بار مصرف طراحی شده است: در Future مشترک می‌شود، یک مقدار دریافت می‌کند و کار را تمام می‌کند. StreamBuilder در Stream مشترک می‌شود که می‌تواند مقادیر زیادی را در طول زمان ارسال کند و UI را در هر رویداد جدید بازسازی می‌کند.

AsyncSnapshot در StreamBuilder چیست؟

AsyncSnapshot — یک شیء تغییرناپذیر که وضعیت فعلی اشتراک (connectionState)، آخرین مقدار دریافت‌شده (data) و شیء خطا (error) را در صورت ارسال استثنا توسط جریان شامل می‌شود.

چگونه خطا را در StreamBuilder پردازش کنیم؟

خطا از طریق ویژگی‌های snapshot.hasError و snapshot.error در تابع builder پردازش می‌شود. اگر جریان با متد sink.addError خطا ارسال کند، AsyncSnapshot error دریافت می‌کند و builder باید پیام مناسب یا UI جایگزین نمایش دهد.

می‌توان از یک Stream در چند StreamBuilder استفاده کرد؟

بله، اگر Stream از نوع broadcast باشد (ایجاد شده از طریق StreamController.broadcast). Stream از نوع single-subscription فقط یک مشترک را می‌پذیرد. برای اشتراک‌گذاری یک جریان بین چند ویجت از کنترل‌کننده broadcast یا بسته rxdart با BehaviourSubject استفاده کنید.

چگونه از بازسازی StreamBuilder در هر رویداد جلوگیری کنیم؟

از پارامتر buildWhen برای فیلتر کردن رویدادهایی که نیاز به بازسازی UI دارند استفاده کنید. همچنین Stream.transformer یا Stream.where را برای فیلتر کردن داده قبل از ارسال به StreamBuilder اعمال کنید.

خلاصه

  • StreamBuilder — ویجتی برای ساخت واکنش‌گرای UI بر اساس جریان داده ناهم‌زمان که از به‌روزرسانی مداوم رابط پشتیبانی می‌کند
  • AsyncSnapshot شامل connectionState (none, waiting, active, done)، data و error — تمام وضعیت‌های جریان
  • StreamController چرخه حیات جریان را مدیریت می‌کند: افزودن داده، پردازش خطاها و بستن جریان
  • Broadcast Stream به چند StreamBuilder اجازه اشتراک در یک جریان را می‌دهد، single-subscription — فقط به یکی
  • پردازش خطا اجباری است: بدون بررسی hasError برنامه ممکن است در وضعیت بارگذاری بماند
  • نشت حافظه — رایج‌ترین مشکل: همیشه StreamController را در dispose ببندید
  • توصیه: همیشه initialData را تنظیم کنید و هر چهار connectionState را برای UX روان پردازش کنید

ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد

IT Sectr از سال 2017 برنامه‌های iOS و Android را برای استارتاپ‌ها و کسب‌وکارها ایجاد می‌کند. ما به شما مشاوره می‌دهیم و بهترین راه‌حل را پیشنهاد خواهیم کرد.

بحث درباره پروژه

همچنین بخوانید