前作
Flutterを知らない人がFlutterでアプリを公開するまでにどのような情報が必要でどのようなエラーを解消したか
Flutter 公式実装例
公式では無い実装例
Flutter 情報集
Flutter関連ニュース、ライブラリ、実装例など関連情報を網羅しているリポジトリ。困ったらチェックするといいだろう。
参考になる書籍など
-
Flutter実践開発 iPhone/Android両対応アプリ開発のテクニック, 渡部陽太, 技術評論者, 2023
- 基本的なところが概ねカバーされており良書である。
-
「内側」から理解する Flutter 入門, 中條 剛(ちゅーやん), 2021, ZENN, https://zenn.dev/chooyan/books/934f823764db62
-
Flutter 大学, https://zenn.dev/flutteruniv
環境構築
基本的には公式ページに沿ってインストールを進めれば問題ない。
Flutter - install flutter
Flutter, Dart SDK バージョン
Dart SDK バージョンはFlutterバージョンと関連している。リストは以下:
Cocoapodsについて
iOS側のライブラリを集めてくれるツールCocoapodsをFlutterでは利用している。パソコンのグローバル環境にあるCocoapodsを使うようである。しかしiOSアプリ開発では、よくrbenvやbundlerを用いて、各プロジェクトごとに別バージョンのCocoapodsを指定していることがよくある。Flutterでも、各プロジェクトごとに別バージョンのCocoapodsを指定する方法はある。ただFlutter SDKそのものを書き換える必要があるので、応急処置的な対応とは言える。しかもFlutter SDKをバージョンアップするごとに対応がいるだろう。
Zenn - Flutter の CocoaPods を rbenv と bundler で管理する
Flutter State Management とは
Providerパッケージ
Simple app state management - flutter 公式
samples/provider_counter/
samples/provider_shopper/
各ウィジェット間で情報のやりとりをする際、クロージャを渡していくと言うやり方でも良いが、ちょっと複雑なアプリになるとすぐ煩雑になる。
そのため、
ChangeNotifier-ChangeNotifierProvider-Consumer
のクラスが用意されている。いわゆるリアクティブプログラミングのように、値の変化を購読し、UIを変更できる。
リアクティブプログラミング的に言えば
Observable-Observableを必要なクラスに渡すクラス-Subscribeを記述するクラス
のような関係となろうか。
2024年現在、Provider + ChangeNotifierで多くの複雑な構築もできるようになっているため、基本はこれを進める。
FlutterのBLoC(Business Logic Component)のライフサイクルを正確に管理して提供するbloc_providerパッケージの解説
ChangeNotifier使用例
ChangeNotifierだが、notifyListner()
メソッドを呼ぶと、購読してるクラスへ値が通知されるようになっている。
また、Flutter公式の機能であるListenableBuilderと併用することもできる。ChangeNotifierだがが更新された際、ListenableBuilderで囲った部分のウィジェットのみをピンポイントで更新させることができる。そのため、パフォーマンス向上に役立つ。Flutterで新しいプロジェクトを作成するときに選べるテンプレアプリでも使用されている。
provider利用側
providerの利用側(UI, widget側)でproviderを参照する方法はいくつかある。以下AIにまとめてもらったが、ドキュメントを見たり自分で書いてみた結果からも間違ってはいないと思う:
AIの解説:
Flutterのprovider
パッケージを使用する際、Provider
からデータを参照する方法はいくつかあります。それぞれの方法には特徴があり、適切な場面で使い分けることが重要です。以下に体系的に解説します。
1. Provider.of<T>(context)
Provider.of<T>(context)
は、指定されたBuildContext
に関連付けられたProvider
からデータを取得する最も基本的な方法です。
使用例:
final myModel = Provider.of<MyModel>(context);
特徴:
-
listen
パラメータ(デフォルトはtrue
)で、リビルドの挙動を制御できます。-
listen: true
: プロバイダの値が変更されたときにウィジェットを再構築します。 -
listen: false
: 値の変更を監視せず、ウィジェットを再構築しません(例えば、イベントハンドラ内で使用する場合など)。
-
注意点:
-
listen: true
の場合、不要な再構築を引き起こす可能性があるため、慎重に使用する必要があります。
2. Consumer<T>
Consumer<T>
は、Provider.of<T>(context)
をラップしたウィジェットで、特定の部分だけを再構築したい場合に便利です。
使用例:
Consumer<MyModel>(
builder: (context, myModel, child) {
return Text(myModel.someValue);
},
)
特徴:
-
builder
関数内でプロバイダの値にアクセスでき、その値に基づいてUIを構築します。 - 親ウィジェット全体を再構築する必要がなく、効率的です。
注意点:
-
Consumer
を使用することで、不要な再構築を防ぐことができますが、過剰にネストするとコードが複雑になる可能性があります。
3. Selector<T, R>
Selector<T, R>
は、Consumer
と似ていますが、特定のプロパティのみを監視し、そのプロパティが変更された場合にのみ再構築を行います。
使用例:
Selector<MyModel, String>(
selector: (context, myModel) => myModel.someValue,
builder: (context, someValue, child) {
return Text(someValue);
},
)
特徴:
-
selector
関数で監視するプロパティを指定できるため、不要な再構築を防ぎます。 - 複雑なモデルの一部だけを監視する場合に特に有用です。
注意点:
-
selector
関数の実装が誤っていると、意図しない再構築が発生する可能性があります。
4. context.watch<T>()
context.watch<T>()
は、Provider.of<T>(context, listen: true)
の短縮形として導入されました。
使用例:
final myModel = context.watch<MyModel>();
特徴:
-
watch
は常にlisten: true
として動作します。 - コードが簡潔になり、可読性が向上します。
注意点:
- 再構築が必要ない場合は使用しないように注意してください。
5. context.read<T>()
context.read<T>()
は、Provider.of<T>(context, listen: false)
の短縮形です。
使用例:
final myModel = context.read<MyModel>();
特徴:
-
read
はlisten: false
として動作します。 - イベントハンドラや初期化処理などで使用する場合に便利です。
注意点:
- UIの構築中に使用すると、意図しない挙動を引き起こす可能性があります。
6. context.select<T, R>()
context.select<T, R>()
は、Selector
ウィジェットの短縮形です。
使用例:
final someValue = context.select((MyModel myModel) => myModel.someValue);
特徴:
-
Selector
と同様に、特定のプロパティのみを監視し、そのプロパティが変更された場合にのみ再構築します。 - コードが簡潔で読みやすいです。
注意点:
- 監視対象のプロパティが明確である場合に使用します。
まとめ
方法 | 再構築の制御 | 使用場面 |
---|---|---|
Provider.of<T>(context) |
可能 | 全体的な値の取得やイベントハンドラ内での使用 |
Consumer<T> |
自動 | 特定の部分だけを再構築したい場合 |
Selector<T, R> |
部分監視 | 特定のプロパティのみを監視したい場合 |
context.watch<T>() |
自動 | 簡潔な記述が必要な場合 |
context.read<T>() |
なし | イベントハンドラや初期化処理内での使用 |
context.select<T, R>() |
部分監視 | 特定のプロパティのみを監視したい場合 |
- 以上、AIの解説。
RiverPod ライブラリ
ConsumerWidgetもステート管理のクラスだが、デフォルトのクラスではなくRiverPodライブラリのクラスである。Providerパッケージと似てはいるが、さらに高機能となっている。
RiverpodとProviderの比較については:
Provider vs Riverpod
ConsumerWidgetはProviderの変化を購読できるWidget(StatlesWidget)である。buildメソッドの中で、複数のプロバイダの値を監視できる点で描きやすいのが特徴のWidgetである。
このほかに、ConsumerというWidget(StatefulWidget)もある。こちらはパフォーマンスの点で優れている。
RiverPod ライブラリそのものの特徴としてとはコンパイルセーフ(コンパイルさえ通れば、安全に実行できる)で、Flutterには依存せず、さまざまなユースケースをサポートしており、ほぼデフォルトのProviderの上位互換である。
またProviderをグローバル定数として宣言するという特徴がある。
ConsumerWidget class - official doc
Riverpodの基本的な使い方を整理してみる
Reading a Provider - RiverPod Doc
【Flutter】Providerのほぼ上位互換、Riverpodの基本的な使い方
const StatelessWidget({ Key? key }) : super(key: key);
これはコンストラクタである。
riverpod_generator, riverpod_annotation
riverpodを使う際に似たようなコードを書かなければいけない場面が多いために、コード生成でその問題を解決してくれる。特に事情がなければriverpodと一緒に必ず利用することを薦める。
riverpod_generator
riverpod_annotation
使用方法
Zenn - Riverpod Generatorで生成できるProviderの種類と書き方(1.2.0時点)
riverpod_lint
riverpod を使う際よくある間違いのコードを検出してくれる。これも使うことを推奨。
riverpod おすすめ構成
※バージョン番号は任意
dependencies:
flutter_riverpod: ^2.5.1
riverpod_annotation: ^2.3.5
dev_dependencies:
riverpod_generator: ^2.4.0
build_runner: ^2.4.11
custom_lint: ^0.6.4
riverpod_lint: ^2.3.10
flutter-bloc
こちらもステートマネジメントのライブラリである。ただシンプルでより使いやすい印象。blocパターン(だいたい、MVVMと同じ気がする)を実現できる。なおblocパターンそのものはこのライブラリを使わなくても、streamやrx_dartなどを使っても実現することはできる。
flutter-bloc
bloc documentation
【Flutter】flutter_blocについてまとめる
Flutterのアプリ設計(Bloc)
BlocBuilder, BlocListener, BlocConsumerの違いについて
BlocBuilderはstateが変更された時にWidgetを返すもの。BlocListenerはWidgetは関係なく、stateが変更された時に何かしたい処理を書くもの。BlocConsumerは両者の特徴を併せ持つ。
Flutter: BlocBuilder vs BlocConsumer vs BlocListener
flutter-hooks
React Hooks のようにステートマネジメントを行うためのライブラリ。
riverpodと併用したい場合はhooks-riverpodを使う。
flutter_redux
Reduxパターンを実現するためのライブラリ。やや更新が古いので不安があるが、FlutterでReduxを実現するためのライブラリで他にいいのがない。
Reduxパターンとは元々Reactに由来するもの。アプリ全体の状態(State)はただ一つのStoreが持つこととなっている。Stateはほかの箇所から変更不能となっている。ただしActionを送信し、ReducerがActionを受け取ることで適切な状態へと更新してくれる。一般には複雑な状態を持つアプリでもStateで明確に表すことができるので、状態を再現したり、テストしたりが簡単になる。デメリットとしては若干学習コストがかかる。
更新が新しいライブラリとしては以下があるが、使用実績が少なくFavorite数は100台となっている。
flutter_mobx
Reactに由来するMobXデザインパターンを実現するためのライブラリである。
Reduxと少し似ており、Actionを用いてアプリの状態(Observable)を変更する。
変更された状態に基づいてアプリで動作や表示を行う際には、ObserverやReactionといった要素を利用する。
GlobalKey
各ウィジェットで情報のやり取りをするための方法の一つ。別のウィジェット、多くは子ウィジェットのメンバーに親からアクセスできるようにする。
GlobalKey> class
FlutterのGlobalKeyについて深掘りしてみる
Youtube - Flutter GlobalKey
pub.devとは
Flutter, dartのAPIが集まる公式サイト
GetXとは
ステート管理、ルーティング管理、Dependency Injectionなどが簡潔に記述できるライブラリ。簡単なアプリであればStatefulWidgetで状態管理しても良いと思われるが、複雑なアプリではRiverPodと並び有効に使える。
リアクティブプログラミングを使いやすいようにしてくれている。
https://github.com/jonataslaw/getx/blob/master/documentation/en_US/state_management.md#reactive-state-manager
ルーティング管理は以下。画面遷移が驚くほど使いやすくなっている。https://github.com/jonataslaw/getx/blob/master/documentation/en_US/route_management.md
GetXの使い方(ルート管理編)
その他、依存性注入、API呼び出し、多言語対応、スナックバーなど数々の機能を簡単に使えるようになっている奇跡のライブラリ。
一般論になるが、依存性注入、ルーティング管理は特に大規模なアプリでは他のウィジェットクラスとは別に管理したほうが、1ファイルの行数が減り見通しが良くなるだろう。GetXでもそのように実装することができる。分離して実装している方の参考:
Sameera-Perera/flutter_e-commerce_item_gridview/routes
Sameera-Perera/flutter_e-commerce_item_gridview/item_bindings
Rx
典型的には、Controller
(値を放出する側=購読される側)を以下のようにして各ウィジェットで初期化する。
class Home extends StatelessWidget {
final controller = Get.put(Controller());
}
購読した値が更新された時、ウィジェットも更新したい時は、そのウィジェットをObx
で囲むだけで良い。
Obx(() => Text(
'clicks: ${controller.count}',
)),
Firebase core とは
Firebaseの基本の組み込みができるFlutter ライブラリ。
Firebase Authenticationとは
ログイン周りを簡単に実装できるFirebaseのフレームワーク
ユーザーの作成、現在のユーザー状態の取得またはユーザー状態の変更の購読、ログイン、ログアウトなど一通りのユーザー操作が簡単に実行可能。
Firebase auth for flutter とは
flutterからFirebase authを使えるようにしてくれるライブラリ
Cloud Storage for Firebase とは
画像・動画などユーザー作成コンテンツを保存するような場合に使用するクラウドストレージ。
Firebase Authと連携できる。
Cloud Storage for Firebase
Cloud Storage for Flutter とは
Cloud Storage for FirebaseをFlutter向けに使えるようにしてくれるライブラリ。
Cloud Firestore とは
Cloud Firestore
Zenn - 【図で解説】Firestoreでできること・できないこと
The Firebase Blog - 5 tips for Firebase Storage
クラウドでのNoSQLデータベース。
flutterでの使い方は以下:
Zenn - Firebase編5:Storage
Firebase公式ドキュメントにも書いてありますが、Firestore内にてdocument(具体的なデータを格納する場所)とcollection(documentを格納する場所)は交互になるようにしてください。collectionがcollectionを格納したり、documentがdocumentを格納することはありません。https://firebase.google.com/docs/firestore/data-model#dart
final messageRef = db
.collection("rooms")
.doc("roomA")
.collection("messages")
.doc("message1");
Cloud Firestore for Flutter Pluginとは
Cloud FirestoreをFlutterで使うためのライブラリ。
Cloud Firestore for Flutter Plugin
ドキュメント https://firebase.flutter.dev/docs/firestore/usage/
Google Ad Mob
flutterでは以下のライブラリで導入できる。
google_mobile_ads
ドキュメントは以下。
Mobile Ads SDK Flutter
ドキュメントだけだとよく分からないところもあったが以下のCodelabはわかりやすかった。
Codelabs - Adding an AdMob banner and native inline ads to a Flutter app
flutter_native_splash
ネイティブアプリのようなスプラッシュ画面をつけたい時にはこのライブラリが使える。
image_picker
画像やビデオを端末のアルバムから取得またはカメラで撮影するためのライブラリ。
video_player
ウィジェット上で動画を再生するためのライブラリ。
pub.dev - video_player
video_compress
動画を圧縮するためのライブラリ。
video_compress
timeago
時間の表示について、「約1時間前」「今から45秒後」といった人間が読んでわかりやすいフォーマットに変換してくれるライブラリ。
pub.dev - timeago
cached network image
ネットワーク上から画像を取得し、キャッシュに貯めたりローディング表示をしたりしながら画像を表示できるウィジェットのライブラリ。
pub.dev - cached_network_image
flutter_launcher_icons
pub.dev - flutter_launcher_icons
iOS、Androidのアプリアイコンを設定できる非常に便利なライブラリ。ymlファイルを用意するなどして設定を記載する。
package_info_plus
アプリ名、バージョン、ビルド番号などのシステム情報をコード上で手軽に取得できるようになる。
ログ出しができるライブラリ
何種類かあるが、loggerがおすすめである。
pub.dev - logger
【2022年】おすすめのロガーパッケージ4選【Flutter】
データを端末に保存
- データベース(SQLite)に保存するsqflite
- 暗号化が可能でデータの読み書きが早いNoSQLデータベースであるhive
- 設定などのちょっとしたデータを保存するshared_preferences
に大別される。
HTTP リクエスト(APIクライアント)用のライブラリ
http
dio
retrofit
chopper
getX
などがある。一番星が多いのはhttpである。
クライアントの自動生成機能があるため、ある程度以上の規模のアプリであればretrofit+dioが良いか。簡単にいうと、自分である程度APIクライアントのクラスを書いた後、build_runnerというライブラリを用いてコードの自動生成ができる。retrofitのドキュメントに詳しく書いてある。
Best Libraries for Making HTTP Requests in Flutter (2023)
dioを利用している場合、pretty_dio_logger というリクエストやレスポンスをログに見やすく出してくれるライブラリもある。
URLを開く
display_number
数字の整形(桁数、四捨五入)ができる。
- 以下、引用
import 'package:number_display/number_display.dart';
final display = createDisplay(length: 8);
main(List<String> args) {
print(display(-254623933.876)); // result: -254.62M
}
bezier_chart
線グラフ、円グラフなどが作れる。が、dart3には非対応。
folding_cell
折り畳まれたカードが開いたり閉じたりするようなアニメーションを表現できる。
flutter_launcher_icons
一つpngファイルを用意さえすれば、iOS, Android, webなど各種プラットフォーム用のサイズのアプリアイコンを自動で生成してくれるライブラリ。便利すぎてデメリットも特にないのでほぼデファクトスタンダードであろう。
リンティング
デフォルトでflutter_lint
というライブラリが入っている。さらに厳しいルールを設けているものとして
-
lint
: https://pub.dev/packages/lint -
very_good_analysis
: https://qiita.com/kenma/items/b085b7598875ac2222c3
がある。チーム開発では厳しいやつを使ったほうがいいかもしれない。
Shimmerエフェクト(ローディング中、UI部品に光が左から右に流れるような画面表示を出すこと)
同名のライブラリがあり、非常に簡単に呼び出せる。
pub.dev/shimmer
ライフサイクル
コードを実行する厳密なタイミングの制御が必要な場合は以下を検討してみるとよいだろう。
- Flutterのデフォルトのライフサイクル
-
RouteObserver -> ウィジェットがpush, popしたタイミングを取得できる。デフォルトの
Navigator.of(context).push<void>
や、GetXライブラリのGet.toNamed()
などでは取得できていた。- ただ、無論、同一画面内である子ウィジェットを切り替え、別の子ウィジェットを表示するなどのケースでは、無論検知できない。
- WidgetsBindingObserver -> フォアグラウンド、バックグラウンドのタイミングなどを取得できる。
-
RouteObserver -> ウィジェットがpush, popしたタイミングを取得できる。デフォルトの
- ライブラリ
- focus_detector -> 2年半ほどリリースがない。
- visibility_detector -> 1年ほど前にリリース。こちらの方が人気はありそう。
ローカライズ
intl + flutter localizationsパッケージを使うと良い。
「flutter_localizations」による多言語対応
画像操作
image
png, jpg,gifといった様々な画像の拡大縮小などの操作ができる。
SVG画像
flutter_svgを使うと良いだろう。
コード生成
build_runnerというパッケージを様々な他のパッケージと一緒に用いて、コード生成を行う。
flutter pub pub run build_runner build
というとんでもない長いコマンドを叩いてコード生成を行うことで有名。
Flutter からiOS, AndroidなどNativeを呼び出す
MethodChannelを使うことで可能である。ただ、冗長であって、一つコードを書いただけで複数のプラットフォームで動くと言うFlutterの利点を殺してしまうことにもなるので、これを多用しなければならない場合はそもそもFlutterを使うこと自体あまり適切ではないだろう。
iOS, Androidアプリに一部だけFlutterを導入
Add-to-appと言う方法で可能となる。Flutterへの移行を段階的に行う際には有効だろう。ただ、NativeとFlutterで密にやり取りをしないといけないとなると面倒なので、独立性の高い機能(画面)をFlutterで作って導入してみることを勧める。
Flutter からiOS, Nativeの画面を表示する
Platform Viewと呼ばれる仕組みを使うと可能ではある。が、やはりただ、冗長であって、一つコードを書いただけで複数のプラットフォームで動くと言うFlutterの利点を殺してしまうことにもなるので、これを多用しなければならない場合はそもそもFlutterを使うこと自体あまり適切ではないだろう。
また、いったんNativeで描画したものをFlutter側でコピーして映すような挙動となるので、パフォーマンス的にもよろしくない。地図やウェブビューで使用することが多いだろうが、それらについてはそれ用のパッケージがpub.devに用意されているので、Platform Viewを自分で直接使うことはそう多くないはず。
UI部品について
総論 Material Design
Flutter doc - material package
マテリアルパッケージをインポートすることでマテリアルデザインを利用可能となる。
どのようなデザインの類型があるかは上記から検索できる。
各ウィジェット(UI部品)のリストは上記から検索できる。
Scaffold
主にアプリの一番基本(ウィジェットの階層の大元)に設定すべきとされるウィジェット。
appBar, backgroundColor, body, bottomNavigationBar, bottomSheet, drawer, endDrawer, floatingActionButtonなど基本となる付属部品をたくさんつけられる。また、それらの部品の見た目・振る舞いに関しても、Scaffoldのプロパティを通じてオプションが様々設定可能となっている。
Flutter doc - Scaffold
【Flutter】Scaffoldの使い方|アプリのレイアウトを作成
Drawer
Scaffoldにセットし、画面右端または左端から出てくるドロワービューを表現するウィジェット。ScaffoldにAppBarを設定していれば、Drawerを開くためのボタンが自動的につけられる。プログラムによりDrawerを開くには、Scaffold.of(context).openDrawer()
関数を呼べば良い。Drawerを閉じるには、Navigator.of(context).pop()
関数を呼べば良い。Drawerの中身としては、典型的にはListViewを使い、縦にコンテンツを並べることが多い。
AppBar
主にScaffoldに設定する、アプリの最上部にあるタイトルバー。
flutter doc - AppBar class
Flutter の AppBar で遊んだら楽しかったよ
BottomNavigationBar
画面の下部に設置する、複数のボタンを備えたタブのこと。主にアプリの基本となる画面への画面遷移に使うことが多いだろう。
Flutter doc - BottomNavigationBar
Flutter doc - BottomNavigationBarItem
Zen - FlutterのBottomNavigationBarを本気で学ぶ
Column
縦にウィジェットを並べたい時に使うウィジェット。ただchildが画面サイズを超えてもスクロールはできない(しかもエラーになる)。たくさんのchildを含みたいのであればListViewを使うことになるか。
Rowクラスはこれの横バージョンである。
SizedBox
子ウィジェットの縦横の長さを厳密に制限できるクラス。
子無しで使えば特定の長さの隙間を表すのにも使える。
たとえば以下は高さ25の隙間を表すことができる。
SizedBox(
height: 25.0
)
FloatingActionButton
あるアプリ(または画面)において最も重要な機能などを担当させるためのボタン。影がついており、他のコンテンツから浮き上がって見え、ユーザーから目立つということが特徴。
一つの画面に二つ以上のFloatingActionButtonを付けると混乱の元になるためするべきではない。またFloatingActionButtonをdisableさせるのも避けた方がいい。
典型的にはScaffoldウィジェットに埋め込む形で利用する。
単に右下に配置することもできるほか、下部のナビゲーションばーがあればそこに埋め込む形で表示することもできる。ScaffoldクラスのfloatingActionButtonLocation変数を使う。
onPressed変数にはasyncキーワードをつけて非同期処理を記述することも可能。
FlutterボタンのonPressedでasyncを使う方法
Asynchronous programming: futures, async, await
SingleChildScrollView
ListViewとは異なり、たった一つのWidgetのみchildとして持つことができるリスト。主に、通常は全体を見せることを予定しているが、端末によっては画面端をはみ出してしまうようなコンテンツについて、スクロールができるようにするために用いる。
Flutter基礎 ~SingleChildScrollView・ListView・ListTile~
Stack
Z軸方向にChildを底から積み重ねていくWidget。
子ウィジェットはpositionedとnon-positionedがあり、positionedは指定した特定の位置に積み上げられる。non-positionedはStackのalignmentプロパティの指示に従って積み上げられる。alignmentはデフォルトではtop-leftである。
Positionedは指定の仕方によってはStackの位置をはみ出すことがあるが、はみ出す部分を描画するか、それともはみ出した部分は描画しないかもoverflowプロパティにより指定できる。
Stack class - Flutter Doc
FlutterのStackを使ってみた
CustomPaint
図形、線分、色など自由な描画をしたい際に用いるWidget。
size, およびCUstomPainterクラスを渡して初期化する。
CustomPainterクラスでは2つのメソッドを実装する必要がある。
Painter内の描画を書き換えるかを指示するためのshouldRepaint(CustomPaiter old)メソッド、および描画の内容を指示するpaint(Canvas, Size)メソッドである。
渡されたcanvasクラスを使って、以下のように豊富な種類の描画を自由に行える。
drawLine(線)
drawRect(四角)
drawCircle(円)
drawArc(アーチ)
drawPath(パス)
drawImage(ビットマップ画像)
drawParagraph(テキスト)
CustomPaint class - Flutter Doc
[Flutter] キャンバス上で任意の図形の描画を行えるCustomPaintを使ってみる
六角形を描くにはどうする?
-> Drawing a Hexagon Grid in Flutter
GestureDetector
タップ、ドラッグなど様々な操作を検知できる。
childにwidgetを指定した場合はそのchildの大きさになる。一方、childがなければ、親のサイズまで広がる。
TextField
TextFormField
TextFieldと同様ユーザー入力を受け付ける。validatorという関数を受け取ることができ、ユーザーが入力した文字列のバリデーションが可能。
TextFormField
TextField、TextFormField、Formの概要
TextEditingController
TextField、TextFormFieldなどに設定することができる。入力された文字列の取得や、プログラムからの文字列の変更、入力された時にする動作を関数として設定するなどが可能。
使い終わった時にはdisposeが必要となるので、statefull widgetのdisposeで一緒にdisposeすると良い。
TextEditingController
[Flutter]TextEditingControllerについて
Icon
さまざまなアイコンがシステム側で用意されている。IconDataとしてIconクラスのコンストラクタに渡す。
用意されているアイコンは、マテリアルアイコンのページを参照。
IconButton
Iconを渡し、タップ可能なボタンを生成する。
Container
Widgetに対してパディング・背景色・大きさの制約などを加えたい場合はContainerでラップすると良い。decorationプロパティにBoxDecorationなどを設定することで、childの背景に装飾を追加することもできる。
InkWell
タップした時の波紋のようなエフェクト(リップルエフェクト)を出せるようにするWidget。Button系のウィジェットにもこのような効果はついていますが、InkWellは任意のウィジェットをchildとして納めることができます。
Flutter doc - InkWell
Flutterのリップルエフェクトは一癖ある
CircleAvatar
ユーザーの顔画像を丸く表示するようなUI部品。正直そんな細かい部品まで公式で用意されているとは驚いた。
典型的には、画像がない場合はユーザーのイニシャルを表示する。
Image
画像を表示する。
NetworkImage
画像のURLを渡して、画像を表示する。
SimpleDialog
showDialog関数を使って呼び出せる、flutterのデフォルトのダイアログ。
flutter doc - showDialog function
flutter doc - SimpleDialog class
PageView
上下・左右にページを捲るような形でスワイプし、複数のウィジェットを連続で表示できるウィジェット。
Expanded
Row, Column, FlexのchildをExpandedでラップすることで、余っているスペース全てをそのchildが占めるようにすることができる。flexプロパティを設定することで、複数のExpandedがある場合、余っているスペースをどの割合で占めるか指定することもできる。
固定サイズにしたいやつはSizedBoxでラップし、伸び縮みさせたいやつはExpandedなどでラップすると良いだろう。
Flexible
Expandedとほぼ同じだが、Row, Column, Flexのchildが余っているスペースを全て埋めることを強制しない点で異なる。
AspectRatio
子ウィジェットを特定の縦横比にしたいときに利用する。
ClipRRect
画像を丸く加工して表示するためのウィジェット。ClipRounded
Rectの略。
fluter doc - ClipRRect
RotationTransition
Widgetを回転させるアニメーションを実現するクラス。
flutter dox - RotationTransition
Center
子ウィジェットを中央に配置する。
ListView
複数の子ウィジェットをスクロール可能な形でリスト表示するウィジェット。子ウィジェットの個数は静的でも、動的でも構わない。
flutter doc - ListView
ListTile
典型的にはListViewの子として使うことを想定したウィジェット。左側、右側に画像をおける場所を持つほか、中央に1〜3行の文字を置けるラベルがある。
Divider
ウィジェット間の区切り線となる、横に細長いウィジェット。
TextFormField
TextFieldを内蔵した入力フォーム。
flutter doc - TextFormField
Zen - コピペで使えるTextFormField
TextButton
輪郭や浮き上がった感じのないテキストそのままのような見た目のボタンを作ることができるウィジェット。
flutter doc - TextButton
【Flutter】TextButtonの使い方|ボーダーなしのフラットボタンを作る
StreamBuilder
非同期的に流れてくるデータを受け取り、特定のウィジェットを再描画するためのウィジェット。Firebase、端末のセンサー類、ネットワーク接続状況、その他自分で定義したストリームなどを受け取ることができる。
(全体ではなく)特定のウィジェットだけ更新させる、ということができるため、再描画コストの低減に役立つ。
flutter doc - StreamBuilder class
FlutterのStreamBuilderをつかって無駄な再描画を減らそう
SafeArea
端末のトップやボトムにあるノッチなどの障害物に子ウィジェットがかからないように十分距離を離してくれるウィジェット。
flutter doc - SafeArea class
FlutterのSafeAreaを使って上下のあいつらを気にしない方法
FittedBox
指定したBoxFit型の引数に応じて、子ウィジェットのサイズや位置を調節してくれる。
- BoxFit.fill: 子ウィジェットのアスペクト比を無視してFittedBoxに合わせる
- BoxFit.contain: 子ウィジェットのアスペクト比を保ちながら可能な限り大きくする
など。
Flutter doc - FittedBox
Flutter doc - BoxFit enum
CircularProgressIndicator & LinearProgressIndicator
円型&線形の進行度表示ウィジェット。
Indeterminateスタイルでは単純に同じアニメーションを繰り返し続けるだけだが、Determinateスタイルでは数値を渡すことで少しずつ進行度が上昇し100パーセントへ向かうようなアニメーションが可能。
Flutter doc - CircularProgressIndicator
Flutter doc - LinearProgressIndicator
RefreshIndicator
スクロール可能なWidget(リストビューなど)をラップして使う。オーバースクロールさせた時に、ぐるぐる回転するインジケータを表示させ、何らかの非同期の処理を行わせ、画面を更新し、インジケータを消すという動作を行える。いわゆるpull to refresh。
Flutter doc - RefreshIndicator
GridView
複数の列・行を持ちうるスクロール可能なウィジェット。
GridView.count
というインストラクタがあり、固定数の要素である場合にはこれを利用する。
return GridView.count(
crossAxisCount: 2,
children: <Widget>[
Text("1")
Text("2")
Text("3")
Text("4")
]
)
GridView.builder
は、要素が動的で大量の要素を持ちうる場合に使用する。これだと、画面上に出現している要素に対してしか計算を行わないため、パフォーマンス的に良い。
FutureBuilder
非同期処理で Widget を生成したいときに使う Widget。
【Flutter Widget of the Week #6】FutureBuilderを使ってみた
Card
角丸で影のある四角形の形をしているウィジェット。
Containerと何が違うのか? という質問がStack Over Flowにあったが、Cardは角丸で影のあるなどの特性を備えた独自の部品であって、Containerは他のウィジェットをラップする目的で使われるものである。
Card class
【Flutter】Card Widgetまとめ
Flutter Card vs Container Widget
Color, Colors
色を表すクラスがColorである。16進数を渡して初期化できる。
Colorの定数を定義した集まりがColorsである。
Builder
ある親Widgetのbuildメソッドの中で、子Widgetがあり、これが親Widgetのcontextにアクセスする場合うまく動作しなくなるのでこのような動作は禁止される。たとえば、MediaQuery.of(context)
、Scaffold.of(context)
などで使用されることがある。
このような場合は、子WidgetをStatelessWidgetのサブクラスとして定義し直せば良い。ただ、それが面倒な場合は、Builderでラップするだけでも良い。
Flutter 様々なコマンド
Double Dot(..) カスケード記法
List list = [];
list.add(color1);
list.add(color2);
list.add(color3);
list.add(color4);
// with cascade
List list = [];
list
..add(color1)
..add(color2)
..add(color3)
..add(color4);
List use of double dot (.) in dart?
A tour of the Dart language - Cascade Notation
n秒後に処理をさせる
Future.delayedが利用できる。
await Future<void>.delayed(Duration(milliseconds: nSeconds));
dynamic キーワードについて。
動的型付けができるようにする。無論多用すると型安全ではなくなることもあり、おすすめはしない。
【Dart】【Flutter】dynamic型(動的型付け)
コンストラクタ
"List Constructor", "Const Constructor", "Factory Constructor"など色々ごちゃごちゃあるようだがすぐに全部理解できる気はしなかったが、わからなくなったら公式ページに都度戻ればいいだろう。
とりあえず"Const Constructor"というのは生成したオブジェクトを一回生成して以降は定数とする(プロパティを後から変えれない)時に使う。
Const
キーワードをコンストラクタの前につけるほか、そのクラスの変数全てにfinalマークをつけることになる。
named-constructors - Flutter Doc
Dartのconstコンストラクタとは
Stream
非同期に連続した値を発信・購読ができるStream型がDart公式として用意されている。
位置パラメータ、デフォルト値
曲線かっこ({})を用いて名前付きパラメータをコンストラクタにつけることができます。
class Customer {
String name;
int age;
String location;
// Named optional parameters
Customer(this.name, {this.age, this.location});
@override
String toString() {
return "Customer [name=${this.name},age=${this.age},location=${this.location}]";
}
}
var customer = Customer("bezkoder", location: "US", age: 26);
print(customer);
// Customer [name=bezkoder,age=26,location=US]
var customer1 = Customer("bezkoder", age: 26);
print(customer1);
// Customer [name=bezkoder,age=26,location=null]
var customer2 = Customer("zkoder");
print(customer2);
// Customer [name=zkoder,age=null,location=null]
またパラメータの後ろに= "DefaultValue"
とすることでデフォルト値をつけることも可能です。
Theme
Flutterアプリを初期作成したときに書いてあるprimarySwatch
(基本となる色見本)は、設定するとさまざまなところで基本の色として使われるようになる。
Zenn - Flutter の primarySwatch は奥が深い
flavorごとに設定を変えるには(develop, staging, releaseなど)
以下が参考になります。
FlutterでDart-defineのみを使って開発環境と本番環境を分ける
Flutterで環境ごとにビルド設定を切り替える — iOS編
flutterのdart-defineは2.2以上ではbase64でエンコードされるので対応したスクリプトの紹介
機密情報をソースコードにハードコーディングしたくない場合
flutter build apk --dart-define-from-file=env.json
といった実行方法をすると、環境変数から値を読み込んでくれるので、ハードコーディングの必要がなくなります。
flutter 3.13くらいから、jsonファイルだけでなく.env
ファイルもライブラリなしでflutter run --dart-define-from-file=.env
で読み込めるようになった。iOS、Androidネイティブがわへそれらの設定値を伝えることも比較的簡単にできるのでおすすめである。
Add .env file support for option --dart-define-from-file #128668
The Right Way to Set Environment Variables with Compile-Time Variables
アプリを実行する際に毎回flutter run --dart-define-from-file=.env
を叩くのも面倒だと思うので、Android StudioのEdit Configuration > Additional ttach argsに--dart-define-from-file=.env
を設定すると、デバッグ実行するごとにこのコマンドが叩かれている扱いとなる。
ゲッター・セッター
get
キーワード・set
キーワードを用いる。
part
キーワード
import
と同様に他のファイルの内容を読み込むが、import
と違いpublicだけではなくprivateのものまで読み込む。該当ファイルらを一つのファイルにしてしまうようなものと言える。通常は使う機会がなく、自動生成系ライブラリを使うときに使うタイミングがあるようである。
Pointクラス
二次元平面上の座標を表すクラス
'dart:math'をインポートすることで利用可能
dart-tutorial - dart-getters-setters
画面遷移(Flutter Default)
Navigatorクラスを使って画面遷移を行う。非常に簡便である。
Flutter doc - Navigator class
FlutterのNavigationとRoutingを理解する
Guard 節
SwiftであるようなGuard節は存在しない。ifを使って早々にreturnして抜けるしかないだろう。
compactMap
SwiftのようなcompactMapは直接はないが、whereType
を使えばnullではない値だけを抽出できる。
final List<String?> hoge = [null, "a", "b", null];
final fuga = hoge.whereType<String>().toList(); // ["a", "b"]
json変換
jsonをオブジェクトに変換、またはその逆をしたい場合は、dart:convert パッケージのjsonDecode()を使うことが考えられる。
中規模以上のプロジェクトなどで、自動でjsonに変換したいなどの場合は、json_serializable, json_annotationなどのライブラリを使える。
日付けの取り扱い
DateTimeクラスで特定の日付を表すことができます。日付の差の計算や、タイムゾーンの指定、何日後・何日前への移動など一通りの操作は行えます。
flutterでDateTimeとStringの変換方法とTimeZoneとLocale
【Dart】【Flutter】DateTime型についてのまとめ
<Widget>
という書き方
<Widget>
という書き方がコードの中に出てくることがありますが、これは、配列の中の型を何にするかを指定するものです。無論Widget
型以外でも、何でも指定できます。
Column(
children: <Widget>[
// Other codes
]
)
参考: What does means in Flutter code?
プログラム全体からアクセスできるよう定数を定義したい
プログラムのトップレベルに定義したり、あるクラスのStatic変数として定義するなどが考えられる。
const rawgKey = const String.fromEnvironment('RAWG_KEY');
// or
class Const {
// Make constructor private to prevent instantiation
Const._();
static const rawgKey = const String.fromEnvironment('RAWG_KEY');
}
Flutter: Defining Constants — The Right Way
RiverPod 導入
pubspec.yml
に下記を追加し、pub getを押す
flutter_riverpod: ^1.0.3
RiverPodの利用
参考:
RiverPod official
Flutter State Management with Riverpod: The Essential Guide
main
riverPodを利用する場合は最初のmain
関数において、runApp
にProviderScope
を渡す。ProviderScope
とは、アプリの全Providerの状態を保持するWidgetである。
void main() {
runApp(
ProviderScope(child: MyApp())
);
}
画面サイズ取得
MediaQuery.of(context).size.width
MediaQuery.of(context).size.height
画像リソースの追加
典型的にはプロジェクトルートにassetsフォルダを作り、画像を置く。pubspec.ymlにて、以下のように追加する。
flutter:
assets:
- assets/
Flutterのバージョンの切り替えをスムーズにやりたい。
fvmがおすすめ。https://fvm.app
使用方法参考
FVMでFlutter SDKのバージョンをプロジェクト毎に管理する
内部設計
Element
Flutterで利用者側にはあまり出てこないが内部で使われている要素。ドキュメントなどを読んでもあまり理解できなかったためAIによる解説を貼ることで終わりにさせていただく。:
もちろんです!Flutterの「Element」について、初心者にもわかりやすく解説します。
---
### **前提知識: Flutterのウィジェットツリー**
FlutterではUIは「ウィジェット(Widget)」によって構築されます。ウィジェットはUIの設計図のようなもので、どのように見えるべきかを定義します。しかし、ウィジェット自体はただの不変(immutable)なオブジェクトであり、実際のUIを描画したり状態を管理したりするわけではありません。
そのため、Flutterにはウィジェットとは別に「Element」という概念が存在します。
---
### **Elementとは何か?**
**Element**は、ウィジェットツリーに基づいて構築される「実行時のオブジェクト」です。具体的には以下のような役割を持っています:
1. **ウィジェットとレンダリングツリーをつなぐ橋渡し**
- ウィジェットは単なる設計図ですが、Elementはその設計図を基に実際に画面に表示されるUI要素を管理します。
- Elementはウィジェットのインスタンスに対応し、ウィジェットのプロパティを元にUIを構築・更新します。
2. **状態(State)の保持**
- StatefulWidgetの場合、Elementはそのウィジェットに関連付けられた状態(State)を保持します。
- これにより、ウィジェットが再構築されても状態が失われることなく維持されます。
3. **ライフサイクルの管理**
- Elementはウィジェットの追加、更新、削除といったライフサイクルを管理します。
- 例えば、ウィジェットが更新された場合、新しいウィジェットと既存のElementを比較して、必要に応じて再構築を行います。
---
### **ウィジェット、Element、レンダリングツリーの関係**
FlutterのUI構築プロセスを理解するために、以下の3つの層を覚えておくと便利です:
1. **ウィジェットツリー(Widget Tree)**
- UIの設計図。すべてのウィジェットがツリー構造で表現されています。
- ウィジェットは不変(immutable)なので、変更が必要な場合は新しいウィジェットを作成します。
2. **エレメントツリー(Element Tree)**
- 実行時に生成されるツリーで、ウィジェットツリーに基づいて構築されます。
- 各Elementはウィジェットと対応しており、ウィジェットのプロパティや状態を管理します。
3. **レンダリングツリー(Render Tree)**
- 実際に画面に描画されるレイアウトやペイントを行うためのツリー。
- Elementがレンダリングツリーを操作し、最終的なUIを描画します。
---
### **簡単な例で理解する**
例えば、以下のようなシンプルなコードを考えます:
dartのコード:
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Center(
child: Text('Hello, Flutter!'),
);
}
}
このコードを実行すると、次のような流れでUIが構築されます:
1. **ウィジェットツリーの構築**
- `MyApp` → `Center` → `Text` の順でウィジェットツリーが作られます。
2. **エレメントツリーの生成**
- Flutterはウィジェットツリーに基づいて、それぞれのウィジェットに対応するElementを生成します。
- 例えば、`Center`ウィジェットには`CenterElement`、`Text`ウィジェットには`TextElement`が対応します。
3. **レンダリングツリーの描画**
- Elementがレンダリングツリーを操作し、最終的に画面に「Hello, Flutter!」というテキストが中央揃えで表示されます。
---
### **なぜElementが必要なのか?**
ウィジェットだけでは不十分な理由は以下の通りです:
- **効率性**: ウィジェットは不変なので、毎回新しいウィジェットを作成する必要があります。一方、Elementは変更可能(mutable)なので、ウィジェットが更新された場合でも必要な部分だけを再構築できます。
- **状態管理**: StatefulWidgetの場合、Elementが状態を保持することで、ウィジェットの再構築時に状態が失われることを防ぎます。
- **ライフサイクル管理**: Elementがウィジェットの追加・更新・削除を管理することで、効率的なUI更新が可能になります。
---
### **まとめ**
- **ウィジェット**はUIの設計図で、不変(immutable)です。
- **Element**は実行時のオブジェクトで、ウィジェットとレンダリングツリーをつなぐ役割を果たします。
- **レンダリングツリー**は実際に画面に描画されるレイアウトやペイントを担当します。
FlutterのUIシステムは「ウィジェット」「Element」「レンダリングツリー」の3層で構成されており、それぞれが協力して効率的で柔軟なUIを実現しています。
もしまだ疑問点があれば、ぜひお聞かせください!
エラー
missing_default_value_for_parameter
The parameter ‘{0}’ can’t have a value of ‘null’ because of its type, but the implicit default value is ‘null’.
With null safety, use the ‘required’ keyword, not the ‘@required’ annotation.
コンストラクタのパラメータにデフォルト値を提供しろというエラーであるので、デフォルト値を設定するか、そもそもその値をnullableにしてしまうか、あるいはそのパラメータに@required
をつけて必須のパラメータにするかの3択となる。
don't support null safety: - package:flutter_riverpod - package:riverpod
riverpodがnullに対応していないのにnullを使用していたのでこのエラーが出た。
null safety 対応バージョンのriverpodもあるようなので、それを代わりに用いたら解消した。
build gradle cannot resolve symbol 'properties'
build.gradleでエラー。
multidex
Permission denied. Please enable Firebase Storage for your bucket by visiting the Storage tab in
the Firebase Console and ensure that you have sufficient permission to properly provision resources
Firebase Console > Storage > rules より、リクエストを許可する条件を記入していなかったので、リクエストが全て拒絶されていた。
Build file android/app/build.gradle line 24 A problem occurred evaluating project ':app' Android Gradle plugin requires Java 11 to run. You are currently using Java 1.8.
Android端末実機で動かそうとした時に発生。
android/build.gradle > dependenciesよりandroidビルドツールとkotlin gradleプラグインのバージョンを下げて解消。
[Flutter] Android Gradle plugin requires Java 11 to runのエラーの直し方
[Get] the improper use of a GetX has been detected.
[Get] the improper use of a GetX has been detected.
You should only use GetX or Obx for the specific widget that will be updated.
If you are seeing this error, you probably did not insert any observable variables into GetX/Obx
or insert them outside the scope that GetX considers suitable for an update
(example: GetX => HeavyWidget => variableObservable).
If you need to update a parent widget and a child widget, wrap each one in an Obx/GetX.
GetXController
クラスを使っているのに、Rxの変数を何も使っていない時に出るエラーの模様。
ウィジェットをObxで囲い忘れている時にも出ることが多い。
Because depends on cached_network_image >=3.2.2 which requires SDK version >=2.18.0 <3.0.0, version solving failed.
Because <MyProject> depends on cached_network_image >=3.2.2 which requires SDK version >=2.18.0 <3.0.0, version solving failed.
pub get failed (1; Because tic_tok_copy depends on cached_network_image >=3.2.2 which requires SDK version >=2.18.0 <3.0.0, version solving failed.)
新しいライブラリを追加しようとしたらエラーになった。
flutter channel stable
とflutter upgrade
を実行。その後pubspec.ymlでflutter sdkバージョンをもっと高いものに書き換えた上で、pub get
を実行すると解消したかのように見えたが、アプリを実行する時にやはり同様のエラーが出続ける。
podfile
のios version を13にあげる
ターミナルでiosフォルダに行き、pod repo update, pod update, pod installを実行
しても治らず。
最終的にpodfile.lockを一回削除してからアプリをビルドしたところ、エラーが解消した。
Specs satisfying the Firebase/Firestore (= 8.15.0), Firebase/Firestore (= 9.4.0)
dependency were found, but they required a higher minimum deployment target.
導入しているFirestoreのバージョンが低すぎて、他のライブラリが要求するバージョンに達していなかった模様。Firestore(ここでは、cloud_firestoreライブラリ)のバージョンを最新版にしてpub getしたところ治った。
Columnの中にGridViewを追加したらレイアウトがメチャクチャになった。
GridViewのshrinkWrapプロパティをtrueにすると治る場合がある。
flutter - ListView - shrinkWrap
StackOverFlow - What does the shrinkWrap property do in Flutter?
ただパフォーマンス的にはあまり良くない模様。
Flutter アプリ開発におけるパフォーマンス改善 Tips
Flexible, Expanded, LimitedBox, SizedBoxなどを使ってListViewの高さを決めてあげることでも治る。
【Flutter】Columnの中でListViewを使う時にエラーが出る
A RenderFlex overflowed by 32 pixels on the bottom.
ウィジェットをSingleChildScrollViewで囲む。
flutter doc - SingleChildScrollView
flutter LoadError - dlsym(Init_ffi_c): symbol not found
pod install中にエラー
以下のflutter の公式wikiを参考にrosetta2をインストール & rubyをインストールし直したら治った。M1 Macでのみ起こる問題なので、M1 Macを利用の方は見ておいた方が良い。
Developing with Flutter on Apple Silicon
[!] CDN: trunk URL couldn't be downloaded: https://cdn.cocoapods.org/CocoaPods-version.yml Response: URL using bad/illegal format or missing URL
pod install中にエラー
色々試したが最終的には下記Issueに従ってCocoapodsを一回アンインストールし、brewを使って再インストールしたら治った。
[!] CocoaPods could not find compatible versions for pod "Firebase/CoreOnly":
In snapshot (Podfile.lock):
Firebase/CoreOnly (= 9.4.0)
In Podfile:
firebase_core (from `.symlinks/plugins/firebase_core/ios`) was resolved to 1.24.0, which depends on
Firebase/CoreOnly (= 9.6.0)
You have either:
* out-of-date source repos which you can update with `pod repo update` or with `pod install --repo-update`.
* changed the constraints of dependency `Firebase/CoreOnly` inside your development pod `firebase_core`.
You should run `pod update Firebase/CoreOnly` to apply changes you've made.
指示通りにpod repo update
をやったが解消しなかった。podfile.lockを削除してからもう一度pod install
を叩くと解消した。
flutter CocoaPods not installed or not in valid state.
以下が参考になります。
【Flutter】エラーCocoaPods not installed. 〜が表示されiosビルドできない
最終的には、よくわからなかったのでCocoapodsやAndroid Studioをアンインストールし、インストールし直しました。
Flutter Error (Xcode) Multiple commands produce GoogleService-Info.plist'
異なるFlavor(dev, stg, prod)用にGoogle-service.info.plistを用意していたらこのようなエラーが発生してした。RunnerをXcodeで開き、target > Build Phases の Copy Bundle Resources からGoogle-service.info.plistを削除したら治った。
参考: FlutterでFirebaseの環境毎設定でMultiple commands produce ~が出るようになってしまった場合
Unhandled Exception: Binding has not yet been initialized. The "instance" getter on the ServicesBinding binding mixin is only available once that binding has been initialized.
Unhandled Exception: Binding has not yet been initialized.
The "instance" getter on the ServicesBinding binding mixin is only available once that binding has been initialized.
Typically, this is done by calling "WidgetsFlutterBinding.ensureInitialized()" or "runApp()" (the latter calls the former). Typically this call is done in the "void main()" method. The "ensureInitialized" method is idempotent; calling it multiple times is not harmful. After calling that method, the "instance" getter will return the binding.
In a test, one can call "TestWidgetsFlutterBinding.ensureInitialized()" as the first line in the test's "main()" method to initialize the binding.
If ServicesBinding is a custom binding mixin, there must also be a custom binding class, like WidgetsFlutterBinding, but that mixes in the selected binding, and that is the class that must be constructed before using the "instance" getter.
- awaitをmainメソッドの内部で使う場合、その前に
WidgetsFlutterBinding.ensureInitialized();
を呼んでおく必要がある。これをmainメソッドの一番最初に持って来たところエラーが解消した。
[VERBOSE-2:dart_vm_initializer.cc(41)] Unhandled Exception: [core/not-initialized] Firebase has not been correctly initialized.
[VERBOSE-2:dart_vm_initializer.cc(41)] Unhandled Exception: [core/not-initialized] Firebase has not been correctly initialized.
Usually this means you've attempted to use a Firebase service before calling `Firebase.initializeApp`.
View the documentation for more information: https://firebase.flutter.dev/docs/overview#initialization
Firebase.initializeApp
は呼んでいるはずだが、上記のエラーが出てしまった。GoogleService-Info.plist
が想定しているフォルダ(iOS/Runner)に置いていることを確かめた後、これをXcodeで開いたRunner.xcworkspace内へとコピーペーストを行う。これにより、XcodeがGoogleService-Info.plist
を参照できるようになり、エラーが解消した。
Flutter: Firebase has not been correctly initialized
The lower bound of sdk must be or higher to enable null safety
sdkのバージョンの最低を2.12.0にあげる。
environment:
sdk: ">=2.12.0 <3.0.0"
How to enable Null-Safety in Flutter?
error The non-nullable local variable must be assigned before it can be used not_assigned_potentially_non_nullable_local_variable at
non-nullの変数については、初期化しないのであれば、late
キーワードをつける。
late String test;
error: The non-nullable local variable 'newTaskTitle' must be assigned before it can be used
error The argument type Object? can't be assigned to the parameter type String argument_type_not_assignable
results = jsonDecode(snapshot.data);
snapshot.data
の型がなんであるかがdartに伝わっていないようなので、型を明示するとともにnullの場合どうするのかを指示するようにした。
results = jsonDecode(snapshot.data as String ?? '');
Annotation must be either a const variable reference or const constructor invocation
ライブラリのdio, retrofitを両方使っている場合、両方にHeaders
というのが定義されているので普通に使うと衝突してしまう。以下のようにdioのHeaders
を隠せば解決する。
import 'package:dio/dio.dart' hide Headers;
// 中略
@GET("/games")
@Headers(<String, dynamic>{
"X-RapidAPI-Host" : "rawg-video-games-database.p.rapidapi.com"
})
Future<List<GamesListElement>> getGames();
環境変数を設定しているはずだが、String.fromEnvironment('XXXXXX');
を使っても値が取れずnullになってしまう。
String.fromEnvironment('XXXXXX');
のドキュメントにも書いてあるが、直前にconst
キーワードをつけないとうまく動作せずnullがかえってきてしまっていた。
const apiKey = const String.fromEnvironment('X_RAPID_API_KEY');
String.fromEnvironment without a const silently does the wrong thing in the VM #55870
Failed to download Kotlin Compiler Maven artifact (org.jetbrains.kotlin:kotlin-dist-for-jps-meta:1.8.20-release). The search was performed in the following repos: https://repo1.maven.org/maven2 https://repository.jboss.org/nexus/content/repositories/public/ You can use Kotlin compiler which is bundled into your IDE. Select 'Bundled' version in 'File | Settings | Build, Execution, Deployment | Compiler | Kotlin Compiler | Kotlin Compiler version
Flutterよくわからなかったが、KotlinJpsPluginSettings
というののダウンロードの設定がFlutterプロジェクトのルートにあるはずの.idea
フォルダの中のkotlinc.xml
の中に書いてある。これの中の<option name="version" value="1.8.20-release" />
というのから-releaseを削除し<option name="version" value="1.8.20" />
としたところ、エラーが消えた。
Kotlin dist downloading failed #126248
Unhandled Exception: DioException [unknown]: null Error: type '_Map' is not a subtype of type 'List?' in type cast
dioを使ってAPIを呼んでいた。レスポンスは{
で始まり}
で終わるオブジェクトの型であってオブジェクトの配列ではないのにも関わらず、APIクライアントの実装のところでList<TestElement>
という形で配列かのような指定をしてしまっていた。TestElement
と直したところ治った。
Expected to find '}'
クロージャについて、クロージャが値を返さない(Voidを返す)時は、=>
という矢印の表記はいらない。
flutter: Expected to find '}'. in if else condition
dart sdk is not found in the specified location
他人の作ったFlutterプロジェクトを開く時によく発生する。Dart SDKとあるが、Dartではなく、Android StudioのFlutterの設定で、Flutter SDKのパスの位置を指定してあげれば解決する。
参考 [Flutter]Dartのプラグインをインストールしたにも関わらず「Dart SDK is not configured」が出てしまう場合の対処法
意図せずして、イベントトラッキングが何度も送信されてしまう。
Widgetのbuild
メソッド内にイベントトラッキング送信のコードを書いてしまっていた。build
メソッドはいつ何度呼ばれてもおかしくないので、そこに高価な処理や、厳密なタイミングが求められる処理は書くべきではない。というかUIの生成以外は書かないほうがいいのではないか。
【Flutter】build() でやってはいけない 3 つのこと
アプリがフォアグラウンド・バックグラウンドになった時に何かを実行したい。
以下はChatGPTが出してきたコードだが、自分でも試したところ想定通りに動作していた。
import 'package:flutter/material.dart';
class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> with WidgetsBindingObserver {
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
super.didChangeAppLifecycleState(state);
if (state == AppLifecycleState.resumed) {
// 画面が再表示されたときに呼ばれる
print('viewDidAppear');
} else if (state == AppLifecycleState.paused) {
// 画面が非表示になったときに呼ばれる
print('viewDidDisappear');
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Flutter Demo Home Page'),
),
body: Center(
child: Text('Hello, world!'),
),
);
}
}
fvm flutter
コマンドで、flutter not found
と出る。
fvmによって該当フォルダでどのflutterを指定していないからそうなる。そのプロジェクトの該当フォルダでどのflutterを使うか指定すれば解消する。
pod install apple silicon cannot load such file -- ffi_c (LoadError)
Apple Silicon macで作業していた。rbenvでrubyを入れ、rbenvでグローバルに設定したrubyにおいてcocoapodsをインストールし、それを見に行くようにしていた。ところがグローバルのrubyが2.2.6とApple Silicon macに対応していないバージョンとなってしまっていた。
.zprofile
にif which rbenv > /dev/null; then eval "$(rbenv init -)"; fi
と記載し、rbenvがある場合はrbenv経由でrubyを実行するようにした。
すると、ruby -v
と実行した時、rbenvでグローバルに設定した3.3.3が出るようになり、本エラーも解消した。
Include of non-modular header inside framework module 'firebase_auth.FLTAuthStateChannelStreamHandler': '/Users/xxxxxxx/ios/Pods/Headers/Public/Firebase/Firebase.h'
Xcode16で、Firebase関連のライブラリ(firebase/flutterfire)を使うと、iOS側をビルドする際にこのエラーが発生する模様。
Runner.xcworkspace をXcodeで開き、
Build Settings
より Allow Non-modular includes in Framework Modules
をYESにすると解消する模様。
NULL is not a subtype of Map
サーバーからの戻り値jsonをオブジェクトに変換しようとした際に起きた。Nullableの値を常に得ようとしてしまっていたことが原因だった。Nullの場合は何もしないようにしたら解消した。json_annotationなどを利用して自動で変換している場合はこのような不具合は起きないだろうが、一応。
class Recipe {
final String id;
final String title;
final String imageUrl;
final List<String> ingredients;
final String instructions;
final NutritionInfo nutrition;
Recipe({
required this.id,
required this.title,
required this.imageUrl,
required this.ingredients,
required this.instructions,
required this.nutrition,
});
factory Recipe.fromJson(Map<String, dynamic> json) {
return Recipe(
id: json['id'].toString(),
title: json['title'],
imageUrl: json['image'],
ingredients: [],//Handled later
instructions: '',//Handled later
nutrition: NutritionInfo.fromJson(json['nutrition']),
);
}
}
class Recipe {
final String id;
final String title;
final String imageUrl;
final List<String> ingredients;
final String instructions;
final NutritionInfo? nutrition;
Recipe({
required this.id,
required this.title,
required this.imageUrl,
required this.ingredients,
required this.instructions,
this.nutrition
});
factory Recipe.fromJson(Map<String, dynamic> json) {
return Recipe(
id: json['id'].toString(),
title: json['title'],
imageUrl: json['image'],
ingredients: [],//Handled later
instructions: '',//Handled later
nutrition: json['nutrition'] == null ? null : NutritionInfo.fromJson(json['nutrition']),
);
}
}
Stack Overflow
sqflite使用時にこのエラーが発生した。Stack Overflowとは一般にメモリが逼迫しこれ以上プログラムを進められなくなったことを指し、何らかの無限ループが発生してしまった際などによく起こる。
ある変数から、その変数自身を誤って参照してしまっていたため、発生してしまった。
Future<Database> get database async {
if (_database != null) return database!;//無限ループ
_database = await _initDatabase();
return _database!;
}
Future<Database> get database async {
if (_database != null) return _database!;//OK!
_database = await _initDatabase();
return _database!;
}
FlutterError (This widget has been unmounted, so the State no longer has a context (and should be considered defunct). Consider canceling any active work during "dispose" or using the "mounted" getter to determine if the State is still active.)
ウィジェットがもう消失しているのにそのウィジェットのcontextプロパティにアクセスしようとしていることが原因であった。
一つの解決策としてはmounted
メソッドを呼び、ウィジェットがまだマウントされている間だけcontextにアクセスするようにしたところ解消した。
その他、このエラーがの解決策としては、当該ウィジェットが消えた際にdisposeメソッドなので何か購読しているものを解除する処理を入れるべき場合もあると思われる。解除すべき購読がないか確かめよう。たとえばTextEditingControllerはウィジェット喪失時に購読解除すべきとされる。
参考: Stack Overflow
FormKeyの配下のcurrentStateがnullになってしまう。
FormKeyはForm系ウィジェットの実装の際に用いる。
- Stateful Widgetを使っていること
- その中に
Form
という親ウィジェットがあり、その配下に一つまたは複数のForm関連ウィジェット(TextFormField
)を置く構成であること - FormKeyを
Form
のkey
のパラメータとして渡していること
以上のような構成になっているかどうか確認しよう。
Error: The pod "Firebase/CoreOnly" required by the plugin "firebase_auth" requires a higher minimum macOS deployment version than the plugin's reported minimum version.
ライブラリ関連のエラーのようだが、実は普段起動しているターゲットではないMacをターゲットにしてアプリを起動しようとしていたことが原因だった。ターゲットをiOS, Androidデバイスに指定して起動したら解消しt
引数に基づき動的に画面遷移先を変更したい。
main.dart
などでMaterialApp
について、
- 変更前:
routes
プロパティを使用して固定のルートマップを定義 - 変更後: より柔軟な
onGenerateRoute
方式に変更
-
パラメーター処理の追加:
- 以前の
routes
では単純に画面を表示するだけでしたが、onGenerateRoute
ではルート名に応じた処理や引数の受け渡しが可能になります。 - 特に
SaveLoadScreen
への引数(isLoadMode
)を適切に処理できるようになります。
- 以前の
-
エラーハンドリングの改善:
- 未定義のルートに対して適切なエラー画面を表示するようになります。
簡単に言うと、従来の静的なルーティングから、引数を処理できる動的なルーティングに変更したことで、遷移時に必要なパラメーターを正しく渡せるようになります。
onGenerateRoute でなくroutes を使って場合は、画面へ引数を渡せないということではない。
routes
を使う場合も引数を渡すことは可能。ただし、それぞれに違いがあります:
routes
での引数の扱い
// 1. 引数を渡す(これは同じ)
Navigator.pushNamed(
context,
AppRoutes.saveLoad,
arguments: {'isLoadMode': false}
);
// 2. 受け取る側(SaveLoadScreen内)
@override
Widget build(BuildContext context) {
// この方法で引数を取得する必要がある
final args = ModalRoute.of(context)!.settings.arguments as Map<String, dynamic>?;
final isLoadMode = args?['isLoadMode'] ?? false;
// ...
}
onGenerateRoute
での引数の扱い
// 1. 引数を渡す(同じ)
Navigator.pushNamed(
context,
AppRoutes.saveLoad,
arguments: {'isLoadMode': false}
);
// 2. 受け取る処理(main.dartのonGenerateRoute内)
case AppRoutes.saveLoad:
final args = settings.arguments as Map<String, dynamic>? ?? {};
return MaterialPageRoute(
builder: (_) => SaveLoadScreen(
// 引数を事前に処理してコンストラクタに渡せる
isLoadMode: args['isLoadMode'] ?? false,
),
);
onGenerateRoute
の主な利点:
- 引数の処理をルーティング時に集中して行える
- ウィジェットのコンストラクタで直接パラメータを受け取れる
- 同じルート名でも引数によって異なる画面を返せる
- 引数に基づいた動的なルート生成が容易
端末の向きを取得したい。
final isLandscape = MediaQuery.of(context).orientation == Orientation.landscape
Android Studio ショートカット
右クリック -> Reformat code with dartfmt
で、コードをflutter風に改行した形へと一発で整形できる。
Text("TokTik", style: TextStyle(fontSize: 35, color: buttonColor, fontWeight: FontWeight.w900,),),
↓
Text(
"TokTik",
style: TextStyle(
fontSize: 35,
color: buttonColor,
fontWeight: FontWeight.w900,
),
),
option + Return(Mac)で、特定のWidgetを別のWidgetでラップさせることが一瞬でできる。
extends StatelessWidget
の上で同じ操作をすると、StatefulWidgetへの自動変換が利用できるようだ。
stless
でStateless Widget, stful
でstateful widgetを一発で作れる。
Command + N で様々なコード生成
Shift2連打
ファイル名、symbol名などで検索。
Control + Shift + F
全ファイルの中身を検索。
Widget Inspector
デバッグビルド中のアプリ内のウィジェットの構造を詳細に把握できる。
Repaint Rainbow
どの範囲のウィジェットが再描画されるかが表示される。パフォーマンスのチェックに便利。
Android 関連
AndroidManifest.xmlとは
アプリの重要な設定を記載するファイル。
AndroidManifest.xml の備忘録
AndroidManifest概要
Android 端末を開発に使う
設定 > 端末について > バージョン > ビルド番号を7連続タップ で開発者モードがONになります。
追加設定 > 開発者オプション > USB デバッグ をONにするとAndrdoid studioから端末へアプリを走らせることができます。
https://smartttphone.com/oppo-developer/
buildgradleについて
Android developer guide - ビルドを設定する
- defaultconfig
典型的には以下のようなものを設定する
// Uniquely identifies the package for publishing.
applicationId = "com.example.myapp"
// Defines the minimum API level required to run the app.
minSdk = 15
// Specifies the API level used to test the app.
targetSdk = 28
// Defines the version number of your app.
versionCode = 1
// Defines a user-friendly version name for your app.
versionName = "1.0"
他にも以下のような値がある。
-
applicationIdSuffix
: ビルドバリアント向けにアプリのIDを変更できる。 -
アプリ名を環境ごとに動的に変更したい場合、ここに適当な変数を定義し、
android/app/src/main/AndroidManifest.xml
内で利用できる。
defaultConfig {
// String型の変数app_nameを定義し、任意の値を入れれる。
resValue "string", "app_name", "FlutterAT" +
(dartEnvironmentVariables.FLAVOR == 'prod' ? '' :".${dartEnvironmentVariables.FLAVOR}")
}
android:label="@string/app_name"
テスト
テストは単純な順にUnit Tests, Widget Tests, Integration Testsが存在。
以下はデフォルトであるWidget Testsの例。日本語コメントは筆者。
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:my_app/main.dart';
void main() {
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
// アプリを起動
await tester.pumpWidget(MyApp());
// カウンターアプリで、最初にカウントが0となっているTextウィジェットがあることを確認
expect(find.text('0'), findsOneWidget);
expect(find.text('1'), findsNothing);
// +ボタン(アイコン)を検索し、タップする
await tester.tap(find.byIcon(Icons.add));
// 少し時間の待受をし、画面が変化し終わるまで待つ。
await tester.pump();
// カウントが0から1になっていることを確認。
expect(find.text('0'), findsNothing);
expect(find.text('1'), findsOneWidget);
});
}
CI/CD
Continuous integration services
GitHub Actions で Automatically manage signing を使って Flutter の ipa ビルドする
デプロイ前の注意点(チェックポイント)
- Android: Build and release an Android app
- iOS: Build and release an iOS app
iOS
自動でApp Store Conectにアップロードする場合や、Automatically manage signingを利用してビルドする場合はApp Store ConnectよりPrivate Keys(.p8 ファイル)を作成し、ダウンロードすることとなる。
参考: Uploading an App Store Connect API Key to Appcircle
そうでない場合はプロビジョニングプロファイルなどを使うこととなるがこれは割愛する。