最初のFlutterプロジェクトを理解する

前回の授業で最初のFlutterプロジェクトを作成しました。このプロジェクトは非常にシンプルで、ボタンをクリックすると数字がカウントアップするだけの内容です。以下にプロジェクトのディレクトリ構造を示します。

プロジェクトのディレクトリ構成

android、web、windows、lib、testなど多くのフォルダとファイルが確認できます。では、開発するソースコードはどこに記述するのでしょうか。androidフォルダ?windowsフォルダ?実は違います。libフォルダ内に記述します。libを開くとmain.dartというファイルが存在します。

このlibフォルダは今後ソースコードを格納する場所となります。記述する言語はDartであり、JavaやC++ではありません。FlutterはDart言語を採用しています。では、なぜandroid、web、windowsといったディレクトリが存在するのでしょうか。

仕組みは単純です。Flutterが複数プラットフォーム向けにコンパイルできるのは、これらのフォルダによって実現されています。

Flutterは1つのDartコードで複数プラットフォームで動作するフレームワークです。libフォルダは自作する全プラットフォーム共通の業務コードを保管します。一方、android / windows / web / macos / linuxといったフォルダは各プラットフォームのネイティブホストプロジェクトで、Flutterを各OSに埋め込んで実行させる役割を持ちます。

1 androidフォルダ

こちらはAndroidプラットフォームのネイティブプロジェクトです。

Androidアプリの実行コンテナを提供し、Flutterの描画エンジンを内包します。Androidに関する低レイヤー設定はこちらで行います。例:アプリの権限設定、アイコン、パッケージ名など。ここでAndroid特有の設定を編集したり、カメラ・Bluetooth・通知・位置情報といったAndroidネイティブAPIを呼び出したりできます。

2 windowsフォルダ

Windowsデスクトップ向けネイティブプロジェクトです。

C++ Win32のネイティブプロジェクトで、Windowsのウィンドウを作成し、Skia描画エンジンを初期化します。SkiaはGoogle製のグラフィックライブラリで、Flutterのデスクトップアプリはこれを利用してウィンドウや各種コントロールを描画します。

windowsディレクトリ内にはWindowsデスクトップソフトの低レイヤー設定が格納されています。例:ウィンドウサイズ、タイトルバー、アイコン、システムトレイ(Windows右下の常駐アイコン)など。

最終的にexe、msix形式にパッケージ化し、Microsoftストアへ公開することも可能です。

3. webフォルダ

Web静的サイト向けプロジェクト(HTML/JS/CSS)で、ウェブの入口ファイルindex.htmlが用意されています。自作したDartソースは最終的にWeb関連のファイルにコンパイルされます。

サイトタイトル、アイコン、静的リソース、ルーティングの設定が可能。コンパイル後に出力されたHTML/CSS/JSをサーバーに配置するだけでブラウザで開けます。

その他のフォルダ

今回プロジェクト作成時にios、macos、Linuxなどのオプションにチェックを入れなかったため、これらのフォルダは存在しません。しかし仕組みはすべて同じです。

ios:Apple iOS向けネイティブXcodeプロジェクト、ipaにパッケージしてiPhone向けにリリース

macos:macOSデスクトップCocoaプロジェクト、Macクライアント用

linux:LinuxデスクトップGTKプロジェクト

Android Studio右上のプラットフォーム選択から切り替えるだけで、各種アプリをコンパイルできます。もちろんコマンドラインから直接実行することも可能です。実はAndroid Studioでプラットフォームを選んで緑の再生ボタンを押す動作は、裏でFlutterコマンドを実行しているだけです。

# Androidを起動、androidディレクトリの設定を読み込む
flutter run -d android

# Windowsデスクトップを起動、windows配下のC++プロジェクトを読み込む
flutter run -d windows

# Webを起動、webディレクトリのhtmlを読み込む
flutter run -d chromeCode language: PHP (php)

windows配下のrunner / main.cppを開いてみましょう

#include <flutter/dart_project.h>
#include <flutter/flutter_view_controller.h>
#include <windows.h>

#include "flutter_window.h"
#include "utils.h"

int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
                      _In_ wchar_t *command_line, _In_ int show_command) {
  // Attach to console when present (e.g., 'flutter run') or create a
  // new console when running with a debugger.
  if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) {
    CreateAndAttachConsole();
  }

  // Initialize COM, so that it is available for use in the library and/or
  // plugins.
  ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);

  flutter::DartProject project(L"data");

  std::vector<std::string> command_line_arguments =
      GetCommandLineArguments();

  project.set_dart_entrypoint_arguments(std::move(command_line_arguments));

  FlutterWindow window(project);
  Win32Window::Point origin(10, 10);
  Win32Window::Size size(1280, 720);
  if (!window.Create(L"myproject", origin, size)) {
    return EXIT_FAILURE;
  }
  window.SetQuitOnClose(true);

  ::MSG msg;
  while (::GetMessage(&msg, nullptr, 0, 0)) {
    ::TranslateMessage(&msg);
    ::DispatchMessage(&msg);
  }

  ::CoUninitialize();
  return EXIT_SUCCESS;
}
Code language: PHP (php)

