Github Copilotとflutterを使ってスニペットメモアプリを爆速で開発してみた。

面倒くさいところは全部Github Copilotに任せて、雑な設計でアプリを作ってみた。
(初投稿です)
## AIとflutterの相性は良い！
### 設計思想
[flutter公式doc](https://docs.flutter.dev/get-started/fundamentals/widgets)にもある通り、ウィジェットがツリー構造になっている。

つまり、

- 関心の分離やモジュール化、カプセル化がしやすい
- コードの全体を把握する必要が無い

だから、

- AIにコードを作らせてもどこで仕様の概略を把握しやすい
- コンテキストウィンドウの上限を超えにくい

という利点がある。

アプリのuiと処理を事前に設計しておけば、すぐに実装に取りかかれるわけだ。

とりあえず実際にコーディングする様子を見た方が理解しやすいと思うので、さっそく紹介していく。

## デモンストレーション

### Github Copilotの使用方法

`ctrl` + `alt` + `i`でGithub Copilotのチャットを起動できるので、アプリの骨格を作る時や複雑な仕様を実装する際に立ち上げておくと便利。

細かい仕様やちょっとした修正を加えたい際は、あらかじめ範囲選択で目星をつけてから`ctrl`+`i`で何をコーディングして欲しいかを打ち込むと、自動で書き換えてくれる。


### コーディング

実際にコーディングしてみる。ここではExpansionTileを使った例を紹介する。

まずはAIに骨格を作らせる。
(Github Copilot freeが生成したコードですが、簡単なスニペットなのでそのまま載せています。著作権上の懸念があるので指摘されたら消します。)
<br>
<details><summary>Github Copilot Chatのログ</summary>

プロンプト:
> ExpansionTileをつかってアイテムを表示させられるようなアプリの骨組みを書いて

回答:
>以下は、`ExpansionTile`を使ってアイテムを表示させるアプリの骨組みです。
>
>```main.dart
>import 'package:flutter/material.dart';
>
>void main() {
>  runApp(const MainApp());
>}
>
>class MainApp extends StatelessWidget {
>  const MainApp({super.key});
>
>  @override
>  Widget build(BuildContext context) {
>    return MaterialApp(
>      home: Scaffold(
>        appBar: AppBar(
>          title: const Text('ExpansionTile Example'),
>        ),
>        body: const ExpansionTileList(),
>      ),
>    );
>  }
>}
>
>class ExpansionTileList extends StatelessWidget {
>  const ExpansionTileList({super.key});
>
>  @override
>  Widget build(BuildContext context) {
>    return ListView(
>      children: const <Widget>[
>        ExpansionTile(
>          title: Text('Item 1'),
>          children: <Widget>[
>            ListTile(title: Text('Sub Item 1')),
>            ListTile(title: Text('Sub Item 2')),
>          ],
>        ),
>        ExpansionTile(
>          title: Text('Item 2'),
>          children: <Widget>[
>            ListTile(title: Text('Sub Item 1')),
>            ListTile(title: Text('Sub Item 2')),
>          ],
>        ),
>        ExpansionTile(
>          title: Text('Item 3'),
>          children: <Widget>[
>            ListTile(title: Text('Sub Item 1')),
>            ListTile(title: Text('Sub Item 2')),
>          ],
>        ),
>      ],
>    );
>  }
>}
>```
>
>このコードでは、`ExpansionTile`を使って3つのアイテムを表示し、それぞれのアイテムには2つのサブアイテムがあります。
</details>
<br>
そしてアプリを起動すると、画像のようなuiが表示される。
<br>

![Listapp.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4000341/5208ae97-db99-952b-3b9d-a9584d42c07c.png)



#### モジュール化


プロジェクトの規模が大きくなってくると一つのソースコードで処理を記述するのが大変になってくる。
そこで、会話のセッションを変えてAIにモジュール化をやらせてみる。
ここではExpantionTileのアイテムが押された際に実行される処理を別のソースコードに書かせてみる。

<br>
<details><summary>Github Copilot Chatのログ</summary>

プロンプト:
> アイテムが押されるとHellow Worldというtextが出力される画面に遷移するようなモジュールをmain.dartとは別に書いて

回答:
>まず、`main.dart`とは別に新しいDartファイルを作成し、アイテムが押されたときに「Hello World」という選択可能なテキストを表示する画面を作成します。
>
>以下の手順に従ってください：
>
>1. `hello_world_screen.dart`という新しいファイルを作成します。
>2. `HelloWorldScreen`という新しいウィジェットを定義します。
>3. `main.dart`ファイルを更新して、HelloWorldScreenへのナビゲーションを追加します。
>
>#### Step 1: Create `hello_world_screen.dart`
>```hello_world_screen.dart
> import 'package:flutter/material.dart';
>
>class HelloWorldScreen extends StatelessWidget {
>  @override
>  Widget build(BuildContext context) {
>    return Scaffold(
>      appBar: AppBar(
>        title: Text('Hello World Screen'),
>      ),
>      body: Center(
>        child: Text(
>          'Hello World',
>          style: TextStyle(fontSize: 24),
>        ),
>      ),
>    );
>  }
>}
>```
>#### Step 2: Update `main.dart`
>```main.dart
>import 'package:flutter/material.dart';
>import 'hello_world_screen.dart';
>
>void main() {
>  runApp(MyApp());
>}
>
>class MyApp extends StatelessWidget {
>  @override
>  Widget build(BuildContext context) {
>    return MaterialApp(
>      home: Scaffold(
>        appBar: AppBar(
>          title: Text('Demonstration App'),
>        ),
>        body: MyHomePage(),
>      ),
>    );
>  }
>}
>
>class MyHomePage extends StatelessWidget {
>  @override
>  Widget build(BuildContext context) {
>    return ListView(
>      children: <Widget>[
>        ExpansionTile(
>          title: Text('Item 1'),
>          children: <Widget>[
>            ListTile(
>              title: Text('Sub Item 1'),
>              onTap: () {
>                Navigator.push(
>                  context,
>                  MaterialPageRoute(builder: (context) => HelloWorldScreen()),
>                );
>              },
>            ),
>            ListTile(title: Text('Sub Item 2')),
>          ],
>        ),
>        ExpansionTile(
>          title: Text('Item 2'),
>          children: <Widget>[
>            ListTile(
>              title: Text('Sub Item 1'),
>              onTap: () {
>                Navigator.push(
>                  context,
>                  MaterialPageRoute(builder: (context) => HelloWorldScreen()),
>                );
>              },
>            ),
>            ListTile(title: Text('Sub Item 2')),
>          ],
>        ),
>        ExpansionTile(
>          title: Text('Item 3'),
>          children: <Widget>[
>            ListTile(
>              title: Text('Sub Item 1'),
>              onTap: () {
>                Navigator.push(
>                  context,
>                  MaterialPageRoute(builder: (context) => HelloWorldScreen()),
>                );
>              },
>            ),
>            ListTile(title: Text('Sub Item 2')),
>          ],
>        ),
>      ],
>    );
>  }
>}
>```
>
>これで、各リストアイテムをタップすると「Hello World」という選択可能なテキストを表示する画面に遷移するようになります。

</details>
<br>
ディレクトリ構造は以下の通り。

    demonstration_app/
    ├── lib/
    │   ├── main.dart
    │   └── hello_world_screen.dart
    ︙
uiはgif画像のようになる。

![interact.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4000341/d4d52a22-de52-731f-c568-cf897ecdfdfb.gif)

こういった感じで
1. 設計を考える
2. AIにコードを書かせる
3. セッションを変えてまたAIにコードを書かせる

という一連の流れを繰り返せば爆速でアプリが作れるというわけだ。
<br>
また、プロジェクトが肥大化してきたら、`ctrl` + `i`で起動できるインラインチャットを使うと良い。
例えば、Hello Worldじゃなくて、flutterのロゴマークを表示させたい場合、次の通りに指示を出すと自動でコードを修正してくれる。
<br>
<details><summary>コード修正の流れ</summary>

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4000341/ee74821e-e4a9-9599-6624-35579451556f.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4000341/fea59a07-e977-b50b-ad0e-83ec9d63dc4f.png)

