【生成AI】FlutterでToDoアプリを作る

### プロンプト

```
FlutterでTodoアプリを実装してください
```

### 回答
Flutterを使用してToDoアプリを実装するための基本的なステップを示します。以下の手順に従ってください。

1. Flutterプロジェクトのセットアップ:
   ```
   flutter create todo_app
   ```

2. プロジェクトに必要な依存関係を追加します。`pubspec.yaml`ファイルを編集し、`flutter/material.dart`と`provider`パッケージを追加します。
   ```yaml
   dependencies:
     flutter:
       sdk: flutter
     provider: ^5.0.0
   ```

3. ToDoアイテムを表すモデルを作成します。`lib/models/todo.dart`を作成し、以下のコードを追加します。
   ```dart
   class Todo {
     final String title;
     final bool isDone;

     Todo({required this.title, this.isDone = false});
   }
   ```

4. ToDoリストの状態を管理するプロバイダーを作成します。`lib/providers/todo_provider.dart`を作成し、以下のコードを追加します。
   ```dart
   import 'package:flutter/material.dart';
   import 'package:todo_app/models/todo.dart';

   class TodoProvider extends ChangeNotifier {
     List<Todo> _todos = [];

     List<Todo> get todos => _todos;

     void addTodo(String title) {
       _todos.add(Todo(title: title));
       notifyListeners();
     }

     void toggleTodoStatus(int index) {
       _todos[index].isDone = !_todos[index].isDone;
       notifyListeners();
     }
   }
   ```

5. メインのToDoアプリケーションを作成します。`lib/main.dart`を編集し、以下のコードを追加します。
   ```dart
   import 'package:flutter/material.dart';
   import 'package:provider/provider.dart';
   import 'package:todo_app/providers/todo_provider.dart';

   void main() {
     runApp(TodoApp());
   }

   class TodoApp extends StatelessWidget {
     @override
     Widget build(BuildContext context) {
       return ChangeNotifierProvider(
         create: (context) => TodoProvider(),
         child: MaterialApp(
           title: 'Todo App',
           home: TodoScreen(),
         ),
       );
     }
   }

   class TodoScreen extends StatelessWidget {
     @override
     Widget build(BuildContext context) {
       final provider = Provider.of<TodoProvider>(context);

       return Scaffold(
         appBar: AppBar(
           title: Text('Todo List'),
         ),
         body: ListView.builder(
           itemCount: provider.todos.length,
           itemBuilder: (context, index) {
             final todo = provider.todos[index];
             return ListTile(
               title: Text(todo.title),
               leading: Checkbox(
                 value: todo.isDone,
                 onChanged: (_) {
                   provider.toggleTodoStatus(index);
                 },
               ),
             );
           },
         ),
         floatingActionButton: FloatingActionButton(
           onPressed: () {
             // ダイアログを表示して新しいToDoを追加
             showDialog(
               context: context,
               builder: (context) {
                 final TextEditingController controller = TextEditingController();
                 return AlertDialog(
                   title: Text('Add Todo'),
                   content: TextField(
                     controller: controller,
                     decoration: InputDecoration(hintText: 'Enter todo title'),
                   ),
                   actions: [
                     TextButton(
                       onPressed: () {
                         Navigator.pop(context);
                       },
                       child: Text('Cancel'),
                     ),
                     TextButton(
                       onPressed: () {
                         provider.addTodo(controller.text);
                         Navigator.pop(context);
                       },
                       child: Text('Add'),
                     ),
                   ],
                 );
               },
             );
           },
           child: Icon(Icons.add),
         ),
       );
     }
   }
   ```

これで、ToDoアプリが実装されました。アプリを実行して、ToDoリストにアイテムを追加し、完了/未完了のステータスを切り替えることができます。

---
### 実際にやってみる
Flutter\todo_app\pubspec.yaml
```yaml
# Dependencies specify other packages that your package needs in order to work.
# To automatically upgrade your package dependencies to the latest versions
# consider running `flutter pub upgrade --major-versions`. Alternatively,
# dependencies can be manually updated by changing the version numbers below to
# the latest version available on pub.dev. To see which dependencies have newer
# versions available, run `flutter pub outdated`.
dependencies:
  flutter:
    sdk: flutter
  provider: ^5.0.0
```

`lib`以下を↓のように編集

```
lib
│  main.dart
│
├─models
│      todo.dart
│
└─providers
        todo_provider.dart
```
何度かエラーが出たのでエラーログをAIに読ませてコードを何度か再生成
Flutter\todo_app\lib\models\todo.dart
```dart
class Todo {
  String title;
  bool isDone;

  Todo({
    required this.title,
    this.isDone = false,
  });

  void toggleDone() {
    isDone = !isDone;
  }
}
```

Flutter\todo_app\lib\providers\todo_provider.dart
```dart
import 'package:flutter/material.dart';
import 'package:todo_app/models/todo.dart';

class TodoProvider extends ChangeNotifier {
  List<Todo> _todos = [];

  List<Todo> get todos => _todos;

  void addTodo(String title) {
    _todos.add(Todo(title: title));
    notifyListeners();
  }

  void toggleTodoStatus(int index) {
    _todos[index].toggleDone(); // Todoモデルのメソッドを呼び出し、isDoneをトグルします
    notifyListeners();
  }
}
```

Flutter\todo_app\lib\main.dart
```dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:todo_app/providers/todo_provider.dart'; // Import your TodoProvider

void main() {
  runApp(TodoApp());
}

class TodoApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (context) => TodoProvider(), // Instantiate TodoProvider here
      child: MaterialApp(
        title: 'Todo App',
        home: TodoScreen(),
      ),
    );
  }
}

class TodoScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // Obtain an instance of TodoProvider using Provider.of
    final provider = Provider.of<TodoProvider>(context);

    return Scaffold(
      appBar: AppBar(
        title: Text('Todo List'),
      ),
      body: ListView.builder(
        itemCount: provider.todos.length,
        itemBuilder: (context, index) {
          final todo = provider.todos[index];
          return ListTile(
            title: Text(todo.title),
            leading: Checkbox(
              value: todo.isDone,
              onChanged: (_) {
                provider.toggleTodoStatus(index);
              },
            ),
          );
        },
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          // Show a dialog to add a new todo
          showDialog(
            context: context,
            builder: (context) {
              final TextEditingController controller = TextEditingController();
              return AlertDialog(
                title: Text('Add Todo'),
                content: TextField(
                  controller: controller,
                  decoration: InputDecoration(hintText: 'Enter todo title'),
                ),
                actions: [
                  TextButton(
                    onPressed: () {
                      Navigator.pop(context);
                    },
                    child: Text('Cancel'),
                  ),
                  TextButton(
                    onPressed: () {
                      provider.addTodo(controller.text);
                      Navigator.pop(context);
                    },
                    child: Text('Add'),
                  ),
                ],
              );
            },
          );
        },
        child: Icon(Icons.add),
      ),
    );
  }
}

```

起動
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1710277/8a497c50-568f-6393-0b68-c8bb0789e7a7.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1710277/9519cca7-5e5a-936f-0a25-573d2cae206a.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1710277/12e4f82d-811f-6f91-1b71-a212c5586841.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1710277/8782561a-fecd-4cfd-d02d-7b3d8b4bc93c.png)