LoginSignup
6
4

【Flutter】わかんないよ!オブジェクト指向なんてわかんないよ!

Last updated at Posted at 2023-09-08

はじめに

「わかんない、わかんないよ!Flutterってなんなの!Dartなんて初めて聞いたし、オブジェクト指向って言われても難しくてわかんないよ!」

どうも、堕罪オサムです。
最近FlutterとDartを学習していて、その一環でFlutterのチュートリアル(?)アプリをいじって遊んだりしてたのですが、これ、ありえないほどむずくねえですか?
なんかこう、知らない単語イッパイだァみたいな……。

そういうわけで理解を深めるべく、そのお遊びでできたコードを一から解説することにしました。

本当になにもわからない私が読んでもわかるように(今後Flutterを使用してアプリを作るときに見返せるように)細かく調べてみたので、よかったら目を通していただけると嬉しいです!

書いたコードはコチラをクリック!
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.amber),
        useMaterial3: true,
      ),
      home: const MyHomePage(title: 'testApp'),
    );
  }
}

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

  final String title;

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

class ResetButton extends ElevatedButton {
  const ResetButton({Key? key, required VoidCallback? callback})
      : super(
            key: key,
            onPressed: callback,
            child: const Icon(
              Icons.delete,
            ));
}

class ReduceButton extends ElevatedButton {
  const ReduceButton({Key? key, required VoidCallback? callback})
      : super(
            key: key,
            onPressed: callback,
            child: const Text(
              ' \u2212 ', // Unicode 文字列 U+2212 はマイナス記号を表す
              style: TextStyle(fontSize: 25),
            ));
}

class MemoDataGetButton extends ElevatedButton {
  const MemoDataGetButton({Key? key, required VoidCallback? callback})
      : super(
          key: key,
          onPressed: callback,
          child: const Icon(Icons.done),
        );
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;
  String _memoText = ''; // テキストを保持するための変数
  final List<String> _memoList = []; // メモのリスト
  final TextEditingController _textEditingController = TextEditingController();

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

  void _resetCounter() {
    setState(() {
      _counter = 0;
    });
  }

  void _reduceCounter() {
    setState(() {
      _counter--;
    });
  }

  void _updateText(String newText) {
    setState(() {
      _memoText = newText;
    });
  }

  void _handleButtonPress() {
    // ボタンが押されたときに新しいメモデータを取得し、_updateTextを呼び出す
    _memoList.add(_memoText); // メモをリストに追加
    _updateText(''); // メモデータを初期化

    // TextFormFieldのテキストを初期化
    _textEditingController.clear();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
      ),
      body: SingleChildScrollView(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              'count',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headlineMedium,
            ),
            ResetButton(callback: _resetCounter),
            ReduceButton(callback: _reduceCounter),
            TextFormField(
              controller: _textEditingController, // TextEditingControllerを指定
              decoration: const InputDecoration(
                labelText: 'memo',
                floatingLabelBehavior: FloatingLabelBehavior.never,
              ),
              keyboardType: TextInputType.multiline,
              maxLines: null,
              onChanged: (newMemo) {
                _updateText(newMemo);
                // メモデータを入力するたびに更新
              },
            ),

            MemoDataGetButton(callback: _handleButtonPress),
            //メモデータを取得するボタン
            SizedBox(
              height: 200,
              width: double.infinity, // メモデータ左寄せ
              child: ListView(
                  children: _memoList
                      .map((memo) => ListTile(
                            leading: const Icon(Icons.fiber_manual_record),
                            title: Text('Item $memo'),
                          ))
                      .toList()),
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }
}

解説

Flutterアプリケーションの初期化とルートウィジェットの設定

この部分のコードは、Flutterアプリケーションのエントリーポイントである main 関数から始まり、アプリケーションの初期設定を行う MyApp クラスについて説明しています。

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.amber),
        useMaterial3: true,
      ),
      home: const MyHomePage(title: 'testApp'),
    );
  }
}
  1. void main() ~ runApp(const MyApp());  ⇒ 一番最初に実行されるコード(多分必須)。
  2. class MyApp extends StatelessWidget ~ const MyApp({super.key}) ⇒MyAppという子クラスStatelessWidgetクラスという親クラス継承(extends)している。 ※StatelessWidgetを継承しているWidgetは定数コンストラクタを含むことができる。
  3. @override ~ Widget build(BuildContext context)… ⇒buildメソッドでMyAppがウィジェットを構築するため@override親クラスStatelessWidget)は書き換えずに子クラスMyApp上書きしている。※MaterialAppはFlutterフレームワークで作成されたアプリケーションのルートとなるウィジェット。アプリのテーマやナビゲーション、ローカライズなどのアプリ全体の管理を行う役割のウィジェット。
  4. return MaterialApp()… ⇒子クラスのMyAppbuildメソッド内にMaterialAppを定義している。homeは最初に起動させたいページ(MyHomePage以降)を設定している。

