icon
🌞

🤟

tweet

ツイート

Flutter開発でのトピック

FlutterFlutter
techtech

はじめに

Flutterを使って開発することが増えてきそうなので、復習のためにも、意識していること・つまづいたことなどを書いていこうと思います。

前提条件

  • MacOS:Ventura
  • VSCode
  • Flutter:3.13.0
  • Dart:3.1.0

ファイル構成

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

では上記について詳しく解説していきます。

android・ios・macos・windows・linux・web

それぞれのOSに関する設定などがあるフォルダ。 Flutterプロジェクトを作成した時に自動で生成されます。

assets

アプリで使用する画像や、lottieを使用するならアニメーションのjsonファイルを配置するフォルダです。

fonts

Iconsからアイコンを選ぶ際に、使いたいアイコンがない場合、ここにttfファイルを配置します。(アイコンの作り方はこちらの記事をご覧ください)

lib

開発では主にこのフォルダをいじっていきます。

constant

アプリで定義する定数や関数ファイルなどをここに置いていきます。
functionsには関数を、dataには配列など定義したい定数のファイルを入れていいきます。

typesには後で説明するlib/data/modelで定義するような型定義ではなく、もっと細々した(例えばpropsなど)型定義をここに配置しています。

data

MVVMアーキテクチャにおける、Modelを実装するためのフォルダです。

具体的にはmodelで型定義を実装し、providerでriverpodやproviderなどを用いた状態管理を実装していきます。

repositoryでは、外部からデータを取ってくる処理を実装する場所です。

l10n

多言語対応するためのarbファイルを配置する場所です。

ui

MVVMアーキテクチャにおける、Viewを実装するためのフォルダです。

pagesに画面を構築するWidget、componentsにコンポーネントのWidgetを実装していきます。

theme:アプリで使用する色やwidthなどを定義する

main.dart

アプリのエントリーポイントとなるファイルで、ここから実行されます。 riverpodを使う場合などには、ここで行います。

app.dart

main.dartのrunAppに渡す上位のクラスです。

ここでテーマや言語などを設定します。

importの順番

ファイルをインポートする順番は以下の通りです。

  1. dart:
  2. flutter
  3. package
  4. アプリ内のファイル

やっぱりインポート順は整えたほうが見やすいですね。

命名規則

Flutterでの命名規則は下記のようになっています。

  • ファイル名、フォルダ名はスネークケース(例:example_widget.dart)
  • クラス名は頭文字大文字のキャメルケース(例:ExampleClass)
  • 変数や関数名は頭文字小文字のキャメルケース(例:returnData)

ポイント

Flutterを使う際に押さえておいたほうが良いと思ったことを解説します。

StatelessWidgetとStatefulWidget

Flutterを始めた時、StatelessWidgetStatefulWidgetのどっちを使えばいいのと迷うと思います。
結論から言うと、基本的にStatelessWidgetを使って、必要があればStatefulWidgetに変えましょう。

変更の仕方は、下記の動画のようにできます。
flutter-convert
StatelessWidgetの上にカーソルがある状態でcommand+.でメニューが表示されるので、Convert to StatefulWidgetを選択すると変換できます。

ScaffoldWidget

アプリのページを作成する際にはScaffoldWidgetを使います。
このScaffoldWidgetの要素には、主に下記のような種類があります。

  • appBar (ヘッダーを配置)
  • bottomNavigationBar (タブバーを表示)
  • floatingActionButton (画面端にボタンを表示)
  • body (メインの画面に表示するもの)

他にも要素はありますが、メインで使うのはこの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;
}

型は以下のようなものがあります。

  • int:整数 (例:1)
  • String:文字列 (例:‘はろー’)
  • bool:真理値 (例:true)
  • double:小数 (例:1.111)

ちなみに、もし引数があってもなくてもどちらでもいい場合は、下記のように記述します。

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で使用したいパッケージをインストールするには、以下の二通りの方法があります。

  1. コマンドでインストールする
  2. 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リクエスト

httpdioといったライブラリを使ってhttpリクエストをする際に、Macを使用している場合にはある設定をしないとエラーが出てしまいます。

なので、macos/Runner/DebugProfile.entitlementsmacos/Runner/Release.entitlementsに下記のコードを追加してください。

<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.network.server</key>
<true/>

これでリクエストを送ってもSocketエラーが出ないようになります。

アプリを起動できない⁉︎

Flutterで開発している時に、右上のボタンからアプリを起動しようにも、できない時があります。
なぜ起動できないのかもわからず、立ち尽くすだけになったことが何度もありました。

そんな時は、ターミナルに下記のコマンドを打ち込んでください。

$ flutter run

このコマンドでもアプリを起動することができます。
コマンドでアプリを起動すると、ターミナルに詳しいエラーの原因が吐かれていることがあるので、それを元に解決してみてください。

AppBarをコンポーネント化

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通信などの詳しいことも今後書いていこうと思います。