【Flutter】Cloud Firestoreのデータを取得してListView.builderで表示する。
はじめに
あるWidgetの下にリストを表示したい時に、
ListViewを使うとはみ出してしまっていた表示が、ListView.builderを使って表示できたので
その時の内容をメモとして残したいと思います。
Firestoreの環境構築やデータをとってくる部分はKboyさんの動画を参考にしてます。
かなりわかりやすいのでこちらも参考にしてみてください。
【Flutter実践】ModelからFirestoreのデータを取得する(mapの使い方も) - YouTube
この記事がどなたかの参考になれば幸いです。
また、もし間違い等ございましたらご指摘いただけるとありがたいです。
目的
Cloud Firestoreにある本一覧のデータをとって、ListView.builderで画面に表示します。
provider(ChangeNotifierとConsumer)を使い、main.dartでUI、main_model.dartで状態管理という書き方でやってみてます。
ListView.builderを使う理由
The standard ListView constructor works well for small lists. To work with lists that contain a large number of items, it’s best to use the ListView.builder constructor.
標準的なListViewのコンストラクタは、小さなリストではうまく機能します。多数のアイテムを含むリストを扱うには、ListView.builder コンストラクタを使用するのがベストです。
Work with long lists | Flutter
環境
MacOS12.1 monterey
Dart SDK version: 2.14.4 (stable)
flutter doctor
[✓] Flutter (Channel stable, 2.5.3, on macOS 12.1 21C52 darwin-arm, locale ja-JP)
[✓] Android toolchain - develop for Android devices (Android SDK version 31.0.0)
[✓] Xcode - develop for iOS and macOS
[✓] Chrome - develop for the web
[✓] Android Studio (version 2020.3)
[✓] Android Studio (version 2020.3)
[✓] Connected device (2 available)
• No issues found!
Firestoreはアプリに導入済とします。
導入については説明しておりません。導入については以下参考にしてください。
【Flutter実践】ModelからFirestoreのデータを取得する(mapの使い方も) - YouTube
Flutter アプリに Firebase を追加する | Firebase Documentation
Firestore, providerバージョン
dependencies:
flutter:
sdk: flutter
provider: ^6.0.2
firebase_core: ^1.11.0
cloud_firestore: ^3.1.6
Firestoreでは以下のデータを持っているとします。
- コレクション 'books'
- 各ドキュメントが持っているフィールド'title'
※画像にはcreatedAtがありますが、今回は使いません。
完成図
作る時の流れ
- Firebaseの初期化処理をかく
- Firestoreのデータを扱うクラスを別ファイルで作る
- modelを作る
- ListView.builderにmodelの処理を反映させる。
1. Firebaseの初期化処理をかく
Firebaseの初期化処理を以下のようにmain()に書きます。
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(MyApp());
}
2. FirestoreのDocumentを扱うクラスを別ファイルで作る
Firestoreのドキュメントが持っているフィールドを扱うクラスを作ります。
今回は本を扱うのでBook classとします。
Bookが持っているフィールドを定義し、
コンストラクタでfirestoreのデータをこのクラスのフィールド'title'に渡すようにします。
import 'package:cloud_firestore/cloud_firestore.dart';
// firestoreのドキュメントを扱うクラスBookを作る。
class Book {
// ドキュメントを扱うDocumentSnapshotを引数にしたコンストラクタを作る
Book(DocumentSnapshot doc) {
// ドキュメントの持っているフィールド'title'をこのBookのフィールドtitleに代入
title = doc['title'];
}
// Bookで扱うフィールドを定義しておく。
String title;
}
3. modelを作る
Firestoreや、UIで表示するデータをやり取りするmodelを作ります。
やっていることは
- Firestoreからデータ(コレクション)を持ってくる。
- 持ってきたコレクションのデータを色々加工して、
ListView.builderで使うためのBookのList booksを用意する。
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:corianderapp/book.dart';
import 'package:flutter/cupertino.dart';
class MainModel extends ChangeNotifier {
// ListView.builderで使うためのBookのList booksを用意しておく。
List<Book> books = [];
Future<void> fetchBooks() async {
// Firestoreからコレクション'books'(QuerySnapshot)を取得してdocsに代入。
final docs = await FirebaseFirestore.instance.collection('books').get();
// getter docs: docs(List<QueryDocumentSnapshot<T>>型)のドキュメント全てをリストにして取り出す。
// map(): Listの各要素をBookに変換
// toList(): Map()から返ってきたIterable→Listに変換する。
final books = docs.docs
.map((doc) => Book(doc))
.toList();
this.books = books;
notifyListeners();
}
}
4. ListView.builderにmodelの処理を反映させる。
main.dart(UI書いているところ。)の中でListView.builderにFirestoreのデータを反映させる。
ChangeNotifierProviderとConsumerを使ってmodelのデータを反映させる。
class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
return MaterialApp(
home: ChangeNotifierProvider<MainModel>(
// createでfetchBooks()も呼び出すようにしておく。
create: (_) => MainModel()..fetchBooks(),
child: Scaffold(
appBar: AppBar(
title: Text('本一覧'),
),
body: Consumer<MainModel>(
builder: (context, model, child) {
// FirestoreのドキュメントのList booksを取り出す。
final books = model.books;
return ListView.builder(
// Listの長さを先ほど取り出したbooksの長さにする。
itemCount: books.length,
// indexにはListのindexが入る。
itemBuilder: (context, index) {
return ListTile(
// books[index]でList booksのindex番目の要素が取り出せる。
title: Text(books[index].title),
);
},
);
},
),
),
),
);
}
}
出来上がったコード全体
import 'package:corianderapp/main_model.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
return MaterialApp(
home: ChangeNotifierProvider<MainModel>(
create: (_) => MainModel()..fetchBooks(),
child: Scaffold(
appBar: AppBar(
title: Text('本一覧'),
),
body: Consumer<MainModel>(
builder: (context, model, child) {
final books = model.books;
return ListView.builder(
itemCount: books.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(books[index].title),
);
},
);
},
),
),
),
);
}
}
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:corianderapp/book.dart';
import 'package:flutter/cupertino.dart';
class MainModel extends ChangeNotifier {
List<Book> books = [];
Future<void> fetchBooks() async {
final docs = await FirebaseFirestore.instance.collection('books').get();
final books = docs.docs.map((doc) => Book(doc)).toList();
this.books = books;
notifyListeners();
}
}
import 'package:cloud_firestore/cloud_firestore.dart';
class Book {
Book(DocumentSnapshot doc) {
title = doc['title'];
}
String title;
}
参考サイト
Firebase環境構築からデータをとってListViewにするところまで、ほぼ参考にしてます。
ただ少し古いので、Firestoreの書き方はエラーになり、変更が必要になります。
Discussion
質問失礼します。
book.dartの String title; にエラーが出ていて、lateをつけろと言われるのですがつけても問題ないでしょうか?