Open18

Firebaseの学習したこと (アプリ側の開発言語は基本Flutter)

miyaken12miyaken12

Firebaseつらつらとどういう新しいことと学んだかつらつら備忘録として書いていきます

以下の機能は個人的に使ったことがあります

  • Remote Config
  • Crashytics
  • Firebase Analytics
  • A/B テスト

使ったことがない機能メインで学習を進めて記載していきます

miyaken12miyaken12

FlutterでのFirebaseの設定

FlutterFire を使って実現できる

iOS

  1. 公式サイトのステップ1 ~ 3を行う↓
    Firebase iOS

Flutterプロジェクトの場合は
GoogleService-Info.plistファイルを ios/Runner/直下に設置

  1. FirebaseのCoreライブラリをインストールする
    pubspec.yaml
dependencies:
  flutter:
    sdk: flutter
  firebase_core: "^1.1.0"
  1. アプリコードでFlutter Fireを初期化するコードを入れてみる

main.dart

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();
  return runApp(ProviderScope(child: MyApp()));
}
  1. Firebaseのコンソール画面で確認する

RealTimeだと即時反映してくれるのでユーザー数の数とか出ていればおk

おまけ
iOSでのビルド時間かかるからこれをPodfireにかく

# ...
target 'Runner' do
  pod 'FirebaseFirestore', :git => 'https://github.com/invertase/firestore-ios-sdk-frameworks.git', :tag => '7.3.0'
# ...
end

Android

iOSとほぼ同様のことをする

  1. 公式サイトのステップ1~3.1 までを行う
    Firebase Android

Flutterプロジェクトの場合は
google-servises.jsonファイルを android/app直下に配置

  1. 各gradleファイルに以下を記載する

android/app/build.gradle

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
apply plugin: 'com.google.gms.google-services' ←これを記載

~~~~~
dependencies {
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    implementation platform('com.google.firebase:firebase-bom:27.0.0') ←これを記載
    implementation 'com.google.firebase:firebase-analytics-ktx' ←これを記載
}

android/build.gradle

    dependencies {
        classpath 'com.android.tools.build:gradle:3.5.0'
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
        classpath 'com.google.gms:google-services:4.3.3' ← これを記載
    }

  1. iOSを先に入れている場合はFlutterの初期化コードを入れているのでそのままビルド、入れていないならiOS の3を行う
  2. iOSの4と一緒
miyaken12miyaken12

Firebase Authのコンソール

Users・・・ユーザーの管理するところ
Sign-in Method・・・ユーザーの管理するところ
Template・・・運用メールの送信内容とかカスタマイズできる
Usage・・・使用状況

miyaken12miyaken12

Firebase Authでメアドのサインインを実装してみる

コンソールでメール/パスワードの部分を有効にする

なんか適当にアカウント作ってみる

Usersのユーザー追加で作れる
適当にメアドとパスワードを入力

firebase_authをインストールする

dependencies:
  firebase_auth: "^1.1.1"

適当にサインイン画面を作る
その画面でメアドとパスワードの入力を受け取る画面を作る
以下のメソッドにメアドとパスワードの文字列を渡してあげる

FirebaseAuth.instance.signInWithEmailAndPassword({required String email, required String password})

使用例

Future<bool> signIn(String email, String password) async {
    try {
      await _auth.signInWithEmailAndPassword(
          email:email, password:password);
      return true;
    } catch (e) {
      print(e);
      return false;
    }
  }

認証が失敗したら例外投げられるのでそこでハンドリングしてあげる

miyaken12miyaken12

Firebase Authの認証状態の確認

2パターンある

  1. authStateChanges()
FirebaseAuth.instance
  .authStateChanges()
  .listen((User user) {
    if (user == null) {
      print('User is currently signed out!');
    } else {
      print('User is signed in!');
    }
  });

authStateChanges()はStream型
こっちが推奨

サインインしてるならUserクラスが、サインインしていないならnullが返ってくる

なんかバグ?かわからないけどなぜかログインしている情報が2回流れてくるし、うまく実装できなかったのでやめた

