質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
Firebase

Firebaseは、Googleが提供するBasSサービスの一つ。リアルタイム通知可能、並びにアクセス制御ができるオブジェクトデータベース機能を備えます。さらに認証機能、アプリケーションのログ解析機能などの利用も可能です。

Flutter

Flutterは、iOSとAndroidのアプリを同じコードで開発するためのフレームワークです。オープンソースで開発言語はDart。双方のプラットフォームにおける高度な実行パフォーマンスと開発効率を提供することを目的としています。

Dart

Dartは、Googleによって開発されたJavaScriptの代替となることを目的に作られた、ウェブ向けのプログラミング言語である。

Q&A

解決済

1回答

889閲覧

FutureProviderが正しい値を返してくれない

mako_0221

総合スコア87

Firebase

Firebaseは、Googleが提供するBasSサービスの一つ。リアルタイム通知可能、並びにアクセス制御ができるオブジェクトデータベース機能を備えます。さらに認証機能、アプリケーションのログ解析機能などの利用も可能です。

Flutter

Flutterは、iOSとAndroidのアプリを同じコードで開発するためのフレームワークです。オープンソースで開発言語はDart。双方のプラットフォームにおける高度な実行パフォーマンスと開発効率を提供することを目的としています。

Dart

Dartは、Googleによって開発されたJavaScriptの代替となることを目的に作られた、ウェブ向けのプログラミング言語である。

0グッド

0クリップ

投稿2022/09/21 03:12

下記のコードのようにRiverPodのFutureProviderを使用しており、FireBaseのAuthenticationの結果を受け取り、navigator.dartでその結果を利用したいと考えております。その利用結果が示す場所はhere3なのですが、ここでなぜか必ずAsyncData<bool>(value: false)でvalueとしてfalseを返してきてしまいます。
仮に下記のsample.dartにあるように、Future型を返すような関数であれば正しく動作してくれるのですが、FutureProviderを利用すると同じように期待する動作をしてくれません。

原因及び解決方法について、アドバイスをいただけると大変ありがたいです。
よろしくお願い申し上げます。

Dart

1//auth.dart 2import 'package:firebase_auth/firebase_auth.dart'; 3import 'package:font_awesome_flutter/font_awesome_flutter.dart'; 4import 'package:riverpod/riverpod.dart'; 5 6final authUser = FutureProvider<bool>((ref) async { //here1 7 try { 8 final FirebaseAuth auth = FirebaseAuth.instance; 9 final UserCredential _result = 10 await auth.createUserWithEmailAndPassword( 11 email: emailController.text, 12 password: maskController.text, 13 ); 14 User _user = _result.user!; // 登録したユーザー情報 15 _user.sendEmailVerification(); 16 return true; 17 } on FirebaseAuthException catch (e) { 18 if (e.code == 'email-already-in-use') { 19 ref.read(completeErrorMessage.notifier).update((state) => awesomeText(icon: FontAwesomeIcons.lightbulb, text: 'EZ01:指定したメールアドレスは登録済みです',color: MyStyle.alertColor)); 20 } else if (e.code == 'invalid-email') { 21 ref.read(completeErrorMessage.notifier).update((state) => awesomeText(icon: FontAwesomeIcons.lightbulb, text: 'EZ02:メールアドレスのフォーマットが正しくありません',color: MyStyle.alertColor)); 22 } else if (e.code == 'operation-not-allowed') { 23 ref.read(completeErrorMessage.notifier).update((state) => awesomeText(icon: FontAwesomeIcons.lightbulb, text: 'EZ03:指定したメールアドレス・パスワードは現在使用できません',color: MyStyle.alertColor)); 24 } else if (e.code == 'weak-password') { 25 ref.read(completeErrorMessage.notifier).update((state) => awesomeText(icon: FontAwesomeIcons.lightbulb, text: 'EZ04:パスワードは6文字以上にしてください',color: MyStyle.alertColor)); 26 } else if (e.code == 'network-request-failed') { 27 ref.read(completeErrorMessage.notifier).update((state) => awesomeText(icon: FontAwesomeIcons.lightbulb, text: 'EZ05:通信環境を確認してください',color: MyStyle.alertColor)); 28 } else{ 29 ref.read(completeErrorMessage.notifier).update((state) => awesomeText(icon: FontAwesomeIcons.lightbulb, text: 'EZ10:予期しないエラーです、管理者へ問い合わせください',color: MyStyle.alertColor)); 30 } 31 return false; 32 } 33});

