Closed16

Flutter + Firebaseでアプリを作っていく中で調べたことのメモ

とみーとみー

新規に開発中のアプリで調べたことについて雑に書いていこうと思います。
Flutter(Dart)、Firebase共にほぼ初心者です。
ただ、すでにある程度進めてしまっている状態でこのスクラップを作ったので、完全に1からではありません。

とみーとみー

UserAccountsDrawerHeader

https://api.flutter.dev/flutter/material/UserAccountsDrawerHeader-class.html
Drawerのヘッダにユーザ情報を表示するためだけに存在するWidget。
超ニッチな感じもするけど、Material Designにしっかり定義されてるので、そういう意味ではあって然るべきクラスなのかもしれない。
https://material.io/components/navigation-drawer#anatomy

↓こういうコードを書くだけで、

    Drawer(
      child: ListView(
        children: [
          UserAccountsDrawerHeader(
            currentAccountPicture: Icon(Icons.account_circle, size: 64),
            accountName: Text("name"),
            accountEmail: Text("test@example.com"),
          ),
        ],
      ),
    );

↓こうなる。便利。

とみーとみー

Dartにはinterface的なもの(Swiftで言うprotocol)は存在しないらしい。
ただ全てのクラスは暗黙的にインタフェースも定義しており、クラスの定義時にimplementsで指定すると、そのクラスのインタフェースを実装出来るとのこと。

class A {
  void funcA() {
    print("A");
  }
}

class B implements A {
  @override
  void funcA() {
    print("B");
  }
}

ここで、class BfuncA()をコメントアウトすると、↓のようなエラーが発生する。

Missing concrete implementation of 'A.funcA'.
Try implementing the missing method, or make the class abstract.

https://dart.dev/guides/language/language-tour#implicit-interfaces

とみーとみー

デフォルト実装が不要な場合はabstract classにして全て抽象メソッドにすれば良さそう。

abstract class A {
  void funcA();
}
とみーとみー

freezedでプロパティを追加したりした時、何故かVScodeで.freezed.dartファイルを一度開いてやらないとずっと反映されないままビルドエラーになったりするんだけど何だろう...

とみーとみー

Riverpod、なんとなく状態管理専用のパッケージに見えてたけど、こんな感じにするとDIにも使えそう。

class FooService {
  ...
}

class BarService {
  final FooService fooService;
  BarService(this.fooService);
  ...
}

final fooServiceProvider = Provider<FooService>((ref) {
  return FooService();
});

final barServiceProvider = Provider<BarService>((ref) {
  final fooService = ref.watch(fooServiceProvider);
  return BarService(fooService);
});

まだ試してないけど、Providerの中身を書き換えることも出来るようでテスト時のモック化も大丈夫そう。
https://riverpod.dev/docs/cookbooks/testing#overriding-the-behavior-of-a-provider-during-tests

というか公式サイトでも言及されてた。
https://riverpod.dev/docs/concepts/providers

Providers are a complete replacement for patterns like Singletons, Service Locators, Dependency Injection or InheritedWidgets.
とみーとみー

SnackBarの表示方法

ScaffoldMessenger.of(context).showSnackBar(snackBar)を使う。
https://flutter.dev/docs/cookbook/design/snackbars

final snackBar = SnackBar(content: Text('Yay! A SnackBar!'));

// Find the ScaffoldMessenger in the widget tree
// and use it to show a SnackBar.
ScaffoldMessenger.of(context).showSnackBar(snackBar);

以前はScaffold.of(context)だったが非推奨になり、ScaffoldMessengerを使うようにとのこと。
Scaffoldの時は画面遷移等で現在のScaffoldが破棄(BuildContextが無効化)された状態でSnackBarを表示しようとした時にエラーになったりしていたが、ScaffoldMessengerにすることでそういったエラーも無くなるらしい。
詳細は以下。
https://flutter.dev/docs/release/breaking-changes/scaffold-messenger

とみーとみー

Firebase App Distribution

https://firebase.google.com/docs/app-distribution?hl=ja
「開始」して、ipa放り込んで、テスターのメールアドレス追加して、「配信」するだけで簡単にすぐ配信出来た。
アプリ側にSDK入れたりは全く必要なし。

ただ配信される側として、iOSにプロファイルのインストールを要求されるのが若干気になったけど、インストールしてしまえば簡単だった。

※2021年5月27日時点でまだベータ版らしいので、仕様が変わる可能性はあります。

とみーとみー

AdHoc配布用IPA作成

(プロビジョニングプロファイルの作成等は完了している前提)

初回のみ

  • flutter build ipaコマンドでxcarchiveを作成。
  • build/ios/archive/XXX.xcarchiveに作成されているので、これを開く。(Xcodeが開くはず)
  • 普段のiOS開発と同じようにDistribute AppからIPAをExportする。

2回目以降

  • 初回でExportした、IPAが入っているディレクトリと同じ場所にあるExportOptions.plistをどこかにコピーする。
  • flutter build ipa --export-options-plist="path/to/ExportOptions.plist"(path/to/はコピー先のディレクトリ)実行。
  • build/ios/ipa内にIPAが作成される。

2回目以降はXcodeを開く必要が無くなる。

参考

https://flutter.dev/docs/deployment/ios

とみーとみー

Firestore Security Rules編集補助プラグイン for VScode

VScodeでRuleを記述するにあたって、せめてシンタックスハイライトは欲しいと思い探してみたところ、
https://marketplace.visualstudio.com/items?itemName=ChFlick.firecode
これが現状良さそう?
インストールしてみてパッと見は良い感じ。