issueにも同じ問題があり↓
issue

  1. FirebasenAuth.instance.currentUser

FirebasenAuth.instance.currentUserを使う

User user = FirebaseAuth.instance.currentUser;

authStateChanges()と同様でUserクラスまたはnullが返ってくる

miyaken12miyaken12

Firebase Authでのサインアップを実装してみる

サインインとおんなじ要領で

Future<UserCredential> createUserWithEmailAndPassword(
{required String email,
required String password}
)

というメソッドを使ってできる

Future<bool> signUp(String email, String password) async {
    try {
      await _auth.createUserWithEmailAndPassword(
          email:email, password: password);
      return true;
    } on FirebaseAuthException catch (e) {
      print(e);
      return false;
    } catch (e) {
      print(e);
      return false;
    }
  }

失敗したら例外が投げられる

miyaken12miyaken12

番外編

ソーシャルログイン(Google,Apple, Twitterなど)ボタンを一から作るのは非常に手間なので、公開されてるパッケージを使うのがオススメ

flutter_signin_buttonsign_button
がNull Safety対応してるし、評価高いのでこれらを使うのがオススメ

どっちがいいかは完全に好み

自分はサイズを選べれる sign_buttonを選択

実装の仕方はすごい簡単で各ドキュメントに記載あるので割愛

miyaken12miyaken12

Firebase AuthでSign in with Appleをやってみる (iOS)

コンソールの準備

ログインプロパイダでAppleの項目を有効にする

実装

  1. Xcodeでプロジェクトファイルに設定追加

Signing&CapabilitiesSign in with Appleを追加する

  1. 公式推奨のパッケージをインストールする

Sing in with Applenのパッケージ sign_in_with_apple
暗号化のパッケージcrypto

pubspec.yaml

dependencies:
  crypto: ^3.0.1
  google_sign_in: ^5.0.2
  1. 実装する

暗号化のコード書く

/// Generates a cryptographically secure random nonce, to be included in a
/// credential request.
String generateNonce([int length = 32]) {
  final charset =
      '0123456789ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvwxyz-._';
  final random = Random.secure();
  return List.generate(length, (_) => charset[random.nextInt(charset.length)])
      .join();
}

/// Returns the sha256 hash of [input] in hex notation.
String sha256ofString(String input) {
  final bytes = utf8.encode(input);
  final digest = sha256.convert(bytes);
  return digest.toString();
}

認可コードを書く


  Future<bool> signInWithApple() async {
    final rawNonce = NonceString.generateNonce();
    final nonce = Sha256String.sha256ofString(rawNonce);
    try {
      // Appleのサインイン
      final appleCredential = await SignInWithApple.getAppleIDCredential(
        scopes: [
          AppleIDAuthorizationScopes.email,
          AppleIDAuthorizationScopes.fullName,
        ],
        nonce: nonce,
      );

      final oauthCredential = OAuthProvider('apple.com').credential(
        idToken: appleCredential.identityToken,
        rawNonce: rawNonce,
      );

      await _auth.signInWithCredential(oauthCredential);
      return true;
    } catch (e) {
      print(e);
      return false;
    }
  }

これをApple用のサインインボタン押した時に呼ぶとおk

miyaken12miyaken12

Firebase AuthでSign in with Appleをやってみる (Android)

FlutterFireでは対応していないし、Firebase側での公式のネイティブSDKでも対応していない
やるならWeb経由でやるみたいだけどめんどいので一旦保留

miyaken12miyaken12

Firebase AuthでGoogle Sign inをやってみる(iOS/Android)

ログインプロパイダでGoogleの項目を有効にする

miyaken12miyaken12

Firebase Emulator をAuthで試す (挫折編)

環境設定

Firebase Local Emulator Suite
を見て、Firebase CLIの環境設定が必要と把握

npmでインストールすることにした

Local Emulator Suiteを入れる前に必要な環境設定

  • Node.js バージョン 8.0 以降。
  • Java バージョン 1.8 以降。