Dart

1//navigator.dart 2import 'dart:developer'; 3import 'dart:ui'; 4 5import 'package:cards/control/auth.dart'; 6import 'package:flutter/material.dart'; 7import 'package:flutter_riverpod/flutter_riverpod.dart'; 8 9class MyNavigator extends ConsumerWidget { 10 final Widget destinationTo; 11 final BuildContext context; 12 final String goBack; 13 final List<GlobalKey<FormState>>? formKeyList; 14 15 MyNavigator( 16 {required this.destinationTo, 17 required this.context, 18 required this.goBack, 19 this.formKeyList, 20 Key? key, 21 required}) 22 : super(key: key); 23 24 25 Widget build(BuildContext context, WidgetRef ref) { 26 final getResult = ref.watch(authUser);//here2 27 // TextButton myNavigator(BuildContext context) { 28 return TextButton( 29 style: TextButton.styleFrom( 30 foregroundColor: MyStyle.mainColor, 31 ), 32 onPressed: () { 33 switch (goBack) { 34 case "進む": 35 for (var i = 0; i < formKeyList!.length; i++) { 36 if (!(formKeyList![i].currentState!.validate())) { 37 return; 38 } 39 } 40 navigatorAnimation( 41 context: context, goBack: goBack, destinationTo: destinationTo); 42 break; 43 case "戻る": 44 navigatorAnimation( 45 context: context, goBack: goBack, destinationTo: destinationTo); 46 break; 47 case "確認メールを送信する": 48 print(getResult);//here3 49 // getResult.then( 50 // (value) { 51 // if (value == false) {//catchでエラー側にthrowされた場合falseをreturnして、さらにここではreturnで処理が終了 52 // return; 53 // } else if (value == true) {// try -catchでエラーが非発生 54 // navigatorAnimation( 55 // context: context, 56 // goBack: goBack, 57 // destinationTo: destinationTo); 58 // } 59 // }); 60 break; 61 } 62 }, 63 child: Text(goBack), 64 ); 65 } 66} 67 68void navigatorAnimation( 69 {required BuildContext context, 70 required String goBack, 71 required Widget destinationTo}) { 72 Offset? begin; 73 Navigator.of(context).push( 74 PageRouteBuilder( 75 pageBuilder: (context, animation, secondaryAnimation) { 76 return destinationTo; 77 }, 78 transitionDuration: const Duration(milliseconds: 300), 79 transitionsBuilder: (context, animation, secondaryAnimation, child) { 80 goBack == "進む" ? begin = const Offset(1.0, 0.0) : 0; 81 goBack == "戻る" ? begin = const Offset(-1.0, 0.0) : 0; 82 goBack == "確認メールを送信する" ? begin = const Offset(1.0, 0.0) : 0; 83 const Offset end = Offset.zero; 84 final Animatable<Offset> tween = Tween(begin: begin, end: end) 85 .chain(CurveTween(curve: Curves.easeInOut)); 86 final Animation<Offset> offsetAnimation = animation.drive(tween); 87 return SlideTransition( 88 position: offsetAnimation, 89 child: child, 90 ); 91 }, 92 ), 93 ); 94} 95

Dart

1//sample.dart 2Future<bool> authUser(ref) async { 3 try { 4 final FirebaseAuth auth = FirebaseAuth.instance; 5 final UserCredential _result = 6 await auth.createUserWithEmailAndPassword( 7 email: emailController.text, 8 password: maskController.text, 9 ); 10 User _user = _result.user!; // 登録したユーザー情報 11 _user.sendEmailVerification(); 12 return Future<bool>.value(true); 13 } on FirebaseAuthException catch (e) { 14 if (e.code == 'email-already-in-use') { 15 ref.read(completeErrorMessage.notifier).update((state) => awesomeText(icon: FontAwesomeIcons.lightbulb, text: 'EZ01:指定したメールアドレスは登録済みです',color: MyStyle.alertColor)); 16 } else if (e.code == 'invalid-email') { 17 ref.read(completeErrorMessage.notifier).update((state) => awesomeText(icon: FontAwesomeIcons.lightbulb, text: 'EZ02:メールアドレスのフォーマットが正しくありません',color: MyStyle.alertColor)); 18 } else if (e.code == 'operation-not-allowed') { 19 ref.read(completeErrorMessage.notifier).update((state) => awesomeText(icon: FontAwesomeIcons.lightbulb, text: 'EZ03:指定したメールアドレス・パスワードは現在使用できません',color: MyStyle.alertColor)); 20 } else if (e.code == 'weak-password') { 21 ref.read(completeErrorMessage.notifier).update((state) => awesomeText(icon: FontAwesomeIcons.lightbulb, text: 'EZ04:パスワードは6文字以上にしてください',color: MyStyle.alertColor)); 22 } else if (e.code == 'network-request-failed') { 23 ref.read(completeErrorMessage.notifier).update((state) => awesomeText(icon: FontAwesomeIcons.lightbulb, text: 'EZ05:通信環境を確認してください',color: MyStyle.alertColor)); 24 } else{ 25 ref.read(completeErrorMessage.notifier).update((state) => awesomeText(icon: FontAwesomeIcons.lightbulb, text: 'EZ10:予期しないエラーです、管理者へ問い合わせください',color: MyStyle.alertColor)); 26 } 27 return Future<bool>.value(false); 28 } 29}

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

