📑

FlutterでGmail監視アプリを作成してみた

2024/01/22に公開

はじめに

現在は卒論のシーズンですね。
うちのゼミでは先生とのやり取りはメールで行っています。諸連絡はもちろん、レジュメや卒論のチェックにもメールを使用しています。チャットツールだとありがたいのになとは思いつつ...

ここからが問題です。
メールで伝えきれない内容についてオンラインビデオ通話で確認し合うのですが、この通話の開始時間がメール到着時刻の30分後だったりするんですよね。
もちろん、こちらが先生にチェック申請のメールを送信してから、いつ先生の返信が来るのかが不明です。そのため、メールの送信後は常に待機しなくてはならないのです。

今回はその対策として、Android上で動作するメール監視アプリを作成しました。
https://github.com/char5742/email_alarm

アプリ概要

今回作成したアプリは、バックグラウンドで1分おきにGmailを取得、目的のメール(sendersで指定したアドレスからのメール)が届いていればアラームを流すというものです。
通知ではなく、アラームであるのがこだわりポイントです。

ホーム画面 設定画面

技術構成

Firebase Authentication, Google Sign Inで認証することで、モバイル上でのGmail API及びメールの取得を可能にしています。
使用した主要ライブラリ

Gmail API

Gmail APIはこの公式のgoogleapisライブラリに含まれています。

  final googleSignIn = GoogleSignIn(
    scopes: [
      GmailApi.gmailReadonlyScope,
    ],
  );
    // [authenticatedClient] は [extension_google_sign_in_as_googleapis_auth] からの拡張メソッドです。
    final httpClient = await googleSignIn.authenticatedClient();
    assert(httpClient != null, 'Authenticated client missing!');
    final gmailApi = GmailApi(httpClient!);
    final reponse = await gmailApi.users.messages.list('me', q: query);
    final messages = reponse.messages;

上記のように GmailApi を使用して簡単にメッセージ(メール)を取得できます。

APIの制限についてはこちら。1分間隔の取得は余裕で行なえます。
https://developers.google.com/gmail/api/reference/quota?hl=ja

GCPでの作業

Gmail APIを有効にし、OAuthの同意画面にてスコープに追加する必要があります。
アプリストアに公開せず、個人で活用するぶんには検証しなくて問題ありません。

バックグラウンド処理

Androidであれば、flutter_background_serviceにてForegroundModeで実行することで楽に処理することができます。
ライブラリ自体はiOSも対応していますが、残念ながらバッテリー効率などのためにバックグラウンド処理が制限されており、最短15分間隔でしか実行できません。

サンプル
('vm:entry-point')
Future<void> onStart(ServiceInstance service) async {
...
  Timer.periodic(Duration(minutes: config.intervalInMinutes), (timer) async {
    service.invoke('check');
  });
}
_checkSubscription = service.on('check').listen((event) async {
      final emails = await ref.refresh(fetchEmailsProvider.future);
      service.invoke('showNotification');
      ...
      await ref.read(alarmServiceProvider).playSound();
      await _showFullScreenNotification(emails.first.content ?? '');
}

通知処理

通知は皆さんご存知flutter_local_notificationsにて実装しました。
idを揃えておけば、flutter_background_service側のIsolateと同じグループで通知を出すことができます。

 const androidPlatformChannelSpecifics = AndroidNotificationDetails(
    ...
    fullScreenIntent: true,
    ...
  );

fullScreenIntentをtrueにすると、ネイティブのアラームみたいな挙動にできます。

アラーム再生

flutter_ringtone_playerにてasAlarmフラグをtrueにすればサイレント/マナーモードを無視して音を流すことができます。

感想

本当ならiOSにも対応して、より透明性をもたせてゼミのみんなに配れるくらいのものを作成したかった...
GAS, FCMを利用するパターンも途中まで開発していましたが、GASにセキュアな乱数が無かったり何かとGCPが必要だったりで挫折しました。(非エンジニアが利用するのにGASだけならまだしもGCPはちょっと)
そして気を取り直してバックグラウンド処理を利用しようものならiOSの15分制限と、壁にぶつかりまくりな開発でした。

Discussion