</details>
<br>
最終的にuiは画像の通りになる。

![Screenshot_20250202_180939(1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4000341/24372b5a-b78c-5208-80cd-2c258bd768fd.png)

このように、ある処理を実装する際、どのぐらい細分化できるかを考えてからAIにコーディングさせるとスムーズに作業が進む。

### スニペットメモアプリの紹介

本当はgithubのプロジェクトをそのまま載せたいところだが、LLMを利用したコード作成において懸念される著作権問題を回避したいので、ここで紹介するだけに留めておく。
著作権問題に関しては
https://levtech.jp/media/article/column/detail_340/
の記事がわかりやすいかも。検索すれば色々出てくるはず。

uiは画像のとおりである。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4000341/4fb08631-7cf3-4917-b93b-e316ef99665a.png)

データベースを1から作るのは面倒くさいのでローカルのディレクトリ構造をそのまま表示させることにした。サーバーにコードを保存させるなら良い感じにデータベースを実装するべきではある。
<br>

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4000341/25b258bf-e13f-4fa9-96cf-ca61b6aa0857.png)

コードをハイライトできるビューはテキストを選択することが不可能なので、選択可能なテキストを表示するためのボタンを右上の左の方に配置した。一方右の方は、押すと一瞬で全文コピーできるようにしてある。

残念ながらハイライト機能はrustにしか対応していない。完全に筆者の好みである。

<br>

## 結論
Github Copilotを有効活用すると爆速でアプリが作れることが判明した。
上記のアプリを完成させるのに要した時間は新規にプロジェクトを作ってから大体5時間ぐらいだ。
歴史がある程度長いプログラミング言語やフレームワークなら、設計さえ考えておけば、AIを使って快適にコーディングができるかも知れない。

皆さんも学習や趣味のプロジェクトなどで使ってみてはいかがだろうか。