👌

flutter勉強会資料(仮)

2025/03/30に公開

はじめに

この記事は、で行う内容をまとめたものです。
イベントに参加していない人も、この記事を読んでFlutter生活をスタートしてください。

Flutter初心者向けガイド:環境構築からプロジェクト作成まで

Flutterは、Googleが提供するオープンソースのUIフレームワークで、美しく高性能なクロスプラットフォームアプリケーションを迅速に開発できるツールとして、多くの開発者に支持されています。この記事では、Flutterを始めて学ぶ方々のために、環境構築から基本概念、プロジェクトの作成までのステップを詳しく解説します。WindowsおよびmacOSを対象に、それぞれの環境でのセットアップ手順も紹介しますので、ぜひ参考にしてください。


1. 環境構築

Flutterで開発を始めるためには、まず開発環境を整える必要があります。以下では、WindowsとmacOSそれぞれの環境構築方法について説明します。

Windowsでの環境構築

1. Flutter SDKのインストール

  1. Flutter公式サイトにアクセス
    Flutter公式サイトにアクセスし、Windows版のFlutter SDKをダウンロードします。

  2. ZIPファイルの解凍
    ダウンロードしたZIPファイルを、C:\src\flutterなどの適当なディレクトリに解凍します。特別な理由がない限り、C:\srcのようなパスに解凍することを推奨します。

  3. 環境変数の設定
    システム環境変数にFlutterのbinディレクトリを追加します。

    • 「スタートメニュー」→「設定」→「システム」→「バージョン情報」→「システムの詳細設定」→「環境変数」
    • 「Path」を編集し、C:\src\flutter\binを追加します。
  4. 依存ツールのインストール
    FlutterはGitを使用するため、Git for Windowsをインストールします。インストール後、コマンドプロンプトやPowerShellを再起動します。

2. Android開発環境のセットアップ

  1. Android Studioのインストール
    Android Studio公式サイトから最新バージョンをダウンロードしてインストールします。

  2. Android SDKの設定
    Android Studioを起動し、初回セットアップの際にSDKのインストールを行います。また、必要なプラグイン(FlutterおよびDartプラグイン)をインストールします。

    • 「Settings」→「Plugins」→「Marketplace」で「Flutter」を検索し、インストールするとDartプラグインも自動的にインストールされます。
  3. エミュレーターの設定
    Android Studio内でAVD(Android Virtual Device)を設定し、エミュレーターを起動できるようにします。

3. 環境の確認

コマンドプロンプトまたはPowerShellを開き、以下のコマンドを実行して環境が正しく設定されているか確認します。

flutter doctor

flutter doctorは、Flutterの依存関係をチェックし、不足しているものがあれば通知します。表示された指示に従って、必要な設定を行ってください。

macOSでの環境構築方法

1. Flutter SDKのインストール

  1. Flutter公式サイトにアクセス
    Flutter公式サイトにアクセスし、macOS版のFlutter SDKをダウンロードします。

  2. ZIPファイルの解凍
    ダウンロードしたZIPファイルを、~/development/flutterなどの適当なディレクトリに解凍します。ディレクトリを作成して、その中にFlutterを配置することを推奨します。

  3. 環境変数の設定
    ターミナルを開き、以下のコマンドを実行してFlutterのbinディレクトリをPATHに追加します。

    export PATH="$PATH:`pwd`/flutter/bin"
    

    この設定を恒久的にするために、~/.bashrc~/.zshrcファイルに追記します。

    echo 'export PATH="$PATH:$HOME/development/flutter/bin"' >> ~/.zshrc
    source ~/.zshrc
    

2. Xcodeのインストール

  1. Xcodeのダウンロードとインストール
    Mac App StoreからXcodeをダウンロードし、インストールします。

  2. コマンドラインツールのインストール
    ターミナルで以下のコマンドを実行します。

    xcode-select --install
    

3. Android開発環境のセットアップ

  1. Android Studioのインストール
    Android Studio公式サイトから最新バージョンをダウンロードしてインストールします。

  2. Android SDKの設定
    Android Studioを起動し、初回セットアップの際にSDKのインストールを行います。また、必要なプラグイン(FlutterおよびDartプラグイン)をインストールします。

    • 「Preferences」→「Plugins」→「Marketplace」で「Flutter」を検索し、インストールするとDartプラグインも自動的にインストールされます。
  3. エミュレーターの設定
    Android Studio内でAVD(Android Virtual Device)を設定し、エミュレーターを起動できるようにします。

4. 環境の確認

powershllまたはコマンドプロンプトを開き、以下のコマンドを実行して環境が正しく設定されているか確認します。

flutter doctor

flutter doctorは、Flutterの依存関係をチェックし、不足しているものがあれば通知します。表示された指示に従って、必要な設定を行ってください。


2. Flutterの基本的な概要

Flutterは、シングルコードベースでiOSとAndroid向けのアプリケーションを開発できるクロスプラットフォームフレームワークです。以下にFlutterの主要な特徴と利点を紹介します。

