Chapter 05

Component(コンポーネント)化

にの
にの
2022.12.13に更新

ディレクトリ構成

コンポーネント化を勉強する前にFlutterにおいてのディレクトリ構成について少し触れておきましょう。

まずは「lib」ディレクトリ直下に
「components」、「screens」ディレクトリを作成し、さらに「screens」ディレクトリ直下に「kad1_01」、「kad1_02」ディレクトリを作成してください。

screens

各画面(ホーム画面やログイン画面など)の基盤となるファイルを管理します。
例えばホーム画面をの場合は「home_screen.dart」、ログイン画面の場合は「login_screen.dart」をこのディレクトリに作成したりします。

components

このディレクトリは「screens」ディレクトリで使用するUIの部品を管理します。
まだ説明してないから意味わからんくてOK☆彡

このディレクトリ構成はあくまで一例です。
他にもいろんな種類の構成があるので、是非調べてみてください。

コンポーネント化

開発を進めていくと、コードもの量も次第に多くなっていき、どこがどのUIの部分なのかわからなくなってしまいます。
そこで使えるのが「コンポーネント化」です。
簡単に説明すると「一部のコードをまとめて部品化する」ことです。

コンポーネント化のメリットは

  • コードの記述がわかりやすくなり、可読性が上がる
  • 同じUIを共通化して使い回すことができるようになる
    この2つです。

実際にコンポーネント化してみた方がわかりやすいのでしてみましょう。
前回の課題2のファイルをみてください。

import 'package:flutter/material.dart';

