Flutterを使って開発することが増えてきそうなので、復習のためにも、意識していること・つまづいたことなどを書いていこうと思います。
Flutterはめちゃくちゃフォルダが多いですが、ほぼlibフォルダしかいじることがありません。
そして、自分はファイル構成を下記のようにするようにしています。
※ファイル構成に関しては、Zennの記事を参考にしています。
.
├── android/ (AndroidOS版のコード)
│
├── assets/ (画像ファイルの置き場所)
│   ├── animation/(lottieで使用するjsonファイル)
│   ├── app/(アプリの画像)
│   ├── xxx/(xxxに関する画像)
│   └── other/
│
├── fonts/ (ttfファイルの置き場)
│   └── xxx.ttf (アイコンを定義する)
│
├── ios/ (iOS版のコード)
│
├── lib/
│   ├── constant/(定数や関数を定義します)
│   │   ├── functions/ (処理系)
│   │   │   ├── time/ (時間関連の処理)
│   │   │   └── xxx/ (xxxの処理)
│   │   ├── hooks/ (hooks)
│   │   ├── types/ (細々した型定義)
│   │   └── data/ (静的データのファイル)
│   │       └── xxx/ (xxxのデータ)
│   │
│   ├── data/ (Modelの実装)
│   │   ├── model/ (型定義)
│   │   │   └── xxx/ (xxxの型)
│   │   ├── provider/ (Providerの実装)
│   │   │   └── xxx/ (xxxのProvider)
│   │   └── repository/ (Repositoryの実装)
│   │       └── xxx/ (xxxのRepository)
│   │
│   ├── l10n/ (多言語対応のためのarbファイルの置き場)
│   │   └── app_xx.arb (xx言語の定義)
│   │
│   ├── ui/ (Viewの実装)
│   │   ├── components/ (コンポーネントの置き場)
│   │   │   ├── xxx/ (xxx関連のコンポーネント)
│   │   │   └── ui/ (細々したUIの置き場)
│   │   │       └── button/
│   │   │
│   │   ├── pages/ (pageの置き場)
│   │   ├── theme/
│   │   │   └── colors.dart (色の定義)
│   │   ├── app.dart (MainAppの定義)
│   │   └── main.dart
│   │
│   ├── linux/ (linuxOS版のコード)
│   │
│   ├── macos/ (MacOS版のコード)
│   │
│   ├── test/ (テストコード)
│   │   └── widget_test.dart
│   │
│   ├── web/ (Web版のコード)
│   │
│   └── windows/ (WindowsOS版のコード)
│
├── .gitignore
├── .metadata
├── analysis_options.yaml
├── anisphere_flutter.iml
├── custom_lint.log
├── pubspec.lock
├── pubspec.yaml
└── README.md
では上記について詳しく解説していきます。
それぞれのOSに関する設定などがあるフォルダ。 Flutterプロジェクトを作成した時に自動で生成されます。
アプリで使用する画像や、lottieを使用するならアニメーションのjsonファイルを配置するフォルダです。
Iconsからアイコンを選ぶ際に、使いたいアイコンがない場合、ここにttfファイルを配置します。(アイコンの作り方はこちらの記事をご覧ください)
開発では主にこのフォルダをいじっていきます。
アプリで定義する定数や関数ファイルなどをここに置いていきます。
functionsには関数を、dataには配列など定義したい定数のファイルを入れていいきます。
typesには後で説明するlib/data/modelで定義するような型定義ではなく、もっと細々した(例えばpropsなど)型定義をここに配置しています。
MVVMアーキテクチャにおける、Modelを実装するためのフォルダです。
具体的にはmodelで型定義を実装し、providerでriverpodやproviderなどを用いた状態管理を実装していきます。
repositoryでは、外部からデータを取ってくる処理を実装する場所です。
多言語対応するためのarbファイルを配置する場所です。
MVVMアーキテクチャにおける、Viewを実装するためのフォルダです。
pagesに画面を構築するWidget、componentsにコンポーネントのWidgetを実装していきます。
theme:アプリで使用する色やwidthなどを定義する
アプリのエントリーポイントとなるファイルで、ここから実行されます。 riverpodを使う場合などには、ここで行います。
main.dartのrunAppに渡す上位のクラスです。
ここでテーマや言語などを設定します。
ファイルをインポートする順番は以下の通りです。
やっぱりインポート順は整えたほうが見やすいですね。
Flutterでの命名規則は下記のようになっています。
Flutterを使う際に押さえておいたほうが良いと思ったことを解説します。
Flutterを始めた時、StatelessWidgetとStatefulWidgetのどっちを使えばいいのと迷うと思います。
結論から言うと、基本的にStatelessWidgetを使って、必要があればStatefulWidgetに変えましょう。
変更の仕方は、下記の動画のようにできます。
StatelessWidgetの上にカーソルがある状態でcommand+.でメニューが表示されるので、Convert to StatefulWidgetを選択すると変換できます。
アプリのページを作成する際にはScaffoldWidgetを使います。
このScaffoldWidgetの要素には、主に下記のような種類があります。
他にも要素はありますが、メインで使うのはこの4つの要素です。
なのでScaffoldWidgetはページ全体の枠組みを作るようなWidgetなので、そのほかではあまり使いません。
また、ScaffoldWidgetを使ったような画面をpagesフォルダに入れていきます。
Dartで型を指定するには、classを使います。
今回は、id(整数)・title(文字列)・completed(真理値)の三つの要素があるものを考えていきます。
class TodoType {
    const TodoType({
        required this.id,
        required this.title,
        required this.completed,
    });
    final int id;
    final String title;
    final bool completed;
}
型は以下のようなものがあります。
ちなみに、もし引数があってもなくてもどちらでもいい場合は、下記のように記述します。
class ExampleType {
    const ExampleType({
        this.id,
    });
    final int? id;
}
普段TypeScriptを使っているので、これは少し面倒だなと思いました。
コンポーネントなどを作成する際に引数を指定する場合、下記のように記述します。
class Example extends StatelessWidget {
    const Example({
        required this.title,
    });
    final String title;
}
また、引数が必ず必要ではない場合には、下記のように記述してください。
class Example extends StatelessWidget {
    const Example({
        this.title
    });
    final String? title;
}
Flutterで使用したいパッケージをインストールするには、以下の二通りの方法があります。
pubspec.yamlに記述する一つ目のコマンドでインストールするには、
$ flutter pub add パッケージ名
のようなコマンドをターミナルで実行します。
二つ目のpubspec.yamlに記述する方法は、
// ^x.x.xのようにバージョンを書き込まなければ最新版がインストールされる
パッケージ名: ^x.x.x
このように記述した後に、ターミナルに、
$ flutter pub get
を打ち込めばインストールできます。
ここからは私がつまづいた点についてお話しします。
画像を表示する際につまづいたのが、assetsフォルダに画像を配置しても画像が表示されない問題でした。
これを解決するには、pubspec.yamlにフォルダのパスを記述する必要があります。
下記のように記述してください。
flutter:
  generate: true
  uses-material-design: true
  assets:
    - assets/xxx/
    - assets/yyy/
