firebase_ui_firestore
今後人気が出るかもしれない?
アンドレアさんが、Twitterでご紹介していた、面白そうなパッケージがあったので、使ってみました。
Firestoreの従量課金対策に使えるそうです?
アンドレアさんのブログ
今回使用したパッケージ
翻訳
Cloud Firestoreで作業する場合、ページネーションは大きなデータセットを扱う際に特に有効です。Firestoreのコレクションには何千ものドキュメントが含まれることがあり、それらをすべて一度に取得すると、パフォーマンスに大きな問題が生じ、帯域幅が過度に消費されることがあります。
また、Firestoreは従量制の料金モデルで運営されているため、無料枠(10万文書あたり0.06ドルの課金)を超える文書を読み込むたびに、料金を支払う必要があります。
さらに、一度に表示できる項目は限られているため(特にモバイルデバイス)、ページネーションを実装して必要なデータだけを取得するのが賢明です。
しかし、Cloud Firestoreからデータを読み込む際に、Flutterアプリでページネーションを有効にするにはどうすればよいでしょうか。
公式ドキュメントを検索してみると、クエリカーソルでデータのページ送りができることがわかります。
しかし、この方法をとると、やはりページネーションのコードをすべて手書きで書く必要があります:
さいしょのとりだし
スタートカーソル、エンドカーソルの管理
スクロールダウンしたときの各ページの取得
ページネーションを処理してくれる、使いやすいドロップイン・ウィジェットがあれば便利だと思いませんか?
幸いなことに、Firebase UI for Firestore パッケージには、まさにそれを実現する FirestoreListView ウィジェットが用意されています。
まず使ってみた
必要なパッケージを追加して、Firestoreにダミーのデータを追加してください。私は、ドキュメントを12個ぐらい用意しました。
flutter pub add firebase_core
flutter pub add cloud_firestore
flutter pub add firebase_ui_firestore
以下のコードを追加するだけで、StreamBuilderと同じ動作をしている機能を使用できます。
final usersQuery = FirebaseFirestore.instance.collection('user').orderBy('name');
FirestoreListView<Map<String, dynamic>>(
query: usersQuery,
itemBuilder: (context, snapshot) {
Map<String, dynamic> user = snapshot.data();
return Text('User name is ${user['name']}');
},
);
公式によると、このような役割を果たしてくれるそうです。
無限大スクロール
無限スクロールとは、ユーザーがアプリケーションをスクロールしている間、データベースからより多くのデータを継続的に読み込むという概念です。アプリケーションのレンダリングを高速化し、ユーザーが見ることのないデータのためのネットワークのオーバーヘッドを削減することができるため、大規模なデータセットを持つ場合に便利です。
Firebase UI for Firestoreは、FirestoreListViewウィジェットでFirestoreデータベースを使用した無限スクロールを実装する便利な方法を提供します。
最低限、ウィジェットはFirestoreクエリとアイテムビルダーを受け付けます。ユーザーがリストをスクロールダウン(または横断)すると、より多くのデータがデータベースから自動的にフェッチされます(順序などのクエリ条件を尊重しながら)。
まず、クエリを作成し、アイテムビルダーを提供します。この例では、usersコレクションからユーザーのリストを表示することにします:
コード全体
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_ui_firestore/firebase_ui_firestore.dart';
import 'package:flutter/material.dart';
import 'package:flutter_tutorial/firebase_options.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
runApp(const 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 FirestoreExample(),
);
}
}
class FirestoreExample extends StatelessWidget {
const FirestoreExample({Key? key}) : super(key: key);
Widget build(BuildContext context) {
final usersQuery =
FirebaseFirestore.instance.collection('user').orderBy('name');
return Scaffold(
appBar: AppBar(
title: const Text('Firebase UI'),
),
body: FirestoreListView<Map<String, dynamic>>(
query: usersQuery,
itemBuilder: (context, snapshot) {
Map<String, dynamic> user = snapshot.data();
return Text('User name is ${user['name']}');
},
),
);
}
}
スクリーンショット
横向きにスクロールもできるらしい?
FirestoreListViewウィジェットはFlutter独自のListViewウィジェットの上に構築されており、オプションで提供できる同じパラメータを受け取ります。例えば、スクロール方向を水平に変更する場合:
FirestoreListView<Map<String, dynamic>>(
scrollDirection: Axis.horizontal,
// ...
);
変更後のコード
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_ui_firestore/firebase_ui_firestore.dart';
import 'package:flutter/material.dart';
import 'package:flutter_tutorial/firebase_options.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
runApp(const 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 FirestoreExample(),
);
}
}
class FirestoreExample extends StatelessWidget {
const FirestoreExample({Key? key}) : super(key: key);
Widget build(BuildContext context) {
final usersQuery =
FirebaseFirestore.instance.collection('user').orderBy('name');
return Scaffold(
appBar: AppBar(
title: const Text('Firebase UI'),
),
body: FirestoreListView<Map<String, dynamic>>(
scrollDirection: Axis.horizontal,// 追加すると水平にスクロールする
query: usersQuery,
itemBuilder: (context, snapshot) {
Map<String, dynamic> user = snapshot.data();
return Text('User name is ${user['name']}');
},
),
);
}
}
表示するアイテム数の数を指定するのを試してみたのですが、変化がなかったので、今回こちらは手をつけないでおきます。
WithConverterを使用できるらしい?
一般に、ネットワークのオーバーヘッドを減らすために、この値をできるだけ小さくしておくとよいでしょう。個々のアイテムの高さ(または幅)が大きい場合は、ページサイズを小さくすることをお勧めします。
タイプ分けされた回答の使用
cloud_firestoreプラグインでは、withConverter APIを使用して、データベースから受け取ったレスポンスをタイプすることができます。詳しくは、ドキュメントをご覧ください。
FirestoreListViewは、箱から出してすぐにこれに対応します。例えば、変換されたクエリをウィジェットに提供するだけです:
モデルを定義したファイルを別に作成して、動作を試してみましょう。user_model.dartを作成して、WithConverterで使用するユーザーモデルを作成します。
その後に、main.dartを編集します。いい感じのコードが書けなかったので、皆さんもいろいろ試してみて、いけてるコードを書いてみてください。
class User {
User({required this.name, required this.age});
User.fromJson(Map<String, Object?> json)
: this(
name: json['name']! as String,
age: json['age']! as int,
);
final String name;
final int age;
Map<String, Object?> toJson() {
return {
'name': name,
'age': age,
};
}
}
WithConverterを使用したコード
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_ui_firestore/firebase_ui_firestore.dart';
import 'package:flutter/material.dart';
import 'package:flutter_tutorial/firebase_options.dart';
import 'package:flutter_tutorial/user_model.dart';
late CollectionReference<User> collection;
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
final collectionRef = FirebaseFirestore.instance.collection('user');
collection = collectionRef.withConverter<User>(
fromFirestore: (snapshot, _) => User.fromJson(snapshot.data()!),
toFirestore: (user, _) => user.toJson(),
);
runApp(const 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 FirestoreExample(),
);
}
}
class FirestoreExample extends StatelessWidget {
const FirestoreExample({Key? key}) : super(key: key);
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Firebase UI'),
),
body: FirestoreListView<User>(
query: collection,
itemBuilder: (context, snapshot) {
// Data is now typed!
User user = snapshot.data();
return Column(
children: [
const SizedBox(height: 20.0),
Text(user.name),
Text(user.age.toString()),
],
);
},
),
);
}
}
スクリーンショット
最後に
使ってみた感想ですが、短いコードを書くだけで、縦にも横にもスクロールするStreamBuilderのような機能を使えるのだろうなと思いました。
読み取りコストを下げることもできるらしいので、うまく使えば課金対策になるかもしれないですね。
でもこの手のパッケージってバージョンアップした時に、Firebaseのエラーで詰まることが多いので、アップデートすれば治りますけど、サポートが切れたら使えなくなるので、代替手段を用意しておく必要がありますね。
Discussion