Flutter + flutter_hooksで始めるUI分離パターン

2022-06-08

はじめに

Flutterを始めました。お前何度始めるんだという意見も最もです。 3度目ぐらいの再入門でびっくりしたのはFlutter君、いつの間にかVer3.00になっていましたね。

RustやGoやPython、VBA、Office Script、C/C++、Rubyと用途に合わせて色んな言語に触れてあれ?どれがどの書き方だっけとなる中、Flutterは手軽さもありつつ、やはりUIと処理部分が一緒くたに書かれているのが気になります。

アルバイトで少し大きめのソフトを書くことになりそうなので、 リハビリも兼ねて備えておこうと思い記事に残します。

Riverpod

リアクティブ・キャッシングとデータバインディングを実現するフレームワーク Riverpod

プロバイダという概念を導入して、FlutterのStateをオーバラップしていい感じに組み合わせ安くしたり、テスト機能を追加したりするパッケージ。

導入

pubspec.yamlに追加する

environment:
  sdk: ">=2.12.0 <3.0.0"
  flutter: ">=2.0.0"

dependencies:
  flutter:
    sdk: flutter
  flutter_hooks: ^0.18.0
  hooks_riverpod: ^2.0.0-dev.9
flutter pub get

準備完了。

Hello world

まずは挨拶。ほんと挨拶は大切なんよ。 普段話さない人でもとりあえず元気に挨拶しておけば後ろ指さされない。

新人の頃に「お前、まじで新人として可愛げはないが、挨拶だけは一番元気なので憎めない」と言われたこともあります。そんな事思っても口に出すなと思いましたが……挨拶してなければ憎まれていたんでしょう。クワバラクワバラ。

main.dartを公式チュートリアルのサンプルのように動かします。

import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';

// 値(ここでは "Hello world")を格納する「プロバイダ」を作成します。
// プロバイダを使うことで値のモックやオーバーライドが可能になります。
final helloWorldProvider = Provider((_) => 'Hello');

void main() {
  runApp(
    // プロバイダをウィジェットで利用するには、アプリ全体を
    // `ProviderScope` ウィジェットで囲む必要があります。
    // ここに各プロバイダのステート(状態)・値が格納されていきます。
    ProviderScope(
      child: MyApp(),
    ),
  );
}

// flutter_hooks 併用時は hooks_riverpod の HookConsumerWidget を継承します。
class MyApp extends HookConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final String value = ref.watch(helloWorldProvider);

    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text('Example')),
        body: Center(
          child: Text(value),
        ),
      ),
    );
  }
}

実行するとこうなります。

Providerを追加する

Global変数としてProviderを追加するのが基本です。 cityproviderとcountrProvidorを追加しました。 使う際にはそれをMyAppの中で読み出しています。

import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';

// 値(ここでは "Hello world")を格納する「プロバイダ」を作成します。
// プロバイダを使うことで値のモックやオーバーライドが可能になります。
final helloWorldProvider = Provider((_) => 'Hello');
final cityProvider = Provider((ref) => 'London');
final countryProvider = Provider((ref) => 'England');

void main() {
  runApp(
    // プロバイダをウィジェットで利用するには、アプリ全体を
    // `ProviderScope` ウィジェットで囲む必要があります。
    // ここに各プロバイダのステート(状態)・値が格納されていきます。
    ProviderScope(
      child: MyApp(),
    ),
  );
}

// flutter_hooks 併用時は hooks_riverpod の HookConsumerWidget を継承します。
class MyApp extends HookConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final String value = ref.watch(helloWorldProvider);
    final String value1 = ref.watch(cityProvider);
    final String value2 = ref.watch(countryProvider);

    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text('Example')),
        body:
            Column(children: <Widget>[Text(value), Text(value1), Text(value2)]),
      ),
    );
  }
}

変数の値を更新する場合

ボタンが押されるたびにカウンタを更新する場合こうなります。

import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';

final counterProvider = StateProvider<int>((ref) => 0);

void main() {
  runApp(ProviderScope(child: MyApp()));
}

class MyApp extends HookConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final int count = ref.watch(counterProvider);

    return MaterialApp(
      home: Scaffold(
        body: Center(child: Text('$count')),
        floatingActionButton: FloatingActionButton(
          onPressed: () {
            ref.read(counterProvider.notifier).state++;
          },
        ),
      ),
    );
  }
}

まとめ

とりあえずこんなところでどうか。 最近、仕事忙しすぎて朝の7時ぐらいから22時ぐらいまで働いてる… 子供の機嫌が悪くなると家庭内の空気が淀むので、できる限り離業したりしつつですが… 妻も疲れていることでしょう……アルバイトも含め一段落したら旅行に行きたいですね