JavaはGoogle Sign Inの時にjdkのセットアップ関連クリアしていたのでNode.jsの環境設定をする

この記事に環境設定

nodebrewをインストール

homebrewは入っているので

$ brew install nodebrew
$ nodebrew -v
nodebrew 1.1.0

Node.jsの最新版をインストール

Fetching: https://nodejs.org/dist/v15.7.0/node-v15.7.0-darwin-x64.tar.gz
Warning: Failed to create the file 
Warning: /Users/XXXX/.nodebrew/src/v15.7.0/node-v15.7.0-darwin-x64.tar.gz: No 
Warning: such file or directory

curl: (23) Failed writing body (0 != 978)
download failed: https://nodejs.org/dist/v15.7.0/node-v15.7.0-darwin-x64.tar.gz

記事同様のエラーが表示されたため、以下のディレクトリを同様に作成

$ mkdir -p ~/.nodebrew/src

そして、インストール

$ nodebrew install-binary latest

Fetching: https://nodejs.org/dist/v16.1.0/node-v16.1.0-darwin-x64.tar.gz
########################################################################################################################################################## 100.0%
Installed successfully

確認

$ nodebrew list
v16.1.0

設定

$ nodebrew use v16.1.0
use v16.1.0

.zshrcにパスを通す

export PATH=$PATH:$HOME/.nodebrew/current/bin

確認してどっちも表示されればおk

$ node -v
v16.1.0

$ npm -v
7.11.2

Firebase CLIをインストール

npmを使ってインストールする

$ npm install -g firebase-tools

確認

$ firebase -V
9.10.2

Firebase CLIでFirebase Emulatorの設定

プロジェクトにログインする

$ firebase login

? Allow Firebase to collect CLI usage and error reporting information? (Y/n) 

YESでもNOでもどっちでもおk

コマンドを叩くとログイン画面がブラウザで開かれる

Firebaseプロジェクトに入ってるアカウントを選択して、認証を許可する

入れるとこんな感じでブラウザとターミナルで表示される

✔  Success! Logged in as <アカウント>

ログアウトする場合は以下のコマンド

$ firebase logout

ログイン状態だと作成したFirebaseプロジェクトを確認できる

$ firebase projects:list

初期化する

$ firebase init

対話型で聞かれるので
どのFirebaseプロジェクトか聞かれるので既存プロジェクトの場合は既存プロジェクト
まだ作ってない場合は新規で選択してあげる

各Emulatorのポート番号どうするか聞かれるのでデフォルトでいい場合はひたすらEnter押してあげる
Emulatorも使う場合はYES

初期化が成功すると以下の3つが生成される

  • firebase.json
  • .firebaserc
  • .gitignore...

Emulatorの実行

$ firebase emulators:start

こんな感じでコンソールが表示

http://localhost:4000 にアクセスするとEmulator UIもこんな感じに

Authを試してみる

上のスクショにもあるように UIにAuthenticationの項目があるので、デバック用のメールアドレスとパスワードを作成する

iOSの場合はInfo.plistにローカル通信の設定と

<key>NSAppTransportSecurity</key>
<dict>
    <key>NSAllowsLocalNetworking</key>
    <true/>
</dict>

FlutterのコードでFirebase SDK初期化後に以下を入れて実行

FirebaseAuth.instance.useEmulator('http://localhost:9099');

してみたが接続エラーでなぜかうまくいかず・・・

miyaken12miyaken12

Crashlyticsの導入

FlutterFireのとFirebaseの公式リファレンスを基本参考に

やること

  1. コンソール画面でCrashlyticsで両OSを有効にする

  2. パッケージをインストール

dependencies:
  flutter:
    sdk: flutter
  firebase_core: ^1.1.0
  firebase_analytics: ^8.0.3
  firebase_crashlytics: ^2.0.2

依存関係もあるから firebase_corefirebase_analyticsも適切なバージョンでインストール

こんな感じの依存エラーが出たらiosのフォルダで

$ pod update

して依存関係を解消する

  1. 各OSで必要な設定をする

Android

gradleファイルに以下を記載
android/build.gradle