主要な特徴

  • 高速な開発サイクル
    「ホットリロード」機能を利用することで、コードの変更を即座にエミュレーターや実機に反映させることができます。これにより、開発効率が大幅に向上します。

  • 豊富なウィジェット
    Flutterは、マテリアルデザインやクパチーノスタイルのウィジェットを豊富に提供しており、美しく一貫性のあるUIを簡単に構築できます。

  • 高性能なレンダリングエンジン
    Skiaという高性能なレンダリングエンジンを使用しており、滑らかで高品質なグラフィックを実現します。

  • 単一コードベース
    iOSとAndroidの両方に対応するため、コードを再利用できるため、開発コストとメンテナンスコストを削減できます。

Flutterの利点

  • 生産性の向上
    豊富なウィジェットとホットリロード機能により、開発スピードが速く、生産性が向上します。

  • カスタマイズ性の高さ
    完全にカスタマイズ可能なウィジェットを提供しており、デザイナーから開発者までが自由にUIを設計できます。

  • 強力なコミュニティとサポート
    Flutterは急速に成長しているコミュニティを持ち、豊富なドキュメントやサンプルコード、パッケージが提供されています。

  • ネイティブパフォーマンス
    Flutterアプリはネイティブコードにコンパイルされるため、高いパフォーマンスを発揮します。

他のクロスプラットフォームフレームワークとの比較

  • React Native
    JavaScriptを使用するReact Nativeと比較して、FlutterはDart言語を使用しますが、Flutterのホットリロード機能やウィジェットの豊富さから、よりスムーズな開発体験を提供します。

  • Xamarin
    C#を使用するXamarinと比較して、Flutterはより軽量で柔軟なUI設計が可能です。また、Flutterのコミュニティの活発さも利点です。


3. Flutterの基本概念とウィジェット

Flutterの理解には、基本的な概念とウィジェットの仕組みを理解することが重要です。以下に主要な概念を解説します。

ウィジェットとは

Flutterでは、すべてがウィジェットとして構築されます。ウィジェットはUIの構成要素であり、レイアウトやスタイル、挙動を定義します。ウィジェットは再利用可能であり、組み合わせて複雑なUIを作成します。

StatefulとStatelessウィジェット

ウィジェットには、「Statefulウィジェット」と「Statelessウィジェット」の2種類があります。

  • Statelessウィジェット
    状態を持たないウィジェットです。表示内容が静的で変化しない場合に使用します。

    import 'package:flutter/material.dart';
    
    class MyStatelessWidget extends StatelessWidget {
      
      Widget build(BuildContext context) {
        return Text('Hello, Flutter!');
      }
    }
    
  • Statefulウィジェット
    状態を持つウィジェットです。ユーザーの入力やデータの変化に応じてUIを更新する場合に使用します。

    import 'package:flutter/material.dart';
    
    class MyStatefulWidget extends StatefulWidget {
      
      _MyStatefulWidgetState createState() => _MyStatefulWidgetState();
    }
    
    class _MyStatefulWidgetState extends State<MyStatefulWidget> {
      String text = 'Hello, Flutter!';
    
      void _updateText() {
        setState(() {
          text = 'Flutter is awesome!';
        });
      }
    
      
      Widget build(BuildContext context) {
        return Column(
          children: [
            Text(text),
            ElevatedButton(
              onPressed: _updateText,
              child: Text('Update'),
            ),
          ],
        );
      }
    }
    

ウィジェットツリーの理解

FlutterのUIはウィジェットツリーと呼ばれる階層構造で構築されます。親ウィジェットが子ウィジェットを持ち、階層的にUIが形成されます。ウィジェットツリーを理解することで、効果的なUI設計が可能になります。


4. フォルダーの基本構成

Flutterプロジェクトのフォルダー構成は、アプリケーションの開発と管理を効率的に行うために設計されています。以下に主要なフォルダーとその役割を説明します。

プロジェクトのルートディレクトリ

Flutterプロジェクトを新規作成すると、以下のようなディレクトリ構造が生成されます。

my_flutter_app/
├── android/
├── ios/
├── lib/
│   └── main.dart
├── test/
├── pubspec.yaml
├── pubspec.lock
├── .gitignore
└── README.md

各フォルダーの役割

  • android/
    Androidプラットフォーム固有の設定やコードが含まれます。GradleビルドスクリプトやAndroidManifest.xmlなどがここに配置されます。

  • ios/
    iOSプラットフォーム固有の設定やコードが含まれます。XcodeプロジェクトファイルやInfo.plistなどがここに配置されます。

  • lib/
    Dartコードが含まれる主要なフォルダーです。main.dartファイルがエントリーポイントとなります。アプリケーションのロジックやUIのコードをここに記述します。

  • test/
    テストコードが含まれます。ユニットテストやウィジェットテストをここで管理します。

  • pubspec.yaml
    プロジェクトの設定ファイルで、依存関係や資産(画像、フォントなど)の宣言を行います。

  • pubspec.lock
    依存パッケージのバージョンを固定するためのファイルです。

  • .gitignore
    Gitで管理しないファイルやフォルダーを指定します。

  • README.md
    プロジェクトの概要やセットアップ手順などを記述するMarkdownファイルです。


