use_of_void_result

このようなエラーメッセージが出ます。

This expression has a type of 'void' so its value can't be used.
Try checking to see if you're using the correct API; there might be a function or call that returns void you didn't expect. Also check type parameters and variables which might also be void.

詳細は下記に書いてはありますが、わかりづらいです。
https://dart.dev/tools/diagnostic-messages#use_of_void_result

具体的にはこんなソースコードで発生します。

import 'package:flutter/material.dart';

void main() {
  runApp(MaterialApp(
    home: Home(),
  ));
}

class Home extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      floatingActionButton: FloatingActionButton(
        onPressed: () async {
          await _onPressed(context);
        },
      ),
    );
  }

  void _onPressed(context) async {
    await Future.delayed(Duration(seconds: 1));
    ScaffoldMessenger.of(context).showSnackBar(SnackBar(
      content: Text("hello"),
    ));
  }
}

こんな感じに修正すればOKです。

FloatingActionButton(onPressed: () => _onPressed(context)),

flutter_localizationsとfirebase_auth_web のintlのバージョンが合わない問題

Because firebase_auth_web 0.3.3 depends on intl ^0.16.1 and no versions of firebase_auth_web match >0.3.3 <0.4.0, firebase_auth_web ^0.3.3 requires intl ^0.16.1.

And because every version of flutter_localizations from sdk depends on intl 0.17.0, flutter_localizations from sdk is incompatible with firebase_auth_web ^0.3.3.

And because firebase_auth 0.20.1 depends on firebase_auth_web ^0.3.3 and no versions of firebase_auth match >0.20.1 <0.21.0, flutter_localizations from sdk is incompatible with firebase_auth ^0.20.1.

モバイルしか作ってないから、関係ないと思っている場合も、 flutter2.0からはwebがデフォルトで有効になっているみたいなので、 --no-enable-webにしても解決されない様子

ということで潔くoverrideする

github.com

dependency_overrides:
    intl: ^0.16.1

firestore: arrayの中のmapの特定の要素を削除する

無効なFCMトークンを削除する処理を書こうとしたときに調べました。

https://firebase.google.com/docs/firestore/manage-data/add-data#update_elements_in_an_array

import * as admin from 'firebase-admin';
// serviceAccountを取得
import { getInitializeAppOptions } from '../getInitializeAppOptions';

admin.initializeApp(getInitializeAppOptions());
const firestore = admin.firestore();
const docRef = firestore
  .collection('publics')
  .doc('map_delete_sample');
main();

async function setup() {
  await
    docRef.set({
      tokens: [
        { token: 'aaa', device: 'android' },
        { token: 'bbb', device: 'apns' }
      ]
    });
}

async function main() {
  await setup();
  await docRef
    .update({
      "tokens": admin.firestore.FieldValue.arrayRemove({ token: 'bbb', device: 'apns' })
    });
  console.log((await docRef.get()).data());
  // { tokens: [ { token: 'aaa', device: 'android' } ] }
}

// ほかのサンプルコードと変数名とか衝突しないようにする
export { }

オブジェクトそのもので比較することで削除対象を特定しているようです。

firebase messaging.sendToDevice()をsinonで置き換える

https://firebase.google.com/docs/cloud-messaging/send-message?hl=ja#send-to-individual-devices

注: 1 つのリクエストでメッセージを送信できる宛先デバイスの最大数は 1,000 台です。配列に 1,000 を超える登録トークンを指定すると、そのリクエストは messaging/invalid-recipient エラーで失敗します。

ということなので分割して送信することにしました。 分割するロジックはfor文で回す感じにしたので、無限ループが怖いので テストコードで確認しました。

他のtest spyみたいなのは使ったことがありますが、 sinonは初めてでしたが、公式のサンプルで使われていたので。

https://firebase.google.cn/docs/functions/unit-testing?hl=ja

import * as admin from 'firebase-admin';
import {
  messaging
} from 'firebase-admin';
import MessagingPayload = messaging.MessagingPayload;
import MessagingOptions = messaging.MessagingOptions;
import MessagingDevicesResponse = messaging.MessagingDevicesResponse;

/** 
 * // https://firebase.google.com/docs/cloud-messaging/send-message?hl=ja#send-to-individual-devices
 * divisionUnit=1000
*/
export async function batchSendToDevice(
  fcmTokens: string[],
  divisionUnit: number,
  message: MessagingPayload,
  options: MessagingOptions)
  : Promise<MessagingDevicesResponse[]> {

  const promises = [];
  for (let i = 0; i < fcmTokens.length; i += divisionUnit) {
    const dividedTokens = fcmTokens.slice(i, i + divisionUnit);
    promises.push(admin.messaging().sendToDevice(dividedTokens, message, options));
  }
  return await Promise.all(promises);
}

テストコード

