🗄️

FireStoreでListを使う

2023/01/15に公開

Listをどうやって扱う?

以前、ある方に指導をしてもらったときに、FireStoreでarray型の使い方を教えてもらったのを思い出した。
Flutterで、Listを使ったときに、FireStoreにJavaScriptで言うところの配列を保存するには、どうすればいいのか?

  • やること
    • Flutter側でListを使う.
    • []にカッコの中に、Formの値を入れる.
    • 成功すると、FireStoreには、array型で保存される.

今回使用したコード

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

import 'firebase_options.dart';

// タイトルを入れるプロバイダー
final textProvider = StateProvider.autoDispose((ref) {
  return TextEditingController(text: '');
});
// 数値を入れるプロバイダー
final doubleProvider = StateProvider.autoDispose((ref) {
  return TextEditingController(text: '');
});
// FireStoreの'arrays'コレクションのすべてのドキュメントを取得するプロバイダー。初回に全件分、あとは変更があるたびStreamに通知される。
final firebaseArraysProvider = StreamProvider.autoDispose((_) {
  return FirebaseFirestore.instance.collection('arrays').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 PostArrayPage(),
    );
  }
}

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

  
  Widget build(BuildContext context, WidgetRef ref) {
    final titleC = ref.watch(textProvider);
    final doubleC = ref.watch(doubleProvider);
    final AsyncValue<QuerySnapshot> firebaseArrays =
        ref.watch(firebaseArraysProvider);
    final FocusNode nodeText = FocusNode();

    return Scaffold(
      appBar: AppBar(
        title: const Text('POST as Array'),
      ),
      body: SafeArea(
        child: Column(
          children: [
            TextFormField(
              decoration: const InputDecoration(hintText: 'title'),
              controller: titleC,
            ),
            TextFormField(
              decoration: const InputDecoration(
                border: OutlineInputBorder(),
              ),
              keyboardType: TextInputType.number, // keybordに数字だけ表示する.
              inputFormatters: <TextInputFormatter>[
                FilteringTextInputFormatter.digitsOnly
              ],
              focusNode: nodeText,
              controller: doubleC,
            ),
            ElevatedButton(
                onPressed: () async {
                  // 配列を送る。
                  await FirebaseFirestore.instance.collection('arrays').add({
                    "values": [
                      titleC.text,
                      doubleC.text
                    ] // []の中に、TextEditingControllerの値を入れる.
                  });
                },
                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['values'] as List; // List型に変換する.
                    return Card(
                      child: ListTile(
                        // postで送った内容を表示する(titleは丸ごと、subtitleは配列の第一要素)
                        title: Text('List型で取得: ${values.toString()}'),
                        subtitle: Row(
                          children: [
                            Text('配列の0番目を取得: ${values[0]}'),
                            SizedBox(width: 20),
                            Text('配列の1番目を取得: ${values[1]}'),
                          ],
                        ),
                      ),
                    );
                  }).toList(),
                );
              },

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

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

スクリーンショット



やってみた感想

あまり情報がないので、データの型が変わると、入力したり、画面に描画する方法が分からなかったりしました。
公式の解説で、配列を扱っているコードがありました。
今回作ったサンプルは、保存したデータは、keybordを数値入力にしてもString型になってしまうので、dropdownに変更したりして、対策をする必要がありそうです。

もし、double型をFireStoreから、取得する場合は、.toDouble()で変換して、あげないと型のエラーが起きてしまうようです。
FireStoreの方はnumber型なんですけどね笑
以前、折れ線グラフのアプリを作ったときに、ハマりました!

https://firebase.google.com/docs/firestore/manage-data/add-data#dart_1

final docData = {
  "stringExample": "Hello world!",
  "booleanExample": true,
  "numberExample": 3.14159265,
  "dateExample": Timestamp.now(),
  "listExample": [1, 2, 3],
  "nullExample": null
};

final nestedData = {
  "a": 5,
  "b": true,
};

docData["objectExample"] = nestedData;

db
    .collection("data")
    .doc("one")
    .set(docData)
    .onError((e, _) => print("Error writing document: $e"));

Discussion