🚀

Flutter アプリ開発におけるパフォーマンス改善 Tips

2021/04/14に公開

Flutter アプリのパフォーマンス改善のために具体的に何ができるのかリスト化してみました。

GridView の shrinkWrap を true にしない

shrinkWrap には以下のような説明が書かれております。

スクロールビューがシュリンクラップしない場合、スクロールビューはscrollDirectionで許可されている最大サイズに拡張されます。
スクロールビューのscrollDirectionに無制限の制約がある場合、shrinkWrapはtrueである必要があります。
スクロールビューのコンテンツをシュリンクラップすると、最大許容サイズに拡張するよりも大幅にコストがかかります。
これは、コンテンツがスクロール中に拡大および縮小する可能性があるためです。つまり、スクロール位置が変更されるたびに、スクロールビューのサイズを再計算する必要があります。

要素数が可変の場合には GridView.builder やレイアウトによっては SliverGrid を活用すると良さそうです。
shrinkWrapをfalseにするためレウアウトがオーバーフローを起こさないように注意が必要そうです。

GridView.builder(
    shrinkWrap: false,
    itemCount: _itemCount,
    gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
      maxCrossAxisExtent: 100.0,
      crossAxisSpacing: 16,
      mainAxisSpacing: 16,
      childAspectRatio: 1 / 2,
    ),
    itemBuilder: (BuildContext context, int index) {
      return Text('index: $index');
    }
),

API 等で取得した可変長データを以下のように表示してしまうとスクロールのガタ付きやfpsの低下を招きます。


GridView.count(
  shrinkWrap: true,
  crossAxisCount: 3
  children: _apiResults(), // API 結果によって可変長のWidgetを表示する
),

画像をキャッシュする

メモリへの負荷を減らすためにネットワーク経由で取得した画像はキャッシュを利用できるようにします。

cached_network_image は簡素なIFで使い勝手がよいのでおすすめです。


CachedNetworkImage(
  imageUrl: "http://via.placeholder.com/350x150",
  placeholder: (context, url) => CircularProgressIndicator(),
  errorWidget: (context, url, error) => Icon(Icons.error),
),

リビルドを必要としない Widget には const をつける

TextEdgeInsets は多様することが多く定数を表示するだけの役割のものもあるかと思います。
その場合には const をつけると リビルド しないことを明示できるので活用しましょう。


Container(
  child: const Text('hoge')
),

リビルドされる Widget を最小限にする

statefulWidget の setState を利用すると State を保持しているWidget全体がリビルドされてしまいます。
そのため再描画が必要なWidgetのみが再描画されるように StreamBuilder 等を利用します。
状態管理の手法はその他様々ありますので公式ページをご参照ください。

以下Flutterでプロジェクト作成時のデフォルトのアプリを StreamController を利用した形に手直ししたものになります。
プロジェクト全体を参照したい方はサンプルプロジェクトをご参照ください。

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  final StreamController<int> _streamController = StreamController<int>();

  void _incrementCounter() {
    // setState(() {
    //   _counter++;
    // });

    _counter++;
    _streamController.sink.add(_counter);
  }

  
  void dispose() {
    _dispose();
    super.dispose();
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              'You have pushed the button this many times:',
            ),
            StreamBuilder(
              stream: _streamController.stream,
              builder: (BuildContext context, snapshot) {
                return Text(
                  '$_counter',
                  style: Theme.of(context).textTheme.headline4,
                );
              },
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }

  void _dispose() {
    _streamController.close();
  }

}

analysis_options.yaml を設定する

直接的にパフォーマンスを改善できるわけではないのですが、Linterの設定をすることで理想のコードとなり結果的にパフォーマンスの改善に繋がることが多いのではないかと思います。
const の利用をすることや、不要な import を避けるなど指摘してくれるので大変助かります。

設定方法はシンプルで、pubspec.yamlと同じ階層に analysis_options.yaml というファイルを配置するだけです。

Linterのルールとなる中身なのですが、私の場合はflutter公式のanalysis_options.yaml を参考にしました。

参考

Flutterのパフォーマンスを改善する
Stateful Widget のパフォーマンスを考慮した正しい扱い方

Discussion