🦌

Flutter プロジェクト立ち上げる時に必要な手順

2023/04/06に公開

現在の Flutter の最新
3.7.9-stable

テンプレートなどがない場合の、Flutter プロジェクト立ち上げの手順

pubspec.yaml

pubspec.yaml
name: hoge_app
description: hoge
publish_to: 'none'
version: 1.0.0+1

environment:
  sdk: '>=2.19.5 <3.0.0'

dependencies:
  flutter:
    sdk: flutter
  flutter_localizations:
    sdk: flutter

  # 状態管理
  flutter_riverpod: ^2.3.2

  # immutable
  equatable: ^2.0.5
  freezed_annotation: ^2.2.0
  json_annotation: ^4.8.0

  # UI
  cupertino_icons: ^1.0.2
  adaptive_dialog: ^1.8.2
  auto_size_text: ^3.0.0
  flutter_svg: ^2.0.4
  extended_image:

  # Device
  permission_handler:
  image_picker:
  share_plus: ^6.3.1
  package_info_plus: ^3.0.3

  # Dart 言語の拡張
  intl: ^0.17.0
  collection: ^1.17.0

  # ロガー
  simple_logger: ^1.9.0+2


dev_dependencies:
  flutter_test:
    sdk: flutter

  # 静的解析
  flutter_lints: ^2.0.0

  # Freezed
  freezed: ^2.3.2
  json_serializable: ^6.6.1

  # Code generator
  build_runner: ^2.3.3
  flutter_gen_runner: ^5.2.0

  # icon
  flutter_launcher_icons: ^0.12.0

  # native splash
  flutter_native_splash: ^2.2.19

flutter_gen:
  output: lib/gen/
  colors:
    inputs:
      - assets/color/colors.xml

flutter:
  generate: true
  uses-material-design: true
  assets:
    - assets/images/

flutter_icons:
  android: true
  ios: true

1. flutter create の実行

flutter create hoge_app

# 必要そうなオプション
## 必須プラットフォームのみの生成(ios, android のみでしか使わないのに、
## web とかのネイティブ実装混じってるのよくあるので、やってほしい)
flutter create hoge_app --platforms [ios, android, windows, linux, macos, web]

## 組織id
## これがないと、iOS の bundleId とかが .example になって後から直すのめんどくさい
flutter create hoge_app --org hogeOrg

やること

  • 必要なオプションをつけて、flutter create の実行

2. fvm の導入

asdf もありますが、なんかたまにバグるので個人的には fvm 一択です。
優しい人は、.tool-version も作ってあげましょう。

前までは README とかに Flutter のバージョンを書いたりしてましたが、更新忘れたりするとめんどくさいので、fvm_config.json 参照してもらったほうがいいです(README 見た後に、結局 fvm_config.json に確認しにいったりするので)。

やること

  • fvm use の実行
  • .gitignore.fvm/flutter_sdk の追加

3. ディレクトリの分割

DDD …的な、普段使ってる or プロジェクトメンバーと相談などして決めたディレクトリを作成し、.gitkeep ファイルを作っておきます。

ディレクトリ構造の例

自分が普段使ってるのは以下の構造です。
多くは、hukusuke1007/flutter_app_template を参考にしています。

lib
├── domain/
│   ├── entities/
│   ├── repositories/
│   └── use_cases/
│
├── presentation
│   ├── pages/
│   │   ├── home/
│   │   ├── settings/
│   │   ...
│   │
│   ├── res/
│   │   ├── color_shemes.dart
│   │   ├── theme.dart
│   │   └── typography.dart
│   │
│   └── widgets/
│
├── utils/
│   ├── extensions/
│   │   ├── build_context_ex.dart
│   │   ...
│   │
│   ├── logger.dart
│   ├── flavor.dart
│   ...
│
├── app.dart      // <= MaterialApp を置く
└── main.dart    // <= runApp() を置く

やること

  • ディレクトリ構造の確定。
  • ディレクトリのそれぞれの使い方を共有するドキュメントを README などに残す。
  • まだファイルの入っていないディレクトリに .gitkeep を入れておく。

4. dart-define-from-file で環境変数を設定する

同時に、アイコンの設定もやっていきます。
↓ 村松さんの神記事があるので、これとほぼ同じことをやっていきます。
この記事をもとに変更した内容は、記事へのリンクを貼るだけでなく、コメントをつけるか、PR などに手順を全て残して置くことをお勧めします。

https://zenn.dev/altiveinc/articles/separating-environments-in-flutter

