9
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Flutter キャッチアップ

Last updated at Posted at 2022-09-17

前作

Flutterを知らない人がFlutterでアプリを公開するまでにどのような情報が必要でどのようなエラーを解消したか

Flutter 公式実装例

All Samples

公式では無い実装例

Flutter 情報集

Solido/awesome-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バージョンと関連している。リストは以下:

flutter dev release archive

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>();

特徴:

  • readlisten: 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の解説。

参考: Document/provider

RiverPod ライブラリ

flutter_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_lint

riverpod おすすめ構成

※バージョン番号は任意

pubspec.yaml
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 のようにステートマネジメントを行うためのライブラリ。

flutter-hooks

riverpodと併用したい場合はhooks-riverpodを使う。

flutter_redux

Reduxパターンを実現するためのライブラリ。やや更新が古いので不安があるが、FlutterでReduxを実現するためのライブラリで他にいいのがない。

Reduxパターンとは元々Reactに由来するもの。アプリ全体の状態(State)はただ一つのStoreが持つこととなっている。Stateはほかの箇所から変更不能となっている。ただしActionを送信し、ReducerがActionを受け取ることで適切な状態へと更新してくれる。一般には複雑な状態を持つアプリでもStateで明確に表すことができるので、状態を再現したり、テストしたりが簡単になる。デメリットとしては若干学習コストがかかる。

flutter_redux

更新が新しいライブラリとしては以下があるが、使用実績が少なくFavorite数は100台となっている。

async_redux

Flutterのアプリ設計(Redux)

flutter_mobx

Reactに由来するMobXデザインパターンを実現するためのライブラリである。

flutter_mobx

Reduxと少し似ており、Actionを用いてアプリの状態(Observable)を変更する。

変更された状態に基づいてアプリで動作や表示を行う際には、ObserverやReactionといった要素を利用する。

GlobalKey

各ウィジェットで情報のやり取りをするための方法の一つ。別のウィジェット、多くは子ウィジェットのメンバーに親からアクセスできるようにする。

GlobalKey> class
FlutterのGlobalKeyについて深掘りしてみる
Youtube - Flutter GlobalKey

ListenableBuilder

pub.devとは

Flutter, dartのAPIが集まる公式サイト

pub.dev

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 (値を放出する側=購読される側)を以下のようにして各ウィジェットで初期化する。

test.dart
class Home extends StatelessWidget {
  final controller = Get.put(Controller());
}

購読した値が更新された時、ウィジェットも更新したい時は、そのウィジェットをObxで囲むだけで良い。

test.dart
            Obx(() => Text(
                      'clicks: ${controller.count}',
                    )),

Firebase core とは

Firebaseの基本の組み込みができるFlutter ライブラリ。

firebase_core

Firebase Authenticationとは

ログイン周りを簡単に実装できるFirebaseのフレームワーク

Firebase Authentication

ユーザーの作成、現在のユーザー状態の取得またはユーザー状態の変更の購読、ログイン、ログアウトなど一通りのユーザー操作が簡単に実行可能。

Firebase auth for flutter とは

flutterからFirebase authを使えるようにしてくれるライブラリ

Firebase auth for flutter

Cloud Storage for Firebase とは

画像・動画などユーザー作成コンテンツを保存するような場合に使用するクラウドストレージ。

Firebase Authと連携できる。
Cloud Storage for Firebase

Cloud Storage for Flutter とは

Cloud Storage for FirebaseをFlutter向けに使えるようにしてくれるライブラリ。

Cloud Storage for 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

test.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

ネイティブアプリのようなスプラッシュ画面をつけたい時にはこのライブラリが使える。

flutter_native_splash

image_picker

画像やビデオを端末のアルバムから取得またはカメラで撮影するためのライブラリ。

pub.dev - 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

pub.dev - 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 というリクエストやレスポンスをログに見やすく出してくれるライブラリもある。

pretty_dio_logger

URLを開く

display_number

数字の整形(桁数、四捨五入)ができる。

  • 以下、引用
display.dart
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_launcher_icons

リンティング

デフォルトでflutter_lintというライブラリが入っている。さらに厳しいルールを設けているものとして

