Signalsの状態管理を使ってみた

# はじめに
この記事ではFlutterの状態管理ライブラリである[signals](https://pub.dev/packages/signals)を簡単に利用しています。
- カウンター+テーマモード変更（signalsのexample)
- todoアプリ
の２つを実装してみます.


記事の内容でアドバイス等ありましたら、コメントや編集リクエストで*優しく*教えてくださると幸いです

# 実装
## インストール
```
flutter pub add signals
```
のコマンドで導入できます。

```yml:pubspec.yaml
dependencies:
  flutter:
    sdk: flutter

  cupertino_icons: ^1.0.2
  signals: ^1.4.1
```
追加されていますね。

## 公式ドキュメントの例の実装
[example](https://pub.dev/packages/signals/example)のコードをそのまま実装します.

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2230776/65a86e9a-1c8f-1a50-6712-a3eeb978f552.png)


例ではデフォルトで作成されるカウンターアプリと同じものに、テーマモードの切り替えの機能が実装されています。

### テーマモードの切り替え部分
```dart:main.dart
final brightness = signal(Brightness.light);
```
この部分ではsignalの初期化を行なっています。
`signal()`の引数が初期値となります。

```dart:main.dart
final themeMode = computed(() {
  if (brightness() == Brightness.dark) {
    return ThemeMode.dark;
  } else {
    return ThemeMode.light;
  }
});
```
`computed()`は既存のシグナルを複数組み合わせて、新しいシグナルを生成することができます。
今回は`brightness`を基にしてthemeModeを生成るようです。

ここで作成したthemeModeは`MyApp`内の
`themeMode: themeMode.watch(context),`
で使われており、.watch()でsignalの変更をUIに反映しています。

```dart:main.dart
Watch((_) {
            final isDark = brightness() == Brightness.dark;
            return IconButton(
              onPressed: () {
                brightness.value = isDark ? Brightness.light : Brightness.dark;
              },
              icon: Icon(isDark ? Icons.light_mode : Icons.dark_mode),
            );
          }),
```
ここではbrightnessシグナルの値によってアイコンボタンの切り替えを行います。また、アイコンボタンが押下されるとbrightnessを更新しています。

### counter部分
カウンターもテーマと同じように作成されています.
```dart:main.dart
  final counter = signal(0);

  void _incrementCounter() {
    counter.value++;
  }
```
counterというシグナルとそれを1インクリメントするメソッドを作成してあります。
値の表示は以下のようにWatch()で行われます。
```dart:main.dart
Watch((context) {
              return Text(
                '$counter',
                style: Theme.of(context).textTheme.headlineMedium!,
              );
            }),
```

## todoアプリ
画像のようなシンプルなtodoアプリを実装してみました。

![Simulator Screenshot - iPhone 15 Pro - 2023-12-03 at 05.28.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2230776/910eec9b-e762-3d8d-dfba-59191993f908.png)

使っているのはexampleにもあったものばかりなので詳細は省きます。

<details><summary>コード</summary>

```dart:main.dart
import 'package:flutter/material.dart';
import 'package:signals/signals_flutter.dart';

// todoリストのシグナルを作成する
final todoItems = signal<List<String>>([]);

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

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

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      title: 'totoアプリ',
      home: ToDoPage(),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text(
          'タスクリスト',
        ),
        backgroundColor: Colors.purple.shade100,
      ),
      // Watch()関数でtodoItemsに基づくUIを管理する
      body: Watch((_) => ListView.builder(
            itemCount: todoItems.value.length,
            itemBuilder: (context, index) {
              return ListTile(
                title: Text(todoItems.value[index]),
                trailing: IconButton(
                  icon: const Icon(
                    Icons.delete,
                    color: Colors.red,
                  ),
                  onPressed: () {
                    var updatedList = List<String>.from(todoItems.value);
                    updatedList.removeAt(index);
                    todoItems.value = updatedList;
                  },
                ),
              );
            },
          )),
      floatingActionButton: FloatingActionButton(
        onPressed: () => _showAddTaskDialog(context),
        backgroundColor: Colors.purple.shade100,
        child: const Icon(
          Icons.add,
        ),
      ),
    );
  }

  void _showAddTaskDialog(BuildContext context) {
    showDialog<String>(
      context: context,
      builder: (BuildContext context) {
        String newTask = '';
        return AlertDialog(
          title: const Text('タスクを追加する'),
          content: TextField(
            onChanged: (value) => newTask = value,
            decoration: const InputDecoration(
              hintText: 'タスクを入力する',
            ),
          ),
          actions: <Widget>[
            TextButton(
              onPressed: () {
                if (newTask.isNotEmpty) {
                  var updatedList = List<String>.from(todoItems.value);
                  updatedList.add(newTask);
                  todoItems.value = updatedList;
                }
                Navigator.pop(context);
              },
              child: const Text('追加'),
            ),
          ],
        );
      },
    );
  }
}

```

</details>

# 最後に
今回は簡単なアプリしか作成しませんでしたが、非常に使いやすい状態管理のパッケージな気がしました。
もう少し規模大きめなアプリでも使ってみたいですね👀

# 参考
https://pub.dev/packages/signals
https://pub.dev/documentation/signals/latest/