Flutterでの開発をスムーズに行うためのTips集

以下のドキュメントにかぶる内容も含まれていますが、僕が良いなと思っているやり方を共有します。

本記事の内容は、ある程度書いていたり色々記事を読んでいると自然と身についていく類のものですが、初めから色々知っていると捗るのと、すでに慣れている場合でも少しは新しい発見があるかもしれません。

また、Flutterの効率良い学び方 にも書いた通り、自分でFlutterコードを書く分にはAndroid Studioの方が捗ると感じているためそれに沿った説明になっていますが、VS Codeでも大体通ずる内容です。また、キーボードショートカットはmacOSでのデフォルトとなっているので、環境が違う場合は随時読み替えてください。

新規アプリ作成

普通にFlutterアプリを作成する手順は公式ドキュメントに分かりやすく記載されています。

基本はその通りで良いですが、ターミナル経由の場合、 flutter create app_name だけだと以下のようなデフォルト値が設定されてしまい、あとから好みのものに変えるのがけっこうな手間です。

  • iOS: Objective-C
  • Android: Java
  • 所属: com.example
  • flutter_driver (UIテスト): 無し

そのため、次のように自分の好みのものをメモしたりコマンドヒストリーから呼び出せるようにしておくと便利です。

flutter create \
--org com.mono0926 \
-i swift \
-a kotlin \
--with-driver-test \
app_name

ちなみに、この設定は以下のセッション動画でオススメされていたものです。

ただ、個人的にはAndroid Studioの”New Flutter Project”でポチポチ選択していくのが好みです(しかしこの場合flutter_driver指定はできなさそう🤔)。

“New Flutter Project”

特に、ネイティブ言語の種類は、後から変えたい場合、作り直すかそれに準じた作業(設定に応じて生じる差分を手動で適用など)が必要になるので、気をつけましょう。

プロジェクトのルートに analysis_options.yaml を追加

アプリを作ったら、個人的にはまず初めに analysis_options.yaml を置いて静的解析を厳しくするのが良いと思っています。

これがあると、一貫性のあるコードが書けるようになりますし、くだらないミスも抑制できます。また、Dartに不慣れな時期こそ、これで矯正しながらコーディングすると捗ります。

以下が僕の設定です。Flutter自体のはこちらです。初めは厳し目にして、Dart言語の思想など汲み取りつつ必要に応じて解除していく、などが良いかなと思っています。

(see 1068) と書かれてコメントアウトされているものはdart-lang/linter/issues/1068 でレビュー中のものです。

また、prefer_mixin・prefer_int_literalsはDart 2.1の機能に対応するもので、ついうっかり新しい言語機能を使い忘れて古い書き方をしてしまうことも防げて良いです👌

dartfmt を活用

Dartコードを書くにあたって、逐一dartfmtを使って整形していくのは実質必須で、以下でも勧められています。どんなに雑に書こうとも、dartfmt一発で一意のきれいなフォーマットに整形してくれます。

To automatically format the code in the current source code window, right-click in the code window and select Reformat Code with dartfmt. You can add a keyboard shortcut to this in Keymap section of IntelliJ Preferences.

ただAndroid Studioでdartfmtをかける方法として、右クリックして”Reformat Code with dartfmt”を選ぶかoption + command + Lのショートカットで実行するやり方が紹介されていて、いまいちに感じます。

右クリックしてdartfmt

一々その操作をするのではなく、セーブすると自動的にdartfmtをかけてくれる以下の設定をする方が遥かに良いです。サブメニューの”Organize imports on save”は、セーブ時にimportの並び順整理および不使用なものの削除をしてくれて、こちらはオンにするか好みに依ると思います(基本的には便利なものの、一時的にコメントアウトしたものに対応するimport分がカットされて面倒なこともたまにあるので)。

別解として、Save Actionsというプラグイン を使う方法もありますが、基本的には追加のプラグイン無しで使える上の方法で充分だと思います。

Dartの末尾コンマは大事

Dartの末尾コンマはその有無によってdartfmt結果が変わったりします。Using trailing commasに以下の図とともに説明されているように、基本的には末尾に ”,” を入れた方がきれいかつ横幅もコンパクトに整形されます。

