Dart/Flutter で気をつけたいところメモ
Dart/Flutter におけるメモ書きを記録していきます。主として自分用のメモですが、フィードバックなどありましたら、ご指摘ください。
Widget
を返す function をつくってしまう
1. 【アンチパターン】/// 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/
Container
を多用してしまう
2. 【アンチパターン】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
Colors.xxx
を多用してしまう
3. 【アンチパターン】背景色、文字色、アイコン色。Dart/Flutter において Colors
は不可欠です。
しかしながら、だからといってページやコンポーネントにベタ書きするのはおすすめできません。なぜなら、ライトモードとダークモードを切り替えたときに、うまくいかないことが多いからです。
実際僕は Colors
をあちこちに書いてしまい、ダークモード対応にとても苦労しています。現在進行系で苦労しています!
改善の一例
Colors
のベタ書きをやめましょう。 ThemeData
をつかえば、ライトモード・ダークモード問わず、色を指定できるはずです。また、 Flutter 3.0 から導入された ThemeExtension
という機能をつかえば、かなり自由に色を扱えるので、ぜひこちらの利用も検討しましょう。
ThemeExtension class - material library - Dart API
ColorScheme
もありますが、こちらについては、また改めて。(あまりよくわかってないので!)
FutureBuilder
を state で保持せず使用してしまう
4. 【アンチパターン】「非同期で処理し、その終了を待ってから画面に表示する」場合、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();
}
},
)
このコードは動作はするものの、正しいとは言えません。なぜなら、非同期処理を FutureBuilder
の future
に直接指定しているからです(「★★★」のところ)。
このため、ビルドされるごとに毎回非同期処理が生成され、余計なレンダリングコストが発生してしまいます。 hasData
, hasError
, loading
で printfデバッグすると、何度も処理されてしまっていることが確認できるはずです。
改善の一例
StatefulWidget
を利用した上で、 state に登録した非同期処理を FutureBuilder
の future
に指定しましょう。つまり、公式サイトどおりに書くだけでいいのです!
///
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
mono さんのツイート