5. プロジェクトの新規作成

Flutterプロジェクトを新規に作成する手順を以下に示します。コマンドラインを使用する方法とAndroid Studioを使用する方法の2つを紹介します。

1. コマンドラインを使用して新規プロジェクトを作成

  1. ターミナルまたはコマンドプロンプトを開く

  2. プロジェクトを作成するディレクトリに移動
    例として、~/developmentディレクトリにプロジェクトを作成する場合:

    cd ~/development
    
  3. Flutterプロジェクトを作成
    flutter createコマンドを使用します。

    flutter create my_flutter_app
    
  4. プロジェクトディレクトリに移動

    cd my_flutter_app
    
  5. プロジェクトを実行
    エミュレーターや実機がセットアップされていることを確認し、以下のコマンドでアプリを実行します。

    flutter run
    

2. Android Studioを使用して新規プロジェクトを作成

  1. Android Studioを起動

  2. 新しいFlutterプロジェクトの作成
    スタート画面で「Start a new Flutter project」を選択します。

  3. プロジェクトタイプを選択
    「Flutter Application」を選択し、「Next」をクリックします。

  4. プロジェクトの設定

    • Project name: プロジェクト名を入力(例:my_flutter_app)
    • Flutter SDK path: Flutter SDKのパスを指定
    • Project location: プロジェクトを作成するディレクトリを指定
    • Description: プロジェクトの説明を入力(任意)

    設定が完了したら「Next」をクリックします。

  5. iOSとAndroidの設定
    必要に応じて、iOSやAndroidのパッケージ名などを設定します。

  6. プロジェクトの作成
    「Finish」をクリックすると、プロジェクトが作成され、Android Studioが新しいプロジェクトを開きます。

  7. アプリの実行
    エミュレーターまたは実機をセットアップし、Android Studio上部の「Run」ボタンをクリックしてアプリを実行します。


Flutterでカウンターアプリを拡張!ウィジェット追加後に処理を追加する方法

Flutterのデフォルトアプリを基に、カウンターの増加方法を「足し算」と「掛け算」から選択できる機能を追加してみましょう。本記事では、ウィジェットを先に追加し、その後で処理を追加するアプローチでステップバイステップに解説します。この方法により、ユーザーインターフェースの構築と機能実装を明確に分離し、理解しやすく進めることができます。

目次

  1. はじめに
  2. プロジェクトの準備
  3. ウィジェットの追加
  4. 処理の追加
  5. コード全体
  6. まとめ

はじめに

Flutterのデフォルトカウンターアプリはシンプルですが、機能を拡張することでFlutterの基本的なコンポーネントやステート管理の理解を深めることができます。今回は、このアプリに「足し算」または「掛け算」を選択できる機能を追加します。ウィジェットの追加を先行させ、その後で必要な処理を実装することで、アプリケーションの構造を整理しやすくします。

プロジェクトの準備

まずは、Flutterのデフォルトプロジェクトを作成します。以下のコマンドを実行してください。

flutter create counter_app
cd counter_app

プロジェクトが作成されたら、lib/main.dart ファイルを開き、デフォルトのカウンターアプリのコードを確認しましょう。

ウィジェットの追加

ここでは、まずユーザーインターフェース(UI)を構築するためのウィジェットを追加します。具体的には、入力フィールド、ラジオボタン、表示用テキスト、ボタンなどを順番に追加します。

1. 列挙型 IncrementMethod の定義

増加方法を明確にするために、列挙型 IncrementMethod を定義します。これにより、増加方法を「足し算」と「掛け算」の2種類に限定できます。

// 1. IncrementMethod 列挙型の定義(増加方法を明確化)
enum IncrementMethod { addition, multiplication }

2. 整数入力用 TextField の追加

ユーザーが整数値を入力できるように、TextField を追加します。これにより、カウンターの増加量をユーザーが指定できるようになります。

// 2. TextField を整数入力用に設定
TextField(
  controller: _integerController, // コントローラーを有効化(後で定義します)
  decoration: InputDecoration(
    labelText: '整数値', // ラベルを追加
    hintText: '整数値を入力してください。',
    helperText: '半角で入力してください。',
    border: OutlineInputBorder(), // 枠線を追加
  ),
  keyboardType: TextInputType.number, // 数値キーボードを表示
),
const SizedBox(height: 20), // スペースを追加

3. 増加方法選択用 RadioExample ウィジェットの追加

増加方法を選択するために、RadioExample ウィジェットを追加します。このウィジェットはユーザーが「足し算」か「掛け算」を選択できるようにするためのものです。

// 3. RadioExample ウィジェットの追加
RadioExample(
  selectedMethod: _selectedMethod, // 選択された方法(後で定義します)
  onMethodChanged: (IncrementMethod? value) {
    if (value != null) {
      setState(() {
        _selectedMethod = value;
      });
    }
  },
),
const SizedBox(height: 20), // スペースを追加

4. ボタンの追加

ユーザーが入力値を表示したり、カウンターをリセットしたりできるように、ボタンを追加します。

// 4. ボタンの追加