import * as admin from 'firebase-admin';
import {
  messaging
} from 'firebase-admin';
import MessagingDevicesResponse = messaging.MessagingDevicesResponse;
import * as sinon from 'sinon';
import { batchSendToDevice } from './batch-send-to-device';

admin.initializeApp();

describe("batchSendToDevice", () => {
  it("Split logic works fine", async () => {
    const sandbox = sinon.createSandbox({});
    const messaging = admin.messaging();

    sandbox.replace(messaging, 'sendToDevice',
      (registrationToken, payload, options) => {

        const response: MessagingDevicesResponse = {
          canonicalRegistrationTokenCount: 1,
          failureCount: 0,
          multicastId: 1,
          results: [],
          successCount: 1
        };

        if (!Array.isArray(registrationToken)) {
          response.multicastId = Number(registrationToken);
          return new Promise((result, reject) => {
            return result(response);
          });
        }

        registrationToken.forEach((e) => {
          response.results.push({ messageId: e });
          return response;
        });
        return new Promise((result, reject) => {
          return result(response);
        });
      });
    const tokens = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11'];
    const ret = await batchSendToDevice(tokens, 5, {}, {});
    console.log(JSON.stringify(ret, null, 2));
  });
});

完全なサンプル https://github.com/na8esin/firebase-practice/tree/master/functions/src

flutter web enableが切り替わらない

flutter webとスマートフォンの環境を併存したくて、 fvmを導入しましたが、うまく切り替わらず。

バージョンは切り替わるんですが、webの有効化みたいなのが 切り替わらず。 ただ、この時にflutter create . を実行してもwebフォルダーは作られず

そしたら、何をもって切り替わらないといっているかというと、 pubspec.yamlでfirebase_authのバージョンを空で設定しているとき、 バージョンが「0.6.2+1」になってしまうということです。

fvmがインストールされてない環境だと、「0.18.4+1」 になります。

ここが何を言っているかわからないと思いますが、 webが有効化されている場合、web用のfirebase_authも 一緒にダウンロードされていて、それがほかのパッケージの バージョンと整合性を取るために適切なバージョンになるということみたいです。

※今回はfirebase_messagingに引っ張られた模様

もともと、flutter configはプロジェクトが変わったとしても、 一律で下記の設定を見てしまいます。(windows)

~\AppData\Roaming\.flutter_settings

ですが、ここで、"enable-web": false,にしても、 設定を消しても切り替わらず。

fvmは別の設定を見ているのか、どっかでキャッシュしているのか?

もちろん、fvmを削除すると、webはfalse状態になります。

というか、クリーンな環境で再度やってみましたが、 masterだとそもそもflutter createすると webフォルダが作成される。。。

新しすぎると、webがfalseにできないか、 明示的にfalseにするしかないのか?

1.27.0-5.0.pre.95の段階では、webをfalseにすると createした時にwebフォルダはできない様子。

wslにflutterをインストールして同居する戦略

~$ sudo snap install flutter --classic
Interacting with snapd is not yet supported on Windows Subsystem for Linux.
This command has been left available for documentation purposes only.

というわけでwget

ここで、テストするときchromeとかどう立ち上げるんだと 思って、この方法は見送り。

結局、fvmは使わない

windowsはいろいろハマって面倒、

github.com 特にCan't load Kernel binary: Invalid kernel binary format version

なので、直接flutterを2バージョンダウンロード ※stableと1.24.0-10.2.pre

stableはgit pullで1.24.0-10.2.preはzipでダウンロード

stableにはパスを通しておく

環境変数でpath設定をします。

設定しないでフルパスでflutter configなどを実行すると、 別windowが立ち上がって実行結果がすぐに消えるので。

この状態だと何とか安定していて、androidエミュレータchromeを別々のvscodeから開いて実行可能になりました。

蛇足: ちなみに、別windowをpowershellで回避してみる

Start-Process -NoNewWindow -Wait -ArgumentList config  C:\src\flutter\flutter_windows_1.24.0-10.2.pre-beta\bin\flutter.bat

長い。。。

.mocharc.jsのspecを設定するとコマンドラインでパスを与えたときに追加される

この辺りに書いてある https://mochajs.org/#merging

例えば、こんな設定があるときに

module.exports = {
  extension: [
    "ts"
  ],
  spec: "test/**/*.spec.ts",
  require: "ts-node/register"
}

npx mocha "./src/*/.spec.ts" と実行してもtestディレクトリ配下のspecも実行される

進捗率・解答率

questionが全部で5問あるとして

user1        
    question1   TRUE
    question2   FALSE
user2       
    question1   TRUE
    question2   FALSE
    question3   FALSE
user3       
    question4   FALSE

正答率 2/6 には意味がありそう

進捗率 6 / 15( 5 * 3 ) よりは平均2問とかのほうがよさそう