がある。チーム開発では厳しいやつを使ったほうがいいかもしれない。

Shimmerエフェクト(ローディング中、UI部品に光が左から右に流れるような画面表示を出すこと)

同名のライブラリがあり、非常に簡単に呼び出せる。
pub.dev/shimmer

ライフサイクル

コードを実行する厳密なタイミングの制御が必要な場合は以下を検討してみるとよいだろう。

  • Flutterのデフォルトのライフサイクル
    • RouteObserver -> ウィジェットがpush, popしたタイミングを取得できる。デフォルトのNavigator.of(context).push<void>や、GetXライブラリのGet.toNamed()などでは取得できていた。
      • ただ、無論、同一画面内である子ウィジェットを切り替え、別の子ウィジェットを表示するなどのケースでは、無論検知できない。
    • WidgetsBindingObserver  -> フォアグラウンド、バックグラウンドのタイミングなどを取得できる。
  • ライブラリ

ローカライズ

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

マテリアルパッケージをインポートすることでマテリアルデザインを利用可能となる。

Material Design - Components

どのようなデザインの類型があるかは上記から検索できる。

flutter doc - Widgets

各ウィジェット(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を使い、縦にコンテンツを並べることが多い。

Drawer class

AppBar

主にScaffoldに設定する、アプリの最上部にあるタイトルバー。
flutter doc - AppBar class
Flutter の AppBar で遊んだら楽しかったよ

BottomNavigationBar

画面の下部に設置する、複数のボタンを備えたタブのこと。主にアプリの基本となる画面への画面遷移に使うことが多いだろう。

Flutter doc - BottomNavigationBar
Flutter doc - BottomNavigationBarItem
Zen - FlutterのBottomNavigationBarを本気で学ぶ

Column

縦にウィジェットを並べたい時に使うウィジェット。ただchildが画面サイズを超えてもスクロールはできない(しかもエラーになる)。たくさんのchildを含みたいのであればListViewを使うことになるか。

Rowクラスはこれの横バージョンである。

Flutter Doc - Column

SizedBox

子ウィジェットの縦横の長さを厳密に制限できるクラス。

子無しで使えば特定の長さの隙間を表すのにも使える。

Flutter doc - SizedBox class

たとえば以下は高さ25の隙間を表すことができる。

SizedBoxTest.dart
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がなければ、親のサイズまで広がる。

Flutter doc - GestureDetector

TextField

Flutter dox - TextField

TextFormField

TextFieldと同様ユーザー入力を受け付ける。validatorという関数を受け取ることができ、ユーザーが入力した文字列のバリデーションが可能。

TextFormField
TextField、TextFormField、Formの概要

TextEditingController

TextField、TextFormFieldなどに設定することができる。入力された文字列の取得や、プログラムからの文字列の変更、入力された時にする動作を関数として設定するなどが可能。

使い終わった時にはdisposeが必要となるので、statefull widgetのdisposeで一緒にdisposeすると良い。

TextEditingController
[Flutter]TextEditingControllerについて

Icon

さまざまなアイコンがシステム側で用意されている。IconDataとしてIconクラスのコンストラクタに渡す。

Flutter doc

用意されているアイコンは、マテリアルアイコンのページを参照。

Google - Material Icons

IconButton

Flutter doc - IconButton

Iconを渡し、タップ可能なボタンを生成する。

Container

Widgetに対してパディング・背景色・大きさの制約などを加えたい場合はContainerでラップすると良い。decorationプロパティにBoxDecorationなどを設定することで、childの背景に装飾を追加することもできる。

Flutter doc - Container

InkWell

タップした時の波紋のようなエフェクト(リップルエフェクト)を出せるようにするWidget。Button系のウィジェットにもこのような効果はついていますが、InkWellは任意のウィジェットをchildとして納めることができます。

Flutter doc - InkWell
Flutterのリップルエフェクトは一癖ある

CircleAvatar

Flutter doc - CircleAvatar

ユーザーの顔画像を丸く表示するようなUI部品。正直そんな細かい部品まで公式で用意されているとは驚いた。

典型的には、画像がない場合はユーザーのイニシャルを表示する。

Image

Flutter doc - Image

画像を表示する。

NetworkImage

Flutter doc - NetworkImage

画像のURLを渡して、画像を表示する。

SimpleDialog

showDialog関数を使って呼び出せる、flutterのデフォルトのダイアログ。

flutter doc - showDialog function
flutter doc - SimpleDialog class

PageView

上下・左右にページを捲るような形でスワイプし、複数のウィジェットを連続で表示できるウィジェット。

flutter doc - PageView

Expanded

Row, Column, FlexのchildをExpandedでラップすることで、余っているスペース全てをそのchildが占めるようにすることができる。flexプロパティを設定することで、複数のExpandedがある場合、余っているスペースをどの割合で占めるか指定することもできる。

固定サイズにしたいやつはSizedBoxでラップし、伸び縮みさせたいやつはExpandedなどでラップすると良いだろう。

flutter doc - Expanded

Flexible

Expandedとほぼ同じだが、Row, Column, Flexのchildが余っているスペースを全て埋めることを強制しない点で異なる。

flutter doc - Flexible

AspectRatio

子ウィジェットを特定の縦横比にしたいときに利用する。

AspectRatio

ClipRRect

画像を丸く加工して表示するためのウィジェット。ClipRoundedRectの略。
fluter doc - ClipRRect

RotationTransition

Widgetを回転させるアニメーションを実現するクラス。

flutter dox - RotationTransition

Center

子ウィジェットを中央に配置する。

flutter doc - Center

ListView

複数の子ウィジェットをスクロール可能な形でリスト表示するウィジェット。子ウィジェットの個数は静的でも、動的でも構わない。
flutter doc - ListView

ListTile

典型的にはListViewの子として使うことを想定したウィジェット。左側、右側に画像をおける場所を持つほか、中央に1〜3行の文字を置けるラベルがある。

ListTile

Divider

ウィジェット間の区切り線となる、横に細長いウィジェット。

flutter doc - 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

複数の列・行を持ちうるスクロール可能なウィジェット。

flutter - GridView

GridView.countというインストラクタがあり、固定数の要素である場合にはこれを利用する。

GridViewTest.dart
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

Colorの定数を定義した集まりがColorsである。

Colors

Builder

ある親Widgetのbuildメソッドの中で、子Widgetがあり、これが親Widgetのcontextにアクセスする場合うまく動作しなくなるのでこのような動作は禁止される。たとえば、MediaQuery.of(context)Scaffold.of(context)などで使用されることがある。

このような場合は、子WidgetをStatelessWidgetのサブクラスとして定義し直せば良い。ただ、それが面倒な場合は、Builderでラップするだけでも良い。

Builder

Flutter 様々なコマンド

[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));