// 入力値を表示するボタン
ElevatedButton(
  onPressed: _showInputValue, // 処理は後で実装します
  child: const Text('入力値を表示'),
),
const SizedBox(height: 10), // スペースを追加

// リセットボタン
ElevatedButton(
  onPressed: _resetCounter, // 処理は後で実装します
  style: ElevatedButton.styleFrom(
    backgroundColor: Colors.red, // ボタンの背景色を赤に設定
  ),
  child: const Text('リセット'),
),

5. RadioExample ウィジェットの作成

増加方法を選択するための RadioExample ウィジェットをステートレスウィジェットとして作成します。このウィジェットはラジオボタンと選択結果の表示を担当します。

// 5. RadioExample をステートレスウィジェットに変更
class RadioExample extends StatelessWidget {
  final IncrementMethod selectedMethod;
  final ValueChanged<IncrementMethod?> onMethodChanged;

  const RadioExample({
    super.key,
    required this.selectedMethod,
    required this.onMethodChanged,
  });

  
  Widget build(BuildContext context) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start, // 左揃えに設定
      children: <Widget>[
        const Text(
          '増加方法を選択してください:',
          style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
        ),
        ListTile(
          title: const Text('足し算'),
          leading: Radio<IncrementMethod>(
            value: IncrementMethod.addition,
            groupValue: selectedMethod,
            onChanged: onMethodChanged,
          ),
        ),
        ListTile(
          title: const Text('掛け算'),
          leading: Radio<IncrementMethod>(
            value: IncrementMethod.multiplication,
            groupValue: selectedMethod,
            onChanged: onMethodChanged,
          ),
        ),
        const SizedBox(height: 10),
        // 選択された増加方法を表示
        Text(
          _selectedMethodText(selectedMethod), // メソッドは後で実装します
          style: const TextStyle(fontSize: 16),
        ),
      ],
    );
  }

  // 選択された方法をテキストで表示するヘルパーメソッド
  String _selectedMethodText(IncrementMethod method) {
    switch (method) {
      case IncrementMethod.addition:
        return '選択された増加方法: 足し算';
      case IncrementMethod.multiplication:
        return '選択された増加方法: 掛け算';
      default:
        return '選択されていません。';
    }
  }
}

処理の追加

ウィジェットの追加が完了したら、次に機能を実装します。これには、ユーザーの入力を管理し、カウンターの増加やリセットを行うための処理が含まれます。

6. TextEditingController の定義

TextField の入力を管理するために、TextEditingController を定義します。これにより、ユーザーが入力した値を簡単に取得できます。

// 6. TextEditingController の定義
final TextEditingController _integerController = TextEditingController();

7. ステート管理の設定

ユーザーの選択やカウンターの状態を管理するために、ステートを設定します。

// ステート管理のための変数
IncrementMethod _selectedMethod = IncrementMethod.addition;
int _counter = 0;

8. カウンター増加メソッドの実装

カウンターを増加させるためのメソッド _incrementCounter を実装します。選択された増加方法に基づいてカウンターを更新します。

// 8. カウンター増加メソッドの実装
void _incrementCounter() {
  setState(() {
    // TextField から値を取得
    int inputValue = int.tryParse(_integerController.text) ?? 1;
    if (_selectedMethod == IncrementMethod.addition) {
      _counter += inputValue;
    } else if (_selectedMethod == IncrementMethod.multiplication) {
      // 初期値が0の場合、掛け算で増やすと常に0になるため、1に初期化する
      _counter = _counter == 0 ? inputValue : _counter * inputValue;
    }
    print("Counter updated using ${_selectedMethod == IncrementMethod.addition ? 'addition' : 'multiplication'} with input $inputValue");
  });
}

9. カウンターリセットメソッドの実装

カウンターと入力フィールドをリセットするためのメソッド _resetCounter を実装します。

// 9. カウンターリセットメソッドの実装
void _resetCounter() {
  setState(() {
    _counter = 0;
    _integerController.clear(); // 入力フィールドをクリア(オプション)
    _selectedMethod = IncrementMethod.addition; // 増加方法をデフォルトに戻す(オプション)
    print("Counter reset to 0");
  });
}

10. 入力値表示ボタンの処理

ユーザーが入力した値を表示するためのメソッド _showInputValue を実装します。このメソッドは、スナックバーを使用して入力値を表示します。

// 10. 入力値表示ボタンの処理
void _showInputValue() {
  final input = _integerController.text;
  if (input.isNotEmpty) {
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(content: Text('入力された値: $input')),
    );
  }
}

リソースの解放

TextEditingController を使用した後は、そのリソースを解放する必要があります。dispose メソッドをオーバーライドして実装します。


void dispose() {
  // コントローラーのリソースを解放
  _integerController.dispose();
  super.dispose();
}

コード全体

以上の手順を反映した lib/main.dart の全体コードは以下の通りです。

import 'package:flutter/material.dart';

// 1. IncrementMethod 列挙型の定義(増加方法を明確化)
enum IncrementMethod { addition, multiplication }

