不要なウィジェットのビルドに対処する方法は?


143

さまざまな理由buildで、ウィジェットのメソッドが再度呼び出されることがあります。

親が更新したために発生することは知っています。しかし、これは望ましくない影響を引き起こします。問題が発生する典型的な状況は、FutureBuilderこの方法を使用する場合です。

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

この例では、ビルドメソッドが再度呼び出されると、別のhttpリクエストがトリガーされます。これは望ましくありません。

これを考慮して、不要なビルドに対処するにはどうすればよいですか?ビルド呼び出しを防ぐ方法はありますか?



4
プロバイダーのドキュメントで、「。valueコンストラクターを使用して値を作成することが望ましくない理由をさらに詳しく説明しているこのstackoverflow回答を参照してください」とここにリンクしています。ただし、ここでは、または回答で値コンストラクターについて言及しません。別の場所にリンクするつもりですか?
スラグ

回答:


224

ビルド方法は、それがあるべきように設計された副作用なし/ピュア。これは、次のような多くの外部要因が新しいウィジェットのビルドをトリガーする可能性があるためです。

  • ルートポップ/プッシュ
  • 画面のサイズ変更、通常はキーボードの外観または向きの変更による
  • 親ウィジェットが子を再作成しました
  • ウィジェットが依存する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
      },
    );
  }
}

私はすでにこれを知っています。私はので、私はここに来たのは本当に最適化再構築したいです

また、子にビルドを強制することなく、ウィジェットを再ビルドできるようにすることもできます。

ウィジェットのインスタンスが同じままの場合。Flutterは意図的に子供を再建しません。これは、不要な再構築を防ぐためにウィジェットツリーの一部をキャッシュできることを意味します。

最も簡単な方法は、dart 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に加入しているstreamsnapshot.connectionStateとなりConnectionState.waiting私のコードリターンAを行ったCircularProgressIndicator、その後、snapshot.connectionStateときそこのデータを変更し、自分のコードが異なるもので、画面のちらつきを作る異なるウィジェットを、返します。
mFeinstein

1
を作成StatefulWidgetし、streamon にサブスクライブしinitState()currentWidgetwith を新しいデータを送信するsetState()ように設定し、メソッドstreamに渡すcurrentWidgetことにしましたbuild()。より良い解決策はありますか?
mFeinstein

1
少し混乱しています。あなたはあなた自身の質問に答えていますが、内容からはそうではありません。
sgon00

8
ええと、ビルドがHTTPメソッドを呼び出すべきではないと言うことは、の非常に実用的な例を完全に無効にしますFutureBuilder
TheGeekZn

6

これらの方法を使用して、不要なビルド呼び出しを防ぐことができます

1)UIの個々の小さな部分の子Statefullクラスを作成します

2)プロバイダーライブラリを使用して、不要なビルドメソッドの呼び出しを停止できる

ようにする以下の状況で、ビルドメソッドの呼び出し

  • initStateを呼び出した後
  • didUpdateWidgetを呼び出した後
  • ときSETSTATEは()と呼ばれています。
  • キーボードが開いているとき
  • 画面の向きが変わったとき
  • 親ウィジェットが構築され、次に子ウィジェットも再構築されます

0

フラッターも持っていValueListenableBuilder<T> class ます。これにより、目的に必要なウィジェットの一部のみを再構築して、高価なウィジェットをスキップできます。

ここでドキュメントを見ることができますValueListenableBuilder flutter docs
または以下のサンプルコードだけ:

  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,
  ),
);
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.