🙅‍♀️

RiverpodでFormのValidationをやりたい!

JboyHashimoto2023/01/25に公開

EnumとGlobalKeyを使う

前回、StatefulWidgetの機能を使って、FormのValidationをやってたのですが、これはよくないよね〜ということで、頑張ってコード書いていい感じのコードが書けましたので記事にしようと思いました。

  • 今回やったこと!
    • Riverpod2.0を使用.
    • Dart2.17以降のEnumを使用.
    • 外部ファイルに分けたりして動作検証してみた。

こんなアプリができました!

Provider後で、別ファイルに分けたので動画と異なる部分があります🙇‍♂️
https://youtu.be/KMdMDPWqNwE

Demoアプリのソース

serviceディレクトリを作成して、enumのファイルと、Providerのファイルを作成してください。

あんまり使ったことないのですけど、Enumを今回エラーのメッセージを呼び出すのに使いました!
以前は、extensionを書いて、長いコードを書かないと使えなかったみたいです!
やってることは、単純でString型の定数が2個入ってるだけ。

service/error_message.dart
enum ErrorMessage {
  formOne('EnumのError1です〜!!!!'),
  formTwo('EnumのError2です〜!!!!'),
  ;//最後にセミコロンつけないとエラーが出る!

  const ErrorMessage(this.ErrorMsg);
  final String ErrorMsg;
}

次に、GlobalKeyとTextEditingControllerを使うための、Providerを定義します。

service/provider.dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

// GlobalKeyを状態としてもつProvider.
final formKeyProvider = Provider((ref) => GlobalKey<FormState>());
// FormOneのProvider.
final textOneProvider = StateProvider.autoDispose((ref) {
  return TextEditingController(text: '');
});
// FormTwoのProvider.
final textTwoProvider = StateProvider.autoDispose((ref) {
  return TextEditingController(text: '');
});

GlobalKeyとは?

https://api.flutter.dev/flutter/widgets/GlobalKey-class.html
翻訳すると...

アプリ全体で一意となるキー。

グローバルキーは、要素を一意に識別します。グローバル・キーは、BuildContextなど、それらの要素に関連する他のオブジェクトへのアクセスを提供します。StatefulWidgetsの場合、グローバルキーは、Stateへのアクセスも提供します。

グローバルキーを持つウィジェットは、ツリー内のある位置から別の位置に移動したときにサブツリーを再ペアレントします。サブツリーを再ペアリングするために、ウィジェットはツリー内の古い場所から削除されたのと同じアニメーションフレームでツリー内の新しい場所に到着する必要があります。

グローバルキーを使用した要素の再保持は、比較的コストがかかります。この操作は、関連するStateとそのすべての子孫に対してState.deactivateの呼び出しをトリガーし、InheritedWidgetに依存するすべてのWidgetを強制的に再構築させるからです。

上記の機能が不要な場合は、代わりにKey、ValueKey、ObjectKey、UniqueKeyを使用することを検討してください。

同じグローバルキーを持つ2つのウィジェットを同時にツリーに含めることはできません。これを実行しようとすると、実行時にアサートされます。

落とし穴

GlobalKeyは、ビルドのたびに再作成されるべきではない。それらは通常、例えばStateオブジェクトによって所有される長寿命なオブジェクトであるべきです。

ビルドごとに新しい GlobalKey を作成すると、古いキーに関連付けられたサブツリーの状態が破棄され、新しいキーのために新しい新鮮なサブツリーが作成されます。これはパフォーマンスに悪影響を与えるだけでなく、サブツリー内のウィジェットで予期せぬ動作を引き起こす可能性があります。たとえば、サブツリー内の GestureDetector は、ビルドごとに再作成されるため、進行中のジェスチャーを追跡することができなくなります。

その代わりに、StateオブジェクトにGlobalKeyを所有させ、State.initStateのようなビルドメソッドの外でインスタンス化するのが良い方法です。

こちらもご覧ください。

ウィジェットがキーを使用する方法の詳細については、Widget.keyでの議論を参照してください。

アプリを実行するコード

main.dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:ui_sample/service/error_message.dart';
import 'package:ui_sample/service/provider.dart';

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

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  static const String _title = 'Flutter Code Sample';

  
  Widget build(BuildContext context) {
    return const MaterialApp(
      restorationScopeId: 'app',
      title: _title,
      home: FormExample(),
    );
  }
}

class FormExample extends ConsumerWidget {
  const FormExample({
    Key? key,
  }) : super(key: key);

  
  Widget build(BuildContext context, WidgetRef ref) {
    // GlobalKeyをを呼び出す.
    final globalKey = ref.watch(formKeyProvider);
    // TextEditingControllerを呼び出す.
    final oneC = ref.watch(textOneProvider.notifier).state;
    final twoC = ref.watch(textTwoProvider.notifier).state;
    // Enumを外部ファイルから呼び出す.
    final enumErrorOne = ErrorMessage.formOne.ErrorMsg;
    final enumErrorTwo = ErrorMessage.formTwo.ErrorMsg;

    return Scaffold(
      appBar: AppBar(
        title: Text('FormTest'),
      ),
      body: Form(
        key: globalKey,
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.center,
          children: [
            TextFormField(
              controller: oneC,
              validator: (value) {
                if (value == null || value.isEmpty) {
                  return enumErrorOne; // エラーメッセージOne.
                }
              },
            ),
            TextFormField(
              controller: twoC,
              validator: (value) {
                if (value == null || value.isEmpty) {
                  return enumErrorTwo; // エラーメッセージTwo.
                }
              },
            ),
            Padding(
              padding: const EdgeInsets.symmetric(vertical: 16.0),
              child: ElevatedButton(
                onPressed: () {
                  if (globalKey.currentState!.validate()) {
                    print(oneC.text);
                    print(twoC.text);
                    ScaffoldMessenger.of(context).showSnackBar(
                      const SnackBar(content: Text('送信完了')),
                    );
                  }
                },
                child: const Text('送信'),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

まとめ

外部ファイルに書いたときは、なんでもRiverpodのProviderで呼び出そうとしてしまうのですが、それをやらなくてもEnumもgo_routerも呼び出せます!
仕事じゃなければ、なんでもRiverpodじゃなくてもいいと思われます。
個人の意見ですがね💁

Flutter大学

Flutter大学はFlutterエンジニアに特化した学習コミュニティです! 初心者から中上級者まで幅広く在籍し、切磋琢磨しています! 入会をご希望の方はこちらから→ https://flutteruniv.com

Discussion

ログインするとコメントできます