🍁

Map型で値を保存してみる

2023/01/16に公開

FireStoreにKey、Valueでデータを保存する

FireStoreにデータをMap型でデータを保存する方法を以前ある方から教わっていたのを思い出して、お店の投稿をするプログラムを作って、Demoアプリを作ってみました。
あまりMap型の情報ってないから、記事書いてみようと思いました。

もしMap型を普段から使っている方いたら、どんな目的で使うか教えていただきたいです🙇‍♂️

FireStoreのデータ

Map型だとこのように、保存されます。どんな仕組みかというと、{}の中に、{}が入っていて、ネストしているデータが保存されています。
バックエンドの言語しか情報がなかったので、あまり参考にならないかもしれないですが、JavaScriptだとこんな感じになります。

ネストしてるでしょう
入れ子って意味らしいですね。

const data = {
  stringExample: 'Hello, World!',
  booleanExample: true,
  numberExample: 3.14159265,
  dateExample: Timestamp.fromDate(new Date('December 10, 1815')),
  arrayExample: [5, true, 'hello'],
  nullExample: null,
  objectExample: {
    a: 5,
    b: true
  }
};

const res = await db.collection('data').doc('one').set(data);

Dartだとこうなる

ElevatedButton(
                onPressed: () async {
                  // Map型でデータを保存。
                  await FirebaseFirestore.instance.collection('maps').add({
                    'shop': {'title': titleC.text, 'review': reviewC.text}
                  });
                },
                child: const Text('投稿')),

GoogleCloudのサイトにもFirebaseの情報がありました。参考になるかも?
https://cloud.google.com/firestore/docs/samples/firestore-data-set-from-map-nested?hl=ja#firestore_data_set_from_map_nested-nodejs

Demoアプリのスクリーンショット

こちらが今回使用したコード

main.dart
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

import 'firebase_options.dart';

// タイトルを入れるプロバイダー
final titleProvider = StateProvider.autoDispose((ref) {
  return TextEditingController(text: '');
});
// レビューを入れるプロバイダー
final reviewProvider = StateProvider.autoDispose((ref) {
  return TextEditingController(text: '');
});

// FireStoreの'maps'コレクションのすべてのドキュメントを取得するプロバイダー。初回に全件分、あとは変更があるたびStreamに通知される。
final firebaseMapsProvider = StreamProvider.autoDispose((_) {
  return FirebaseFirestore.instance.collection('maps').snapshots();
});

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();

  await Firebase.initializeApp(
    options: DefaultFirebaseOptions.currentPlatform,
  );

  runApp(
    const ProviderScope(child: MyApp()),
  );
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const PostMapPage(),
    );
  }
}

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

  
  Widget build(BuildContext context, WidgetRef ref) {
    final titleC = ref.watch(titleProvider);
    final reviewC = ref.watch(reviewProvider);
    final AsyncValue<QuerySnapshot> firebaseArrays =
        ref.watch(firebaseMapsProvider);
    final FocusNode nodeText = FocusNode();

    return Scaffold(
      appBar: AppBar(
        title: const Text('POST as Map'),
      ),
      body: SafeArea(
        child: Column(
          children: [
            TextFormField(
              decoration: const InputDecoration(hintText: 'タイトル'),
              controller: titleC,
            ),
            TextFormField(
              decoration: const InputDecoration(hintText: 'レビュー'),
              controller: reviewC,
            ),
            ElevatedButton(
                onPressed: () async {
                  // Map型でデータを保存。
                  await FirebaseFirestore.instance.collection('maps').add({
                    'shop': {'title': titleC.text, 'review': reviewC.text}
                  });
                },
                child: const Text('投稿')),
            Expanded(
                // Streamに通知が来た(=初回もしくは変更時の)
                child: firebaseArrays.when(
              // データがあった(データはqueryの中にある)
              data: (QuerySnapshot query) {
                // post内のドキュメントをリストで表示する
                return ListView(
                  // post内のドキュメント1件ずつをCard枠を付けたListTileのListとしてListViewのchildrenとする
                  children: query.docs.map((document) {
                    var values = document['shop'] as Map; // List型に変換する.
                    return Card(
                      child: ListTile(
                        // postで送った内容を表示する(titleは丸ごと、subtitleは配列の第一要素)
                        title: Text('Map型で取得: ${values.toString()}'),
                        subtitle: Column(
                          children: [
                            SizedBox(height: 20),
                            Text('Keyを指定してデータを取得'),
                            SizedBox(height: 20),
                            Text('購入した商品: ${values['title'].toString()}'),
                            SizedBox(height: 20),
                            Text('レビュー: ${values['review'].toString()}'),
                          ],
                        ),
                      ),
                    );
                  }).toList(),
                );
              },

              // データの読み込み中(FireStoreではあまり発生しない)
              loading: () {
                return const Text('Loading');
              },

              // エラー(例外発生)時
              error: (e, stackTrace) {
                return Text('error: $e');
              },
            ))
          ],
        ),
      ),
    );
  }
}

一応知り合いの人なんですけど、むっくさんの技術記事が参考になるなと、個人的に思います。

https://zenn.dev/mukkun69n/articles/7ac3f1a9111cad

やってることは、型をMapにcastしてkeyを指定してデータを取得しているだけ。
なんのことかわからないですね😅

説明すると、mathScoresがMap型の変数の名前で、'John'がKeyで、80がValueです。
FireStoreのデータもMap型だったりするんですけどね。
<String, dynamic>だから、Map型

// 生徒の数学の点数をまとめたMap型の変数
Map<String, int> mathScores = {
  'John' : 80,
  'Mary' : 60,
}

最後に

FireStoreで、Map型って情報があまりないですが、一応書けるので文法上は問題ない?

  • やったことは単純で
    • FireStoreにMap型でデータを保存.
    • DartのMap型でFireStoreの値を習得する.
    • book['page1']こんなふうに書く.

Discussion