Метод build разработан таким образом, что он должен быть pure / без побочных эффектов . Это связано с тем, что многие внешние факторы могут инициировать создание нового виджета, например:
- Route pop / push, для анимации входа / выхода
- Изменение размера экрана, как правило, из-за появления клавиатуры или изменения ориентации
- Родительский виджет воссоздал своего потомка
- Унаследованный виджет, от которого зависит виджет (
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
},
);
}
}
- Также возможно сделать виджет способным к восстановлению, не заставляя его потомков строить тоже.
Когда экземпляр виджета остается прежним; Флаттер целенаправленно не восстановит детей. Это означает, что вы можете кэшировать части своего дерева виджетов, чтобы предотвратить ненужные перестройки.
Самый простой способ - использовать конструкторы 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
в поле вашего класса, хотя это менее рекомендуется, так как оно не требует горячей перезагрузки.