Future.delayed constructor

dynamic キーワードについて。

動的型付けができるようにする。無論多用すると型安全ではなくなることもあり、おすすめはしない。
【Dart】【Flutter】dynamic型(動的型付け)

コンストラクタ

"List Constructor", "Const Constructor", "Factory Constructor"など色々ごちゃごちゃあるようだがすぐに全部理解できる気はしなかったが、わからなくなったら公式ページに都度戻ればいいだろう。

とりあえず"Const Constructor"というのは生成したオブジェクトを一回生成して以降は定数とする(プロパティを後から変えれない)時に使う。

Constキーワードをコンストラクタの前につけるほか、そのクラスの変数全てにfinalマークをつけることになる。

named-constructors - Flutter Doc
Dartのconstコンストラクタとは

Stream

非同期に連続した値を発信・購読ができるStream型がDart公式として用意されている。

位置パラメータ、デフォルト値

曲線かっこ({})を用いて名前付きパラメータをコンストラクタにつけることができます。

test.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の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】環境変数をファイルで設定する

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のものまで読み込む。該当ファイルらを一つのファイルにしてしまうようなものと言える。通常は使う機会がなく、自動生成系ライブラリを使うときに使うタイミングがあるようである。

[Dart]partとimportの違い

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ではない値だけを抽出できる。

dart.test.dart
final List<String?> hoge = [null, "a", "b", null];
final fuga = hoge.whereType<String>().toList(); // ["a", "b"]

【Flutter】dartでListからnullを除外する

json変換