MyHomePage ウィジェットの設定

この部分のコードは、MyHomePage ウィジェットが状態を持ち、その状態を管理するための State オブジェクトを生成するためのものです。

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

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}
  1. class MyHomePage extends StatefulWidget ~ const MyHomePage… ⇒ StatefulWidgetという親クラスがMyHomePageを継承(extends)している。また、MyHomePageクラスのコンストラクタは、super.keyrequired this.titleの2つのパラメーターを受け取っている。required this.titleは、コンストラクタ呼び出し時にtitleパラメーターが必要であることを示していいる。 
  2. final String title; ⇒final文字列(String)のtitleを代入する。↑で指定されているrequired this.titleコンストラクタ呼び出し時titleパラメーターが必要なのでfinalで関連付ける。
  3. State<MyHomePage> createState() => _MyHomePageState(); ⇒createStateメソッドの実装。State<MyHomePage>は、Stateクラスのジェネリクス型引数としてMyHomePageクラスを指定している。この指定により*S*tateオブジェクトがどのウィジェットの状態を管理するかを示し、ここではMyHomePageウィジェットの状態を管理するためのStateオブジェクトを作成している。またcreateStateで作成したstateが変化したときに再描画される。

ボタンクラスの設定

このセクションでは、3つのボタンクラス(ResetButton、ReduceButton、MemoDataGetButton)の設定について説明します。

class ResetButton extends ElevatedButton {
  const ResetButton({Key? key, required VoidCallback? callback})
      : super(
            key: key,
            onPressed: callback,
            child: const Icon(
              Icons.exposure_zero,
            ));
}

このコードではボタンクラスが3つ(ResetButton・ReduceButton・MemoDataGetButton)あり、どれも同様に作られている。よってこの3つの命名以外はテンプレートのようになっています。

  1. class ResetButton extends ElevatedButton ⇒ ElevatedButtonを継承したボタンのクラスを作る。※ ElevatedButton:立体的な効果を持ったボタンを作るためのウィジェット
  2. const ResetButton({Key? key, required VoidCallback? callback}) ⇒ResetButton のコンストラクタ。VoidCallbackonPressedなどのボタンのイベント処理のときに使うもの。また引数を受け取らず何も返さない関数の型。?null許容することを示している。{}の中に定義されているパラメーター(引数)は、コンストラクタ呼び出し時に渡すことができる。requiredがあるためcallbackの引数を指定しなければならない。
  3. super(…) ⇒親クラスElevatedButtonのコンストラクタを呼び出している。keyonPressedchildコンストラクタ引数onPressedが発火するタイミング(ResetButton押下時)のアクションをcallbackとして受け取っている(ちなみにそれぞれのボタンのcallback時の引数はのちに定義していく)。childではボタンアイコンの指定をしている。

ウィジェット内部の状態

このセクションでは、_MyHomePageStateクラス内でウィジェット内部の状態を管理する方法について説明します。

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;
  String _memoText = ''; // テキストを保持するための変数
  final List<String> _memoList = []; // メモのリスト
  final TextEditingController _textEditingController = TextEditingController();

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

void _incrementCounter() のほかに_resetCounter() ・_reduceCounter() ・_updateText(String newText) が同じようなコードになります。

  1. class _MyHomePageState extends State<MyHomePage> ⇒State<MyHomePage>を継承した_MyHomePageStateクラスを作る(State クラスは MyHomePage ウィジェットの内部状態を管理する)。
  2. int _counter = 0; ⇒_counterウィジェット内で使用される整数値変数。初期値は 0
  3. String _memoText = ''; ⇒テキストフィールドに入力されたメモのテキストを保持するための文字列変数_updateText メソッドで新しいテキストが更新されると、この変数も更新される。
  4. final List<String> _memoList = []; ⇒メモのリストを保持するための文字列リストを作る。_handleButtonPress メソッド内で新しいメモが _memoList に追加される。
  5. final TextEditingController _textEditingController = TextEditingController(); ⇒_textEditingControllerはテキストフィールドの入力を制御するための TextEditingController オブジェクトです。テキストフィールドにテキストを入力する際に使用されます。
  6. void _incrementCounter() {} ⇒カウンターをインクリメントするための_incrementCounter() メソッドを作る。setState内で変数の値を変更することでウィジェットの再描画をハンドルすることが可能。_counter++;_counterウィジェット内で使用される整数値変数増加させるという処理。

