FlutterとFirebaseでstorageに保存した画像のリスト表示してみる

Flutterとは

FlutterはiOS/Android/Webアプリをクロスプラットフォームで開発できるGoogle社が提供しているフレームワークです。

Widget(ウィジェット)というUIを構成しているパーツをツリー状に組み合わせることでUIを構成できます。

Firebaseとは

Firebaseとはモバイル・Webアプリ向けのGoogleが提供しているプラットフォームです。
今回はFirebaseの機能の一つであるCloud FirestoreとCloud Storageを使って画像を表示してみたいと思います。
Firestoreに登録してある値からStorageのURLとテキスト(画像の説明用)を取得するイメージです。

開発環境

・OS:Windows10
・IDE:Android Studio

構築方法は割愛しますが、主にflutterとfirebase周りは公式ドキュメントを参考にしてください。flutterとAndroid Studioのバージョンでエラーが出ることがありますが基本的にどちらも最新にしておけば問題ないと思います。

Firebaseのプロジェクト作成

AndroidStudioでFlutterプロジェクトを作成した状態で、とりあえずFirebaseとプロジェクトを連携させるところからスタートします。
1…firebaseコンソールページ(https://console.firebase.google.com/u/0/?hl=ja)に行きプロジェクトを作成(名前は任意で設定します)

2…プラットフォームを選択の個所Androidのアイコンを選択します。

3…AndroidStudioのAndroidManifest.xmlかbuild.gradleのファイルを参考にパッケージネームをコピペしてfirebaseに貼り付ける

パッケージネーム場所(app直下のbuild.gradle)

4…google-service.jsonをダウンロードしてそれをandroidstudioのappの下にに貼り付ける

5…root直下とapp直下のbuild.gradleのファイルにFirebase側で表示されている。コードを追加する。
・root直下は「classpath 'com.google.gms:google-services:[バージョン名]'」を追加。
・app直下は「apply plugin: 'com.google.gms.google-services'」を追加。

必要なパッケージをインストールする

今回つかうパッケージは以下です。
Firebase_core
cloud_firestore
firebase_storage
chached_network_image

上記のパッケージのバージョンを以下のようにpubspec.yamlにコピペしてPub Getを押してインストールします。もしコードを記述する段階でパッケージエラーが出たらその都度パッケージをインストールします。

pubspec.yaml内

firestoreのコレクションを設定する

コレクションを以下のように設定します。
今回はgame_imgというコレクションを作成し、ドキュメント中に表示するリストのアイテムをそれぞれ設定します。
ドキュメントIDは任意でも自動生成でも構いません。
フィールドの中身は以下の形です(自分の好きな画像を設定してみてください)
・category→ゲームのカテゴリー
・imgURL→firestrageのURL
・title→ゲームのタイトル

Firebaseのコレクション

firestorageの設定

imgURLに設定したパスでfirestorageに画像を保存します。

Firestorageの設定


コードの記述

では実際にコードを記述します。main.dartとgame_list.dartというファイルのlibフォルダに作成します。

main.dart

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();
  runApp(GameList());
}

game_list.dart

import 'package:cached_network_image/cached_network_image.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'package:firebase_storage/firebase_storage.dart';
import 'game_detail.dart';
// ゲーム一覧画面

//Storageに保存した画像のURLを取得する際のコード
class NetworkImageBuilder extends FutureBuilder {
  NetworkImageBuilder(Future<String> item)
      : item = item,
        super(
          future: item,
          builder: (context, snapshot) {
            if (snapshot.hasData) {
              return CachedNetworkImage(
                imageUrl: snapshot.data,
                placeholder: (context, url) => CircularProgressIndicator(),
                errorWidget: (context, url, error) => Icon(Icons.error),
              );
            } else {
              return CircularProgressIndicator();
            }
          },
        );
  final Future<String> item;
}

class GameList extends StatelessWidget {
  final Stream<QuerySnapshot> _stream = FirebaseFirestore.instance
      .collection("game_img")
      .orderBy("category")
      .snapshots();
  void _handleCheckbox(bool? e) {}

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text('Game List'),
          centerTitle: true,
          elevation: 10,
        ),
        body: StreamBuilder<QuerySnapshot>(
            stream: _stream,
            builder:
                (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
              if (snapshot.connectionState == ConnectionState.waiting) {
                return Text('Loading...');
              }
              return ListView(
                children: snapshot.data!.docs.map((DocumentSnapshot document) {
                  Map<String, dynamic> data =
                      document.data()! as Map<String, dynamic>;
                  return ListTile(
                    leading: NetworkImageBuilder(FirebaseStorage.instance
                        .ref(data['imgURL'])
                        .getDownloadURL()),
                    title: Text(data['title']),
                    subtitle: Text(data['category']),
                    // trailing: Icon(Icons.more_vert),
                    trailing: new Checkbox(
                      activeColor: Colors.blue,
                      value: data['check'],
                      onChanged: _handleCheckbox,
                    ),
                  );
                }).toList(),
              );
            }));
  } // children:
} // listTiles

コードの説明

main.dartではGameListクラスが動くように命令してるだけです。このときFirebase.initializeApp();を記述しないとFirebaseが動かないので記述します。