void main() {
  runApp(const MyApp());
}

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

  // アプリケーションのルートウィジェット
  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        // テーマの設定
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

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

  final String title;

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

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

  // 6. TextEditingController の定義
  final TextEditingController _integerController = TextEditingController();

  // ステート管理のための変数
  IncrementMethod _selectedMethod = IncrementMethod.addition;

  // 8. カウンター増加メソッドの実装
  void _incrementCounter() {
    setState(() {
      // TextField から値を取得
      int inputValue = int.tryParse(_integerController.text) ?? 1;
      if (_selectedMethod == IncrementMethod.addition) {
        _counter += inputValue;
      } else if (_selectedMethod == IncrementMethod.multiplication) {
        // 初期値が0の場合、掛け算で増やすと常に0になるため、1に初期化する
        _counter = _counter == 0 ? inputValue : _counter * inputValue;
      }
      print("Counter updated using ${_selectedMethod == IncrementMethod.addition ? 'addition' : 'multiplication'} with input $inputValue");
    });
  }

  // 9. カウンターリセットメソッドの実装
  void _resetCounter() {
    setState(() {
      _counter = 0;
      _integerController.clear(); // 入力フィールドをクリア(オプション)
      _selectedMethod = IncrementMethod.addition; // 増加方法をデフォルトに戻す(オプション)
      print("Counter reset to 0");
    });
  }

  // 10. 入力値表示ボタンの処理
  void _showInputValue() {
    final input = _integerController.text;
    if (input.isNotEmpty) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('入力された値: $input')),
      );
    }
  }

  
  void dispose() {
    // コントローラーのリソースを解放
    _integerController.dispose();
    super.dispose();
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        // AppBarの背景色をテーマのinversePrimaryに設定
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
      ),
      body: SingleChildScrollView( // ウィジェットが多くなった際にスクロール可能に
        padding: const EdgeInsets.all(16.0), // パディングを追加して余白を確保
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch, // 横幅を広げて見やすく
          children: <Widget>[
            // 2. TextField を整数入力用に設定
            TextField(
              controller: _integerController, // コントローラーを有効化
              decoration: InputDecoration(
                labelText: '整数値', // ラベルを追加
                hintText: '整数値を入力してください。',
                helperText: '半角で入力してください。',
                border: OutlineInputBorder(), // 枠線を追加
              ),
              keyboardType: TextInputType.number, // 数値キーボードを表示
            ),
            const SizedBox(height: 20), // スペースを追加
            const Text(
              'You have pushed the button this many times:',
              textAlign: TextAlign.center, // テキストを中央揃え
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headlineMedium,
              textAlign: TextAlign.center, // テキストを中央揃え
            ),
            const SizedBox(height: 20), // スペースを追加
            // 3. RadioExample ウィジェットの追加
            RadioExample(
              selectedMethod: _selectedMethod,
              onMethodChanged: (IncrementMethod? value) {
                if (value != null) {
                  setState(() {
                    _selectedMethod = value;
                  });
                }
              },
            ),
            const SizedBox(height: 20), // スペースを追加
            // 4. ボタンの追加
            ElevatedButton(
              onPressed: _showInputValue,
              child: const Text('入力値を表示'),
            ),
            const SizedBox(height: 10), // スペースを追加
            ElevatedButton(
              onPressed: _resetCounter,
              style: ElevatedButton.styleFrom(
                backgroundColor: Colors.red, // ボタンの背景色を赤に設定
              ),
              child: const Text('リセット'),
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter, // カウンター増加メソッドを呼び出す
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ), // この後のカンマは自動フォーマッティングのために残しています
    );
  }
}

// 5. RadioExample をステートレスウィジェットに変更
class RadioExample extends StatelessWidget {
  final IncrementMethod selectedMethod;
  final ValueChanged<IncrementMethod?> onMethodChanged;

  const RadioExample({
    super.key,
    required this.selectedMethod,
    required this.onMethodChanged,
  });

  
  Widget build(BuildContext context) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start, // 左揃えに設定
      children: <Widget>[
        const Text(
          '増加方法を選択してください:',
          style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
        ),
        ListTile(
          title: const Text('足し算'),
          leading: Radio<IncrementMethod>(
            value: IncrementMethod.addition,
            groupValue: selectedMethod,
            onChanged: onMethodChanged,
          ),
        ),
        ListTile(
          title: const Text('掛け算'),
          leading: Radio<IncrementMethod>(
            value: IncrementMethod.multiplication,
            groupValue: selectedMethod,
            onChanged: onMethodChanged,
          ),
        ),
        const SizedBox(height: 10),
        // 選択された増加方法を表示
        Text(
          _selectedMethodText(selectedMethod),
          style: const TextStyle(fontSize: 16),
        ),
      ],
    );
  }

  // 選択された方法をテキストで表示するヘルパーメソッド
  String _selectedMethodText(IncrementMethod method) {
    switch (method) {
      case IncrementMethod.addition:
        return '選択された増加方法: 足し算';
      case IncrementMethod.multiplication:
        return '選択された増加方法: 掛け算';
      default:
        return '選択されていません。';
    }
  }
}

まとめ