// メイン関数(最初に呼び出される関数, 1つのプログラムに1つ)
void main() {
  runApp(const MyApp());
}

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

  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      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> {
  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("プログラム研究部"),
      ),
      body: Center(
        child: Row(
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
          children: [
            Container(
              width: 80,
              height: 80,
              decoration: BoxDecoration(
                color: Colors.blue,
                shape: BoxShape.circle,
              ),
              child: Center(
                child: Text(
                  "まる",
                  style: TextStyle(
                    fontWeight: FontWeight.bold,
                  ),
                ),
              ),
            ),
            Text(
              "+",
              style: TextStyle(
                fontSize: 32,
              ),
            ),
            Container(
              width: 80,
              height: 80,
              decoration: BoxDecoration(
                color: Colors.red,
              ),
              child: Center(
                child: Text(
                  "しかく",
                  style: TextStyle(
                    fontWeight: FontWeight.bold,
                  ),
                ),
              ),
            ),
            Text(
              "=",
              style: TextStyle(
                fontSize: 32,
              ),
            ),
            Container(
              width: 80,
              height: 80,
              decoration: BoxDecoration(
                color: Colors.purple,
                borderRadius: BorderRadius.circular(15),
              ),
              child: Center(
                child: Text(
                  "?",
                  style: TextStyle(
                    fontWeight: FontWeight.bold,
                  ),
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

例えば、「+」が表示されているTextウィジェットがあります。

Text(
  "+",
  style: TextStyle(
    fontSize: 32,
  ),
);

このTextウィジェットを「main.dart」ファイルの一番下の行に新しく「ArithmeticText」クラスを作り、その中でreturnさせましょう。

mian.dart
// ↑MyHomePageクラス
    );
  }
}

+class ArithmeticText extends StatelessWidget {
+  const ArithmeticText({
+    Key? key,
+  }) : super(key: key);

+  
+  Widget build(BuildContext context) {
+    return Text(
+      "+",
+      style: TextStyle(
+        fontSize: 32,
+      ),
+    );
+  }
+}

新しいクラスを追加できたら、「MyHomePage」クラスに「ArithmeticText」クラスをWidgetとして追加してみましょう。

main.dart
    Container(
      width: 80,
      height: 80,
      decoration: BoxDecoration(
        color: Colors.blue,
        shape: BoxShape.circle,
      ),
      child: Center(
	child: Text(
	  "まる",
	  style: TextStyle(
	    fontWeight: FontWeight.bold,
	  ),
	),
      ),
    ),
-   Text(
-     "+",
-     style: TextStyle(
-	fontSize: 32,
-     ),
-   ),
+   ArithmeticText(),
    Container(
      width: 80,
      height: 80,
      decoration: BoxDecoration(
	color: Colors.red,
      ),
      child: Center(
	child: Text(
	  "しかく",
	  style: TextStyle(
	    fontWeight: FontWeight.bold,
	  ),
	),
      ),
    ),
    Text(
      "=",
      style: TextStyle(
	fontSize: 32,
      ),
    ),
    Container(
      width: 80,
      height: 80,
      decoration: BoxDecoration(
	color: Colors.purple,
	borderRadius: BorderRadius.circular(15),
      ),
      child: Center(
	child: Text(
	  "?",
	  style: TextStyle(
	    fontWeight: FontWeight.bold,
	  ),
	),
      ),
    ),

これで実行してみると、課題2と何も変わらずUIが表示されたと思います。
このようにコンポーネント化することでUIの内容は変わらないのにコードが短くなって読みやすくなります。

さらに、似たような、「=」を表示するTextウィジェットがありますよね。

Text(
  "=",
  style: TextStyle(
    fontSize: 32,
  ),
),

「+」を表示するか「=」を表示するかの違いなだけなので、同じクラスでまとめれそうです。
さてどうやってまとめるのでしょうか。考えてみてください引数です。

クラスに引数を持たせて「+」や「=」を指定してあげれるようにしてあげれば解決です。
クラスに引数を持たせる方法は

class ArithmeticText extends StatelessWidget {
  const ArithmeticText({
    Key? key,
    // 引数を要求するように設定しクラス内で宣言してあるoperationに格納
+   required this.operation,
  }) : super(key: key);
  
+ final String operation;

  
  Widget build(BuildContext context) {
    return Text(
-     "+",
+     operation,
      style: TextStyle(
        fontSize: 32,
      ),
    );
  }
}

変更を保存するとWidgetとして追加した「ArithmeticText()」にエラーが出たと思います。
引数を指定していないからです。修正しましょう。

    Container(
      width: 80,
      height: 80,
      decoration: BoxDecoration(
        color: Colors.blue,
        shape: BoxShape.circle,
      ),
      child: Center(
	child: Text(
	  "まる",
	  style: TextStyle(
	    fontWeight: FontWeight.bold,
	  ),
	),
      ),
    ),
-   ArithmeticText(),
+   ArithmeticText(operation: '+'),
    Container(
      width: 80,
      height: 80,
      decoration: BoxDecoration(
	color: Colors.red,
      ),
      child: Center(
	child: Text(
	  "しかく",
	  style: TextStyle(
	    fontWeight: FontWeight.bold,
	  ),
	),
      ),
    ),
-   Text(
-     "=",
-     style: TextStyle(
-	fontSize: 32,
-     ),
-   ),
+   ArithmeticText(operation: '='),
    Container(
      width: 80,
      height: 80,
      decoration: BoxDecoration(
	color: Colors.purple,
	borderRadius: BorderRadius.circular(15),
      ),
      child: Center(
	child: Text(
	  "?",
	  style: TextStyle(
	    fontWeight: FontWeight.bold,
	  ),
	),
      ),
    ),

できましたでしょうか。
これで同じUIを共通化して使い回すことができるようになりました。
これがコンポーネント化です。めちゃくちゃ便利やろって話。
でもせっかくコードが短くなったのに同じファイルにいろんなクラスを置いていたらまたごちゃごちゃしてくるので別ファイルに切り離してあげましょう。
「components」ディレクトリに「arithmetic_text.dart」ファイルを作成してください。

そしてファイルの中に「ArithmeticText」クラスを移動させましょう。

import 'package:flutter/material.dart';

class ArithmeticText extends StatelessWidget {
  const ArithmeticText({
    Key? key,
    required this.operation,
  }) : super(key: key);

  final String operation;

  
  Widget build(BuildContext context) {
    return Text(
      operation,
      style: TextStyle(
        fontSize: 32,
      ),
    );
  }
}

そしてまた「main.dart」ファイルでエラーが発生したと思います。
「ArithmeticText」クラスが「main.dart」ファイルからなくなったからFlutterが「ArithmeticTextどこいったん?」って言って泣いてます。
そんな時はちゃんと言葉で伝えてあげましょう。
「これ、俺の住所だから。いつでもおいでね。」と。
「遠くなるのは距離だけだよ。」と。

main.dart
 import 'package:flutter/material.dart';
 // 「これ俺の住所だから。いつでもおいでね。」
+import 'package:flutter_study/components/arithmetic_text.dart';

 // メイン関数(最初に呼び出される関数, 1つのプログラムに1つ)
 void main() {
   runApp(const MyApp());
 }

チャラい説明にはなりましたが、説明は以上で終わりです。
では次の実習課題2で一旦区切りになりますので、頑張ってみてください!!!