やること

  • 記事の通りの手順を実装して、dart-define-file の作成。
  • flavor.dart を置いておく。

5. 静的解析ファイルの編集

flutter_lints はすでに入っているので、中身を書いていきます。
デフォルトの設定でも運用可能ですが、デフォルトにはないルールなど、ちゃんと設定しましょう。
特に、追加したほうがいいものを書いておきます。

# これがないと、dynamic を返す関数が容認されてしまう
always_declare_return_types: true

# 個人的には相対パスがお勧めです。
prefer_relative_imports: true
or 
always_use_package_imports: true

# どっちでもいいが、" と ' がどっちも許されているのはよくない
prefer_single_quotes: true: true or false

# 余計な変更差分を増やさないために、true がお勧めだが好み
require_trailing_commas: true or false

# 議論が分かれるところなので、どちらにしよ明示的にしておくのが理想です
use_build_context_synchronously: true or false

6. ロガーの実装

print では警告が出て(設定次第だが)、ロガーにはさまざまな便利なパッケージがあるので、導入しておくのがお勧め。
debugPrint でもいいが、問題が出たファイルに飛べるようになったり、ログレベルを設定したりできるので、パッケージとかを使うのがお勧めです。
おすすめ紹介など色々あるので、色々試してみてください。

重要な機能だが、ただのログ出力なので、悩みすぎて時間取られないように注意

グローバルlogger 変数などで、どこからでも使えるようにするのがお勧めです。
↓ 例
https://github.com/hukusuke1007/flutter_app_template/blob/main/lib/utils/logger.dart

やること

  • ロガーを実装、グローバル変数で提供する。

7. flutter_gen の導入

画像やフォントなどのアセットを、直接文字列で指定することを防げます。

以下、flutter_gen の引用。

❌ Bad
What would happen if you made a typo?

Widget build(BuildContext context) {
  return Image.asset('assets/images/profile.jpeg');
}

// The following assertion was thrown resolving an image codec:
// Unable to load asset: assets/images/profile.jpeg

⭕️ Good
We want to use it safely.

Widget build(BuildContext context) {
  return Assets.images.profile.image();
}

引用元: https://pub.dev/packages/flutter_gen#motivation

color 指定を xml ファイルでやる方法もお勧めですが、どっちでもいいです。
https://pub.dev/packages/flutter_gen#colors

やること

  • flutter_gen の導入

8. テーマの実装

MaterialApp.theme, MaterialApp.darkTheme に渡す ThemeData です。

ライトテーマとダークテーマ、とりあえず両方作っておくことをお勧めします。
ライトテーマのみの実装の場合は、MaterialApp.darkTheme にライトテーマと同じ変数を渡すなどするのが良いと思います。

実装のポイントは、色以外の部分の実装を共通化することです。
ダークテーマとライトテーマで設定が重複しないように工夫しましょう。

デザインがマテリアルデザインに準拠している場合は、ColorScheme クラスで場合分けをするのがお勧めです。

color_schemes.dart
import '../../gen/colors.gen.dart';

class AppColorScheme {
  const AppColorScheme._();

  static ColorScheme get light => const ColorScheme.light().copyWith(
    // primary
    primary: ColorName.primary,
    onPrimary: ColorName.white,

    // surface
    surface: ColorName.white2,
    onSurface: ColorName.black,
    onSurfaceVariant: ColorName.lightGray,

    // background
    background: ColorName.white,
    
    //...
  );
  
  static ColorScheme get dark => const ColorScheme.dark()
}

theme.dart
import 'color_schemes.dart';

class AppTheme {
  static ThemeData _baseThemeData({
    required ColorScheme colorScheme,
    required TextTheme textTheme,
  }) {
    final baseData = ThemeData.from(
      colorScheme: colorScheme,
      textTheme: textTheme,
    );

    return baseData.copyWith(
      // scaffold
      scaffoldBackgroundColor: colorScheme.background,
      appBarTheme: const AppBarTheme(
        centerTitle: true,
      ),
      bottomNavigationBarTheme: BottomNavigationBarThemeData(
        backgroundColor: ColorName.darkGray,
        selectedItemColor: colorScheme.onSurface,
        unselectedItemColor: colorScheme.onSurfaceVariant,
      ),

      // button
      elevatedButtonTheme: ElevatedButtonThemeData(
        style: ElevatedButton.styleFrom(
          elevation: 0,
        ),
      ),

      // input
      inputDecorationTheme: const InputDecorationTheme(
        // ...
      ),
      
      // ...
    );
  }