本記事では、Flutterのデフォルトカウンターアプリに対して、以下のような機能拡張を行いました。

  1. ウィジェットの追加: TextFieldRadioExample ウィジェット、表示用テキスト、ボタンなどを順番に追加し、ユーザーインターフェースを構築しました。
  2. 処理の追加: TextEditingController の定義、ステート管理、カウンターの増加およびリセット機能、入力値表示機能などを実装しました。

このアプローチにより、まずUIを構築し、その後で機能を追加することで、アプリの構造を整理しやすくしました。Flutterのウィジェットシステムとステート管理を効果的に活用することで、柔軟で拡張性の高いアプリケーションを開発することができます。

ぜひ、この記事を参考にして、自分自身でさらなる機能追加やカスタマイズに挑戦してみてください。Flutterの持つ無限の可能性を活用して、独自のアプリケーションを作り上げましょう!

Flutterでカウンターアプリを拡張!

Flutterのデフォルトアプリを基に、カウンターの増加方法を「足し算」と「掛け算」から選択できる機能を追加してみましょう。本記事では、ウィジェットを先に追加し、その後で処理を追加するアプローチでステップバイステップに解説します。この方法により、ユーザーインターフェースの構築と機能実装を明確に分離し、理解しやすく進めることができます。

目次

  1. はじめに
  2. プロジェクトの準備
  3. ウィジェットの追加
  4. 処理の追加
  5. コード全体
  6. まとめ

はじめに

Flutterのデフォルトカウンターアプリはシンプルですが、機能を拡張することでFlutterの基本的なコンポーネントやステート管理の理解を深めることができます。今回は、このアプリに「足し算」または「掛け算」を選択できる機能を追加します。ウィジェットの追加を先行させ、その後で必要な処理を実装することで、アプリケーションの構造を整理しやすくします。

ウィジェットの追加

ここでは、まずユーザーインターフェース(UI)を構築するためのウィジェットを追加します。具体的には、入力フィールド、ラジオボタン、表示用テキスト、ボタンなどを順番に追加します。

1. 列挙型 IncrementMethod の定義

増加方法を明確にするために、列挙型 IncrementMethod を定義します。これにより、増加方法を「足し算」と「掛け算」の2種類に限定できます。

// 1. IncrementMethod 列挙型の定義(増加方法を明確化)
enum IncrementMethod { addition, multiplication }

2. 整数入力用 TextField の追加

ユーザーが整数値を入力できるように、TextField を追加します。これにより、カウンターの増加量をユーザーが指定できるようになります。

// 2. TextField を整数入力用に設定
TextField(
  controller: _integerController, // コントローラーを有効化(後で定義します)
  decoration: InputDecoration(
    labelText: '整数値', // ラベルを追加
    hintText: '整数値を入力してください。',
    helperText: '半角で入力してください。',
    border: OutlineInputBorder(), // 枠線を追加
  ),
  keyboardType: TextInputType.number, // 数値キーボードを表示
),
const SizedBox(height: 20), // スペースを追加

3. 増加方法選択用 RadioExample ウィジェットの追加

増加方法を選択するために、RadioExample ウィジェットを追加します。このウィジェットはユーザーが「足し算」か「掛け算」を選択できるようにするためのものです。

// 3. RadioExample ウィジェットの追加
RadioExample(
  selectedMethod: _selectedMethod, // 選択された方法(後で定義します)
  onMethodChanged: (IncrementMethod? value) {
    if (value != null) {
      setState(() {
        _selectedMethod = value;
      });
    }
  },
),
const SizedBox(height: 20), // スペースを追加

4. ボタンの追加

ユーザーが入力値を表示したり、カウンターをリセットしたりできるように、ボタンを追加します。

// 4. ボタンの追加

// 入力値を表示するボタン
ElevatedButton(
  onPressed: _showInputValue, // 処理は後で実装します
  child: const Text('入力値を表示'),
),
const SizedBox(height: 10), // スペースを追加

// リセットボタン
ElevatedButton(
  onPressed: _resetCounter, // 処理は後で実装します
  style: ElevatedButton.styleFrom(
    backgroundColor: Colors.red, // ボタンの背景色を赤に設定
  ),
  child: const Text('リセット'),
),

5. RadioExample ウィジェットの作成

増加方法を選択するための RadioExample ウィジェットをステートレスウィジェットとして作成します。このウィジェットはラジオボタンと選択結果の表示を担当します。

// 5. RadioExample をステートレスウィジェットに変更
class RadioExample extends StatelessWidget {
  final IncrementMethod selectedMethod;
  final ValueChanged<IncrementMethod?> onMethodChanged;

