🔰

Flutter HooksでTodoアプリを作ってみた

2023/12/15に公開1

概要

急遽インターンでFlutterを書かないといけないことになったのでFlutter入門がてら
簡単なTodoアプリを作ってみました。冗長な部分もあるかと思いますが、これからFlutter入門される方のお役に立てたら幸いです。

計画

  • HooksWidgetで一画面で作成
  • FlutterHooksのuseStateを使ってTodoを管理する配列を用意する
  • TODO入力用のTextFieldを用意する
  • ADDボタンでTODOを作成
  • ゴミ箱ボタンで削除

実際に書いたコード

main.dart

import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';

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

class MyApp extends StatelessWidget {
  Widget build(BuildContext context) {
    return MaterialApp(
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends HookWidget {

  final FocusNode _focusNode = FocusNode(); // TextFieldのフォーカスを管理するための変数

  
  Widget build(BuildContext context) {

    final TextEditingController _controller = TextEditingController(); // TextFieldの入力を管理するための変数
    final todoArray = useState([]); // TODOリストを管理する配列を作成

    return Scaffold(
        appBar: AppBar(title: const Text("TODO APP")),
        body: Column(
          children: [
            TextField( // 入力するためのTextField
              focusNode: _focusNode,
              controller: _controller,
            ),
            TextButton( //TODOを追加するためのボタン
                onPressed: () {
                  _focusNode.unfocus(); // ボタンが押された時にTextFieldのフォーカスを外す
                  todoArray.value = [...todoArray.value, _controller.text]; // 入力されたTODOを配列の後ろに追加する
                  _controller.clear(); // 入力されたテキストをクリアする
                },
                child: Text("ADD")),
            Expanded( //ここにTODOリストを表示する
                child: ListView.builder( // ListView.builderを使ってtodoArrayの配列からTODOのリストを作成する
              itemCount: todoArray.value.length,
              itemBuilder: (context, index) {
                return ListTile( //ListTileを使って1つ1つのTODOを表示する
                  title: Text(todoArray.value[index]),
                  trailing: IconButton( // 削除ボタン
                    icon: Icon(Icons.delete),
                    onPressed: () {
                      final newList = [...todoArray.value]; // 配列の中身をコピー
                      newList.removeAt(index); // 選択したTODOを削除
                      todoArray.value = newList; //削除された新しい配列でtodoArrayを更新する
                    },
                  ),
                );
              },
            ))
          ],
        ));
  }
}

実行してみる

TextFieldに文字を入力してみます


文字が入力されました。

ADDボタンを押してみます


TASK1が追加されてTextFieldのフォーカスも解除されているみたいです。

いくつかTODOを増やしてみます


TASK2とTASK3を追加できました。

ゴミ箱ボタンで削除できるかやってみます


TASK1が消えました。

期待通り動いてくれたみたいです。

振り返り

ReactのuseStateとは少し違う雰囲気で戸惑いましたが、とりあえずTODOアプリとして動くものができたので一安心です。TextFieldの監視のcontrollerの部分の理解がイマイチなので、もう少ししらべてみようと思います。

Discussion

JboyHashimotoJboyHashimoto

Tetsuさんへのコメント

おお!
...スプレッド演算子を使った例と、ボタンを押すとfocusNodeで入力フォームの選択が解除されて、keyboardが閉じる仕組み良いですね!

もしflutter_hooksで、TextEditingControllerを使うなら、useTextEditingControllerを使うのが、普通なのでこちらが良いですよ。
https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useTextEditingController-constant.html

それと、constがついてない箇所があったのと、MyAppクラスに@overrideが書かれていなかったので、私少しコード修正してみました。問題なく動いています。
よろしければ参考にしてみてください。

なぜ、constつけるのか?
https://pryogram.com/flutter/const-widget-mean/

main.dart
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';

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

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

  
  Widget build(BuildContext context) {
    return MaterialApp(
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends HookWidget {
  MyHomePage({super.key});
  // TextFieldのフォーカスを管理するための変数
  final FocusNode _focusNode = FocusNode();

  
  Widget build(BuildContext context) {
    // useTextEditingControllerを使ってTextFieldの入力を管理する。自動でdisposeされる仕組みがある
    final TextEditingController controller =
        useTextEditingController(); // TextFieldの入力を管理するための変数
    final todoArray = useState([]); // TODOリストを管理する配列を作成

    return Scaffold(
        appBar: AppBar(title: const Text("TODO APP")),
        body: Column(
          children: [
            const SizedBox(height: 50),
            SizedBox(
              width: 300,
              height: 50,
              child: TextField(
                // 入力するためのTextField
                focusNode: _focusNode,
                controller: controller,
                decoration: const InputDecoration(
                    hintText: "TODOを入力してください", border: OutlineInputBorder()),
              ),
            ),
            TextButton(
                //TODOを追加するためのボタン
                onPressed: () {
                  _focusNode.unfocus(); // ボタンが押された時にTextFieldのフォーカスを外す
                  todoArray.value = [
                    ...todoArray.value,
                    controller.text
                  ]; // 入力されたTODOを配列の後ろに追加する
                  controller.clear(); // 入力されたテキストをクリアする
                },
                child: const Text("ADD")),
            Expanded(
                //ここにTODOリストを表示する
                child: ListView.builder(
              // ListView.builderを使ってtodoArrayの配列からTODOのリストを作成する
              itemCount: todoArray.value.length,
              itemBuilder: (context, index) {
                return ListTile(
                  //ListTileを使って1つ1つのTODOを表示する
                  title: Text(todoArray.value[index]),
                  trailing: IconButton(
                    // 削除ボタン
                    icon: const Icon(Icons.delete),
                    onPressed: () {
                      final newList = [...todoArray.value]; // 配列の中身をコピー
                      newList.removeAt(index); // 選択したTODOを削除
                      todoArray.value = newList; //削除された新しい配列でtodoArrayを更新する
                    },
                  ),
                );
              },
            ))
          ],
        ));
  }
}