  static ThemeData get light {
    return _baseThemeData(
      colorScheme: AppColorScheme.dark,
      textTheme: Typography.material2021(),
    );
  }
 
  static ThemeData get dark {
    return light;
  }

}
app.dart
  return MaterialApp(
    theme: AppTheme.light,
    darkTheme: AppTheme.dark,
  );

やること

  • テーマデータの実装

9. 多言語対応

これは任意です。
画面に表示する文字列を変数に入れて、対応する言語ごとに切り替えるという技術です。

安定なのは Flutter 標準の flutter_localizations です。
https://docs.flutter.dev/development/accessibility-and-localization/internationalization

そのほか、色々な多言語対応パッケージを比較しているものがあるので、試してみてください。
https://zenn.dev/flutteruniv_dev/articles/20220422-140216-flutter-localizations

やること

  • 多言語対応

10. Navigation のルール設定

Flutter におけるページ遷移は色々なので、ルールを決めておくことを推奨します。

名前付きルーティングのパッケージもあるので、採用して、ルールを決めておきましょう。

僕はしばらくは go_router とかは使わないので、Flutter 標準の Navigator 1.0 でのルール例を書いときます(Navigator 2.0 はキャッチアップできてない)。

import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';

/// 4. Equatable で引数の設定 => `toString` , `operator ==` が override されてるなど便利
class HomePageArgs extends Equatable {
  const HomePageArgs({required this.title});
  final String title;

  
  List<Object?> get props => [title];
}

class HomePage extends StatelessWidget {
  const HomePage._(this.args);
  final HomePageArgs args;

  /// 1. ルートの名前文字列
  static const routeName = '/home';

  /// 2. Route の static メソッドの実装
  static Route<void> route(HomePageArgs args) {
    return MaterialPageRoute(
      /// 3. RouteObserver のログのため、RouteSettings は必須
      settings: RouteSettings(name: routeName, arguments: args),
      builder: (_) => HomePage._(args),
    );
  }

  
  Widget build(BuildContext context) {
    return const Scaffold(
      body: Center(child: Text('HomePage')),
    );
  }
}

	// 使う側
	onPressed: () {
	  Navigator.of(context).push(HomePage.route());
	}

ボトムナビゲーションバーのページ遷移に関しては、以下のような実装をお勧めします。
https://codewithandrea.com/articles/multiple-navigators-bottom-navigation-bar/

やること

  • ナビゲーション方法の確定
  • ナビゲーションシステムの実装

11. そのほか、使いそうなパッケージのインポート

ガチでプロジェクトによるので、例だけ貼っときます。
使わないパッケージは抜いておくなどの対応は常にしておきましょう。
パッケージのリストは、分類分けしておくことを推奨します。

#pubspec.yaml

やること

  • パッケージのインポート

12. 共通コンポーネントの作成

確認ダイアログ、エラーダイアログ、スナックバーなど、実装者どうしで競合しそうなものを、すでに作っておきましょう。
プロジェクトがスムーズになります。

やること

  • 共通コンポーネントの実装

13. .vscode/settings.json の設定

スニペットなども作っておくと便利です。

{
  "cSpell.words": [
    "Bitcode",
    "cupertino",
    "LTRB",
    "Rgba",
    "riverpod",
    "unawaited",
    "unfocus",
    "xcconfig",
  ],
  "cSpell.ignorePaths": ["vscode-extension", "node_modules", ".git", ".vscode", "**/settings.json", ".firebaserc"],
  "[dart]": {
    "editor.formatOnSave": true,
    "editor.formatOnType": true,
    "editor.rulers": [80],
    "editor.selectionHighlight": false,
    "editor.suggest.snippetsPreventQuickSuggestions": false,
    "editor.suggestSelection": "first",
    "editor.tabCompletion": "onlySnippets",
    "editor.wordBasedSuggestions": false,
    "editor.defaultFormatter": "Dart-Code.dart-code",
    "editor.codeActionsOnSave": {
      "source.organizeImports": true,
      "source.fixAll": true
    }
  },
  "dart.renameFilesWithClasses": "always",
  // flutter のパスを fvm にする
  "dart.flutterSdkPath": ".fvm/flutter_sdk",
  "dart.flutterGenerateLocalizationsOnSave": "all",
  // Remove .fvm files from search
  "search.exclude": {
    "**/.fvm": true
  },
  // Remove from file watching
  "files.watcherExclude": {
    "**/.fvm": true
  }
}

Discussion