moriman

2022/09/21 08:32

解決方法とありますが、関数でできるのであれば「関数でやる」ということが解決方法のような気がします。 多分関数でやるのが一般的なやり方のような気がします(よく目にする、という意味で。) 質問は、FutureProviderで同じことをすることができるかどうかを知りたい、ということですかね?
mako_0221

2022/09/21 13:40

コメント有難うございます。まだ、経験が浅く、FutureProviderを利用するしか実現できないと誤解していたのですが、関数でやるとはどのようなアプローチかぜひアドバイスを願えれば幸いです
moriman

2022/09/21 14:28

https://codewithandrea.com/articles/flutter-presentation-layer/ 典型的な認証(サインイン)フローでRiverpodを使っているので、ほぼ上記の記事の通りかと思います。 上記記事では匿名認証で、質問はユーザー登録ですが基本的には同じ流れ。 色々見てみましたが、とりあえず上記のやり方がオーソドックスなのでこれだけ押さえておけば良いのではないかと思います。 というか「Future型を返すような関数であれば正しく動作してくれるのですが」とあるので、関数を使ったらできた、ということではないんですかね? いや、どちらでも良いのですが。
mako_0221

2022/09/21 15:18

コメントありがとうございます。結論からすると題意が不明確でお詫びを申し上げます。 申し上げていた関数を利用することで、認証と画面遷移の動きは実現できております。 実はそこで動作を見たときに、「確認メールを送信する」ボタンをタップするとテンポが遅れて画面が遷移するために、CircularProgressIndicator()を描画する必要があるという問題に到達しておりました。 そこで、認証過程中にこれを表示するという問題に対して、諸々調べていたところ、FutureProviderで制御することが望ましいのかという地点に達し、CircularProgressIndicator()利用以前にFutureProviderがうまく実装できないというところでの質問でした。 そもそも、CircularProgressIndicator()でローディング中を表示するためにFutureProviderで実装することが適切ではないというご意見もあるのかもしれませんが、現状を改めて報告をさせていただきます。
guest

回答1

0

ベストアンサー