こちらの方がダウンロード数は多く、以前はおすすめされたりしていたようだが、Archivedされて更新が完全に止まっている模様。
https://marketplace.visualstudio.com/items?itemName=toba.vsfire

とみーとみー

Firestore Security Rulesを記述するときに参考にした情報

他にも色々な優良情報があるかもしれませんが、現時点で参考にした情報。


https://firebase.google.com/docs/firestore/security/get-started?hl=ja
https://firebase.google.com/docs/reference/rules/rules?hl=en
何はともあれまずは公式。


https://tech-blog.sgr-ksmt.org/2018/12/11/194022/
諸々のtips集。
ここに貼られている著者様の他記事も要参照。


https://www.youtube.com/watch?v=fHFoqJpkbJg
シンプルなTODOアプリを作りながらFirestoreのデータ設計やSecurity Rulesの書き方を解説されています。


https://www.youtube.com/watch?v=Twd7i2H-jaQ
グループ向けTODOアプリを作りながらFirestoreのデータ設計やSecurity Rulesの書き方を解説されています。
前述のシンプルなTODOアプリの応用編。


https://qiita.com/KosukeSaigusa/items/18217958c581eac9b245
簡単な家計簿アプリを開発する想定でSecurity Rulesの書き方や考え方について解説されています。


基本的にどの記事(動画)も、ただ書き方を説明するだけではなく「何故そう書くのか」を細かく説明して下さっているので非常に参考になりました。

とみーとみー

[Dart]NullableなListのnullを取り除き、Non-NullableなListにする方法

SwiftではcompactMap()でやっていたこと。

List<int?>をList<int>に変換するには:

List<int?> a = [null,2,null];
final List<int> b = a.whereType<int>().toList();

whereType<XXX>で指定した型(ここではint)にマッチする要素のみを抽出、その型のIterableを返してくれるらしい。
これはNullableに限らず色々応用出来そう。

参考:

とみーとみー

アプリアイコンの設定

flutter_launcher_iconsパッケージを使用し、ベースとなる画像から各プラットフォーム毎のアイコン画像を生成します。

flutter_launcher_icons のインストール

$ flutter pub add -d flutter_launcher_icons

投稿時点では0.9.0

dev_dependencies:
  flutter_launcher_icons: ^0.9.0

pubspec.yamlに設定を追記

flutter_icons:
  android: true
  ios: true
  image_path_ios: "assets/app_icon_ios.png"
  image_path_android: "assets/app_icon_android.png"
  adaptive_icon_background: "#ffffff"
  adaptive_icon_foreground: "assets/app_icon_android_foreground.png"
  • android / ios: true / false でそのプラットフォームの画像を生成するか否かを指定。trueの場合はプロジェクト生成時にデフォルトで作られているFlutterアイコンを上書きする形で画像を生成する。また画像パスを文字列で指定することもでき、その場合はその名前で画像が新たに生成される。
  • image_path_ios: iOSで使用するベース画像を指定
  • image_path_android: Androidで使用するベース画像を指定
  • adaptive_icon_background: Android 8.0(API Level 26) 以降で使用されるAdaptive Iconのバックグラウンドレイヤを指定する。色("#ffffff")もしくは画像パスを指定。
  • adaptive_icon_foreground: Android 8.0(API Level 26) 以降で使用されるAdaptive Iconのフォアグラウンドレイヤを指定する。画像パスを指定。

Android 8.0(API Level 26)以降はadaptive_icon_xxxのAdaptive Iconを、それ以前はimage_path_androidに指定した画像が使用されるものと思われます。
(前者はmipmap-anydpi-v26にリソースが作られ、後者はmipmap-hdpimipmap-xxxhdpiにリソースが作られる。)

画像の生成

$ flutter pub run flutter_launcher_icons:main

ベース画像に大きめの画像を用意しておけば、あとはよしなにリサイズしてくれるようです。
ここまでの簡単な設定をしておけば、後はコマンド一発でアイコン画像が設定された状態で出来上がるので便利ですね。

参考:

とみーとみー

画面の空白部分をタップした時のTextFieldのフォーカスの外し方

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () {
        FocusManager.instance.primaryFocus?.unfocus();
      },
      child: Scaffold(
        ...
      )
    );
}

以下の1行でフォーカスが外れ、キーボードが閉じるようです。

// ⭕️
FocusManager.instance.primaryFocus?.unfocus();

これに関してググると、

// ❌
FocusScope.of(context).unfocus();

こういった実装や、

// ❌
FocusScopeNode currentFocus = FocusScope.of(context);
if (!currentFocus.hasPrimaryFocus) {
  currentFocus.unfocus();
}

こういったものが出てくるのですが、私のアプリではおかしな挙動になってしまいダメでした。(あまりちゃんと理解が出来ていません)

参考:

とみーとみー

複数のStreamを1つのStreamにまとめる

List<Stream<T>>Stream<List<T>> にして、各々のStreamの最新値をリストにしたStreamを作成する方法。
残念ながらバニラDartでは簡単には出来なさそうなので、RxDartを使用し、Rxではお馴染みのcombineLatestを使用します。
https://pub.dev/packages/rxdart

RxDartのインストール

$ flutter pub get rxdart

サンプルコード

import 'package:rxdart/rxdart.dart';

...

final List<Stream<XXX>> xxxStreams = ...
final Stream<List<XXX>> combinedStream = CombineLatestStream.list(xxxStreams).toList())

RxDartは独自にObservable型等を作らずに、Dart標準のStreamクラスを拡張する形で実装されているようなので、若干導入の敷居が下がって良さそうですね。

このスクラップは2022/06/10にクローズされました