Flutter
Overview
Flutterは、Googleが提供するオープンソースのUIフレームワーク。
- クロスプラットフォーム対応 ひとつのコードベースでiOS、Android、Web、デスクトップ(Windows, macOS, Linux)など、複数のプラットフォームで動作するアプリを作成できる。
- ウィジェットベース FlutterではUIを構築するのに「ウィジェット」を使い、豊富なUIコンポーネントが用意されている。
- 高パフォーマンス ネイティブコードにコンパイルされるので、他のクロスプラットフォームツールに比べてパフォーマンスが良い。
- ホットリロード コードの変更をすぐにアプリに反映できる機能があり、開発スピードが速い。
マルチプラットフォーム技術の中にはXamarinのようにネイティブUIををラップした形式のものがある。
こうしたフレームワークでは複数プラットフォームのAPIをラップする過程で吸収しきれない差異が発生するケース。
一方FlutterはネイティブUIを使わない独自のレンダリングのしくみを持っている。
そのためiOSでデバッグしたあと、Androidでデバッグすると想定したUIになっていないと言った動作差異が限りなく小さくなっている。
Flutterは宣言UIに属す
FlutterではUIの設計図はウィジェットの親子関係であり、buildメソッドの実装そのもの。
そしてそれ以外の場所でUIを変更する手段はない。
これらの特徴からFlutterは宣言的UIフレームワークの1つに数えられる。
宣言的UIフレームワークは従来までのUIフレームワークと違い、簡潔で保守性の高いコードを実現できます(しっかりと設計しなければこの限りではありませんが)。iOSやAndroidのネイティブアプリ開発においても、宣言的UIフレームワークが台頭しています。 この宣言的UIはしばしば以下のような式で表現されます。
UI = f(State)
↑をFlutterに書き換えると左辺のUIは画面の表示結果、右辺の f はウィジェットの build メソッドにあたる。
ウィジェットを設計実装するときに、この式を意識する。
この式が成立しないウィジェットは設計に問題があるサイン。
f は関数のため副作用を持たない。f に同じ State(状態)を与えると、必ず同じUIになるということ。
たとえば、ウィジェットクラスがfinal以外のフィールドを持っている場合は、この式が成立しない可能性。
Flutterの例外処理
Flutterアプリはフレームワークが例外を捕捉する機構を持っており、例外がスローされてもプログラムが終了するとは限らない。
フレームワークは2つの例外ハンドラを提供している。
Flutterのフレームワーク自身がトリガするコールバック(レンダリング処理やウィジェットのbuildメソッドなど)で発生した例外はFlutterError.onErrorにルーティングされます。デフォルトではログをコンソールに出力する動作ですが、コールバックを上書きして独自に処理することも可能。
void main() {
FlutterError.onError = (details) {
// do something
};
runApp(const MyApp()); }
それ以外のFlutter内で発生した例外(ボタンのタップイベントハンドラなど)は PlatformDispatcher でハンドリングする。
void main() {
PlatformDispatcher.instance.onError = (error, stack) {
print(error);
return true; // 例外を処理した場合はtrueを返す。
}
}
Widget(ウィジェット)
Flutterアプリ開発の中心となるWidget(ウィジェット)について。
FlutterのUIを構築しているパーツのことをWidgetと呼ぶ。
FlutterアプリのUIはウィジェットの階層構造(ウィジェットツリー)をもとに作られる。
ほとんどのウィジェットは次の2つのクラスに分類できる。
- StatelessWidget
- 状態をもたない
- StatefulWidget
- 状態をもつ
ウィジェットのコンストラクタは名前付き引数、第一引数を Key 型とすることが慣習とされている。
パッケージ管理ツール
Dart言語は標準でパッケージ管理ツールを提供しており、Flutterのプロジェクトで利用できる。
多くのパッケージは pubspec.yaml に記述をコマンドを実行することで導入ができる。
依存するパッケージを追加するには YAML を直接編集するか、コマンドを実行する。
flutter pub add http
パッケージの指定方法
通常はキャレットで指定するのが推奨されている。
# 2.1.0以上、互換性のある限り最新のバージョンを利用する
shared_preferences: ^2.1.0`
パッケージバージョンの更新方法
導入しているパッケージの最新バージョンや、更新の可否は以下のコマンドで確認できる。
flutter pub outdated
アプリの日本語対応
Flutterではサードライブラリをインストールし日本語対応をする。
端末の設定が影響を受けるため、アプリとして強制の言語設定をすることが大事。
Flutterはデフォルトでは英語で開発されているため、特に対応しなければ意図せず英語が表示されたり、英語圏の日付フォーマットが適用されるため注意。
そのため日本向けのアプリを開発するためには、日本向けのローカライズが必要になる。
アプリを日本語化対応しても、iOSアプリをアプリストアに公開する際はもう1つ対応が必要。
修正するファイルは ios/Runner/Info.plist
Info.plistはアプリの構成情報を記述するXML形式のファイルでありCFBundleLocalizationsキーにサポートする言語を記述する。
アセット
Flutterではアプリに同梱する画像やテキストファイルなどをアセット呼ぶ。
Flutterが対応している画像フォーマットは以下。
- JPEG
- WebP
- GIF
- PNG
- BMP
- WBMP
端末の解像度に応じて画像を切り替える
スマートフォンのディスプレイ解像度はさまざま。
解像度別にいくつか画像を用意し、実行時に切り替える手法がある。
以下のようなディレクトリ構成でアセットを配置する。
具体的には「2x」や「3x」といった「数値 + x」の表記は、ディスプレイの解像度の密度(DPI)に対応した画像サイズを指定するためのもの。
通常、デバイスのディスプレイが高解像度になると、1x(標準解像度)の画像では見た目が粗くなるため、より高解像度の画像を準備しておく必要がある。
具体的には以下のように使用する。
- 1x:通常の解像度(72px x 72px)
- 2x:2倍の解像度(144px x 144px) Retinaディスプレイなどで使用される
- 3x:3倍の解像度(216px x 216px) 一部の4Kディスプレイなどで使用される
数値と末尾に「x」で終わるディレクトリを作成すると、解像度別にバリエーションとして解釈される。
例
assets/circle.png が縦横72pxだとしたら、これを基準に2xには縦横144px、3xには縦横216pxの画像を配置する
アセットパスの自動生成
存在しないパスを参照するとアプリケーションがクラッシュする。
そのためパスを自動生成してくれるものを自動生成する。
Environment
Flutter で API キーを保存する方法: --dart-define と .env ファイル
最大限のセキュリティを必要とする。
アプリケーションの場合は、リバースエンジニアリングで簡単にenvを解読できてしまう。
ENV導入については次のルールを念頭におく必要がある。
- APIキーをバージョン管理に追加しない。
- APIキーをクライアントに保存する場合は、必ず難読化する。
テーマ
アプリ全体のビジュアルを管理する。
useMaterial3 はGoogleが提唱するマテリアルデザインの新しいバージョンで、従来のものよりも表現力が豊かでアクセシビリティ高いデザインになっている。
Material Design
Material Design 3ガイドライン概要
Material Design3とは
2014年にGoogleから発表されたMaterial Design。
2018年にMaterial Themingが登場し、今年2021年にMaterial Youが新たに発表された。
発表当初は「Material You」という呼称での発表だったが、先日ガイドラインが登場し、そこでは「Material Design 3」、略称として「M3」と記載されるように。
このタイミングで、Material ThemingをM2、Material YouをM3と整理された様子。
難読化
アプリのリリース バージョンをビルドするときにコード全体を難読化することが必要。
公式ドキュメントでは、これを行う方法について説明しています。
Dartコードの難読化(リファレンス)
FlutterのDartコードの難読化について
また、APIキーをクライアントに保存する際には難読化を使用してリスクを軽減できますが、機密性の高いキーはサーバー上に保存する必要がある。
アプリアイコンを手軽に作成するパッケージ
iOSとAndroidそれぞれにアプリアイコンファイルを作成し、設定するのは手間がかかる。
そこで、アプリアイコンを手軽に生成するパッケージがflutterにはある。
flutter_launcher_iconsというパッケージ。
スプラッシュ画面
iOSとAndroidでスプラッシュ画面の位置付けが異なる、また実装方法もまったくバラバラ。
FlutterのパッケージでiOSとAndroidのスプラッシュ画面を自動生成するflutter_native_splashというパッケージが用意されている。
ローカルにデータを保存
Flutterでローカルにデータを保存する方法は主に以下のような選択肢がある。
- テキストや画像ファイルを直接保存する
SharedPreferenceやNSUserDefaultsを使ってキーと値のペアを保存する- SQLiteなどをDBとして保存する
状態管理
Flutterアプリでは、状態を更新したりその更新を契機にウィジェットを再構築する戦略を状態管理と呼ぶ。
国内では Riverpod というパッケージを中心にアプリを設計される例が多い。
状態管理パッケージとしてはスタンダートな存在。
Riverpod とは
Riverpodは「Flutter/Dartのリアクティブキャッシュフレームワークである」と明言している。
状態変化に反応して(リアクティブ)ウィジェットを更新するしくみが提供されており、その状態を保持する(キャッシュ)という意味。
状態の保持(キャッシュ)に関してはウィジェットのライフサイクルに左右されず、破棄のタイミングをコントロールできる(またはウィジェットのライフサイクルと合わせることも可能)
状態管理パッケージとしての側面以外にも、クラスの依存関係を解決するためのしくみを提供している(依存性の注入:Dependency Injection)
Riverpodと主要なクラス。
- Provider
- 状態をキャッシュし、RefやWidgetRefを介して状態を提供する
- RefとWidgetRef
- ConsumerWidget
- ウィジェットのサブスクラス
FlutterでのアプリID設定について
FlutterではアプリID(= アプリケーションのパッケージ名)は、iOSとAndroidでそれぞれ別々の場所に定義されており pubspec.yaml では管理されない。
したがって com.example.xoda を使いたい場合は、iOSとAndroidのネイティブプロジェクトに個別に設定する必要がある。
アプリIDの一括変更をするには?
Flutter公式ツールでは一括変更機能はないが flutter_rename_app や change_app_package_name といったパッケージを使うことで、アプリIDのリネームを一括で行うことができる。
Androidの設定場所(パッケージ名)
Androidでは AndroidManifest.xml と Gradleファイル、さらに Java/Kotlinのソースコードパスに定義があります。
<!-- android/app/src/main/AndroidManifest.xml -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.xoda">
<!-- android/app/build.gradle -->
defaultConfig {
applicationId "com.xxx.xoda" // ここ
...
}
ディレクトリ構造も一致している必要がある
android/app/src/main/kotlin/com/xxx/xoda/MainActivity.kt
iOSの設定場所(Bundle Identifier)
iOSでは Info.plist と Xcodeプロジェクト設定で管理されています。
ios/Runner/Info.plist
ここではBundle Identifier自体は記述しないが、Xcodeで設定された値を使うようになっている。
- Xcodeでの設定(GUIで設定)
ios/Runner.xcodeprojを Xcode で開く- Runner ターゲットを選択 → General タブ → Bundle Identifier を com.naohito.xoda に設定

