11
5

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.

flutter_hooksでカスタムhook作ってみる

Last updated at Posted at 2022-11-11

flutter_hooksでカスタムhook作ってみる

reactのエンジニアがflutter_hooksを使用してカスタムhookを作成するまでの備忘録

flutter_hooksの説明

flutter_hooksはReact hooksにインスパイアされ、useStateやuseEffectなどの関数ベースの機能を
flutterで使用するためのライブラリです。

flutter_hooksを使用すると、Stateを持つ必要があった際に、StatefulWidgetなどを使用せずに済むため
コードを簡潔に記述することができます。
flutter_hooksを使用すると以下のように変換できます。いつものカウンターアプリです。

before

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});
  final String title;
  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }
}

after

class MyHomePage extends HookWidget {
  final String title;
  const MyHomePage({super.key, required this.title});

  @override
  Widget build(BuildContext context) {
    final count = useState(0);
    void incrementCounter() {
      count.value++;
    }

    return Scaffold(
      appBar: AppBar(
        title: Text(title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '${count.value}',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: incrementCounter,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }
}

かなり簡潔になったことがわかってもらえると思います。

カスタムhookの作成

Reactでhookを使用している方ならcustom Hookをよく使用しているのかなと思います。
主な用途としては、UIとロジックの分離をおこなったり、ロジックの共通化を行うために使用したりすると思います。

flutterではこんな感じに使用します。
先ほどのCounterのカスタムhookを作成します。

import 'package:flutter_hooks/flutter_hooks.dart';

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

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

class UseCounter {
  final int count;
  final void Function() increment;
  UseCounter({required this.count, required this.increment});
}

useCounterを使います。

class HomePage extends HookWidget {
  final String title;
  const HomePage({super.key, required this.title});

  @override
  Widget build(BuildContext context) {
    final state = useCounter();
    return Scaffold(
      appBar: AppBar(
        title: Text(title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '${state.count}',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: state.increment,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }
}

ついでにuseEffectも使用してみる

useEffectの機能も追加して以下の10カウントずつ通知してくれるcounterを作成します。

スクリーンショット 2022-11-11 17.40.36.png

useEffectを使用して、countを監視し、countが10になった時にダイアログを出します。

class HomePage extends HookWidget {
  final String title;
  const HomePage({super.key, required this.title});

  @override
  Widget build(BuildContext context) {
    final state = useCounter();

    useEffect(() {
      if (state.count % 10 == 0) {
        Future.microtask(() => showDialog(
            barrierDismissible: false,
            context: context,
            builder: (context) => CupertinoAlertDialog(
                  title: Text("${state.count}になりました"),
                  actions: [
                    CupertinoDialogAction(
                      child: const Text('close'),
                      onPressed: () {
                        Navigator.of(context).pop();
                      },
                    )
                  ],
                )));
      }
    }, [state.count]);
    
    return Scaffold(
      appBar: AppBar(
        title: Text(title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '${state.count}',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: state.increment,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }
}

useEffectもロジックの部分なので、これもカスタムhookに入れたいなーと思ったので
以下のような感じでuseHomePageというカスタムhookを作成しました。
useCounterは先ほど上で作成したものです。
また、dialogを出すために、contextを使用するので、hookで用意されているuseContextを使用します。

UseHomePage useHomePage() {
  final counter = useCounter();
  final context = useContext();
  useEffect(() {
    if (counter.count % 10 == 0) {
      Future.microtask(() => showDialog(
          barrierDismissible: false,
          context: context,
          builder: (context) => CupertinoAlertDialog(
                title: Text("${counter.count}になりました"),
                actions: [
                  CupertinoDialogAction(
                    child: const Text('close'),
                    onPressed: () {
                      Navigator.of(context).pop();
                    },
                  )
                ],
              )));
    }
  }, [counter.count]);

  return UseHomePage(counter: counter);
}

class UseHomePage {
  final UseCounter counter;
  UseHomePage({required this.counter});
}

useHomePageを使用します。
useEffectがなくなって、ロジックの部分が1行に圧縮されたことがわかると思います。

class HomePage extends HookWidget {
  final String title;
  const HomePage({super.key, required this.title});

  @override
  Widget build(BuildContext context) {
    final state = useHomePage();
    return Scaffold(
      appBar: AppBar(
        title: Text(title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '${state.counter.count}',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: state.counter.increment,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }
}
11
5
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
11
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?