コンマの入れ具合については、Flutter/Dart コーディング スタイル の内容くらいがバランス良い気がしています。

Alt + EnterによるAssist・Quick Fix活用

JetBrains製IDEでおなじみの機能ですが、IDEから次のように何か提案があると行にマークがついてAlt + Enterでその候補から選択するとコードを修正してくれます(マークをクリックでもできますがキーボードショートカットの方が捗ります)。

Alt + Enter

Flutterコードを書くにあたって多用するのが、Widgetでラップおよび逆にWidgetを削除する機能です。これを知らないと特にネストの深いWidgetの編集の際に無駄な苦労を強いられます。

Widgetでラップおよび逆にWidgetを削除

同様のことは右にあるFlutter Outlineからの右クリックでも可能です。

Flutter Outlineからの右クリック

Flutter Performance タブの活用

FlutterのWidgetのbuildメソッドはその種類や書き方によって頻繁に呼ばれることがあります。buildメソッドやその他ライフサイクルの関係するところにログを仕込むのも良いですが、とりあえずそれぞれのWidgetのリビルド回数見るだけで大体動きが掴めたりするので、ちょくちょく観察しています。

Performance タブ

以下の記事を書く際にも多用しました。

しかし、たまに表示がおかしいことがあるので、フルリロードしたり、それでも直らない時はIDEを再起動したりしています🤔

UIのプレビュー機能は無いの?

一応ありますが、まだ実験的な機能で不安定なので僕は切ってしまいました。

きちんと動くようになると便利なので期待しています。次のIssueを購読しておくと、動きがあると分かります。

常用したいパッケージ

利用するパッケージはアプリによって異なりますが、その中でも個人的にこれは常に入れておくと捗ると思うものを列挙します。

quiver

Dartはまあまあ気に入っているものの、「これが簡単に書けないのか」と驚くことがちょくちょくあります。

例えば、0から順に10個の要素が欲しい時(いわゆるrange関数)に、次のようなコードを書く必要があります。

List<int>.generate(10, (i) => i)

quiverをインストールしておけば、次のようにシンプルに書けて良いです。

import 'package:quiver/iterables.dart';
range(0, 10)

JavaScriptにおけるLodash のようなものですが、Dart本体には最近のJavaScriptよりも便利なメソッドの不足を感じるのでより必要度が高い気がしています。

というわけで、Dartコード書いててちょっと不便だと思ったらquiverで簡単に書けないか疑うと良いと思います。主要な機能は README に列挙されています。

同様に、Dart本体には入ってないがパッケージで補填できるものとして、 tuple などがあります。

ちなみに、Dart本体改善について今後期待できそうなものは以下でよくまとめられています。

rxdart

初めは、Dartには標準でStreams API があるので、rxdartはそれで足りない時に限定的に導入すれば良いかなと思ったものの、大抵足りなくなるので最近はプロジェクトを作ったらすぐ入れています。

ちょっとしたStreamの処理ならRxDart無くとも済みますが、BLoCパターンを使う場合は必須だと思っています。

