2020/10/26 Firebase : Cloud Firestore/Usageの訳パート1

 

 

 

プロジェクトでCloud Firestoreパッケージを使う場合、プロジェクトファイルのトップでインポートしてください。

import 'package:cloud_firestore/cloud_firestore.dart';

Firestoreを使用する前に、まずFlutterFireを初期化したことを確認してください。


新しいFirestoreインスタンスを生成するには、以下のようにFirebaseFirestoreクラスのゲッター、instanceを参照してください。

FirebaseFirestore firestore = FirebaseFirestore.instance;

デフォルトでは、これにより、プラットフォームにFlutterFireをインストールするときに使用されるデフォルトのFirebaseアプリを使用してFirestoreとやり取りできます。 ただし、セカンダリFirebaseアプリでFirestoreを使用する場合は、instanceForメソッドを使用します。工事中🏗

FirebaseApp secondaryApp = Firebase.app('SecondaryApp');
FirebaseFirestore firestore = FirebaseFirestore.instanceFor(app: secondaryApp);

Collections & Documents

Firestoreは、「コレクション」に含まれる「ドキュメント」内にデータを格納します。 ドキュメントには、ネストされたコレクションを含めることもできます。 たとえば、ユーザーはそれぞれ、「Users」コレクション内に独自の「ドキュメント」を保存します。 collectionメソッドを使用すると、コード内でコレクションを参照できます。

下記のサンプルはusersコレクションを参照し、ボタンが押された時に新しいuserドキュメントを生成します(書き込みます)。

import 'package:firebase_core/firebase_core.dart';
import 'package:cloud_firestore/cloud_firestore.dart';

class AddUser extends StatelessWidget {
  final String fullName;
  final String company;
  final int age;

  AddUser(this.fullName, this.company, this.age);

  @override
  Widget build(BuildContext context) {
    // Create a CollectionReference called users that references the firestore collection
    CollectionReference users = FirebaseFirestore.instance.collection('users');

    Future<void> addUser() {
      // Call the user's CollectionReference to add a new user
      return users
          .add({
            'full_name': fullName, // John Doe
            'company': company, // Stokes and Sons
            'age': age // 42
          })
          .then((value) => print("User Added"))
          .catchError((error) => print("Failed to add user: $error"));
    }

    return FlatButton(
      onPressed: addUser,
      child: Text(
        "Add User",
      ),
    );
  }
}

Read Data

Cloud Firestoreを使用すると、コレクションまたはドキュメントの値を読み取ることができます。 これは、1回限りの読み取り、またはクエリ内のデータが変更されたときのリアルタイム更新によって提供されます。


One-time Read

コレクションまたはドキュメントを1回読み取るには、Query.getメソッドまたはDocumentReference.getメソッドを呼び出します。 以下の例では、FutureBuilderを使用してリクエストの状態を管理します。

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,
          ),

          AddUser('Test Tarou','TesTes.inc',20),

          GetUserName(
              'rQhMqCKtlwLPtiKK0azZ',
          ),

        ],
      ),
    );
  }
}


//https://firebase.flutter.dev/docs/firestore/usage#collections--documents
class AddUser extends StatelessWidget {
  final String fullName;
  final String company;
  final int age;

  AddUser(this.fullName, this.company, this.age);

  @override
  Widget build(BuildContext context) {
    // Create a CollectionReference called users that references the firestore collection
    CollectionReference users = FirebaseFirestore.instance.collection('users');

    Future<void> addUser() {
      // Call the user's CollectionReference to add a new user
      return users
          .add({
        'full_name': fullName, // John Doe
        'company': company, // Stokes and Sons
        'age': age // 42
      })
          .then((value) => print("User Added"))
          .catchError((error) => print("Failed to add user: $error"));
    }

    return FlatButton(
      color:Colors.yellow,
      onPressed: addUser,
      child: Text(
        "Add User",
      ),
    );
  }
}

//https://firebase.flutter.dev/docs/firestore/usage#read-data
class GetUserName extends StatelessWidget {
  final String documentId;

  GetUserName(this.documentId);

  @override
  Widget build(BuildContext context) {
    CollectionReference users = FirebaseFirestore.instance.collection('users');

    return FutureBuilder<DocumentSnapshot>(
      future: users.doc(documentId).get(),
      builder:
          (BuildContext context, AsyncSnapshot<DocumentSnapshot> snapshot) {

        if (snapshot.hasError) {
          return Text("Something went wrong");
        }

        if (snapshot.connectionState == ConnectionState.done) {
          Map<String, dynamic> data = snapshot.data.data();
          return Text("Full Name: ${data['full_name']} ${data['age']}");
        }

        return Text("loading");
      },
    );
  }
}


Realtime changes

FlutterFireは、コレクションとドキュメントへのリアルタイムの変更を処理するためのサポートを提供します。

最初のリクエストで新しいイベントが提供され、変更が発生するたびにコレクション/ドキュメントにその後の変更が(streamのイベントとして)加えられます(変更、削除、または追加)。

CollectionReferenceとDocumentReferenceはどちらも、Streamを返すsnapshots()メソッドを提供します。

Stream collectionStream = FirebaseFirestore.instance.collection('users').snapshots();
Stream documentStream = FirebaseFirestore.instance.collection('users').doc('ABC123').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');

    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(),
          ),
        );
      },
    );
  }
}

StreamBuilderなので、コンソールでusersコレクション内のドキュメントのデータを書き換えると、即時にスクリーンの表示に反映されます。


デフォルトでは、メタデータにのみ影響する変更がある場合、リスナーは更新されません。