Windows Desktop向けにビルドする場合、このmain.cppが真のエントリーポイントとなります。先にこのC++ファイルが実行された後、lib/main.dartへ処理が移ります。

同様にandroidディレクトリを開くと、KotlinまたはJavaで開発する通常のAndroidアプリとほぼ同じディレクトリ構造が見えます。

srcディレクトリ、AndroidManifest.xml、MainActivityなどが存在し、Android開発経験者には馴染みのある構成です。事実、FlutterでAndroidアプリを開発する際は、まずこのandroidディレクトリから処理が始まります。

webディレクトリを開くと、Webの入口となるindex.htmlも確認できます。

dartファイルについて

lib配下のmain.dartが今後開発の中心となります。lib内にサブフォルダを作成し、別のdartファイルを追加することも可能です。

main.dartを開いてみましょう

// Materialコンポーネントライブラリをインポート
import 'package:flutter/material.dart';

// プログラムエントリー関数
void main() {
  runApp(const MyApp());
}

// アプリルートコンポーネント(ステートレス)
class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      // 全体テーマ設定
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
      ),
      // ホーム画面
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

// ホーム画面(ステートフルコンポーネント)
class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  // 画面タイトル引数
  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

// ホーム画面のステート管理クラス
class _MyHomePageState extends State<MyHomePage> {
  // カウンター変数
  int _counter = 0;

  // カウンター加算メソッド
  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    // 画面の土台レイアウト
    return Scaffold(
      // 上部ナビゲーションバー
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
      ),
      // 画面本体コンテンツ
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Text('You have pushed the button this many times:'),
            Text(
              '$_counter', // 数値変数を表示
              style: Theme.of(context).textTheme.headlineMedium, // 文字スタイル
            ),
          ],
        ),
      ),
      // 右下フローティングボタン:onPressedで_incrementCounter実行、tooltipはヒントテキスト、childはアイコン設定
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }
}Code language: JavaScript (javascript)

1. エントリー部分

import 'package:flutter/material.dart';
void main() {
  runApp(const MyApp());
}
Code language: JavaScript (javascript)
  • Google公式のMaterial UIコンポーネント群(ボタン・画面・テキストなど)を読み込み。Appleにも独自UIフレームワークが存在します。
  • mainはDartプログラム唯一のエントリーポイント。runApp()でアプリを起動し、ルートコンポーネントMyAppをマウントします。

2. MyApp 全体ルートコンポーネント

StatelessWidget(状態を持たないコンポーネント)です。後ほど詳しく解説するので、今は概要だけ把握してください。

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  // アプリのルートコンポーネント
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}
Code language: JavaScript (javascript)
  • 全体設定:アプリ名、グローバルテーマ、起動時のホーム画面
  • MaterialApp:Materialデザインのアプリ外殻。ルーティング・テーマ・タイトルを一括管理。
  • homeでアプリ起動時の既定画面MyHomePageを指定

3. MyHomePage ホーム画面

ウィンドウまたはアプリ起動時に標準表示される画面で、StatefulWidget(状態を持つコンポーネント)です。詳細は後述します。

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});
  
  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}Code language: JavaScript (javascript)
  • カウンターの数値といった可変データを持つため、状態付きコンポーネントを使用。
  • 外部からタイトルtitleを受け取り、状態クラス内ではwidget.titleで参照可能。
  • 専用の_MyHomePageStateクラスを必ず併記し、画面変数・インタラクションロジックを保管する。

4. _MyHomePageState

状態ロジッククラス。画面の描画・更新・動作ロジックを処理する役割です。

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {

    return Scaffold(
      appBar: AppBar(

        backgroundColor: Theme.of(context).colorScheme.inversePrimary,

        title: Text(widget.title),
      ),
      body: Center(

        child: Column(

          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Text('You have pushed the button this many times:'),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headlineMedium,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }
}Code language: JavaScript (javascript)
  1. int _counter = 0:画面の可変データ、カウント数
  2. _incrementCounter():ボタンクリックイベント
    • setState():Flutterに「データが変更されたのでUIを再描画せよ」と通知
    • カウンターを+1する
  3. build():画面更新のたび実行され、レイアウト全体を返す

5. Scaffold 画面テンプレート

画面の土台となるレイアウトテンプレートで、_MyHomePageState内に記述します。

  • appBar:上部タイトルバー
  • body:画面本体コンテンツ
    • Center:要素を中央寄せ、Column:テキストを垂直に並べる
  • floatingActionButton:右下の丸いプラスボタン。クリック時加算メソッドに紐付け

全体の実行フロー

  1. プログラム起動 → main実行 → MyApp読み込み
  2. MyAppMaterialAppを描画 → ホーム画面MyHomePageを読み込み
  3. 画面にタイトルバー・中央テキスト・加算ボタンが描画される
  4. ボタンをクリック → _incrementCounter実行 + setStateで画面再描画 → 数字が更新

最初のFlutterプロジェクトを理解する

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です