Ответ 1
Метод сборки спроектирован таким образом, что он должен быть чистым/без побочных эффектов. Это связано с тем, что многие внешние факторы могут инициировать создание нового виджета, например:
- Route pop/push, для анимации входа/выхода
- Изменение размера экрана, как правило, из-за появления клавиатуры или изменения ориентации
- Родительский виджет воссоздал своего потомка
- InheritedWidget виджет зависит от изменения (
Class.of(context)
)
Это означает, что метод build
не должен вызывать HTTP-вызов или изменять любое состояние.
Как это связано с вопросом?
Проблема, с которой вы сталкиваетесь, заключается в том, что ваш метод сборки имеет побочные эффекты/не является чистым, что делает проблемным вызов посторонней сборки.
Вместо того чтобы предотвращать вызов сборки, вы должны сделать свой метод сборки чистым, чтобы его можно было вызывать в любое время без каких-либо последствий.
В случае вашего примера вы бы преобразовали свой виджет в StatefulWidget
затем initState
этот 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
},
);
}
}
- Также возможно сделать виджет способным к восстановлению, не заставляя его потомков строить тоже.
Когда экземпляр виджета остается прежним; Флаттер целенаправленно не восстановит детей. Это означает, что вы можете кэшировать части вашего дерева виджетов, чтобы предотвратить ненужные перестройки.
Самый простой способ - использовать конструкторы dart const
:
@override
Widget build(BuildContext context) {
return const DecoratedBox(
decoration: BoxDecoration(),
child: Text("Hello World"),
);
}
Благодаря этому ключевому слову const
экземпляр DecoratedBox
останется прежним, даже если build вызывался сотни раз.
Но вы можете достичь того же результата вручную:
@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
и все * Transition, такие как AlignTransition
.
Вы также можете хранить subtree
в поле вашего класса, хотя это менее рекомендуется, так как оно прерывает горячую перезагрузку.