Open5

Dart/Flutter で気をつけたいところメモ

daisukedaisuke

Dart/Flutter におけるメモ書きを記録していきます。主として自分用のメモですが、フィードバックなどありましたら、ご指摘ください。

daisukedaisuke

1. 【アンチパターン】Widget を返す function をつくってしまう

/// widget を返す function の一例
Widget _getContainerWidget(){
  return Container(
    height: 100.0,
    width: 100.0,
    color: Colors.blue
  );
}

このような「Widget を返す function」は、問題なさそうに見えます。実際、見た目や動作は意図したとおりです。

しかし、この方法は避けるべきです。なぜなら、 Flutter では複数回ビルド(描画)されるためです。「Widget を返す function」は、キャッシュが効かず、リビルドの度にレンダリングされてしまうため、パフォーマンス上好ましくありません。

今回は、比較的小さい Widget でしたが、 Widget Tree の上位でこの実装をしてしまうと、リビルドの範囲が広くなってしまいます。

改善の一例

よりよい方法は、 StatelessWidget, StatefulWidget をつかうことです。

/// たとえば StatelessWidget をつかうと、無駄なレンダリングを避けられる
class BlueContainer extends StatelessWidget {
  const BlueContainer({
    Key? key,
  }) : super(key: key);

  
  Widget build(BuildContext context) {
    return Container(
      height: 100.0,
      width: 100.0,
      color: Colors.blue
    );
  }
}

リンク

Class Widgets vs Functional Widgets | フューチャー技術ブログ
https://future-architect.github.io/articles/20220316a/

daisukedaisuke

2. 【アンチパターン】Container を多用してしまう

Flutter の Container Widget は、さまざまな項目を設定できる、汎用的な Widget です。padding, color, height, width など、幅広い用途で利用できます。このため、「とりあえず Container」で実装してしまいがちです。

しかし、常に Container Widget をつかう実装は好ましくありません。なぜなら、 Container Widget は const を指定できないからです。 const を指定できないということは、描画のコストが増してしまうということです。

改善の一例

const を利用できる専用 Widget を使いましょう。具体的には、SizedBox, Padding, ColoredBox などの Widget です。


リンク

Stateful Widget のパフォーマンスを考慮した正しい扱い方 | by mono  | Flutter 🇯🇵 | Medium
https://medium.com/flutter-jp/state-performance-7a5f67d62edd

daisukedaisuke

3. 【アンチパターン】Colors.xxx を多用してしまう

背景色、文字色、アイコン色。Dart/Flutter において Colors は不可欠です。

しかしながら、だからといってページやコンポーネントにベタ書きするのはおすすめできません。なぜなら、ライトモードとダークモードを切り替えたときに、うまくいかないことが多いからです。

実際僕は Colors をあちこちに書いてしまい、ダークモード対応にとても苦労しています。現在進行系で苦労しています!

改善の一例

Colors のベタ書きをやめましょう。 ThemeData をつかえば、ライトモード・ダークモード問わず、色を指定できるはずです。また、 Flutter 3.0 から導入された ThemeExtension という機能をつかえば、かなり自由に色を扱えるので、ぜひこちらの利用も検討しましょう。

ThemeExtension class - material library - Dart API
https://api.flutter.dev/flutter/material/ThemeExtension-class.html

ColorScheme もありますが、こちらについては、また改めて。(あまりよくわかってないので!)

daisukedaisuke

4. 【アンチパターン】FutureBuilder を state で保持せず使用してしまう

「非同期で処理し、その終了を待ってから画面に表示する」場合、Dart/Flutter では FutureBuilder を使用します。

たとえば、「ユーザーの一覧を取得し、その数を表示する」処理は、つぎのようになります。

FutureBuilder<List<UserProfile>>(
  future: _getUserProfiles(), // ★★★ 非同期処理を直接指定している!
  builder: (
    BuildContext context,
    AsyncSnapshot<List<UserProfile>> _snapshot
  ){
    if (_snapshot.hasData) {
      final List<UserProfile> _userProfiles = _snapshot.data!;
      return Text('ユーザー:${_userProfiles.length}人');
    } else if (_snapshot.hasError) {
      return const DisplayErrorWidget();
    } else {
      return const DisplayLoadingWidget();
    }
  },
)

このコードは動作はするものの、正しいとは言えません。なぜなら、非同期処理を FutureBuilderfuture に直接指定しているからです(「★★★」のところ)。

このため、ビルドされるごとに毎回非同期処理が生成され、余計なレンダリングコストが発生してしまいます。 hasData, hasError, loading で printfデバッグすると、何度も処理されてしまっていることが確認できるはずです。

改善の一例

StatefulWidget を利用した上で、 state に登録した非同期処理を FutureBuilderfuture に指定しましょう。つまり、公式サイトどおりに書くだけでいいのです!

///
class DisplayUserProfileCount extends StatefulWidget {
  const DisplayUserProfileCount();

  
  _DisplayUserProfileCountState createState() => _DisplayUserProfileCountState();
}

///
class _DisplayUserProfileCountState extends State<DisplayUserProfileCount> {

  ///
  late final Future<List<UserProfile>> _futureGetUserProfiles;

  ///
  
  void initState() {
    super.initState();
    _futureGetUserProfiles = _getUserProfiles();
  }

  ///
  
  Widget build(BuildContext context) {

    return FutureBuilder<List<UserProfile>>(
      future: _futureGetUserProfiles, // ★★★ state に保持した future を指定する!
      builder: (
        BuildContext context,
        AsyncSnapshot<List<UserProfile>> _snapshot
      ){
        if (_snapshot.hasData) {
          final List<UserProfile> _userProfiles = _snapshot.data!;
          return Text('ユーザー:${_userProfiles.length}人');
        } else if (_snapshot.hasError) {
          return const DisplayErrorWidget();
        } else {
          return const DisplayLoadingWidget();
        }
      },
    );
  }

}

リンク

FutureBuilder class - widgets library - Dart API
https://api.flutter.dev/flutter/widgets/FutureBuilder-class.html

mono さんのツイート
https://twitter.com/_mono/status/1531221783420219392