flutterとfirebaseに慣れるために以下の記事を参考にしてアプリの開発を行いました。
https://rightcode.co.jp/blog/information-technology/flutter-firebase-bulletin-board-app-make
今回は、実際にfirestoreとflutterを連携していきますが、いくつかつまづいた点があったのでメモっておきます。
まず、cloud firestoreでデータベースを作成しました。
この辺は上の記事を見て適当にやりました。
firestore上に以下のようなドキュメントを作成。
firebaseのコードを使用するために、pubspec.yaml(next.jsでいうところのpackage.json)に以下のコードを追記。
dependencies:
flutter:
sdk: flutter
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.2
# firestoreのプラグイン(---以下の2行を追記してください---)
firebase_core: ^1.0.1
cloud_firestore: ^2.5.4
firebaseのデータベースと連携するためのコードを追加したmain関数を書きます。
一応、以下のコードの軽い解説をしてくれてる記事を載せときます。
https://qiita.com/edasan/items/f32bc58a4afd0ca92432
// (変更前のコード)
// void main() {
// runApp(const MyApp());
// }
// firebaseを導入した場合のmain関数(変更後のコード)
// 非同期関数になるので、戻り値の型もFuture型になる(待ってもあたいは返ってこないFuture型や)
Future<void> main() async {
// アプリの起動時にfirebaseと連携する(firebaseの機能をたくさん使う場合はこの記述がいいみたい)
WidgetsFlutterBinding.ensureInitialized();
await Firebase?.initializeApp();
runApp(MyApp());
}
vscodeのデバッグモードを起動したら、以下のようなエラーが発生しました。
何が起きとるんや...
Error running pod install Error launching application on iPhone 13.
Error output from CocoaPods:
↳
[!] Automatically assigning platform `iOS` with version `9.0` on target `Runner` because no platform was
specified. Please specify a platform for this target in your Podfile. See
`https://guides.cocoapods.org/syntax/podfile.html#platform`.
調査を進めたところ、ios直下のPodfileに問題があるような感じ。
vimでPodfileの中身を確認したところ、以下のようになっていた。
Podfileを一回消して、もう一回作ったり、platform: ios, '9.0
→ platform: ios, '10.0
のように書き換えたりしたが動かん。。。
# Uncomment this line to define a global platform for your project
# platform :ios, '12.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
---省略---
post_install do |installer|
installer.pods_project.targets.each do |target|
flutter_additional_ios_build_settings(target)
end
end
なんだかんだ試行錯誤をしているうちに以下の記事に遭遇
https://zenn.dev/junki555/articles/8d0b962d2e6c1fa4d8ca
ここで初歩的なミスに気づく。
なんと、platform :ios, '12.0`
のところがデフォルトでコメントアウトされていた。
コメントアウトを外して、ビルドを走らせると上記のエラーは出ませんでした。
ですが、新しいエラーに遭遇!
google-services.plistとは、cloud firestoreとアプリを連携する際に生成されるファイルである。
参考記事通りに、ios/Runnerディレクトリに挿入したはずなのに、ないと言われている。
Firebase has not been correctly initialized. Have you added the "google-services.plist" file to the project?
おそらく、このファイルへのパスが通っていないことが原因だと考えた。
色々調べたら以下の記事を見つけた。
https://fukatsu.tech/file-treatment-xcode
この記事を参考に、Xcodeを開いてgoogle-services.plistをドラッグ&ドロップでRunnerディレクトリの後ろに追加。Copy items if needed
とCreate groups
にチェックを入れて設定画面を閉じる。
ビルドを走らせたら、エラーが消えていました。
次に状態を持つ見た目の描画を担う_MyHomePageStateウィジェットのbodyのフィールドを以下のように書き換えます。
body: StreamBuilder<QuerySnapshot>(
stream: FirebaseFirestore.instance.collection("posts").snapshots(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(child: CircularProgressIndicator());
}
return ListView(
children: snapshot.data!.docs.map((DocumentSnapshot document) {
final documentData = document.data() as Map;
return Card(
child: ListTile(
title: Text(documentData["content"]),
subtitle: const Text("サブタイトル"),
),
);
}).toList(),
);
},
),
ちなみにmapメソッドを実行した後で、toList()メソッドを呼び出すのは、mapメソッドによって生成されるのはあくまでMap型の変数だからです。
Map型とはjsのオブジェクトのようなものであるため、リストに直さないとリストのメソッドが使えなかったり、今回のようにフィールドにリストを持つキーで受け取れないといった問題が生じます。
詳しくは以下の記事を参考にしてください。
bodyのトップウィジェットであるStreamBuilderについても説明します。
こちらのウィジェットは、streamキーを持っており、このstreamでデータを受け取るたびに再描画を行うようです。
ちなみに、streamとは日本語で「小川」という意味で、何らかのデータが流れてくるイメージで覚えて貰えば結構です。
今回の場合ですと、postsコレクションのスナップショットを受け取っています(非同期的に受け取っている)。
FirebaseFirestore.instance.collection("posts").snapshots()
このデータの方は以下のようになっていました。Stream型ですね。
実際に受け取るのはQuerySnapshotになります。
builderのフィールドのコードですが、参考にしていた記事とは少し違うコードになっています。
変えた部分は以下になります。
final documentData = document.data() as Map;
return Card(
child: ListTile(
title: Text(documentData["content"]),
...
元々のコードは以下になります。
return Card(
child: ListTile(
title: Text(document.data()['content']),
参考にしていた記事のコードをそのまま書くとdocument.data()['content']のところで以下のエラーが出ます。
The method '[]' can't be unconditionally invoked because the receiver can be 'null'.
Try making the call conditional (using '?.') or adding a null check to the target
対策をググっていたら、以下の記事に辿り着きました。
この記事によると、Objectを一回Mapにキャストすることでアクセスできるようになるとのことだったので実践してみた。
https://qiita.com/qst_exe/items/79a444bbb13df0ce6a6d
一応ここまでやったら、以下のような画面が出力されるはず。
キリがいいので今回の記事はここまで