_resetCounter() ・_reduceCounter() ・_updateText(String newText) についての説明

 void _resetCounter() {
    setState(() {
      _counter = 0;
    });
  }

  void _reduceCounter() {
    setState(() {
      _counter--;
    });
  }

  void _updateText(String newText) {
    setState(() {
      _memoText = newText;
    });
  }
  1. void _resetCounter(){} ⇒setState内で変数の値を変更することでウィジェットの再描画をハンドルすることが可能。 _counter = 0;_counter変数で使用される整数値変数をリセットするという処理。
  2. void _reduceCounter() {} ⇒ setState内で変数の値を変更することでウィジェットの再描画をハンドルすることが可能。_counter--;_counter変数で使用される整数値変数減少させるという処理。
  3. _updateText(String newText) {} ⇒void _updateText(String newText)は、void _updateTextが新しいテキスト情報を受け取るための引数 newText を持っていることを示す。setState内で変数の値を変更することでウィジェットの再描画をハンドルすることが可能。 _memoText = newText;では新しいテキスト newText_memoText に代入され、メモのテキストが新しい値に更新される。

ボタンが押されたときの処理

このセクションでは、ボタンが押されたときに実行される _handleButtonPress メソッドについて説明します。

void _handleButtonPress() {
    // ボタンが押されたときに新しいメモデータを取得し、_updateTextを呼び出す
    _memoList.add(_memoText); // メモをリストに追加
    _updateText(''); // メモデータを初期化

    // TextFormFieldのテキストを初期化
    _textEditingController.clear();
  }
  1. void _handleButtonPress(){} ⇒MemoDataGetButtonを押下した際のコールバック処理が書かれている。(のちにMemoDataGetButton(callback: _handleButtonPress)が出てくる)
  2. _memoList.add(_memoText); ⇒新しいメモデータ _memoText_memoList と呼ばれるリストに追加する。
  3. _updateText(''); ⇒_updateText メソッドを呼び出し、メモのテキストデータ _memoText を初期化(空文字列 '') になるようにする。
  4. _textEditingController.clear(); ⇒_textEditingController を使用して、テキストフィールドのテキストをクリアまたは初期化する。テキストを入力後、ボタンを押下するとテキストボックスが初期化される。

ウィジェットの描画と Scaffold

このセクションでは、build メソッドに焦点を当てて説明します。

 @override
  Widget build(BuildContext context) {
    return Scaffold(...)
  1. @override Widget build(BuildContext context) ⇒buildメソッドはstatefulWidgetから継承されたMyHomePageウィジェットを描画する。BuildContext context はbuildメソッドの引数でこれを使用してウィジェットがどのように配置され、スタイルが適用されるかを決定する。
  2. return Scaffold() ⇒Scaffoldはほかのウィジェットを描画するためのウィジェット。それを返している。Scaffoldのプロパティは多く実装されている。(body・appBar・floatingActionButton…など)

参考サイト・記事・単語まとめ

クラス – プログラミング用語解説|Unity高校&ゲームスクールのG学院

インスタンス – プログラミング用語解説|Unity高校&ゲームスクールのG学院

継承 – プログラミング用語解説|Unity高校&ゲームスクールのG学院

Dart のコンストラクタを理解する - エキサイト TechBlog.

【Flutter】build() でやってはいけない 3 つのこと - Qiita

【Flutter初心者向け】MaterialAppとScaffoldとは??サンプルアプリをもとに分かりやすく解説 - 文系プログラマー「いお」が語る

Dart のコンストラクタの基本 (できることまとめ)

【5分でわかる】Widgetとは? | Flutter入門|KIMURA LOG

FlutterのBuildContextとは何か - Qiita

【Flutter】UI構築のスタート地点Scaffoldを徹底解説【サンプルコードあり】

コンストラクタ:Classのインスタンスを作成したとき(newしたとき)に呼び出される初期化処理

パラメーター:ソフトウエアを実行したりプログラム内で関数を呼び出したりする時に、一連の処理を指定するために与える情報(「処理結果に影響を与える外部から投入される変動要素」のこと プログラムの世界では「引数(仮引数)」と呼ばれる場合もあります。 引用https://wa3.i-3-i.info/word1443.html)

superクラス:親クラスの別名

ジェネリクス型引数:主にクラスや関数を汎用的に設計するための仕組み ⇒ジェネリクスを使うことで、クラスや関数が様々な型に対して動作する柔軟性を持つことができます。例えば、StateクラスはState<MyHomePage>と指定することで、MyHomePageクラスだけでなく、他のウィジェットの状態も管理することができる。

6
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
4