その他
    ホーム 職種別 エンジニア Flutterによるアプリ開発 <Firebase連携編>
    Flutterによるアプリ開発 <Firebase連携編>
     

    Flutterによるアプリ開発 <Firebase連携編>

    ゲームデザイン部中村です。

    引き続きFlutterによるアプリ開発について記載していきます。前回・前々回については以下のリンクからご覧ください。

    Flutterによるアプリ開発<導入編>

    Flutterによるアプリ開発<ウィジェットレイアウト編>

    Flutterの導入とウィジェットレイアウトについて説明してきましたが、SNSやショッピングサービスなどではアプリのみですべてのサービスを完結させることはできません。サーバ上にユーザ情報を格納し、SNSではサーバから友達の情報を取得しなければならず、ショッピングサービスでは商品の在庫があるかをサーバに問い合わせなければなりません。このようなとき、サーバ側の環境構築をすべきなのですが、非常に面倒です。

    そこで今回は幅広くサーバ側にサービスを用意している、Flutterと非常に相性の良いFirebaseとの連携を行います。

    今回は画像多めでお送りします。

    Firebaseとは

    Googleが提供するアプリケーション開発プラットフォームです。代表的な6つのサービスがあり、その特徴は「クロスプラットフォーム」「インフラ構築不要」ということにあります。

    Authenticationはユーザ認証を手軽に導入できます。メール/パスワードだけでなく、Google・Twitter・Facebook・Appleなどのユーザ認証を一つのソースコードで行うことができます。

    CloudFirestoreはスケーラブルなNoSQLデータベースです。MySQLやPostgreSQLとは異なり、データを保存するドキュメントがあり、コレクションの中に複数のドキュメントが存在します。

    RealtimeDatabaseはJSON形式のデータを保存するNoSQLクラウドデータベースです。保存されたデータは接続しているすべてのクライアントで同期され、リアルタイムに更新されます。

    Storageは写真や動画などを保存、提供するストレージサービスです。

    HostingはWebコンテンツホスティングサービスです。Webアプリ、静的コンテンツ、動的コンテンツをCDNに配信することができます。

    CloudFunctionsはサーバレスにHTTPSリクエストをトリガーとしてバックエンドコードを実行します。マネージド環境で実行されるため、個別のインフラ構築が不要です。

    以上6つのサービスから、今回はCloudFirestoreにデータを格納しStorageに画像を保管するという流れを説明していきます。

    Firebaseプロジェクトの作成

    まずはFirebaseコンソールへ移動しましょう。

    「プロジェクトを追加」ボタンから作成を開始します。

    「まずプロジェクトに名前をつけましょう」と表示されるので今回は「mTest」と名付けました。「続行」します。

    「このプロジェクトで Google アナリティクスを有効にする」のチェックがありますので、ONのままにして「続行」します。

    「Google アナリティクスの構成」では「新しい構成」から名前をつけ、アナリティクスの地域を日本にしました。

    以上でプロジェクトが作成されます。

    iOS構成設定

    プロジェクト画面が開いたら、iOSでFirebaseを利用するための構成を進めます。

    「アプリにFirebaseを追加して利用開始しましょう」からiOSを選択します。

    「アプリの登録」ではバンドルIDを登録します。今回は「com.sample.mtest」としました。その他の項目は空欄で「アプリを登録」。

    「設定ファイルのダウンロード」が表示されますので、GoogleServices-Info.plistをダウンロードします。SDKの追加、初期化コードの追加は行いませんのでそのまま「次へ」と進み「コンソールに進む」で完了です。

    CloudFirestoreの初期設定

    Flutterで表示するための仮データを作成していきましょう。

    まずは左側メニューからCloudFirestoreを選択し、「データベースの作成」をクリックしましょう。

    データベースの作成では「テストモードで開始する」にしておきます。作成後にセキュリティルールを設定しない限り30日間ユーザ認証なしに誰でも参照できるようになるので、ここままサービスリリースできない点はご注意ください。

    「CloudFirestoreのロケーション」はasia-northeast1にしておきます。テストのうちは特に気にする必要はありませんが、普段から同じロケーションに設定しておくと忘れなくて良いと思います。

    しばらく待つとデータベースが作成されるので、「コレクションの追加」を行います。今回は仮データとして食べ物を5つ用意します。

    必要なデータとしては「name」「price」「image」を用意します。imageには後で用意する食べ物画像のファイル名を指定します。

    同じ要領でうどん、パスタ、カレーライス、ピザを用意しました。

    Storageの初期設定

    続いてStorageに食べ物の画像を設置します。こちらはすでにファイルストレージとして利用できるようになっていますので、「ファイルをアップロード」から5枚の食べ物画像をアップロードします。これらの画像はいらすとやさんからお借りしました。

    また、今回はFirebaseAuthを利用したユーザ認証については省略しているため、Storageのルールを以下のように編集します。これによって、ユーザ認証なしでStorageのファイルを読み書きできます。

    rules_version = '2';
    service firebase.storage {
      match /b/{bucket}/o {
        match /{allPaths=**} {
          allow read, write;
        }
      }
    }

    FlutterとFirebase連携

    Firebaseでの仮データができたのでFlutterからFirebaseにアクセスするための設定を進めていきます。

    FlutterにFirebaseSDKを導入

    ウィジェットレイアウト編でも登場したpubspec.yamlにプラグインを記述します。

    dependencies:
      flutter:
        sdk: flutter
      http: ^0.12.2
      # 以下を追加
      firebase_core: ^0.5.0
      cloud_firestore: ^0.14.0
      firebase_storage: ^5.2.0

    pubspec.yamlに追記をするとホットリロードが動作するので問題なくアプリが動くことを確認します。

    続いて、FirebaseにバンドルIDをcom.sample.mtestと登録したので、アプリ側のバンドルIDも変更します。FlutterアプリはVSCodeからプロジェクトを生成すると自動でcom.example.{プロジェクト名}というバンドルIDに設定されます。そのため、VSCodeの文字列検索からこのバンドルIDを探し、全て差し替えます。

    続いて、iOS構成設定でダウンロードしたGoogleServices-Info.plistをプロジェクト内に配置します。このファイルにはiOSアプリからFirebaseへアクセスするための認証情報が含まれています。その内容はFirebaseのプロジェクト毎に異なるので注意してください。

    また、このファイルの追加にはXCodeが必要です。MacのFinderからプロジェクトを開き、ios/Runner.xcodeprojをダブルクリックしてXCodeを起動しましょう。配置位置は以下の画像を参照してください。

    FlutterにてFirebaseからデータを取得する

    初期化コード

    以上でFirebaseとの連携準備は完了しましたので、ソースコードの方を編集してFirebaseからデータを取得しましょう。

    まずはFirebaseを利用するための初期化を行います。main.dartの上部でFirebaseをimportします。

    ~~~
    import 'package:firebase_core/firebase_core.dart';
    import 'package:cloud_firestore/cloud_firestore.dart' as cloud_firestore;
    import 'package:firebase_storage/firebase_storage.dart' as firebase_storage;

    これら3行でFirebaseのコア、Firestore、Storageをimportできます。FirestoreとStrageについてはasオプションで別名利用できるようにしています。

    さらに、もともとあったvoid main()関数を以下のように書き換えます。

    Future<void> main() async {
      WidgetsFlutterBinding.ensureInitialized();
      await Firebase.initializeApp();
      runApp(MyApp());
    }

    main関数をFutureにすることで、内部でawaitが使えるようになります。これによって、アプリの起動直前にFirebaseの初期化を行ない終了まで待つことができます。ただし、アプリが起動しないとFlutterで用意されたメソッドが呼び出せないため、WidgetsFlutterBinding.ensureInitialized();でFlutterの機能を呼び出せるようにしています。これはおまじないと思っていただいて問題ありません。

    ここまでの書き換えではまだFirebaseの機能を呼び出していないため、前回作成した天気のリスト表示が同じ用に表示されることを確認してください。

    CloudFirestoreから食べ物リストを取得

    続いて、CloudFirestoreからデータを取得してきましょう。Scaffoldを以下のように編集します。

    Scaffold(
      body: Padding(
        padding: const EdgeInsets.all(8.0),
        child: StreamBuilder<cloud_firestore.QuerySnapshot>(
          stream: cloud_firestore.FirebaseFirestore.instance.collection('items').snapshots(),
          builder: (context, snapshot) {
            if (!snapshot.hasData) return LinearProgressIndicator();
            return ListView(
              children: snapshot.data.docs.map[1]e)=>ListTile(
                title: Text("${e.get('name')}"),
                subtitle: Text("${e.get('price')}円"),
              .toList(),
            );
          },
        )
      )
    );

    ここではStreamBuilderが登場します。

    ウィジェットレイアウト編ではFutureBuilderを利用してHTTPレスポンスを画面に描画しました。FutureBuilderは一度の呼び出し結果を待ち、レイアウトに反映します。

    それに対してStreamBuilderstreamに指定したsnapshotに変更がある度、自動でレイアウトに反映します。CloudFirestoreはこの機能を利用してデータの変更を検知しレイアウトを更新できるようになっています。

    上記Scaffoldの書き換えによって以下のようなレイアウトができたと思います。

    それでは実際にStreamBuilderの効果を確認しましょう。ブラウザのCloudFirestoreとiOSシミュレータを画面上に並べて表示し、食べ物名や金額を編集してみましょう。ブラウザでデータを編集すると即座にiOSシミュレータに反映されることが確認できます。

    Storageから画像を取得

    CloudFirestoreに用意した食べ物について、それぞれのアイコンを表示するためにStorageから画像を取得しましょう。

    アイコンのファイル名をCloudFirestore内のデータにimageとして指定してあるため、このファイル名でStorageに画像の問い合わせを行います。まずはHTTPリクエストで画像を表示するために以下のプラグインを追加します。

    dependencies:
      ~~~~
      cached_network_image: ^2.4.1

    また、このプラグインをmain.dartにimportします。

    import 'package:cached_network_image/cached_network_image.dart';

    また、main.dartの最下部に以下のクラスを追加します。このクラスではFuture<String>を指定して、そこで受け取る文字列を画像のURLとしてCachedNetworkImageに渡すFutureBuilderとして機能します。

    ~~~~
    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;
    }

    上記クラスをListTile内で以下のように利用します。

    ListTile(
      leading: NetworkImageBuilder(firebase_storage.FirebaseStorage.instance.ref().child(e.get('image')).getDownloadURL()),
      title: Text("${e.get('name')}"),
      subtitle: Text("${e.get('price')}円"),
    )

    ここでStorageから画像を取得するURLをNetworkImageBuilderに指定しています。firebase_storage.FirebaseStorage.instance.ref()でStorageにアクセスするリファレンスを取得し、child(e.get('image'))で該当のファイルを指定、getDownloadURL()でファイルにアクセスするURLを取得しています。このときgetDownloadURL()Future<String>を返却するため、一旦FutureBuilderでURLを待ち、その後CachedNetworkImageで画像の取得を待つ、という流れが必要となります。

    以上の編集によって以下のようなリストが確認できると思います。これでStorageから画像を取得することができました。

    以上、三回に渡ってFlutterによるアプリ開発を記述してきました。

    導入編ではFlutterのSDKを用意しアプリ画面が表示されるまで説明しました。

    ウィジェットレイアウト編ではアプリらしいレイアウトを作るための方法と、HTTPを通してサーバからデータを取得する方法を説明しました。

    そして今回はFlutterと相性の良いFirebaseとの連携について、FirestoreとStorageを用いて説明しました。FirebaseについてはAuthenticationを導入してユーザ認証の実装をおすすめします。

    ここまで記述してきた情報によって様々なアプリ開発が可能かと思います。是非アプリ開発を行ってみてください。

    References

    References
    1 e)=>ListTile( title: Text("${e.get('name')}"), subtitle: Text("${e.get('price')}円"),
    記事を共有