jsonをオブジェクトに変換、またはその逆をしたい場合は、dart:convert パッケージのjsonDecode()を使うことが考えられる。

中規模以上のプロジェクトなどで、自動でjsonに変換したいなどの場合は、json_serializable, json_annotationなどのライブラリを使える。

JSON and serialization

日付けの取り扱い

DateTimeクラスで特定の日付を表すことができます。日付の差の計算や、タイムゾーンの指定、何日後・何日前への移動など一通りの操作は行えます。

flutterでDateTimeとStringの変換方法とTimeZoneとLocale
【Dart】【Flutter】DateTime型についてのまとめ

<Widget>という書き方

<Widget>という書き方がコードの中に出てくることがありますが、これは、配列の中の型を何にするかを指定するものです。無論Widget型以外でも、何でも指定できます。

test.dart
Column(
  children: <Widget>[
  // Other codes
  ]
)

参考: What does means in Flutter code?

プログラム全体からアクセスできるよう定数を定義したい

プログラムのトップレベルに定義したり、あるクラスのStatic変数として定義するなどが考えられる。

const.dart
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関数において、runAppProviderScopeを渡す。ProviderScopeとは、アプリの全Providerの状態を保持するWidgetである。

void main() {
  runApp(
      ProviderScope(child: MyApp())
  );
}

画面サイズ取得

test.dart
MediaQuery.of(context).size.width
MediaQuery.of(context).size.height

画像リソースの追加

典型的にはプロジェクトルートにassetsフォルダを作り、画像を置く。pubspec.ymlにて、以下のように追加する。

pubspec.yaml
flutter:
  assets:
    - assets/

Flutter doc - 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択となる。

See https://dart.dev/tools/diagnostic-messages?utm_source=dartdev&utm_medium=redir&utm_id=diagcode&utm_content=missing_default_value_for_parameter#missing_default_value_for_parameter

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 stableflutter 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メソッドの一番最初に持って来たところエラーが解消した。

Flutter: Unhandled Exception: ServicesBinding.defaultBinaryMessenger was accessed before the binding was initialized

[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にあげる。

pubscpec.yml
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キーワードをつける。

.dart
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

.dart
results = jsonDecode(snapshot.data);

snapshot.dataの型がなんであるかがdartに伝わっていないようなので、型を明示するとともにnullの場合どうするのかを指示するようにした。

.dart
results = jsonDecode(snapshot.data as String ?? '');

How can I resolve "The argument type 'Object?' can't be assigned to the parameter type 'String'.dart(argument_type_not_assignable)"?

Annotation must be either a const variable reference or const constructor invocation

ライブラリのdio, retrofitを両方使っている場合、両方にHeadersというのが定義されているので普通に使うと衝突してしまう。以下のようにdioのHeadersを隠せば解決する。

rest_client.dart
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();

Stack Over Flow - Annotation must be either a const variable reference or const constructor invocation

環境変数を設定しているはずだが、String.fromEnvironment('XXXXXX');を使っても値が取れずnullになってしまう。

String.fromEnvironment('XXXXXX');のドキュメントにも書いてあるが、直前にconstキーワードをつけないとうまく動作せずnullがかえってきてしまっていた。

test.dart
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が出してきたコードだが、自分でも試したところ想定通りに動作していた。

MyHomePage.dart
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に対応していないバージョンとなってしまっていた。

.zprofileif 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にすると解消する模様。

参考: include of non-modular header inside framework module 'firebase_messaging.FLTFirebaseMessagingPlugin' #12962

NULL is not a subtype of Map

サーバーからの戻り値jsonをオブジェクトに変換しようとした際に起きた。Nullableの値を常に得ようとしてしまっていたことが原因だった。Nullの場合は何もしないようにしたら解消した。json_annotationなどを利用して自動で変換している場合はこのような不具合は起きないだろうが、一応。

recipe_before_change.dart
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']),
    );
  }
}
recipe_after_change.dart
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とは一般にメモリが逼迫しこれ以上プログラムを進められなくなったことを指し、何らかの無限ループが発生してしまった際などによく起こる。

ある変数から、その変数自身を誤って参照してしまっていたため、発生してしまった。