buildscript {
    
    dependencies {
        classpath 'com.android.tools.build:gradle:3.5.0'
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
        classpath 'com.google.gms:google-services:4.3.3'
        // これ記載↓
        classpath 'com.google.firebase:firebase-crashlytics-gradle:2.5.1'
    }
}

app/build.gradle

apply plugin: 'com.google.firebase.crashlytics'

iOS

Xcode開いて Build PhasesにCrashlyticsをSDKを初期化するスクリプトを追加

自動アップロードしてくれる仕組み?的に
Input FilesにDebug版とRelease版のDWARFフォルダとInfo.plistファイルを指定

*${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Resources/DWARF/${TARGET_NAME}

  • ${INFOPLIST_FILE}
  • ../build/ios/Debug-iphoneos/Runner.app.dSYM/Contents/Resources/DWARF/${TARGET_NAME}
  1. コードを記載してみる

ボタンを押したらクラッシュさせるかを確認するアラートダイアログを適当な画面で実装

void showAlertDialog(BuildContext context) {
    showDialog(
      context: context,
      builder: (BuildContext context) {
        return AlertDialog(
          title: Text(
            '危険',
            style: const TextStyle(
              color: Colors.red,
              fontWeight: FontWeight.bold,
            ),
          ),
          content: const Text(
            'クラッシュさせますか?',
          ),
          actions: <Widget>[
            TextButton(
              onPressed: () {
                AppLogger.logger.d('crash');

                FirebaseCrashlytics.instance.setCustomKey('test', 'crashTest');
                FirebaseCrashlytics.instance.crash();
              },
              style: TextButton.styleFrom(
                primary: Colors.red,
              ),
              child: const Text('はい'),
            ),
            TextButton(
              onPressed: () => Navigator.pop(context),
              style: TextButton.styleFrom(
                primary: Colors.black,
              ),
              child: const Text('いいえ'),
            ),
          ],
        );
      },

クラッシュ

FirebaseCrashlytics.instance.crash();

カスタムログ

FirebaseCrashlytics.instance.setCustomKey('test', 'crashTest');

自動収集をしない設定

await FirebaseCrashlytics.instance
      .setCrashlyticsCollectionEnabled(false);

Flutterの例外もキャッチできるようにする

 FlutterError.onError = FirebaseCrashlytics.instance.recordFlutterError;

非同期をキャッチする

runZonedGuarded(() {
    runApp(ProviderScope(child: MyApp()));
  }, (error, stackTrace) {
    FirebaseCrashlytics.instance.recordError(error, stackTrace);
  });
  1. 確認する

クラッシュボタンを押す

アプリを再起動する

コンソール画面で確認する

こんな感じで両OSでてくる

Android

iOS

※ 罠

Crashlyticsのテスト

Xcode で [stop Stop running the scheme or action] をクリックして、アプリの初期インスタンスを閉じます。この初期インスタンスには、Crashlytics の動作を妨げるデバッガが含まれています。


iOS 14だと再起動できない

この2つの現象のせいでiOS 14の実機でデバックモードで試してもなかなかクラッシュログが流れてこなかった

回避策
リリースモードで実行

$ flutter run --release  


クラッシュさせる

再起動(再起動にログが送信される)

コンソールで確認すると出てくるはず

うまくいかない&Tips

コマンド

  • Xcodeの環境変数を確認する
# 例
$ xcodebuild -showBuildSettings | grep "SRC\|PROJECT"
  • dSYMファイルを探す
mdfind -name .dSYM | while read -r line; do dwarfdump -u "$line"; done | grep <dSYMのUUID>
  • アップロードする
$ ~<Podsフォルダがあるディレクトリまでのパス>/Pods/FirebaseCrashlytics/upload-symbols -gsp ~<GoogleService-Info.plistがあるディレクトリまでのパス>/GoogleService-Info.plist -p ios <dSYMがあるパス>

わからない

  • コマンドでUUIDを指定しても特定のdSYMファイルが探しても見つからない
  • 一部シンボルが不足している(Flutterのとか)
miyaken12miyaken12

Performance

導入はすごい簡単

  • SDKインストール
  • 適当に実機またはシュミレータを動かしてアプリの操作をする 以下のこともついでにする(自動収集してくれるので)
    • 通信処理のある機能
    • バックグラウンド⇄フォアグラウンド交互に入る

インストール

dependencies:
  flutter:
    sdk: flutter
  firebase_performance: ^0.7.0+2

適当に操作すると一定時間後こんな感じでコンソール画面が更新される

自動収集するもの

  • アプリの起動時間(iOS/Android)
  • アプリの画面のレンダリング(iOS/Android)(iOS/Android)
  • ネットワークリクエスト

トレースの種類

  • トレースの種類 アプリ開始トレース—ユーザーがアプリを開いてからアプリが応答するまでの時間を測定するトレース
  • App-in-foregroundトレース—アプリがフォアグラウンドで実行され、ユーザーが利用できる時間を測定するトレース
  • App-in-backgroundトレース—アプリがバックグラウンドで実行されている時間を測定するトレース

カスタムトレース作ってコードの入れることもできるけれども、特に何か測りたいものが現状ないので保留
気が向いたら追記する

miyaken12miyaken12

FireStore関連で読んだもの

DB設計皆無なので、NoSQLといえど右も左もわからないのでとりあえず色々読んだ or 読んでいるもの

随時更新していく

[サーバーレス開発プラットフォーム Firebase入門] (https://www.amazon.co.jp/サーバーレス開発プラットフォーム-Firebase入門-掌田-津耶乃/dp/4798057754)

公式ドキュメント

Cloud FireStore

記事

Cloud Firestoreを実践投入するにあたって考えたこと

Firestore で、DB設計を考える際に参考になった情報

miyaken12miyaken12

Cloud Messaging

資料

以下を読んだ

手順

  1. iOSのプッシュ通知の証明書発行ならびにプロジェクトファイルの設定
    これを見た→FlutterFireに書いてある説明

  2. 証明書でのp12ファイル作ったらFirebaseコンソールにp12ファイルをアップロード

  3. 実装する

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

dependencies:
  flutter:
  firebase_messaging: ^10.0.0
  flutter_local_notifications: ^5.0.0+4

Podfileでの依存関係が何かあれば、pod updateするなりして解決する

iOSの実装をする

通知のパーミッションダイアログの表示
表示のタイミングは機能によって変わるが、特に通知を生かしたアプリ内機能はなくプッシュ通知のみを想定だと起動時にパーミッションダイアログを表示

main.dartに記載↓

// iOSのプッシュ通知の設定リクエスト
  await messaging.requestPermission(
    alert: true,
    announcement: false,
    badge: true,
    carPlay: false,
    criticalAlert: false,
    provisional: false,
    sound: true,
  );

フォアグラウンドで通知を表示する実装

main.dartに記載↓

// iOS のプッシュ通知のフォアグラウンドの表示
  await FirebaseMessaging.instance.setForegroundNotificationPresentationOptions(
    alert: true,
    badge: true,
    sound: true,
  );

バックグラウンドで通知を表示する実装

main.dartに記載↓

Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
  // アプリケーションコンテキスト外のIsolateで実行しているため
  // ここでアプリケーションの状態やUIの更新はしてはダメ
  // ドキュメントより
  await Firebase.initializeApp();
}

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

  // バックグラウンド時
  FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
}

通知のバックグラウンドでの呼び出しは一番トップ上?のとこでないといけない(main.dartとか)

Androidでの通知の実装

FlutterFireではフォアグラウンドの表示は提供してないのでflutter_local_notifications パッケージを使う必要がある

チャンネルを指定するための AndroidNotificationChannel のインスタンスを生成しておく
使い回しおkなのでconst

class NotificationChannel {
  static const androidChannel = AndroidNotificationChannel(
    'high_importance_channel',
    'High Importance Notifications',
    'This channel is used for important notifications.',
    importance: Importance.max,
  );
}

通知チャンネルを指定しておく
main.dartに記載

// Androidのチャンネル指定
await flutterLocalNotificationsPlugin
      .resolvePlatformSpecificImplementation<
          AndroidFlutterLocalNotificationsPlugin>()
      ?.createNotificationChannel(NotificationChannel.androidChannel);

デフォルトのFCMのチャンネルではなく、独自のチャンネルを使うようにするため、マニュフェストファイルに以下を記載
(あんまここAndroidの通知の仕様把握し切れておらず・・)

<meta-data
                android:name="com.google.firebase.messaging.default_notification_channel_id"
                android:value="@string/default_notification_channel_id"
                />

FlutterLocalNotificationsPluginのインスタンスはどっかにグローバル変数で用意した

final flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();

App系ウィジットを返すだけのapp.dartファイルに切り出す
例えば、どの画面でも通知を表示できるようにそのApp系ウィジットを返すbuildメソッド内で通知を表示する

class MyApp extends StatelessWidget {
  
  Widget build(BuildContext context) {
    FirebaseMessaging.onMessage.listen((RemoteMessage message) {
      print('Got a message whilst in the foreground!');
      print('Message data: ${message.data}');

      final notification = message.notification;
      final android = message.notification?.android;

      final channel = NotificationChannel.androidChannel;

      if (notification != null && android != null) {
        print(notification.body);
        FlutterLocalNotificationsPlugin().show(
          notification.hashCode,
          notification.title,
          notification.body,
          NotificationDetails(
            android: AndroidNotificationDetails(
              channel.id,
              channel.name,
              channel.description,
              icon: 'launch_background',
            ),
          ),
        );
      }
    });

バックグラウンドで通知を表示する実装
iOSと一緒
これをiOSの方で実装しておけば両OSともバックグラウンドで表示できる

Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
  // アプリケーションコンテキスト外のIsolateで実行しているため
  // ここでアプリケーションの状態やUIの更新はしてはダメ
  // ドキュメントより
  await Firebase.initializeApp();
}

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

  // バックグラウンド時
  FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
}
  1. Firebaseのコンソールでメッセージを送信してみる

新しい通知 > 適当に入力欄にタイトルと本文を埋める > ターゲットを両OS指定 (別のアプリをターゲットにするでもう片方のOSを指定できる)


確認 > 公開 で送信できる

  1. 実機で確認する
    iPhoneは実機のみでしかプッシュ通知の確認ができない
    Androidも通知を送りまくってるとエミュレーターが重くなって真っ黒現象が多かったので実機で確認がオススメ
    各OSとアプリの状態(フォアグラウンド&バックグラウンド)に合わせてこんな感じで出る

iOS


Android


Androidで通知の確認する際は端末の設定も確認して、適切でなければ以下を参考に設定を切り替える↓
https://zenn.dev/miyaken12/scraps/7315c4662a7648#comment-c56a4127a4bfd6

miyaken12miyaken12

InAppMessaging

すごい簡単
パッケージをインストールするだけ

dependencies:
  flutter:
    sdk: flutter
  firebase_in_app_messaging: ^0.5.0+4

あとはコンソールでポチポチアプリ内メッセージを作成する

ざっと

  • ダイアログのタイプ カード、モーダル、画像のみ、トップバナーの4種類
  • 色設定 背景、テキストなど
  • テキスト メッセージ、本文など
  • 画像(URLで)
  • ボタン
    • テキスト
    • タップ時の遷移先

なことができる

表示のされ方が以下なので

要件がうまく当てはまらない場合はInAppMessagingは適さないかも

例 アプリ起動時に毎回表示するダイアログ(次回以降表示しないボタン込みのやつ)

こんな感じででた

あとイベントログを仕込んでそのイベントをトリガーにして表示は可能かも?
https://firebase.google.com/docs/in-app-messaging/compose-campaign?hl=ja

トップ画面で表示したいけどログイン済みのみ場合出したい場合は
ログイン済みであることを識別できるイベントログを仕込んでそのログでトリガーすれば未ログイン時は出さないとかできるかも?