実行できないので想像ですが。

  1. まずはauthUserの動作が想定通り動いているかdebugPrint等でメッセージを出して調べるべき。
    ダイアログが表示されていないからfalseではないはずという理由なのかな

  2. watchの利用上の注意としてUI操作時のFunctionやinitState等ではreadの方を使って下さいとあります。
    なのでonPressed内の関数で別途final getResult = ref.read(authUser);で取り出した情報を使うべきなのかもしれない。

  3. FutureProviderの結果についてはthenではなくwhenでの処理を想定しているように見えます。つまりデータが未確定の状態があるということです。
    whenを考慮した実装にするべきでは。

    そしてonPressedの処理関数に関してgoBack == "確認メールを送信する"の時valueがtrue以外の場合nullにして押せないようにする。

    onPressed内ではvalueがtrueのはず(呼び出し可能な状態では)なのでgetResult.then(での判断は行わずnavigatorAnimationの呼び出しのみにする。

2番目のやつが理由なのかな?
使い方(実装方法)としては3番目が正しいように思える。

投稿2022/09/21 08:56

ta.fu

総合スコア1667

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

mako_0221

2022/09/21 15:21

コメントをお寄せいただき御礼を申し上げます。加えてご返信が遅くなったことをお詫び申し上げます。 ご指摘いただいた内容を実際に自分のコードに組み込みながら試行錯誤を繰り返しているため、手を動かして理解を進めた上で改めてコメントをさせていただければ幸いです。 何卒、よろしくお願い申し上げます。 *現状whenを利用すべきという点までは自分の理解がやっと追いついてきたところです・・・新たなことを学ぶのは地道で険しい道のりですが、Flutter & Dartは楽しいです!いつもご回答ありがとうございます。
mako_0221

2022/09/21 16:34 編集

一つやりながら理解が追いついてきた点がございます。コメントの#2に関連して以下のように実装しました。 onPressed:(){ case "確認メールを送信する": final authC = ref.read(authProvider); authC.when( data: (data) { if (data == true) { navigatorAnimation(); } else { return; } }, //error & loadingは一旦省略 ); } すると確かにDocに記載があるようにreadは度だけproviderの変更を検知してくれます。ふむふむとなるほどと思ったのですが、例えば認証の過程でtry-catchで1回めにエラーにthrowされたとして2回目にユーザーが誤りに気づいて(例えばwifi非接続だった!*あくまで例えです。。。)正しい状態で改でonPressされたとしても、一回しか変更を検知しないため、初めにエラーに行ってfalseを返してしまっているので、print(data)の結果は常にfalseになってしまい、ユーザーは正しくwifiに接続したとしても、もう認証のステップに進ことができなくなってしまいます。(1回しかproviderを見に行かない結果、falseしか返さなくなってしまう) これを解消するにはどうすれば良いのだろうと色々思考を巡らせていただのですが、最後にopPressed直後にref.refresh(authProvider);することで実装自体はできてしまいました。 これはできてしまうけれども、考え方やアプローチとして不適切でしょうか?
mako_0221

2022/09/21 16:43

申し訳ございません、#3のポイントについては、どうしても半分理解ができませんでした。when()で実装することは理解できて、本件、上記のコメントの通り、whenで実装し直しております。valueがtrue以外の場合、それはつまりproviderがfalseを返してきた場合には(本件で言えば、認証の過程でerrorにthrowされた)ボタンを押せなくするということでしょうか? onPressed:(){ case "確認メールを送信する": final authC = ref.read(authProvider); authC.when( data: (data) { if (data == true) { navigatorAnimation(); } else { //here true以外なのでこの場合ボタンを押せなくする?? } }, //error & loadingは一旦省略 ); } //hereで記載はしていますが、実際にはボタンのdisabled化はonPressed自体をNullにしなくてはいけないので、ここのelseではなくてもっと上層で判定して分岐させなくてはならないということなのかと思いますが、その思考であっているのでしょうか? ただ、falseになってしまうパターンでdisabled化してしまった場合、「あ、wifiに接続するのを忘れていたから接続してもう一回トライしてみよう」というユーザーに対して、再度タップして送信させることを不可能としてしまうように気がするのですが、いかがでしょうか? 発想が根本的ずれていたら申し訳ございません。
ta.fu

2022/09/21 22:50

仕様を満たすのであればどのような実装であれ問題ないです。 ちなみに3に関してはTextButtonの構築部分でwhenを使うという意味です。onPressedの中で使うことは想定していません。 初期に提示されたコードでは"確認メールを送信する"の時にfalseだとreturnをしています。つまり押しても何もしないという意味ですね。そいう動作はUIとして敬遠されるものだと思ったため「押せない」というUIにするべきだと思い書きました。実装としては上記の場合onPressedをnullにする感じです。 データをどの段階で確認するのか、UIをどのように構築するのか、などについては「仕様」を満たすのであればどのような実装にするのも本人次第です。 次に「falseの場合に再接続をする」という仕様を盛り込むのはいいのですが、それに関して初期に提示された質問にはなにも書かれていないので回答にはその部分を盛り込めません。 後付けで追記されて、どうでしょうかと書かれても、そうですか、と返答するしかないです。 まあ、ボタン名を「再接続」などにして、onPressedで再接続処理をするというのもいいとは思いますが。
mako_0221

2022/09/22 01:04

ta.fu様 おはようございます。なるほど、意図されている趣旨・仕様について理解することができました。 ユーザー目線でUIの構築をすべくもう一度その点は再構築して実装してみようと思います。 お力添えに重ねて御礼申し上げます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問