Chapter 18

[v0.14.0以下版] Flutter Hooksとは

村松龍之介
村松龍之介
2023.02.12に更新

本題から外れるため、詳細には説明しませんが、 flutter_hooks を導入するといくつかの便利な機能が使用可能になります。
ここに一部の機能を書き連ねます。

共通の使い方

flutter_hooks に共通する useXxx() というメソッドは、 StatefullWidgetStatelessWidget では使用できません。
ではどうしたら使えるかというと、 HookWidget[1] または HookBuilder[2] を使います。

HookXxx の違い

  • HookWidget
    build メソッドで hooks が使用できる。 StatefullWidgetStatelessWidget の代わりに使う。
  • HookBuilder
    Consumer Widgetと同じく、囲った以下のWidgetのみ hooks が利用できるようになる。
  • StatefulHookWidget
    hooks が使用できる StatefullWidget [3]

useProvider(provider)

Riverpodの解説本なので一番に挙げました。

provider を読み取り、その変化によってWidgetを再構築させます。

ConsumerWidgetwatch と比較した、 useProvider の利点は以下の通りです。

  • build メソッドの引数に ScopedReader watch を追加する必要がない
  • 再構築を抑えることができる select 構文が使用できる

それ以外は watch とほぼ同じように使えます。

useEffect()

HookWidgetでは StatefullWidgetの initState() が使用できません。
単に利用できなくなるのであれば困りますが、 useXxx メソッドで代替できます。


Widget build(BuildContext context) {
  useEffect(() {
    // 第二引数に指定した要素に変化がなければ再実行されないため、
    // `StatefullWidget` の `initState` のように初回ビルドのみ実行させたい処理を指定できる
    fetchData();
    // 戻り値として関数を返す。この関数は dispose されたときに実行されるため、
    // `StatefullWidget` の `onDispose` と同じように利用できる
    // 特に必要ない場合は `null` を返せばOK
    return null;
  // このListで指定した要素が更新されたとき、 `useEffect` が再実行される。つまり、空Listを指定しておけば再実行されない。
  }, const []);
}

useTextEditingControllerなどなど

TextEditingController[4] を生成できる。
dispose などの取り回しが不要で1行で簡潔に利用することができます。


Widget build(BuildContext context) {
  final textEditingController = useTextEditingController(text: 'initial text');
  return TextField(
    controller: textEditingController,
    ...
  )
}

他にも、一例ですが以下のように豊富な関数が用意されています。

  • useAnimation
  • useAnimationController
  • useFocusNode
  • usePageController
  • useScrollController
  • useSingleTickerProvider
  • useStreamController
  • useTabController

useMemoized

インスタンスをキャッシュし、Widgetが再構築されても、キャッシュに保存したインスタンスを使用してくれます。

// `useMemoized` を使用すれば、Widgetが再構築されても、nowの値は変わりません
final now = useMemoized(() => DateTime.now());

後述の useFuture / useStream と併用することも多いです。

useFuture

Flutterの FutureBuilder を使用するとネストが発生してしまいますが、 useFuture を使うことでネストを浅くすることができます。

FutureBuilder で書いた場合


Widget build(BuildContext context) {
  return FutureBuilder<PackageInfo>(
    future: PackageInfo.fromPlatform(),
    builder: (context, snapshot) {
      if (snapshot.hasError) {
        return const Text('Errorが発生しました');
      }
      if (snapshot.hasData && snapshot.data != null) {
        final packageInfo = snapshot.data!;
        return Text('${packageInfo.appName}');
      }
      return const Text('loading');
    },
  );
}

useFuture で書いた場合


Widget build(BuildContext context) {
  // useMemoizedを使用することで、結果がキャッシュされてWidget再構築時に再処理されなくなる
  final future = useMemoized(PackageInfo.fromPlatform);
  final snapshot = useFuture(future, initialData: null);
  if (snapshot.hasError) {
    return const Text('Errorが発生しました');
  }
  if (snapshot.hasData && snapshot.data != null) {
    final packageInfo = snapshot.data!;
    return Text('${packageInfo.appName}');
  }
  return const Text('loading');
}

useStream

useStream は、使用する値がFutureではなくStreamであるという事実を除いて、 useFuture と使用方法は同じです。

参考リンク

flutter_hooks
https://pub.dev/packages/flutter_hooks

脚注
  1. https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/HookWidget-class.html ↩︎

  2. https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/HookBuilder-class.html ↩︎

  3. https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/StatefulHookWidget-class.html ↩︎

  4. https://api.flutter.dev/flutter/widgets/TextEditingController-class.html ↩︎