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

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

ただいまの
回答率

88.20%

Flutter,Firebase : DocumentSnapshot型の値の取得方法、

解決済

回答 1

投稿

  • 評価
  • クリップ 0
  • VIEW 615

moriman

score 119

https://firebase.flutter.dev/docs/firestore/usage#document--query-snapshots

import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:flutter_signin_button/button_builder.dart';

import './register_page.dart';
import './signin_page.dart';

import 'package:cloud_firestore/cloud_firestore.dart';

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();
  runApp(AuthExampleApp());
}

/// The entry point of the application.
///
/// Returns a [MaterialApp].
class AuthExampleApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        title: 'Firebase Example App',
        theme: ThemeData.dark(),
        home: Scaffold(
          body: AuthTypeSelector(),
        ));
  }
}

/// Provides a UI to select a authentication type page
class AuthTypeSelector extends StatelessWidget {
  // Navigates to a new page
  void _pushPage(BuildContext context, Widget page) {
    Navigator.of(context).push(
      MaterialPageRoute<void>(builder: (_) => page),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Firebase Example App"),
      ),
      body: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: <Widget>[
          Container(
            child: SignInButtonBuilder(
              icon: Icons.person_add,
              backgroundColor: Colors.indigo,
              text: 'Registration',
              onPressed: () => _pushPage(context, RegisterPage()),
            ),
            padding: const EdgeInsets.all(16),
            alignment: Alignment.center,
          ),
          Container(
            child: SignInButtonBuilder(
              icon: Icons.verified_user,
              backgroundColor: Colors.orange,
              text: 'Sign In',
              onPressed: () => _pushPage(context, SignInPage()),
            ),
            padding: const EdgeInsets.all(16),
            alignment: Alignment.center,
          ),
          UserInformation(),
        ],
      ),
    );
  }
}

class UserInformation extends StatelessWidget {
  @override
  Widget build(BuildContext context) {

    CollectionReference users = FirebaseFirestore.instance.collection('users');
    users.get().then((QuerySnapshot querySnapshot) {
      querySnapshot.docs.forEach((doc) {
        //↓docはDocumentSnapshot型なのでこの形の参照で値が得られるのか?
        print('😈${doc["full_name"]},${doc.runtimeType},${doc.reference}');

        //↓doc.data()の返り値はMap<String,dynamic>なのでこの形で値が得られることに
        //全く疑問は無い。
        print('👼${doc.data()["full_name"]}');
      });
    });

    return StreamBuilder<QuerySnapshot>(
      stream: users.snapshots(),
      builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
        if (snapshot.hasError) {
          return Text('Something went wrong');
        }

        if (snapshot.connectionState == ConnectionState.waiting) {
          return Text("Loading");
        }

        //ListViewをColumnの中に入れようとするとエラーが出るので、
        //↓Expandedでラップする必要がある。
        return Expanded(
          child: new ListView(
            children: snapshot.data.docs.map((DocumentSnapshot document) {
              return new ListTile(
                title: new Text(document.data()['full_name']),
                subtitle: new Text(document.data()['company']),
              );
            }).toList(),
          ),
        );
      },
    );
  }
}
//result
flutter: 😈Test Tarou,QueryDocumentSnapshot,DocumentReference(users/CgewvkhWjYeuW5trtFJ8)
flutter: 👼Test Tarou
flutter: 😈Test Jirou,QueryDocumentSnapshot,DocumentReference(users/CzyuXDo6TQuKATaOuKeU)
flutter: 👼Test Jirou
flutter: 😈Test Sabu,QueryDocumentSnapshot,DocumentReference(users/nww8lBuC8j2q05cXQBUA)
flutter: 👼Test Sabu
flutter: 😈Test Gorouxx,QueryDocumentSnapshot,DocumentReference(users/rQhMqCKtlwLPtiKK0azZ)
flutter: 👼Test Gorouxx
flutter: 😈Test Tarou,QueryDocumentSnapshot,DocumentReference(users/CgewvkhWjYeuW5trtFJ8)
flutter: 👼Test Tarou
flutter: 😈Test Jirou,QueryDocumentSnapshot,DocumentReference(users/CzyuXDo6TQuKATaOuKeU)
flutter: 👼Test Jirou
flutter: 😈Test Sabu,QueryDocumentSnapshot,DocumentReference(users/nww8lBuC8j2q05cXQBUA)
flutter: 👼Test Sabu
flutter: 😈Test Gorouxx,QueryDocumentSnapshot,DocumentReference(users/rQhMqCKtlwLPtiKK0azZ)
flutter: 👼Test Gorouxx


FlutterFireのドキュメントのサンプルを動かしていたのですが、
二つ疑問が発生しました。

(1)コードのコメントにも書いてますが、
querySnapshot.docs.forEach
のコールバックの引数docはDocumentSnapshot型だと思うのですが、
doc["full_name"]の形で、各ドキュメントの"full_name"フィールドの値を取得できています。
これに違和感を感じているのですが、DocumentSnapshot型はMap型なのでしょうか?
document_snapshot.dartを見てもそのようには見えないのですが、
Map型じゃないのに
doc["full_name"]の形で値が取得できるのでしょうか。
まあできているのですが。
というかdoc["full_name"]で値が取得できるのならdata()メソッドは必要ないんじゃないか、
と思ってしまいます。

(2)'users'コレクションには現在ドキュメントが四つ存在しているのですが、
なぜか4つのドキュメントが2回、4✖️2=8回コンソールに表示されています。
上記のコードだと各ドキュメントが1回ずつ、つまり4回表示されるはずだと思うのですが、
なぜ各ドキュメントが2回表示されるのでしょうか。

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

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

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

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

checkベストアンサー

0

(1) doc["full_name"]の形でアクセスできるのは、以下の様に、[]演算子のオーバーライドが実装されているからです。doc["full_name"]と書けば、get("full_name")が呼ばれることになります。

dynamic operator [](dynamic field) => get(field);

data()メソッドと[]演算子(get()メソッド)の違いですが、前者は、Mapを返すだけなので、もし存在しないフィールド名でアクセスしても、nullを返すだけですが、後者は、例外を返します。また、後者は、FieldPathクラスを渡すこともでき、マップで入れ子になっているデータにも一気にアクセスすることができます。

doc[FieldPath(['name', 'first'])];

(2) おそらく、buildメソッドが2回動いたのだと思います。buildメソッドは複数回動くものだと想定しておいて下さい。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2020/10/28 11:36

    回答を頂きましてありがとうございました。

    (1)確かにありますね(笑)二つの違いも納得できました。
    (2)なるほどですね。そう言われれば確かにそうですね、その発想は思いつきませんでした。
    ちょっともう、Flutterの中の話になると考えてもキリがない、というか限界があると思うのですが、
    一応少し試したみた結果、
    停止→再実行すると一回の表示でした。
    Hot Restartすると、質問文に記載したように二回表示されました。
    昨日はHot Restartしていたような気がします。
    はっきりとは分かりませんが、再実行とHot Restartの違いなのかもしれません。

    streamでもそうですし、それ以外の状態管理でも、状態が変わるたびにbuildメソッドが実行されるというのはそうですよね。
    ありがとうございました。

    キャンセル

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

  • ただいまの回答率 88.20%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る