Dart3.0で対応したRecord型を使用してFlutterにCustomHook導入しよう

# はじめに

Flutter3.10が正式リリースとなりました。
https://docs.flutter.dev/release/release-notes/release-notes-3.10.0

Flutter3.10の中で、Dart3が実行されるようになりました！！

今回Dart3で新しく追加されたRecord型を使用して、FlutterでCustomHookの導入をしていきます。

## flutter でなんでCustom Hookを書くの?

Flutterのエンジニアの方はCustom Hookを聞いたことありますでしょうか？
flutter hookは聞いたことがあり、StatefullWidgetをStatelessWidgetに書き直して
簡略化することができるといったイメージしかないのではないでしょうか？

本当はhookを使いこなせば、ChangeNotifierやStateNotifierを置き換えることができ
UIのロジック部分をhookに全て任せることができます。
※現在はChangeNotifierやStateNotifierなどを使用されている方が多い印象です。

hookをよく使うReactなどでは、UIで使用するロジック部分はCustom Hookで記載することが一般的です。

Reactに多少なりとも影響を受けているFlutterなので、今後Custom Hookで記載することが一般的になるかもしれません。
書き方など慣れておいて損はないと思います。ということでflutterでCustom Hookを作っていきましょう。

## なんでRecord型を使用してCustom Hookを書くの？

今までFlutterでCustom Hookを書く場合は以下のような書き方をしていました。

```dart
import 'package:flutter_hooks/flutter_hooks.dart';

UseCounter useCounter() {
  final count = useState(0);
  void increment() {
    count.value++;
  }

  return UseCounter(
    increment: increment,
    count: count.value,
  );
}

class UseCounter {
  final void Function() increment;
  final int count;

  UseCounter({
    required this.increment,
    required this.count,
  });
}
```

こちら、Reactの実装者なら違和感があると思うのですが、
戻り値について、わざわざクラスを作成してあげて、そのクラスを返却するように記載しなければいけませんでした。
このUseCounterクラスは基本的には無意味です。ただuseCounterの戻り値を定義するためだけのものです。

この欠点をDart3のRecord型の導入により克服することができるようになりました。

```dart
import 'package:flutter_hooks/flutter_hooks.dart';

final useCounter = () {
  final count = useState(0);
  void increment() {
    count.value++;
  }

  return (
    increment: increment,
    count: count.value,
  );
};
```

上記のコードのように、戻り値のクラスを作成することなく、Record型を定義してあげることで
Custom Hookを簡単に作成できるようになりました。
かなり簡潔に書けたことがわかると思います。


## Custom Hookのテスト
次にテストを書いていきたいと思います。

テストを書いていくためには、先ほど書いたコードに少し型定義をしてあげないといけません。
(ここを省略する方法あればコメントで教えていただけると嬉しいです。)

```dart
import 'package:flutter_hooks/flutter_hooks.dart';

typedef UseCounterRecord = ({
  void Function() increment,
  int count,
});

typedef UseCounter = UseCounterRecord Function();

final UseCounter useCounter = () {
  final count = useState(0);
  void increment() {
    count.value++;
  }

  return (
    increment: increment,
    count: count.value,
  );
};

```

実装には手をつけていないですが、変更した点は、
`UseCounterRecord`と`UseCounter`の方を定義しました。
`UseCounterRecord`についてはテストの際に使用します。
次にテストを書いていきます。

```dart
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_hooks_test/flutter_hooks_test.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:<path>use_counter.dart';

void main() {
  testWidgets('use counter ...', (tester) async {
    late UseCounterRecord hook;
    var buildCount = 0;
    await tester.pumpWidget(MaterialApp(
      home: HookBuilder(builder: (context) {
        buildCount++;
        hook = useCounter();
        return Container();
      }),
    ));

    expect(buildCount, 1);
    expect(hook.count, 0);

    // incrementの実行
    hook.increment();

    // 描画が終わるまで待機
    await tester.pumpAndSettle();

    expect(buildCount, 2);
    expect(hook.count, 1);
  });
}
```

上記のテストコードのキモは、HookBuilder内でbuildを行い
その中で`useCounter`のhookを取得します。
hookをlateで宣言してtest全体で使用できるようにしています。この際にdynamicで定義しても良いのですが、UseCounterRecordを定義しておくことで後のテストも書きやすくなります。
(関数から戻り値の推定方法が知れれば、わざわざ戻り値を別定義とせずに使えるはずなのでここをどうにかしたい。)

あとは、hookの関数を実行して、テストを行うことができるという感じです。
mockを使いたい場合は、`useCounter`の引数にmockインスタンスを渡すことで使用できたりします。

## Custom Hookを使用するメリット

### Build Contextを使用することができる
hooksはWidget　ツリー内に存在するため、Build Contextを使用することができます。(useContextが使用可能)
ChangeNotifierなどでは、BuildContextを呼び出せずに、alertの表示やtoastの表示など困ったことないですか？
正直なところ私は、toastの表示などはロジックになるかなと思うので、Cusotm Hook内で`ScaffoldMessenger.of(context).showSnackBar();`などが
実行できることはメリットかなと思います。

## Custom Hookのデメリット

- riverpodでCacheできない？
    - riverpodを使用するとChangeNotifierなどは一度作成するとCacheされるので次読み込む際に、同じ状態を担保することができる。
        - 基本的にChangeNotifierなどはautoDisposeなどで使用しているWidgetが破棄されると一緒に破棄されると思うのでデメリットにはならない？？？？？
- 関数型なのでMock化できない
    - custom hookに渡す引数をmock化することは可能だが、custom hook自体をmock化し、Stubを用意することなどはできない。
    - 私ができないと思っているだけで本当はやり方ある？？
    - custom hookをmockにする需要はある？？ ChangeNotifierをMock化してテストしたい要望がある時ってどんな時だろう。。