before.dart
 Future<Database> get database async {
    if (_database != null) return database!;//無限ループ
    _database = await _initDatabase();
    return _database!;
  }
after.dart
 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をFormkeyのパラメータとして渡していること

以上のような構成になっているかどうか確認しよう。

Form

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方式に変更
  1. パラメーター処理の追加:

    • 以前のroutesでは単純に画面を表示するだけでしたが、onGenerateRouteではルート名に応じた処理や引数の受け渡しが可能になります。
    • 特にSaveLoadScreenへの引数(isLoadMode)を適切に処理できるようになります。
  2. エラーハンドリングの改善:

    • 未定義のルートに対して適切なエラー画面を表示するようになります。

簡単に言うと、従来の静的なルーティングから、引数を処理できる動的なルーティングに変更したことで、遷移時に必要なパラメーターを正しく渡せるようになります。

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の主な利点:

  1. 引数の処理をルーティング時に集中して行える
  2. ウィジェットのコンストラクタで直接パラメータを受け取れる
  3. 同じルート名でも引数によって異なる画面を返せる
  4. 引数に基づいた動的なルート生成が容易

端末の向きを取得したい。

final isLandscape = MediaQuery.of(context).orientation == Orientation.landscape

Android Studio ショートカット

右クリック -> Reformat code with dartfmtで、コードをflutter風に改行した形へと一発で整形できる。

test.dart
Text("TokTik", style: TextStyle(fontSize: 35, color: buttonColor, fontWeight: FontWeight.w900,),),

test.dart
            Text(
              "TokTik",
              style: TextStyle(
                fontSize: 35,
                color: buttonColor,
                fontWeight: FontWeight.w900,
              ),
            ),

option + Return(Mac)で、特定のWidgetを別のWidgetでラップさせることが一瞬でできる。

截屏2022-06-15 17.53.55.png

extends StatelessWidgetの上で同じ操作をすると、StatefulWidgetへの自動変換が利用できるようだ。

stlessでStateless Widget, stfulでstateful widgetを一発で作れる。

Command + N で様々なコード生成

截屏2022-07-26 21.03.36.png

Shift2連打

ファイル名、symbol名などで検索。

Control + Shift + F

全ファイルの中身を検索。

Widget Inspector

デバッグビルド中のアプリ内のウィジェットの構造を詳細に把握できる。

Repaint Rainbow

どの範囲のウィジェットが再描画されるかが表示される。パフォーマンスのチェックに便利。

Repaint Rainbow

Android 関連

AndroidManifest.xmlとは

アプリの重要な設定を記載するファイル。

AndroidManifest.xml の備忘録
AndroidManifest概要

Android 端末を開発に使う

設定 > 端末について > バージョン > ビルド番号を7連続タップ で開発者モードがONになります。
追加設定 > 開発者オプション > USB デバッグ をONにするとAndrdoid studioから端末へアプリを走らせることができます。
https://smartttphone.com/oppo-developer/

buildgradleについて

Android developer guide - ビルドを設定する

Gradle (build.gradle) 読み書き入門

  • 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内で利用できる。

app/build.gradle
defaultConfig {
    // String型の変数app_nameを定義し、任意の値を入れれる。
    resValue "string", "app_name", "FlutterAT" + 
            (dartEnvironmentVariables.FLAVOR == 'prod' ? '' :".${dartEnvironmentVariables.FLAVOR}")
}
android/app/src/main/AndroidManifest.xml
android:label="@string/app_name"

テスト

Testing Flutter apps

codelabs - Flutter アプリのテスト方法

テストは単純な順にUnit Tests, Widget Tests, Integration Testsが存在。

以下はデフォルトであるWidget Testsの例。日本語コメントは筆者。

test/widget_test.dart
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 ビルドする

デプロイ前の注意点(チェックポイント)

iOS

自動でApp Store Conectにアップロードする場合や、Automatically manage signingを利用してビルドする場合はApp Store ConnectよりPrivate Keys(.p8 ファイル)を作成し、ダウンロードすることとなる。

参考: Uploading an App Store Connect API Key to Appcircle

そうでない場合はプロビジョニングプロファイルなどを使うこととなるがこれは割愛する。

9
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?