  const RadioExample({
    super.key,
    required this.selectedMethod,
    required this.onMethodChanged,
  });

  
  Widget build(BuildContext context) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start, // 左揃えに設定
      children: <Widget>[
        const Text(
          '増加方法を選択してください:',
          style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
        ),
        ListTile(
          title: const Text('足し算'),
          leading: Radio<IncrementMethod>(
            value: IncrementMethod.addition,
            groupValue: selectedMethod,
            onChanged: onMethodChanged,
          ),
        ),
        ListTile(
          title: const Text('掛け算'),
          leading: Radio<IncrementMethod>(
            value: IncrementMethod.multiplication,
            groupValue: selectedMethod,
            onChanged: onMethodChanged,
          ),
        ),
        const SizedBox(height: 10),
        // 選択された増加方法を表示
        Text(
          _selectedMethodText(selectedMethod), // メソッドは後で実装します
          style: const TextStyle(fontSize: 16),
        ),
      ],
    );
  }

  // 選択された方法をテキストで表示するヘルパーメソッド
  String _selectedMethodText(IncrementMethod method) {
    switch (method) {
      case IncrementMethod.addition:
        return '選択された増加方法: 足し算';
      case IncrementMethod.multiplication:
        return '選択された増加方法: 掛け算';
      default:
        return '選択されていません。';
    }
  }
}

処理の追加

ウィジェットの追加が完了したら、次に機能を実装します。これには、ユーザーの入力を管理し、カウンターの増加やリセットを行うための処理が含まれます。

6. TextEditingController の定義

TextField の入力を管理するために、TextEditingController を定義します。これにより、ユーザーが入力した値を簡単に取得できます。

// 6. TextEditingController の定義
final TextEditingController _integerController = TextEditingController();

7. ステート管理の設定

ユーザーの選択やカウンターの状態を管理するために、ステートを設定します。

// ステート管理のための変数
IncrementMethod _selectedMethod = IncrementMethod.addition;
int _counter = 0;

8. カウンター増加メソッドの実装

カウンターを増加させるためのメソッド _incrementCounter を実装します。選択された増加方法に基づいてカウンターを更新します。

// 8. カウンター増加メソッドの実装
void _incrementCounter() {
  setState(() {
    // TextField から値を取得
    int inputValue = int.tryParse(_integerController.text) ?? 1;
    if (_selectedMethod == IncrementMethod.addition) {
      _counter += inputValue;
    } else if (_selectedMethod == IncrementMethod.multiplication) {
      // 初期値が0の場合、掛け算で増やすと常に0になるため、1に初期化する
      _counter = _counter == 0 ? inputValue : _counter * inputValue;
    }
    print("Counter updated using ${_selectedMethod == IncrementMethod.addition ? 'addition' : 'multiplication'} with input $inputValue");
  });
}

9. カウンターリセットメソッドの実装

カウンターと入力フィールドをリセットするためのメソッド _resetCounter を実装します。

// 9. カウンターリセットメソッドの実装
void _resetCounter() {
  setState(() {
    _counter = 0;
    _integerController.clear(); // 入力フィールドをクリア(オプション)
    _selectedMethod = IncrementMethod.addition; // 増加方法をデフォルトに戻す(オプション)
    print("Counter reset to 0");
  });
}

10. 入力値表示ボタンの処理

ユーザーが入力した値を表示するためのメソッド _showInputValue を実装します。このメソッドは、スナックバーを使用して入力値を表示します。

// 10. 入力値表示ボタンの処理
void _showInputValue() {
  final input = _integerController.text;
  if (input.isNotEmpty) {
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(content: Text('入力された値: $input')),
    );
  }
}

リソースの解放

TextEditingController を使用した後は、そのリソースを解放する必要があります。dispose メソッドをオーバーライドして実装します。


void dispose() {
  // コントローラーのリソースを解放
  _integerController.dispose();
  super.dispose();
}

コード全体

以上の手順を反映した lib/main.dart の全体コードは以下の通りです。

import 'package:flutter/material.dart';

// 1. IncrementMethod 列挙型の定義(増加方法を明確化)
enum IncrementMethod { addition, multiplication }

