📟
Firestoreでページネーションを使う
ページネーションとは?
Webサイトやアプリ内の情報を複数のページに分割して表示する手法です。 一度に全てのデータを表示するのではなく、一部だけを表示し、ユーザーが次のページや前のページに移動することで残りの情報を確認出来るようにします。
こんな感じのものを作りました。ちょっとわかりにくいですが、画面下にローディング表示されます。
🔥Firestore使うときには対策が必要
Firestoreはデータを読み取るのは、1日5万件までは無料で、上限を超えると課金されます😱
なので、SNSのような多くのユーザーが使うアプリや、多くのデータを表示するアプリで課金されないように対策が必要です。
公式の情報だとわかりにくい
AIじゃないと詳しく教えてくれない!
🔥ダミーのデータを作るメソッド
Firestoreは、Supabaseのように、CSVをimportしてダミーのデータを入れることができないので、for文でループして、addメソッドを指定した回数実行するコードを作りました。
ボタンを押すときに、実行すればOK!
// ダミーデータを作成する
Future<void> createDummyData() async {
final firestore = FirebaseFirestore.instance;
final collection = firestore.collection('users');
// for文で15件のダミーデータを作成する
for (int i = 0; i < 30; i++) {
await collection.add({
'name': 'User $i',
'createdAt': Timestamp.now(),
});
}
}
AppBarのところに、プラスボタンを配置して、ここでダミーのデータを保存するメソッドを実行します。
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
actions: [
IconButton(
// ダミーデータを作成するボタンを追加する
onPressed: () async {
await createDummyData();
},
icon: const Icon(Icons.add),
),
],
title: const Text('User View'),
),
今回実装した機能は、こちらのコードです。綺麗なコードではないですね💦
全体のコード
ページネーションのコード
import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
class UserView extends StatefulWidget {
const UserView({Key? key}) : super(key: key);
_UserViewState createState() => _UserViewState();
}
class _UserViewState extends State<UserView> {
final ScrollController _scrollController = ScrollController();
final FirebaseFirestore _firestore = FirebaseFirestore.instance;
final List<DocumentSnapshot> _users = [];
bool _isLoading = false;
DocumentSnapshot? _lastDocument;
final int _documentLimit = 15;
void initState() {
super.initState();
_scrollController.addListener(_scrollListener);
_loadUsers();
}
void _loadUsers() async {
if (_isLoading) return;
setState(() {
_isLoading = true;
});
QuerySnapshot querySnapshot;
if (_lastDocument == null) {
querySnapshot = await _firestore
.collection('users')
.orderBy('createdAt', descending: true)
.limit(_documentLimit)
.get();
} else {
querySnapshot = await _firestore
.collection('users')
.orderBy('createdAt', descending: true)
.startAfterDocument(_lastDocument!)
.limit(_documentLimit)
.get();
}
if (querySnapshot.docs.isNotEmpty) {
_lastDocument = querySnapshot.docs.last;
_users.addAll(querySnapshot.docs);
}
setState(() {
_isLoading = false;
});
}
void _scrollListener() {
if (_scrollController.position.atEdge &&
_scrollController.position.pixels != 0 && !_isLoading) {
_loadUsers();
}
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('User View'),
),
body: ListView.builder(
controller: _scrollController,
itemCount: _users.length + (_isLoading ? 1 : 0),
itemBuilder: (context, index) {
if (index < _users.length) {
final user = _users[index];
return ListTile(
title: Text(user['name']),
);
} else {
// ローディングインジケーターを表示
return Padding(
padding: EdgeInsets.symmetric(vertical: 32.0),
child: Center(
child: CircularProgressIndicator(),
),
);
}
},
),
);
}
void dispose() {
_scrollController.dispose();
super.dispose();
}
}
アプリを実行するコード
main.dart
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:page_nation_app/user_view.dart';
import '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(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: const UserView(),
);
}
}
最後に
今回は、ページネーションなるものを作ってみました。
riverpodを使用して状態の管理をおこなおうとしたのですが、うまくいきませんでした!
これは今後の課題ですね。
Discussion