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

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

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

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

Flutter

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

Dart

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

Q&A

解決済

2回答

1619閲覧

Flutter ChangeNotifierによるStreamの取得でConnectionState.waitingのエラーが発生する

mu-------

総合スコア9

Firebase

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

Flutter

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

Dart

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

1グッド

0クリップ

投稿2020/02/21 04:15

編集2020/02/22 03:35

Flutter初心者です。

Firebaseを使用して、
メールアドレスによる認証を実装しており、
認証に成功したユーザーのマイページ閲覧画面、編集画面を作成しております。

FirebaseからユーザーIDの情報を取ってくるのに、
下記のApiを作成しました。

dart

1「AuthApi.dart」 2 3class AuthApi { 4 5 //connect Firebase 6 final FirebaseAuth _auth = FirebaseAuth.instance; 7 8 //create user obj based on FirebaseUser 9 User _userFromFirebaseUser(FirebaseUser user) { 10 return user != null ? User(uid: user.uid) : null; 11 } 12 13 //auth change user stream 14 Stream<User> get user { 15 return _auth.onAuthStateChanged.map(_userFromFirebaseUser); 16 } 17}

そして、ChangeNotifierで上記のuserを下記のように取得しています。

dart

1「MypageNotification.dart」 2 3class MyPageNotification with ChangeNotifier { 4 //get user stream 5 Stream<User> user = AuthApi().user; 6}

また、上記ChangeNotifierをMaterialRouteの上部に設置し、どのウィジェットも読み取れるようにしています。

マイページ閲覧画面とマイページ編集画面で取ってきたユーザーIDを使用し、
ユーザーIDに基づいた情報(名前等)を表示、編集しようと思っています。
下記は、閲覧・表示画面の実装の一部です。
ユーザーIDをStreamBuilderで取ってきて、それをusersInfo(IDを渡してユーザーの情報を取得)の引数として渡しています。

dart

1StreamBuilder<User>( 2 stream: myPageNotification.user, 3 builder: (context, user) { 4 5 print(user); // 編集画面のみ「AsyncSnapshot<User>(ConnectionState.waiting, null, null)」が表示される 6 7 return user.data == null // 編集画面のみ常にnullとなる 8 ? Loading() 9 : StreamBuilder<List<UsersInfo>>( 10 stream: myPageNotification.usersInfo(user.data.uid), 11 builder: (context, usersInfo) { 12 return Container(

閲覧画面ではこの実装方法で情報が正しく表示されるものの、
編集画面では、上記の「return user.data」の部分でnullが返却され、
print(user)の部分で
「AsyncSnapshot<User>(ConnectionState.waiting, null, null)」という値が表示されます。

しかし、「MypageNotification.dart」を下記のようにuserを二つに分けて、
閲覧画面と編集画面で別々にユーザーIDを取ってくるとうまく値が取得できます。

dart

1「MypageNotification.dart」 2 3class MyPageNotification with ChangeNotifier { 4 //get user stream 5 Stream<User> userShow = AuthApi().user; 6 Stream<User> userEdit = AuthApi().user; 7}

上記方法で実装はできるのですが、なぜこのような現象になるのかわからず、ご教示いただければ幸いです。

また、Flutter初心者で実装方法が合っているか不確かで、マイページのようにユーザー情報を表示する画面ではどのようにChangeNotifierで値を渡しているのかご教示いただけると大変嬉しいです。
(ChangeNotifier以外でも良い方法があれば教えてください。。)

ご回答の程、よろしくお願いいたします!!

###追記 2020/02/22
ページの遷移にはNavigator.pushを使用し、
MaterialAppでルートを定義し、遷移しています。
void main() => runApp(MyApp());

dart

1class MyApp extends StatelessWidget { 2 3 Widget build(BuildContext context) { 4 return MultiProvider( 5 providers: [ 6 ChangeNotifierProvider<MainNotification>( 7 create: (_) => MainNotification()), 8 ChangeNotifierProvider<MyPageNotification>( 9 create: (_) => MyPageNotification()), 10 ], 11 child: const MaterialApp( 12 debugShowCheckedModeBanner: false, 13 initialRoute: '/', 14 onGenerateRoute: Router.generateRoute, 15 ), 16 ); 17 } 18}
popobot👍を押しています

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

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

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

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

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

guest

回答2

0

ベストアンサー

※ 長くなったので、別の回答にしました

質問の内容を元に、ConnectionState.waitingのままになるコードを書いてみました。
閲覧画面側でStreamからデータを取得済みなので、編集画面側で再度Streamをlisten()してももうデータがこないため、waitingのままなんだと思います

import 'package:firebase_auth/firebase_auth.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { login(); return MultiProvider( providers: [ ChangeNotifierProvider<MyPageNotification>( create: (_) => MyPageNotification(), ), ], child: MaterialApp( title: 'Test', home: Scaffold( appBar: AppBar(), body: UserViewPage(), ), ), ); } Future<void> login() async { if (await FirebaseAuth.instance.currentUser() == null) { await FirebaseAuth.instance.signInAnonymously(); } } } class UserViewPage extends StatelessWidget { @override Widget build(BuildContext context) { final myPageNotification = Provider.of<MyPageNotification>(context); return Container( child: Column( children: [ StreamBuilder<User>( stream: myPageNotification.user, builder: (context, user) { return user.data == null ? const CircularProgressIndicator() : Text(user.data.uid); }, ), RaisedButton( child: const Text('編集へ'), onPressed: () => Navigator.of(context).push( MaterialPageRoute<Widget>( builder: (context) { return MultiProvider( providers: [ ChangeNotifierProvider<MyPageNotification>.value( value: myPageNotification), ], child: UserEditPage(), ); }, ), ), ), ], ), ); } } class UserEditPage extends StatelessWidget { @override Widget build(BuildContext context) { final myPageNotification = Provider.of<MyPageNotification>(context); return Scaffold( appBar: AppBar(), body: Container( child: Column( children: [ StreamBuilder<User>( stream: myPageNotification.user, builder: (context, user) { print(user); return user.data == null ? const CircularProgressIndicator() : Text(user.data.uid); }, ), ], ), ), ); } } class AuthApi { //connect Firebase final FirebaseAuth _auth = FirebaseAuth.instance; //create user obj based on FirebaseUser User _userFromFirebaseUser(FirebaseUser user) { return user != null ? User(uid: user.uid) : null; } //auth change user stream Stream<User> get user { return _auth.onAuthStateChanged.map(_userFromFirebaseUser); } } class MyPageNotification with ChangeNotifier { //get user stream Stream<User> user = AuthApi().user; } class User { User({this.uid}); String uid; }

修正版のコードを書いてみました。他にも実装方法は色々あると思いますが、自分が馴染みのあるStreamProviderを使った実装にしてみました。MaterialAppの前でStreamProviderでonAuthStateChangedをlistenするようにして、ユーザ情報が必要な箇所ではConsumerを使ってデータを取得するようにしています。
ChangeNotifierは使ってないので、消しました。

import 'package:firebase_auth/firebase_auth.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { login(); return MultiProvider( providers: [ StreamProvider<User>( create: (_) => AuthApi().user, ), ], child: MaterialApp( title: 'Test', home: Scaffold( appBar: AppBar(), body: UserViewPage(), ), ), ); } Future<void> login() async { if (await FirebaseAuth.instance.currentUser() == null) { await FirebaseAuth.instance.signInAnonymously(); } } } class UserViewPage extends StatelessWidget { @override Widget build(BuildContext context) { final user = Provider.of<User>(context); return Container( child: Column( children: [ Consumer<User>( builder: (context, user, _) { return user == null ? const CircularProgressIndicator() : Text(user.uid); }, ), RaisedButton( child: const Text('編集へ'), onPressed: () => Navigator.of(context).push( MaterialPageRoute<Widget>( builder: (context) { return MultiProvider( providers: [ Provider<User>.value(value: user), ], child: UserEditPage(), ); }, ), ), ), ], ), ); } } class UserEditPage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(), body: Container( child: Column( children: [ Consumer<User>( builder: (context, user, _) { print(user); return user == null ? const CircularProgressIndicator() : Text(user.uid); }, ), ], ), ), ); } } class AuthApi { //connect Firebase final FirebaseAuth _auth = FirebaseAuth.instance; //create user obj based on FirebaseUser User _userFromFirebaseUser(FirebaseUser user) { return user != null ? User(uid: user.uid) : null; } //auth change user stream Stream<User> get user { return _auth.onAuthStateChanged.map(_userFromFirebaseUser); } } class User { User({this.uid}); String uid; }

投稿2020/02/22 06:02

popobot

総合スコア6586

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

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

mu-------

2020/02/22 08:26

MaterialAppの前でStreamProviderを使用するとエラーが表示されなくなりました!!! 何度も質問答えていただき、本当にありがとうございます。 Userと同じようにStreamをChangeNotifierで渡しているUsersInfoの方は、なぜかエラーも表示されずうまくいきました。。。 理解できていない部分も多くありますが、頑張って勉強します…! 丁寧なご説明、本当にありがとうございました!!!
popobot

2020/02/22 08:50 編集

うまくいってよかったです。自分も勉強になりました。もう少し、詳細なコードを教えてもらえば、早く解決できたとやや反省もしてます。 > Userと同じようにStreamをChangeNotifierで渡しているUsersInfoの方は、なぜかエラーも表示されずうまくいきました。。。 コードがないのでなんとも言えないですが、UsersInfo側のStreamは、StreamBuilderに渡す直前に生成されていて、各画面で別々のStreamになっているんじゃないかと思います。 後、Streamの管理部分だけをいえば、ChangeNotifierはあまり意味がない気もしました。
guest

0

※ 以下の回答は正しくありませんでした。onAuthStateChangedのStreamにおいては該当しません。


最初の回答

通常StreamStreamBuilder等で複数回listen()できません。
StreamController.broadcast()等~~asBroadcastStream~~を使って、BroadcastStreamにすればできると思います。

以下の記事が詳しく書かれていたので、参考にするといいと思います。

Stream/Sinkを使いこなす! Stream/RxDart初心者のためのBLoC入門 part2


また、Flutter初心者で実装方法が合っているか不確かで、マイページのようにユーザー情報を表示する画面ではどのようにChangeNotifierで値を渡しているのかご教示いただけると大変嬉しいです。

(ChangeNotifier以外でも良い方法があれば教えてください。。)

ChangeNotifierなどの、状態を管理するライブラリとしては、 providerを使うのが一般的だと思います。もしご存じなければ、調べてみてもいいかもしれません。


2020-02-22 追記

手元で検証したところ、onAuthStateChangedStream を複数回 listen しても問題なかったです。
以下、検証したコードを貼っておきます。

なお、以下のコードで、 userPage()をさらに増やしてホットリロードすると、ConnectionState.waitingのままになる現象が再現しました。ビルドし直すと直ります。発生している現象はこれかもしれないです。

import 'package:firebase_auth/firebase_auth.dart'; import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Test', home: Scaffold( appBar: AppBar(), body: MyHomePage(), ), ); } } class MyHomePage extends StatefulWidget { @override _MyHomePageState createState() => _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { MyPageNotification myPageNotification = MyPageNotification(); @override void initState() { super.initState(); login(); } Future<void> login() async { if (await FirebaseAuth.instance.currentUser() == null) { await FirebaseAuth.instance.signInAnonymously(); } } @override Widget build(BuildContext context) { return Container( child: Column( children: [ userPage(), userPage(), ], ), ); } Widget userPage() { return StreamBuilder<User>( stream: myPageNotification.user, builder: (context, user) { print(user); return user.data == null ? const CircularProgressIndicator() : Text(user.data.uid); }, ); } } class AuthApi { //connect Firebase final FirebaseAuth _auth = FirebaseAuth.instance; //create user obj based on FirebaseUser User _userFromFirebaseUser(FirebaseUser user) { return user != null ? User(uid: user.uid) : null; } //auth change user stream Stream<User> get user { return _auth.onAuthStateChanged.map(_userFromFirebaseUser); } } class MyPageNotification with ChangeNotifier { //get user stream Stream<User> user = AuthApi().user; } class User { User({this.uid}); String uid; }

投稿2020/02/21 08:46

編集2020/02/22 08:06
popobot

総合スコア6586

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

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

mu-------

2020/02/21 10:48

ご回答ありがとうございます!! asBroadcastStreamを加えたのですが、まだうまくいきません。。。 下記二箇所に入れて試したのですが、入れる場所が間違っているでしょうか?? お手数で本当に申し訳ないのですが、ご教示いただけると助かります。。 AuthApi.dartの下記部分 //auth change user stream Stream<User> get user { return _auth.onAuthStateChanged .map(_userFromFirebaseUser) .asBroadcastStream(); } MypageNotification.dartの下記部分 //get user stream Stream<User> user = AuthApi().user.asBroadcastStream();
mu-------

2020/02/21 14:17

情報ありがとうございます!!! いえいえ、ご回答いただけるだけで嬉しいです!!! すいません、StreamController.broadcastやBehaviorSubjectは今回のAuthApiのようにStream形で返却される箇所で使用できるのでしょうか? いまいち今回の場合での使用方法がわからず、もしわかればご教示いただきたいです。。! 何度も質問してしまい申し訳ありません!!
popobot

2020/02/21 23:27 編集

> すいません、StreamController.broadcastやBehaviorSubjectは今回のAuthApiのようにStream形で返却される箇所で使用できるのでしょうか? 以下のような処理の流れで行けると思います。 // StreamControllerをbroadcastで宣言 StreamController<FirebaseUser> controller = StreamController<FirebaseUser>.broadcast(); // onAuthStateChangedをlistenしてStreamControllerにsinkする FirebaseAuth.instance.onAuthStateChanged.listen((user) => controller.sink.add(user)); // StreamControllerのstreamは複数箇所でlisten()可能になる Stream<FirebaseUser> broadcastStream = controller.stream; // 不要になったらcloseする(disposeで) controller.close(); 以下の質問のコードも参考になろと思います https://stackoverflow.com/questions/53841750/flutter-stream-has-already-been-listened-to
popobot

2020/02/21 23:26 編集

自分の手元でコードを動かしてみましたが、onAuthStateChangedを複数回listen()してもちゃんと、データが流れてきました。FirebaseのStreamは最初からbroadcastかもしれません。 検証に使ったコードを貼ったので、動かしてみてください。 そもそも以下のエラーも出ていないことを考えると、違う原因な気がしてきます。すみません。 Stream has already been listened to 手元の環境でも起動直後は、両方ともConnectionState.waitingが表示されますが、すぐにConnectionState.activeになりました。 手元でもホットリロードだとConnectionState.waitingのままになることがありましたが、ビルドし直してみてもダメでしょうか
popobot

2020/02/21 22:28

後はFlutterやFirebaseのライブラリが最新かどうかなども確認するといいかもです。
mu-------

2020/02/22 03:37 編集

たくさん調べていただいて、大変恐縮です。。 ビルドし直すとは、スマホからアプリをアンインストールし、再度Android Studioでデバックボタンもしくは実行ボタンを押す、という操作で合っているでしょうか。 合っている場合、何度も試していますが、うまくいきません…。 ライブラリは最新であることを確認しております! ページを増やすとは、どのように増やしているのでしょうか。 閲覧画面から編集画面へ、Navigator.pushを使用しているのですが、こちらに問題があるのでしょうか。。 追記致しましたので、見ていただければと思います。 何度もご対応いただき、本当にありがとうございます!!
popobot

2020/02/22 04:12

ビルドし直しは、実行ボタンを押すことを意味しています。ホットリロードの問題ではなさそうですね なるほど、先に閲覧画面でlisten()した後に、編集画面でlisten()しているんですね... これだと閲覧画面で、すでにデータが取得されてしまっているので、編集画面ではデータがこないのかもしれません。 ChangeNotifierProviderとStreamBuilderの組み合わせがややうまく行ってない気がします。 MyAppでStreamProviderでStreamをlisten()するようにして、各画面ではConsumerでStreamの値を読めばいい気がします。 後で時間があったらサンプル書いてみようと思います。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問