🔎

if文でFirestoreが検索できる?

2023/02/01に公開

たまたま見つけた!

Firestoreのデータを検索する方法が、where文を使えばできるらしいですが、うまくいかなくて困っていたのですが、こちらのYouTube動画でif文を使って名前で検索する方法を見つけました!
https://www.youtube.com/watch?v=WbXTl9tiziI

  • やること
    • Firestoreにmoviesコレクションを作成する
    • titleフィールドとgenreフィールドを作成する
    • 映画のタイトルとジャンルを入力してデータを保存する


Demoアプリのソースコード

今回はこのようなアプリを作成いたしました!

https://youtu.be/BlMmsnHYY70

完成品

https://github.com/sakurakotubaki/MovieSearch

以前作成した映画の情報を保存するアプリに検索機能を追加しただけです。これを応用すれば便利な機能が開発できるかも?

ファイルの構成はこれだけです。

lib
├── firebase_options.dart
├── main.dart
├── model
│   └── movie_model.dart
└── ui
    └── movie_page.dart

公式のモデルクラスをそのまま定義

model/movie_model.dart
import 'package:cloud_firestore/cloud_firestore.dart';

class Movie {
  Movie({required this.title, required this.genre});

  Movie.fromJson(Map<String, Object?> json)
      : this(
          title: json['title']! as String,
          genre: json['genre']! as String,
        );

  final String title;
  final String genre;

  Map<String, Object?> toJson() {
    return {
      'title': title,
      'genre': genre,
    };
  }
}

final moviesRef =
    FirebaseFirestore.instance.collection('movies').withConverter<Movie>(
          fromFirestore: (snapshot, _) => Movie.fromJson(snapshot.data()!),
          toFirestore: (movie, _) => movie.toJson(),
        );

検索機能をつけたアプリの画面
Formに値を入力するとonChangeで保存されて、下のコードに渡されて、if文で検索ワードを判定してヒットしたデータだけ表示してくれるようです。

ui/movie_page.dart
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firestore_search/model/movie_model.dart';
import 'package:flutter/material.dart';

class MoviePage extends StatefulWidget {
  const MoviePage({Key? key}) : super(key: key);

  
  State<MoviePage> createState() => _MoviePageState();
}

class _MoviePageState extends State<MoviePage> {
  // 検索Formの値を保存する変数.
  var title = "";

  
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
            backgroundColor: Colors.redAccent,
            // AppBar検索Form.
            title: Card(
              child: TextField(
                decoration: const InputDecoration(
                    prefixIcon: Icon(Icons.search), hintText: '映画を検索...'),
                onChanged: (val) {
                  // ここにFormの値が入ってくる.
                  setState(() {
                    title = val; // 上で定義した変数に値を保存する.
                  });
                },
              ),
            )),
        body: StreamBuilder<QuerySnapshot<Movie>>(
          // StreamBuilderで全てのデータを習得.
          // クラスを型として使う.
          stream: moviesRef.snapshots(), // withConverterのデータをstreamで流す.
          builder: (context, snapshot) {
            if (snapshot.hasError) {
              return Center(
                child: Text(snapshot.error.toString()),
              );
            }

            if (!snapshot.hasData) {
              return const Center(child: CircularProgressIndicator());
            }

            final data = snapshot.requireData;

            return ListView.builder(
                itemCount: data.size, // データの数をカウントする.
                itemBuilder: (context, index) {
                  if (title.isEmpty) {
                    // ここから、検索ワードを調べる.
                    final doc = data.docs[index].data();
                    return ListTile(
                      title: Text(
                        doc.title,
                        maxLines: 1,
                        overflow: TextOverflow.ellipsis,
                        style: const TextStyle(
                            color: Colors.black54,
                            fontSize: 16,
                            fontWeight: FontWeight.bold),
                      ),
                      subtitle: Text(
                        doc.genre,
                        maxLines: 1,
                        overflow: TextOverflow.ellipsis,
                        style: const TextStyle(
                            color: Colors.black54,
                            fontSize: 16,
                            fontWeight: FontWeight.bold),
                      ),
                    );
                  }
                  final doc = data.docs[index].data();
                  if (doc.title
                      .toString()
                      .toLowerCase()
                      .startsWith(title.toLowerCase())) {
                    return ListTile(
                      title: Text(
                        doc.title,
                        maxLines: 1,
                        overflow: TextOverflow.ellipsis,
                        style: const TextStyle(
                            color: Colors.black54,
                            fontSize: 16,
                            fontWeight: FontWeight.bold),
                      ),
                      subtitle: Text(
                        doc.genre,
                        maxLines: 1,
                        overflow: TextOverflow.ellipsis,
                        style: const TextStyle(
                            color: Colors.black54,
                            fontSize: 16,
                            fontWeight: FontWeight.bold),
                      ),
                    );
                  }
                  return Container(); // データがなかったら何もないContainerを表示する.
                });
          },
        ));
  }
}

アプリを実行するコード

main.dart
import 'package:firebase_core/firebase_core.dart';
import 'package:firestore_search/ui/movie_page.dart';
import 'package:flutter/material.dart';

import 'firebase_options.dart';

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

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

まとめ

if文で条件が一致したら候補を表示できるのは、始めて知りました!
でも本格的な検索機能を使うとしたら、外部サービスに課金しないとダメでしょうね。
できるだけ無料の機能を使いたい💦

補足情報

Firebaseに詳しい方から、コメントをいただいたのですが、どうやら全部のデータを取得して、アプリ側で表示を変えているだけの様に、見えるみたいです!

読み取り回数が多いと、もし1万件とかだったら、メモリに負荷がかかってします!
パフォーマンスが悪い!
でも、使い方次第では、この方法でも対応できる要件はあるかもしれないですね。

やはり、検索機能を使えるサービスが必要ですね😅
面白い機能だなと、個人的に思うので、記事は消さずに残しておきます。

なので、where文を使った記事も書きました!
ご興味ある方は見てみください!

https://zenn.dev/flutteruniv_dev/articles/13fd1d2e2d3397

Discussion