Як боротися з небажаною побудовою віджетів?


143

З різних причин іноді buildметод моїх віджетів викликається знову.

Я знаю, що це відбувається тому, що батьків оновлено. Але це викликає небажані наслідки. Типова ситуація, коли це спричиняє проблеми, це при використанні FutureBuilderцього способу:

@override
Widget build(BuildContext context) {
  return FutureBuilder(
    future: httpCall(),
    builder: (context, snapshot) {
      // create some layout here
    },
  );
}

У цьому прикладі, якщо метод збирання потрібно було викликати ще раз, він викликав би ще один http-запит. Що небажано.

Враховуючи це, як боротися з небажаним складанням? Чи є який-небудь спосіб запобігти побудові виклику?


1
ця публікація може вам допомогти .. /programming/53223469/flutter-statelesswidget-build-called-multiple-times/55626839#55626839
зайчик

4
У документації про постачальника, яку ви посилаєте тут, висловлюєтесь "Дивіться цю відповідь stackoverflow, яка детальніше пояснює, чому використання конструктора .value для створення значень небажано". Однак ви не згадуєте тут конструктор значення або у своїй відповіді. Ви мали на увазі посилання десь ще?
Сурагч

Відповіді:


225

Метод побудови розроблений таким чином, що він повинен бути чистим / без побічних ефектів . Це пов’язано з тим, що багато зовнішніх факторів можуть викликати нову збірку віджетів, наприклад:

  • Маршрут поп / push
  • Зміна розміру екрана, як правило, через зовнішній вигляд клавіатури або зміну орієнтації
  • Батьківський віджет відтворив свою дитину
  • InheritedWidget віджет залежить від ( Class.of(context)шаблону) зміни

Це означає, що buildметод не повинен викликати http-дзвінок або змінювати будь-який стан .


Як це пов’язано з питанням?

Проблема, з якою ви стикаєтесь, полягає в тому, що ваш метод збірки має побічні ефекти / не є чистим, що робить сторонні дзвінки побудови складними.

Замість того, щоб запобігати побудові виклику, ви повинні зробити свій метод збірки чистим, щоб його можна було викликати в будь-який час без впливу.

У випадку з вашим прикладом ви перетворите свій віджет у StatefulWidgetвитяг, який виклик HTTP у initStateваш State:

class Example extends StatefulWidget {
  @override
  _ExampleState createState() => _ExampleState();
}

class _ExampleState extends State<Example> {
  Future<int> future;

  @override
  void initState() {
    future = Future.value(42);
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return FutureBuilder(
      future: future,
      builder: (context, snapshot) {
        // create some layout here
      },
    );
  }
}

Я це вже знаю. Я прийшов сюди, бо дуже хочу оптимізувати перебудови

Можна також зробити віджет, здатний відбудовуватися, не примушуючи своїх дітей також будувати.

Коли примірник віджета залишається таким же; Флетер цілеспрямовано не відновлює дітей. Це означає, що ви можете кешувати частини дерева віджетів, щоб уникнути зайвих перебудов.

Найпростіший спосіб - використовувати constконструктори дротиків :

@override
Widget build(BuildContext context) {
  return const DecoratedBox(
    decoration: BoxDecoration(),
    child: Text("Hello World"),
  );
}

Завдяки цьому constключовому слову, примірник DecoratedBoxзалишиться колишнім, навіть якщо збірка називалася сотні разів.

Але ви можете досягти того ж результату вручну:

@override
Widget build(BuildContext context) {
  final subtree = MyWidget(
    child: Text("Hello World")
  );

  return StreamBuilder<String>(
    stream: stream,
    initialData: "Foo",
    builder: (context, snapshot) {
      return Column(
        children: <Widget>[
          Text(snapshot.data),
          subtree,
        ],
      );
    },
  );
}

У цьому прикладі, коли StreamBuilder буде повідомлено про нові значення, subtreeвін не відбудується, навіть якщо StreamBuilder / Column. Це трапляється тому, що, завдяки закриттю, примірник MyWidgetне змінився.

Цей візерунок багато використовується в анімації. Типовими є використання AnimatedBuilderта всі переходи, такі як AlignTransition.

Ви також можете зберігати subtreeв полі свого класу, хоча і не рекомендується, оскільки воно порушує функцію гарячого перезавантаження.


2
Чи можете ви пояснити, чому зберігання subtreeв полі класу порушує перезавантаження?
mFeinstein

4
Проблема, з якою у мене виникає, StreamBuilderполягає в тому, що коли з'являється клавіатура, екран змінюється, тому маршрути повинні перебудовуватися. Так StreamBuilderвідновлюється і створюється новий StreamBuilder, і він підписується на stream. Коли StreamBuilderпідписка на a stream, snapshot.connectionStateстає, ConnectionState.waitingщо змушує мій код повернути a CircularProgressIndicator, а потім snapshot.connectionStateзмінюється, коли є дані, і мій код поверне інший віджет, завдяки чому екран мерехтить різними речами.
mFeinstein

1
Я вирішив зробити StatefulWidget, підписатися на streamна initState()і встановити currentWidgetз setState()як streamпередає нові дані, переходячи currentWidgetдо build()методу. Чи є краще рішення?
mFeinstein

1
Я трохи розгублений. Ви відповідаєте на власне запитання, однак із вмісту це не схоже.
sgon00

8
Так, кажучи, що збірка не повинна викликати метод HTTP повністю перемагає дуже практичний приклад a FutureBuilder.
TheGeekZn

6

Ви можете запобігти небажаному виклику побудови, використовуючи цей спосіб

1) Створіть повний державний клас для окремої невеликої частини інтерфейсу користувача

2) Використовуйте бібліотеку постачальника , тому використовуючи її, ви можете зупинити виклик небажаного способу побудови.

У наведених нижче ситуаціях виклик методу побудови

  • Після виклику initState
  • Після виклику didUpdateWidget
  • коли викликається setState () .
  • коли клавіатура відкрита
  • коли орієнтація екрана змінилася
  • Батьківський віджет будується, а потім дочірній віджет також відновлюють

0

Flutter також є ValueListenableBuilder<T> class . Це дозволяє відновити лише деякі необхідні для вашого призначення віджети та пропустити дорогі віджети.

ви можете ознайомитись з документами тут ValueListenableBuilder, що розвіваються,
або просто зразок коду нижче:

  return Scaffold(
  appBar: AppBar(
    title: Text(widget.title)
  ),
  body: Center(
    child: Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: <Widget>[
        Text('You have pushed the button this many times:'),
        ValueListenableBuilder(
          builder: (BuildContext context, int value, Widget child) {
            // This builder will only get called when the _counter
            // is updated.
            return Row(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: <Widget>[
                Text('$value'),
                child,
              ],
            );
          },
          valueListenable: _counter,
          // The child parameter is most helpful if the child is
          // expensive to build and does not depend on the value from
          // the notifier.
          child: goodJob,
        )
      ],
    ),
  ),
  floatingActionButton: FloatingActionButton(
    child: Icon(Icons.plus_one),
    onPressed: () => _counter.value += 1,
  ),
);
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.