assetsフォルダ内に新しくフォルダを作成した際には、そのパスもどんどん記述していってください。
上記のように記述すると、
Image.assets('assets/xxx/example.png')
このようにassetsフォルダ内の画像を表示することができます。
httpやdioといったライブラリを使ってhttpリクエストをする際に、Macを使用している場合にはある設定をしないとエラーが出てしまいます。
なので、macos/Runner/DebugProfile.entitlementsとmacos/Runner/Release.entitlementsに下記のコードを追加してください。
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.network.server</key>
<true/>
これでリクエストを送ってもSocketエラーが出ないようになります。
Flutterで開発している時に、右上のボタンからアプリを起動しようにも、できない時があります。
なぜ起動できないのかもわからず、立ち尽くすだけになったことが何度もありました。
そんな時は、ターミナルに下記のコマンドを打ち込んでください。
$ flutter run
このコマンドでもアプリを起動することができます。
コマンドでアプリを起動すると、ターミナルに詳しいエラーの原因が吐かれていることがあるので、それを元に解決してみてください。
AppBarをコンポーネント化する際に少しつまづいたので、その解決法についてお話しします。
ScaffoldのappBarにはただのWidgetではなく、PreferredSizeWidgetというWidgetを渡す必要があり、AppBarをコンポーネント化するとWidgetになるのでエラーになってしまいます。
なので、下記のように記述しなければなりません。
class ExampleAppBar extends StatelessWidget implements PreferredSizeWidget {
    @override
    Size get preferredSize => Size.fromHeight(50);
    @override
    Widget build(BuildContext context) {
        return AppBar();
    }
}
上記のようにimplements PreferredSizeWidgetによって、コンポーネントがPreferredSizeWidgetだと宣言し、その宣言を実現するために上記の、
@override
    Size get preferredSize => Size.fromHeight(50);
が必要となります。
自分もFlutterを勉強中でまだまだわからないこともありますが、ようやく慣れてきたので、このように記事を書いてみました。
始めたての頃に、知らなくてつまづいたことや、もっと早く知っておきたかったことを中心に書いてみたので、始めたばかりの方は参考にできる部分があれば参考にしていただけると幸いです。
riverpodを使った状態管理や、http通信などの詳しいことも今後書いていこうと思います。