DartPad ではRxDartを使えないので、そこでStream扱いたい時は標準のStreams APIで我慢しています( ´・‿・`)

intl

多言語対応のためのパッケージですが、多言語対応せずとも例えばDateTimeをISO8601以外の文字列として表示したい場合などにも必要なので初めから入れておいた方が良いかなと思っています。
(大抵のアプリはまず1言語のみの対応でも文言は初めから多言語対応可能なようにしておくのが良いと思っていますが)

弊パッケージ

個人的に作る必要性に迫られて作ったので、以下も大抵使っています。よかったらどうぞです。

それぞれ、記事も書きました。

その他、公式ドキュメントの Commonly Used Dart Libraries など見るのもおすすめです。

アノテーション活用

DartはJavaなどでおなじみのアノテーションがあります。以下などが代表例です。

@override

これを付けておくと、Overrideしたつもりがタイポなどで別メソッド扱いになってしまったというミスをした場合、警告を出してくれます。IDE使ってお行儀良く書いていると自然に付与されますし、付け忘れた場合も指摘されます。

@overrideに関する警告

@required

必須なパラメーターに@requiredを付けておくと、不指定の時に警告を出してくれます。assertで拾うのもありですが、いずれにしろこちらも指定しておくと意図しない引数渡し忘れを実行前に気付けます。

@requiredな引数欠損の警告

@immutable

これが付いていると、そのクラスのフィールドは不変(再代入不可)であることを示せます。

まず、Widget基底クラスについているため、

Widgetのソース

final ではないフィールドが宣言されていると警告が出ます。

@immutableなのに非finalなフィールドが宣言されている時の警告

もちろん、自作クラスに付与して同じく不変であることを示せます。

ただ、 final は再代入不可であって、例えばListの中身が不変であることなどは保証できず、その場合は UnmodifiableListView などを使います。

しかし、これは変更操作をコンパイル時に防ぐものではなく、例えば次のような操作をすると、

final x = UnmodifiableListView([1]);
x.add(2);

次のような実行時エラーとなります。もう少し確実に防ぎたいものですが🤔

UnmodifiableListViewに変更操作した時の実行時エラー

Builder Widgetで入れ子のBuildContextを提供

例えば、次のように SnackBar を表示したいとします。

SnackBarの表示例

次のように書くと普通に動くと思いきや、、、

次のようなエラーが出てしまいます🤯

SnackBarの扱いを間違えた時に出るエラー

エラーに臆さずによく読んでみると、次のように書かれています。

  • Scaffold.of()がScaffoldを含まないcontextで呼ばれている
  • Builderを使うのが最もシンプルな回避策
  • Widget分割するのが(パフォーマンス観点で)効率的
  • GlobalKeyをScaffoldに入れて、currentStateプロパティでScaffoldStateを取得する(あまりオススメしない)

このエラーメッセージの言う通り、確かにcontextはHomeに渡ってきているものであり、Scaffold配下のものではありません。

(このようにFlutterでは実行時エラーが出た時に原因と解決策など含めた丁寧なエラーが吐かれるので、適当にググって場当たり的な対処をする前にまずはよく読んで考えましょう。)

Widget分割

まず、最も推奨っぽいWidget分割するパターンを試してみます。Widget分割は面倒に感じるかもしれませんが、これもIDEの機能でできます。

次のように右クリックのRefactorから選んでも良いですが、

右クリックからのWidget分割

command + shift + Aのクイックアクションから選ぶ方が効率的です。

クイックアクションからのWidget分割

分割すると次のようになって、Snackbarも無事に表示できました。Scaffold配下をconstにできて、パフォーマンス的にもベターになりました。

Builder Widget

とはいえ、分けるとやはりコード量が少し膨らむので、同じbuildメソッド内で完結したい時もあり得ます。そこでBuilder Widgetの出番です。これを使うと次のようにしっかりと動作するコードが書けます。

各種Widgetでもchildではなくbuilder引数が提供されているものがありますが、それらは利用者がBuilderを別途使わずに済むような配慮と言えます。

そもそもBuildContextとは?

ちなみに、BuildContextとはなんなのかというと、実体はElementです。Widget(やStatefulWidgetのState)のbuildメソッドに渡す際にElementそのものを露呈せずにBuildContext抽象クラスとすることで見せたくないものを隠蔽しています。

左: Widgetのbuildメソッド | 右: 呼び出し元のElementがbuildメソッドに自身を渡している
Element の継承関係

つまりBuildContextからその上位のElementツリー構造を辿れます。元々のエラーは、そこから必要なWidget(この例の場合Scaffold)を辿れないが故に発生しており、そのWidget配下のBuildContext(Element)にアクセスする手段として以下のいずれかを行なった、と考えると分かりやすいです。

  • 別Widgetに切り出す
  • Builder Widgetを経由する

より詳しくは、Flutter の Widget ツリーの裏側で起こっていること を参照してください。

PlaceholderでUIのTODO的な部分を示す

UIを組んでいて、諸事情で表示するものが不確定だったり、他の実装に集中したいので後回しにすることがありますが、まさそういう時に使えるものとしてPlaceholderがあります。

Placeholderの利用例

コード:

Containerなどで済ませてしまうこともできますが、意図的にも見た目的にも作業途中であることが明確になるので、Placeholderを活用した方が良いです。

FlutterのWidgetは、他にもこんなのまで用意されているのかと驚くことがちょくちょくありますが、Flutter Widget of the Weekのプレイリスト などしっかり目を通したりすると追いやすいです。

キーボードショートカットの覚え方

よくキーボードショートカットのチートシートなどがありますが、僕はそういうものには全く頼っていません。なぜなら見なくともIDE上の操作で分かるからです。
(チートシートをできることの一覧の俯瞰のために使うならありだと思いますが。)

まず、IDEのHelpメニューを見る(command + shift + / でも表示可能)と、次のようにテキスト入力欄がありそこにテキストを入力するとやりたい操作を検索できます。
(上述のcommand + shift + Aのクイックアクションのショートカットもここに記載されていますね。)

ヘルプメニュー
ヘルプメニュー経由でのコマンド検索

このようにやりたいコマンドを選びつつ頻度の高いものはキーボードショートカットも覚えてすぐ実行できるようにしています。

なので、まずは以下程度の最小限のものだけ覚えて、触りながら必要に応じて順次覚えていけば良いと思いますし、頻度の低いものはQuick Action経由などで済ませてしまって十分だと思っています。

command + shift + O: ファイル検索

shift2連打: 全検索

command + N: ひな形生成

チートシート

公式チートシートもあります。キーボードショートカットについては上述の通りあまり頼らずIDE触りながら覚えれば良いと思いますが、その他Tipsも盛り込まれているので、一読しておくと良いです。

基本的にDebug実行で良い気がする

普通にRunで実行するとブレークポイントが止められません。

そのため、Runで実行中にブレークポイントを止めたくなったら、Debugで再起動するハメになります。

次のように、あえてDebugではなくRunにする理由が無いように思うので、常時Debugで良い気がしています🤔

“Run”と”Debug”の違い

Xcodeなどではこれら区別されておらず、Runでブレークポイントも止まって、Flutterも分けずで良い気がしていますが、なぜ分けているのでしょう🤔
(「デフォルトデバッグ実行で、オプションとしてデバッガーをアタッチしないモードもある」くらいが使いやすい気がします。)

XcodeのRunコマンド

紛らわしいですが、上述の”Run”・”Debug”はモードとしてはともに”Debug Mode”であり、モードは他に以下もあって、これらはそれぞれ適した場面で使う必要があります。

  • Profile Mode: パフォーマンスチューニングの際などにプロファイリングする時用
  • Release Mode: リリースビルド用(JITではなくAOTになってHot Reloadが効かなくなる代わりに速くなる)
Profile ModeとRelease Mode

パフォーマンスが落ちるなど実際にリリースする状態とは異なるため、通常開発する際の”Debug Mode”時は次のように右上にDEBUGバナーが表示されます。

MaterialApp WidgetのdebugShowCheckedModeBannerfalse にすることで、Debug Modeでも非表示にすることもできますが、区別が付かなくなってミスの原因になりかねないので、通常デフォルトの true とした方が良いと思います(スクショの見栄えを良くしたい時など一時的にfalseにする程度にとどめるのが良いと思います)。

ビルドのモードについて、詳しくは以下に載っています:

Live Templates活用

Live Templatesといって、プレースホルダー付きのスニペットを簡単に呼び出す機能があります。

Live Templates

例えば、StatefulWidgetのひな形を書きたい場合、 stful と入力(途中まで打って候補から選択でもOK)してタブかエンターを押すと次のように一瞬でStatefulWidgetのコードが出力されて、そのまま最低限必要なところのみ追加で入力します。

Live Templates活用例

自作することもできるので、ボイラープレート的なコードを書くことが何回かあったら既存のテンプレートを参考にしながらその都度登録すると良いです。

source_genbuild_runner によるコード生成もあり

ボイラープレート的なコード記述の省力化の手段として、用途によってはLive Templatesよりもsource_genbuild_runner によるコード生成が適していることがあります。代表的な例としてJSONのモデル変換コードがあり、それは json_serializable というパッケージが提供されています。

詳しくは次の記事を見てください。


ちょっと雑多な感じになりましたが、ざっとTipsをまとめてみました。まだ書き忘れていることがありそうで、気付き次第追記していきます🐶

本記事は具体的なTips集でしたが、Flutterを体系的に学ぶおすすめの流れは以下ご覧ください。