void main() {
  runApp(const MyApp());
}

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

  // アプリケーションのルートウィジェット
  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        // テーマの設定
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

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

  final String title;

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

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

  // 6. TextEditingController の定義
  final TextEditingController _integerController = TextEditingController();

  // ステート管理のための変数
  IncrementMethod _selectedMethod = IncrementMethod.addition;

  // 8. カウンター増加メソッドの実装
  void _incrementCounter() {
    setState(() {
      // TextField から値を取得
      int inputValue = int.tryParse(_integerController.text) ?? 1;
      if (_selectedMethod == IncrementMethod.addition) {
        _counter += inputValue;
      } else if (_selectedMethod == IncrementMethod.multiplication) {
        // 初期値が0の場合、掛け算で増やすと常に0になるため、1に初期化する
        _counter = _counter == 0 ? inputValue : _counter * inputValue;
      }
      print("Counter updated using ${_selectedMethod == IncrementMethod.addition ? 'addition' : 'multiplication'} with input $inputValue");
    });
  }

  // 9. カウンターリセットメソッドの実装
  void _resetCounter() {
    setState(() {
      _counter = 0;
      _integerController.clear(); // 入力フィールドをクリア(オプション)
      _selectedMethod = IncrementMethod.addition; // 増加方法をデフォルトに戻す(オプション)
      print("Counter reset to 0");
    });
  }

  // 10. 入力値表示ボタンの処理
  void _showInputValue() {
    final input = _integerController.text;
    if (input.isNotEmpty) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('入力された値: $input')),
      );
    }
  }

  
  void dispose() {
    // コントローラーのリソースを解放
    _integerController.dispose();
    super.dispose();
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        // AppBarの背景色をテーマのinversePrimaryに設定
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
      ),
      body: SingleChildScrollView( // ウィジェットが多くなった際にスクロール可能に
        padding: const EdgeInsets.all(16.0), // パディングを追加して余白を確保
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch, // 横幅を広げて見やすく
          children: <Widget>[
            // 2. TextField を整数入力用に設定
            TextField(
              controller: _integerController, // コントローラーを有効化
              decoration: InputDecoration(
                labelText: '整数値', // ラベルを追加
                hintText: '整数値を入力してください。',
                helperText: '半角で入力してください。',
                border: OutlineInputBorder(), // 枠線を追加
              ),
              keyboardType: TextInputType.number, // 数値キーボードを表示
            ),
            const SizedBox(height: 20), // スペースを追加
            const Text(
              'You have pushed the button this many times:',
              textAlign: TextAlign.center, // テキストを中央揃え
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headlineMedium,
              textAlign: TextAlign.center, // テキストを中央揃え
            ),
            const SizedBox(height: 20), // スペースを追加
            // 3. RadioExample ウィジェットの追加
            RadioExample(
              selectedMethod: _selectedMethod,
              onMethodChanged: (IncrementMethod? value) {
                if (value != null) {
                  setState(() {
                    _selectedMethod = value;
                  });
                }
              },
            ),
            const SizedBox(height: 20), // スペースを追加
            // 4. ボタンの追加
            ElevatedButton(
              onPressed: _showInputValue,
              child: const Text('入力値を表示'),
            ),
            const SizedBox(height: 10), // スペースを追加
            ElevatedButton(
              onPressed: _resetCounter,
              style: ElevatedButton.styleFrom(
                backgroundColor: Colors.red, // ボタンの背景色を赤に設定
              ),
              child: const Text('リセット'),
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter, // カウンター増加メソッドを呼び出す
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ), // この後のカンマは自動フォーマッティングのために残しています
    );
  }
}

// 5. RadioExample をステートレスウィジェットに変更
class RadioExample extends StatelessWidget {
  final IncrementMethod selectedMethod;
  final ValueChanged<IncrementMethod?> onMethodChanged;

  const RadioExample({
    super.key,
    required this.selectedMethod,
    required this.onMethodChanged,
  });

  
  Widget build(BuildContext context) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start, // 左揃えに設定
      children: <Widget>[
        const Text(
          '増加方法を選択してください:',
          style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
        ),
        ListTile(
          title: const Text('足し算'),
          leading: Radio<IncrementMethod>(
            value: IncrementMethod.addition,
            groupValue: selectedMethod,
            onChanged: onMethodChanged,
          ),
        ),
        ListTile(
          title: const Text('掛け算'),
          leading: Radio<IncrementMethod>(
            value: IncrementMethod.multiplication,
            groupValue: selectedMethod,
            onChanged: onMethodChanged,
          ),
        ),
        const SizedBox(height: 10),
        // 選択された増加方法を表示
        Text(
          _selectedMethodText(selectedMethod),
          style: const TextStyle(fontSize: 16),
        ),
      ],
    );
  }

  // 選択された方法をテキストで表示するヘルパーメソッド
  String _selectedMethodText(IncrementMethod method) {
    switch (method) {
      case IncrementMethod.addition:
        return '選択された増加方法: 足し算';
      case IncrementMethod.multiplication:
        return '選択された増加方法: 掛け算';
      default:
        return '選択されていません。';
    }
  }
}

まとめ

本記事では、Flutterのデフォルトカウンターアプリに対して、以下のような機能拡張を行いました。

  1. ウィジェットの追加: TextFieldRadioExample ウィジェット、表示用テキスト、ボタンなどを順番に追加し、ユーザーインターフェースを構築しました。
  2. 処理の追加: TextEditingController の定義、ステート管理、カウンターの増加およびリセット機能、入力値表示機能などを実装しました。

このアプローチにより、まずUIを構築し、その後で機能を追加することで、アプリの構造を整理しやすくしました。Flutterのウィジェットシステムとステート管理を効果的に活用することで、柔軟で拡張性の高いアプリケーションを開発することができます。

ぜひ、この記事を参考にして、自分自身でさらなる機能追加やカスタマイズに挑戦してみてください。Flutterの持つ無限の可能性を活用して、独自のアプリケーションを作り上げましょう!

まとめ

この記事では、Flutterの環境構築から基本的な概念、フォルダー構成、プロジェクトの新規作成方法までを初心者向けに解説しました。これらのステップを踏むことで、Flutterを用いたアプリ開発の第一歩を踏み出すことができます。

Flutterは強力なツールであり、コミュニティも活発に成長しています。公式ドキュメントやコミュニティリソースを活用しながら、継続的に学習を進めていくことをお勧めします。次回は、Flutterのウィジェットについてさらに詳しく掘り下げていきますので、お楽しみに!


参考資料


ハッシュタグ

#Flutter #初心者向け #モバイル開発 #クロスプラットフォーム #環境構築 #アプリ開発

Discussion