Flutterでの開発をスムーズに行うためのTips集
以下のドキュメントにかぶる内容も含まれていますが、僕が良いなと思っているやり方を共有します。
本記事の内容は、ある程度書いていたり色々記事を読んでいると自然と身についていく類のものですが、初めから色々知っていると捗るのと、すでに慣れている場合でも少しは新しい発見があるかもしれません。
また、Flutterの効率良い学び方 にも書いた通り、自分でFlutterコードを書く分にはAndroid Studioの方が捗ると感じているためそれに沿った説明になっていますが、VS Codeでも大体通ずる内容です。また、キーボードショートカットはmacOSでのデフォルトとなっているので、環境が違う場合は随時読み替えてください。
[追記 2019/03/03] 最近はVS Codeに絞るのも良いかなと思ってきています(→ 2022年2月からVS Codeに乗り換えました)
新規アプリ作成
普通にFlutterアプリを作成する手順は公式ドキュメントに分かりやすく記載されています。
基本はその通りで良いですが、ターミナル経由の場合、 flutter create app_name
だけだと以下のようなデフォルト値が設定されてしまい、あとから好みのものに変えるのがけっこうな手間です。
- 所属(アプリIDの前半部分に利用される): com.example
- flutter_driver (UIテスト): 無し
そのため、次のように自分の好みのものをメモしたりコマンドヒストリーから呼び出せるようにしておくと便利です。
flutter create \
--org com.mono0926 \
--with-driver-test \
app_name
ちなみに、この設定は以下のセッション動画でオススメされていたものです。
ただ、個人的にはAndroid Studioの”New Flutter Project”でポチポチ選択していくのが好みです(しかしこの場合flutter_driver指定はできなさそう🤔)。
VS Codeの場合は、次のようにDartの拡張の設定で指定できます。
ネイティブアプリ側の言語設定は、現在では以下がデフォルトです(以前は逆でしたが変わりました)。
- iOS: Swift (not Objective-C)
- Android: Kotlin(not Java)
大抵の場合この選択のままの方が良いですが、どうしても変えたい時があればプロジェクト作成時に注意して選択しましょう。
特に、ネイティブ言語の種類は、後から変えたい場合、作り直すかそれに準じた作業(設定に応じて生じる差分を手動で適用など)が必要になるので、少し面倒なことになります。
また、個人的には最近はさらに工夫してプロジェクト生成後にいつも行う調整をスクリプトで適用する仕組みを整えることもしていて、ちょっとしたプロジェクトを作る機会が多い場合は捗るようになります。
プロジェクトのルートに analysis_options.yaml を追加
アプリを作ったら、まず初めに analysis_options.yaml を置いて静的解析を厳しくするのが良いと思っています。詳しくは以下の記事に書きました。
dartfmt を活用
Dartコードを書くにあたって、逐一dart formatを使って整形していくのは実質必須で、以下でも勧められています。どんなに雑に書こうとも、 dart format
一発で一意のきれいなフォーマットに整形してくれます。
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のショートカットで実行するやり方が紹介されていて、いまいちに感じます。
一々その操作をするのではなく、セーブすると自動的にdart formatをかけてくれる以下の設定をする方が遥かに快適です。サブメニューの”Organize imports on save”は、セーブ時にimportの並び順整理および不使用なものの削除をしてくれて、こちらはオンにするか好みに依ると思います(基本的には便利なものの、一時的にコメントアウトしたものに対応するimport分がカットされて面倒なこともたまにあるので)。
また、フォーマットのかかっていない既存プロジェクトに対して、一斉に実行したい場合は以下のコマンドでできます。
( -w
無しでdryrunになりますがgit管理などしていれば特に怖がらずに -w
付きで実行してしまった問題ないです)
dart format --fix lib
(ちなみに、dart format
は以前は dartfmt
と呼ばれていましたが、dartのサブコマンドに整理されました。)
Dartの末尾コンマは大事
Dartの末尾コンマはその有無によってdartfmt結果が変わったりします。Using trailing commasに以下の図とともに説明されているように、基本的には末尾に ”,” を入れた方がきれいかつ横幅もコンパクトに整形されます。
コンマの入れ具合については、Flutter/Dart コーディング スタイル の内容くらいがバランス良い気がしています。
Alt + EnterによるAssist・Quick Fix活用
JetBrains製IDEでおなじみの機能ですが、IDEから次のように何か提案があると行にマークがついてAlt + Enterでその候補から選択するとコードを修正してくれます(マークをクリックでもできますがキーボードショートカットの方が捗ります)。
Flutterコードを書くにあたって多用するのが、Widgetでラップおよび逆にWidgetを削除する機能です。これを知らないと特にネストの深いWidgetの編集の際に無駄な苦労を強いられます。
同様のことは右にあるFlutter Outlineからの右クリックでも可能です。
Flutter Performance タブの活用
FlutterのWidgetの扱いが適切でないと、buildメソッドが意図せぬ高い頻度で呼ばれてしまうことがあります。buildメソッドやその他ライフサイクルの関係するところにログを仕込むのも良いですが、とりあえずそれぞれのWidgetのリビルド回数見るだけで大体動きが掴めたりするので、ちょくちょく観察しています。
以下の記事を書く際にも多用しました。
しかし、たまに表示がおかしいことがあるので、フルリロードしたり、それでも直らない時はIDEを再起動したりしています🤔
UIのプレビュー機能は無いの?
一応ありますが、まだ実験的な機能で不安定なので僕は切ってしまいました。
きちんと動くようになると便利なので期待しています。次のIssueを購読しておくと、動きがあると分かります。
2019年9月に、Flutter開発チーム内部でプロトタイプとして開発中のHotUIという機能についての文書が公開され、将来これに近い形のものがIDEに統合されると予想されるので、それがとても楽しみです。
以下の機能などが検討されているようで、ここに関しては SwiftUI に対して明確に劣っている部分で不満だったので、対応されると嬉しいです。
- Widget全体およびその中の一部分のプレビュー表示
- プレビューUIからのコード変更操作
初回実装時は、シミュレーターでのレイアウト結果をHot Reloadで確認しながら実装すれば良いだけなのでプレビューが欲しいと思うことはあまりないですが、後から各種ソースコードがどういうUIを構築しているのか確認したいときなどにプレビューがあるととても捗るはずです。
常用したいパッケージ
利用するパッケージはアプリによって異なりますが、その中でも個人的にこれは常に入れておくと捗ると思うものを列挙します。
quiver
Dartはまあまあ気に入っているものの、「これが簡単に書けないのか」と驚くことがちょくちょくあります。
例えば、0から順に10個の要素が欲しい時(いわゆるrange関数)に、次のようなコードを書く必要があります。
List<int>.generate(10, (i) => i)
quiverをインストールしておけば、次のようにシンプルに書けて良いです。
import 'package:quiver/iterables.dart';
range(0, 10)
Flutterフレームワークではテストで使われているため dev_dependencies
に指定されていてそのまま参照できますがが、アプリコードとして使いたい場合は別途アプリの pubspec.yaml
の dependencies
に指定が必要です。
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言語のみの対応でも文言は初めから多言語対応可能なようにしておくのが良いと思っていますが)
弊パッケージ
個人的に作る必要性に迫られて作ったので、以下も大抵使っています。よかったらどうぞです。
- bloc_provider: BLoCを扱いやすくするため
- simple_logger: シンプル・便利なロガー
それぞれ、記事も書きました。
ドキュメント不足なものが多いですが、他にも色々公開しています。
他にも常用するレベルの便利なパッケージが多いですが、公式ドキュメントの Commonly Used Dart Libraries など見るのもおすすめです。
アノテーション活用
DartはJavaなどでおなじみのアノテーションがあります。以下などが代表例です。
@override
これを付けておくと、Overrideしたつもりがタイポなどで別メソッド扱いになってしまったというミスをした場合、警告を出してくれます。IDE使ってお行儀良く書いていると自然に付与されますし、付け忘れた場合も指摘されます。
@required
必須なパラメーターに@requiredを付けておくと、不指定の時に警告を出してくれます。assertで拾うのもありですが、いずれにしろこちらも指定しておくと意図しない引数渡し忘れを実行前に気付けます。
@immutable
これが付いていると、そのクラスのフィールドは不変(再代入不可)であることを示せます。
まず、Widget基底クラスについているため、
final
ではないフィールドが宣言されていると警告が出ます。
もちろん、自作クラスに付与して同じく不変であることを示せます。
ただ、 final
は再代入不可であって、例えばListの中身が不変であることなどは保証できず、その場合は UnmodifiableListView などを使います。
しかし、これは変更操作をコンパイル時に防ぐものではなく、例えば次のような操作をすると、
final x = UnmodifiableListView([1]);
x.add(2);
次のような実行時エラーとなります。もう少し確実に防ぎたいものですが🤔
その他、以下なども活用するとミス防止に役立ちます。
- @mustCallSuper: 継承先で
super.xxx
のようにoverride
したメソッドを呼び忘れると警告表示 - @protected: メソッドを、継承先での利用のみ許可して、外から呼ぶと警告表示
- @visibleForTesting: そのパッケージの
lib
フォルダあるいはテスト用のtest
フォルダ以外から呼ぶと警告表示
Builder Widgetで入れ子のBuildContextを提供
例えば、次のように SnackBar を表示したいとします。
次のように書くと普通に動くと思いきや、、、
次のようなエラーが出てしまいます🤯
エラーに臆さずによく読んでみると、次のように書かれています。
- Scaffold.of()がScaffoldを含まないcontextで呼ばれている
- Builderを使うのが最もシンプルな回避策
- Widget分割するのが(パフォーマンス観点で)効率的
- GlobalKeyをScaffoldに入れて、currentStateプロパティでScaffoldStateを取得する
このエラーメッセージの言う通り、確かにcontextはHomeに渡ってきているものであり、Scaffold配下のものではありません。
(このようにFlutterでは実行時エラーが出た時に原因と解決策など含めた丁寧なエラーが吐かれるので、適当にググって場当たり的な対処をする前にまずはよく読んで考えましょう。)
Widget分割
まず、最も推奨っぽいWidget分割するパターンを試してみます。Widget分割は面倒に感じるかもしれませんが、これもIDEの機能でできます。
次のように右クリックのRefactorから選んでも良いですが、
command + shift + Aのクイックアクションから選ぶ方が効率的です。
分割すると次のようになって、SnackBarも無事に表示できます。ついでにScaffold配下をconstにできて、パフォーマンス的にもベターになりました。
Builder Widget
とはいえ、分けるとやはりコード量が少し膨らむので、同じbuildメソッド内で完結したい時もあり得ます。そこでBuilder Widgetの出番です。これを使うと次のようにしっかりと動作するコードが書けます。
各種Widgetでもchildではなくbuilder引数が提供されているものがありますが、それらは利用者がBuilderを別途使わずに済むような配慮と言えます。
そもそもBuildContextとは?
ちなみに、BuildContextとはなんなのかというと、実体はElementです。Widget(やStatefulWidgetのState)のbuildメソッドに渡す際にElementそのものを露呈せずにBuildContext抽象クラスとすることで見せたくないものを隠蔽しています。
つまりBuildContextからその上位のElementツリー構造を辿れます。元々のエラーは、そこから必要なWidget(この例の場合Scaffold)を辿れないが故に発生しており、そのWidget配下のBuildContext(Element)にアクセスする手段として以下のいずれかを行なった、と考えると分かりやすいです。
- 別Widgetに切り出す
- Builder Widgetを経由する
より詳しくは、Flutter の Widget ツリーの裏側で起こっていること を参照してください。
PlaceholderでUIのTODO的な部分を示す
UIを組んでいて、諸事情で表示するものが不確定だったり、他の実装に集中したいので後回しにすることがありますが、まさそういう時に使えるものとして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で良い気がしています🤔
Xcodeなどではこれら区別されておらず、Runでブレークポイントも止まって、Flutterも分けずで良い気がしていますが、なぜ分けているのでしょう🤔
(「デフォルトデバッグ実行で、オプションとしてデバッガーをアタッチしないモードもある」くらいが使いやすい気がします。)
紛らわしいですが、上述の”Run”・”Debug”はモードとしてはともに”Debug Mode”であり、モードは他に以下もあって、これらはそれぞれ適した場面で使う必要があります。
- Profile Mode: パフォーマンスチューニングの際などにプロファイリングする時用
- Release Mode: リリースビルド用(JITではなくAOTになってHot Reloadが効かなくなる代わりに速くなる)
パフォーマンスが落ちるなど実際にリリースする状態とは異なるため、通常開発する際の”Debug Mode”時は次のように右上にDEBUGバナーが表示されます。
MaterialApp WidgetのdebugShowCheckedModeBannerを false
にすることで、Debug Modeでも非表示にすることもできますが、区別が付かなくなってミスの原因になりかねないので、通常デフォルトの true
とした方が良いと思います(スクショの見栄えを良くしたい時など一時的にfalseにする程度にとどめるのが良いと思います)。
ビルドのモードについて、詳しくは以下に載っています:
Live Templates活用
Live Templatesといって、プレースホルダー付きのスニペットを簡単に呼び出す機能があります。
例えば、StatefulWidgetのひな形を書きたい場合、 stful
と入力(途中まで打って候補から選択でもOK)してタブかエンターを押すと次のように一瞬でStatefulWidgetのコードが出力されて、そのまま最低限必要なところのみ追加で入力します。
自作することもできるので、ボイラープレート的なコードを書くことが何回かあったら既存のテンプレートを参考にしながらその都度登録すると良いです。
自作したテンプレートは以下に保存されるので、チームメンバー間や自分マシン間などで共有すると便利です。
~/Library/Preferences/AndroidStudio3.5/templates
source_gen・build_runner によるコード生成もあり
ボイラープレート的なコード記述の省力化の手段として、用途によってはLive Templatesよりもsource_gen・build_runner によるコード生成が適していることがあります。代表的な例としてJSONのモデル変換コードがあり、それは json_serializable というパッケージが提供されています。
詳しくは次の記事を見てください。
ちょっと雑多な感じになりましたが、ざっとTipsをまとめてみました。まだ書き忘れていることがありそうで、気付き次第追記していきます🐶
本記事は具体的なTips集でしたが、Flutterを体系的に学ぶおすすめの流れは以下ご覧ください。