5
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

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

Posted at

はじめに

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を書く場合は以下のような書き方をしていました。

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型の導入により克服することができるようになりました。

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のテスト

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

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

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,
  );
};

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

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化してテストしたい要望がある時ってどんな時だろう。。
5
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?