ドキュメントまたはクエリのメタデータが変更されたときにイベントを受信する場合は、includeMetadataChangesパラメータ(defalut値はfalse)をsnapshotsメソッドに渡すことができます。

FirebaseFirestore.instance
  .collection('users')
  .snapshots(includeMetadataChanges: true)

Document & Query Snapshots

クエリを実行すると、FirestoreはQuerySnapshotまたはDocumentSnapshotのいずれかを返します。

「クエリを実行」とはfirestoreのデータを問い合わせるメソッドを実行すると、ということでしょうね。


QuerySnapshot

FirebaseFirestore.instance
    .collection('users')
    .get()
    .then((QuerySnapshot querySnapshot) => {
        querySnapshot.docs.forEach((doc) {
            print(doc["first_name"]);
        });
    });

 

QuerySnapshotは、コレクションクエリの結果として返されます。QuerySnapshotを使ってコレクションを調べることができます。

例えば、

  • 「コレクションの中にドキュメントが何個存在するか?」
  • コレクションの中のドキュメントにアクセスする方法
  • 最後のクエリから変更されたドキュメントはどれか?

などです。

QuerySnapshotの中のドキュメントにアクセスするには、docsプロパティを参照します。

docsプロパティはList<DocumentSnapshot>を返します。

FirebaseFirestore.instance
    .collection('users')  //←CollectionReference型
    .get()  
    .then((QuerySnapshot querySnapshot) => {
        querySnapshot.docs.forEach((doc) {
            print(doc["first_name"]);
        });
    });

get()メソッドはQuery型(query.dart)で定義されている。

CollectionReferenceクラスが、QueryクラスのサブクラスなのでCollectionReference型もget()メソッドを(継承して)持っている。

//query.dart

Future<QuerySnapshot> get([GetOptions options]) async {
...
}

get()メソッドの返り値はFuture<QuerySnapshot>型。

querySnapshot.docsが

List<QueryDocumentSnapshot>

型を返す。(collection_reference.dart)

//main.dart

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) {
        print('${doc["full_name"]},${doc.runtimeType},${doc.reference}');
        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(),
          ),
        );
      },
    );
  }
}

 


DocumentSnapshot

DocumentSnapshotは、クエリから、またはドキュメントに直接アクセスすることによって返されます。 データベースにドキュメントが存在しない場合でも、スナップショットが常に返されます。

ドキュメントが存在するかどうかを判断するには、existsプロパティを使用します。

//main.dart
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>[
          UserInformation(),
        ],
      ),
    );
  }
}

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

    CollectionReference users = FirebaseFirestore.instance.collection('users');
    //↓存在するdocumentIDを指定すると
    //コンソールに'Document exists on the database'と表示される。
    users.doc('CzyuXDo6TQuKATaOuKeU')
        .get()
        .then((DocumentSnapshot documentSnapshot) {
      if (documentSnapshot.exists) {
        print('Document exists on the database');
      }
    });

    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(),
          ),
        );
      },
    );
  }
}

FirebaseFirestore.instance
    .collection('users')
    .doc(userId)
    .get()
    .then((DocumentSnapshot documentSnapshot) {
      if (documentSnapshot.exists) {
        print('Document data: ${documentSnapshot.data()}');
      } else {
        print('Document does not exist on the database');
      }
    });
//main.dart
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>[
          UserInformation(),
        ],
      ),
    );
  }
}

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

    CollectionReference users = FirebaseFirestore.instance.collection('users');
    users.doc('CzyuXDo6TQuKATaOuKeU')
        .get()
        .then((DocumentSnapshot documentSnapshot) {
      if (documentSnapshot.exists) {
        print('Document exists:${documentSnapshot.data()}');
      }else{
        print('Document does not exist on the database');
      }
    });

    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(),
          ),
        );
      },
    );
  }
}

ドキュメントが存在する場合は、dataメソッドを呼び出すことでそのデータを読み取ることができます。このメソッドはMap <String、dynamic>を返し、存在しない場合はnullを返します。


//main.dart

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>[
          GetUserName('CzyuXDo6TQuKATaOuKeU'),
        ],
      ),
    );
  }
}

class GetUserName extends StatelessWidget {
  final String documentId;

  GetUserName(this.documentId);

  @override
  Widget build(BuildContext context) {
    CollectionReference users = FirebaseFirestore.instance.collection('users');

    return FutureBuilder<DocumentSnapshot>(
      future: users.doc(documentId).get(),
      builder:
          (BuildContext context, AsyncSnapshot<DocumentSnapshot> snapshot) {
        if (snapshot.hasError) {
          return Text("Something went wrong");
        }

        if (snapshot.connectionState == ConnectionState.done) {
          Map<String, dynamic> data = snapshot.data.data();//(A)
          return Text("Full Name: ${data['full_name']} ${data['age']}::::${snapshot.data.get(FieldPath(['full_name', 'first_name']))}");
        }

        return Text("loading");
      },
    );
  }
}

↑に表示している、

documentID:’CzyuXDo6TQuKATaOuKeU’

のドキュメントのfull_nameフィールドの値をmap型にした。

このドキュメントの

first_nameフィールド

last_nameフィールド

を取得したい時に、

(A)のようにまずマップ型として取得して、そのフィールドを

data[‘full_name’]

のように参照することもできるが、

DocumentSnapshotクラスのget()メソッドを使って直接、

snapshot.data.get(FieldPath([‘full_name’, ‘last_name’]))

のようにネストされているデータ構造の値を取得することもできますよ、という話。


次のページへ>>

 

 

 

参考

https://firebase.flutter.dev/docs/firestore/usage

コメントを残す

メールアドレスが公開されることはありません。