ویجت FutureBuilder و بارگیری مجدد future در فلاتر

Moderate future reloading with flutter FutureBuilder
Moderate future reloading with flutter FutureBuilder
How to Moderate future reloading with flutter FutureBuilder widget

اگر با فلاتر(Flutter) آشنایی داشته باشید، حتما می‌دونید که ویجت FutureBuilder نمونه‌ای عالی از قابلیت ترکیب‌پذیری (Composability) فلاتر هست که می‌تونه عملیات یا پروسه‌ای از نوع Future رو در بر بگیره و امکان مدیریت حالت‌های مختلف Loading، Result و Error رو به آسانی مهیا می‌کنه.

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

https://virgool.io/@skmohammadi/%D8%A7%D9%81%D8%B2%D9%88%D9%86%D9%87-syntax-highlighter-%D8%A8%D8%B1%D8%A7%DB%8C-%D9%88%DB%8C%D8%B1%DA%AF%D9%88%D9%84-xpu1h4keneax

جلوگیری از بارگیری ناخواسته future در FutureBuilder

هنگامی که شما یک state در ویجت پدر رو تغییر می‌دید یا از یک route به route دیگه منتقل می‌شید و یا هر کاری که منجر به بازسازی Widgets Tree بشه، اتفاقی که می‌اوفته اینه که متد build از ویجت بالادستی یا جاری دوباره اجرا میشه و اگر داخل متد build عملیاتی مثل دریافت دیتا از بیرون یا اجرای یک عملیات زمان‌بر قرار گرفته باشه، این می‌تونه خیلی پرهزینه و زائد باشه.

ابتدا نگاهی به ساختار فراخوانی FutureBuilder بندازیم:

@override
Widget build(BuildContext context) {
  return FutureBuilder(
    future: _CustomOperation(),
    builder: (BuildContext context, AsyncSnapshot snapshot) {
      if (snapshot.hasError) {
        return Text('Error');
      } else if (snapshot.connectionState == ConnectionState.done) {
        return Text('Date');
      } else {
        return Text('loading');
      }
    },
  );
}

در قطعه کد بالا پارامتر future به صورت یک متد(تابع) از نوع Future فراخوانی شده. ویجت FutureBuilder مادامی که مقدار future تغییر کنه، شروع به گرفتن دیتای جدید میکنه و برای جلوگیری از این کار باید تابع مورد نظرمون به صورت ثابت تعریف بشه و موقع rebuild شدن ویجت، اون تابع باز تعریف نشه.

برای این منظور کافیه به شکل زیر عمل کنیم:

class _ChildWidgetState extends State<ChildWidget> {
  Future _future;

  @override
  void initState() {
    super.initState();
    _future = _CustomOperation();
  }

...

  @override
  Widget build(BuildContext context) {
    return FutureBuilder(
      future: _future,
      builder: (BuildContext context, AsyncSnapshot snapshot) {
        if (snapshot.hasError) {
          return Text('Error');
        } else if (snapshot.connectionState == ConnectionState.done) {
          return Text('Date');
        } else {
          return Text('loading');
        }
      },
    );
  }
}

همان طور که مشخصه متغیر جدیدی به نام future_ تعریف کردیم و داخل متد initState مقداردهی کردیمش. راجع به متد initState همین قدر لازمه بدونید که موقع ساخته شدن اولیه ویجت فراخوانی میشه فقط یکبار. به همین خاطر متغیر future_ هم تنها یکبار مقدار دهی میشه. در ادامه متغیر future_ رو به جای CustomOperation_ به FutureBuilder پاس دادیم.

این تغییرات از اجرای ناخوسته عملیات future ما جلوگیری میکنه.

اجرای بارگیری مجدد future از داخل FutureBuilder

تنها کار لازم تغییر یا بازتعریف پارامتر future از FutureBuilder هست و به سادگی می‌تونیم متد runFuture_ رو به ChildWidget اضافه می‌کنیم و کارش اینه که مقدار future_ رو بازنشانی کنه :)

void _runFuture() {
  _future = _CustomOperation();
}

و به شکل زیر این متد رو استفاده می‌کنیم:

@override
Widget build(BuildContext context) {
  return FutureBuilder(
    future: _future,
    builder: (BuildContext context, AsyncSnapshot snapshot) {
      if (snapshot.hasError) {
        return Text('Error');
      } else if (snapshot.connectionState == ConnectionState.done) {
        return Column(
          children: <Widget>[
            RaisedButton(
              onPressed: () {
                _runFuture();
              },
              child: Text('Re-Run Future'),
            ),
            Text('Date'),
          ],
        );
      } else {
        return Text('loading');
      }
    },
  );
}

اجرای بارگیری مجدد future از بیرون FutureBuilder

حال فرض کنید بخواهیم داخل ParentWidget بر اساس یک سری تغییرات (مثل تغییر یک state)، ویجت فرزند رو مجبور به اجرای مجدد future کنیم تا دیتا دوباره دریافت بشه.

ابتدا لازمه در ParentWidget یک GlobalKey برای دسترسی به ChildWidget تعریف کنیم:

GlobalKey<_ChildWidgetState> _keyChild = GlobalKey();

حالا باید این key تعریف شده رو به ویجت ChildWidget پاس بدیم. با این کار قادر هستیم با استفاده متد currentState از کلید ایجاد شده، متد runFuture_ رو اجرا کنیم.

class ParentWidget extends StatefulWidget {
  @override
  _ParentWidgetState createState() => _ParentWidgetState();
}

class _ParentWidgetState extends State<ParentWidget> {
  GlobalKey<_ChildWidgetState> _keyChild = GlobalKey();

  @override
  Widget build(BuildContext context) {
    return Column(
      children: <Widget>[
        RaisedButton(onPressed: () {
          _keyChild.currentState._runFuture();
        }),
        ChildWidget(
          key: _keyChild,
        )
      ],
    );
  }
}


فکر می‌کنم برای تازه‌کارهایی مثل خودم خیلی مشکل‌گشا باشه :)

اگر نظر، پیشنهاد و سوالی داشتید مطرح کنید، انشالله جواب میدم :D