game_list.dart内ではFirestrageに保存した画像をhttpリクエストで表示するために用いたNetworkImageBuilderクラスと二つ目はWidgetでUIを構成し、それぞれの要素がどのような処理内容を示したGamelistクラスがあります。以下はそれぞれのクラスについての説明です

NetworkImageBuilder

クラスの中にはNetworkImageBuilderメソッドがあり、メソッド内では条件分岐でsnapshot(firebaseから取得したデータの型)がhasData(データがあるかどうか)が正の場合はCachedNetworkImage6)メソッドが返され、負の場合はCircularProgressIndicatorメソッドが返されロードしているアイコンが表示されます。
このクラスではFuture<string>itemオブジェクトを宣言している。Futureとは非同期処理を行うためのメソッドで、Futureには未完了(プログラム処理中)、同期成功(value)、同期失敗(error)の3つの状態がある。
このクラス内ではbuilderの中にCachedNetworkImageメソッドがあり、処理内容として値が取得できてかつ取得した画像が処理中の場合は負の時同様CircularProgressIndicatorが返され、取得できてかつ画像が処理できた場合はsnapshot.dataが返され、処理した画像を参照するためのURLが取得できる。また取得できてかつ画像がエラーした場合は、errorWidgetとしてエラーのアイコンのウィジェットが表示される。上記の処理を非同期で行うという理由でFutureを用いた。

Gamelistクラス

GameListクラスはStatelessWigetを継承したクラスです。
Flutterではステート(画面更新)管理をする必要があるWidgetをStatefulWidgetといい、必要がないWidgetをStatelessWidgetといい、それぞれの用途に合わせて継承する必要があり、GameListクラスはFirebaseから値を取得する度に画面が更新されるがそれらの処理はStreamBuilderを用いて行うため、StatelessWidgetを継承します。
クラス内ではStream型の関数として_streamを宣言してます。Streamとはデータを外部から取得する等の処理をしたい場合に使うメソッドで
FirebaseFirestore.instance.collection(“game_img”).snapshot();でgame_imgコレクションのデータを取得することができます。orederBy(“category”)は図のコレクションのフィールドのcategoryをアルファベットの順番に並び替えするものです。取得した値に関しては、ウィジェット内で参照することで表示できる。StreamBuilderはstreamに指定したsnapshotに変更がある度に自動でレイアウトが反映されます。
StreamBuilder内ではsnapshot.connectionState==ConnectionState.waitingの条件分岐により同期が行われている際中は「Loading…」と表示され、同期が完了すればFirebaseのデータが表示されたListViewウィジェットのリストが表示される。ListViewは複数のListTileをまとめたもので、ListTileの中にはleadingとtitleとsubtitleとtrailingの変数があり、leadingは先頭に表示するWidgetで
NetworkImageBuilder(FirebaseStorage.instance.ref(data[‘imgURL’]).getDownloadURL()),で参照したゲームの画像が表示されます。
titleは中央に表示するListTileのタイトルでTextウィジェットを用いて表示する。subtitleはtitleの下に表示するサブタイトルで、Textウィジェットを用いて表示します。
tranlingは末尾に表示するウィジェットでmore_vertというアイコンを表示しています。
フィールド内のデータを参照する際は、data[‘フィールド名’]で参照できます。

エミュレーターに出力してみた

こんな感じで表示して、アイコンや他のUIを付け加えるといい感じになります。

出力結果

参考文献

1.Flutter環境構築:
(https://zenn.dev/kazutxt/books/flutter_practice_introduction/viewer/tutorial_environment)
2.Firestore登録、更新、取得、削除(https://gurutaka-log.com/flutter-firebase-firestore-sample)
3.SingleChildScrollViewウィジェット
(https://www.egao-inc.co.jp/programming/%E3%80%90flutter%E3%80%91%E7%AB%AF%E6%9C%AB%E7%B8%A6%E3%82%B5%E3%82%A4%E3%82%BA%E3%82%92%E8%B6%85%E3%81%88%E3%81%9F%E3%82%89%E8%87%AA%E5%8B%95%E3%81%A7%E3%82%B9%E3%82%AF%E3%83%AD%E3%83%BC%E3%83%AB/)
4.Firebaseのデータをリストで表示する方法
(https://spirits.appirits.com/role/engineer/11825/)
5.Firestorage Package
(https://pub.dev/packages/firebase_storage)
6.Cached network image Package
(https://pub.dev/packages/cached_network_image)
7.StreamBuilder class
(https://api.flutter.dev/flutter/widgets/StreamBuilder-class.html)
8.Cloud Firestore Plugin for Flutter
(https://pub.dev/packages/cloud_firestore)
9.Cloud FirestoreのSnapshot三種や他の型のまとめ
(https://qiita.com/kabochapo/items/1ef39942ac1206c38b2d)
10.Cloud Firestore公式リファレンス
(https://firebase.flutter.dev/docs/firestore/usage/#realtime-changes)
11.async/awaitの基本的な使い方
(https://zenn.dev/iwaku/articles/2020-12-29-iwaku)
12.Futureの基本的な使い方
(https://flutternyumon.com/how-to-use-future/)
13. Flutter build error が起きたときにするべきこと
(https://qiita.com/najeira/items/fdcca746b5b005017ac8/)
14. CloudFirestoreからデータの取得&データの書き込み方
(https://qiita.com/smiler5617/items/8dcf720f5477a5f4b7d7)


この記事が気に入ったらサポートをしてみませんか?