Flutterでアプリのバックグラウンド状態で位置情報を使用しながら処理できるパッケージを作った

[株式会社Never](https://neverjp.com/)のshoheiです。

株式会社Neverは「NEVER STOP CREATE 作りつづけること」をビジョンに掲げ、理想を実現するためにプロダクトを作り続ける組織です。モバイルアプリケーションの受託開発、技術支援、コンサルティングを行っております。アプリ開発のご依頼や開発面でのお困りの際はお気楽にお問合せください。

https://neverjp.com/service/

# 概要

弊アプリ（世界遺産ウォーキング）のユーザーさんから**アプリを閉じていても目的地に到達した事を教えて欲しい**というリクエストがあったので、その機能を実現するためのパッケージを作りました。

パッケージをアプリに導入したところ無事ストアの審査も通ったので宣伝（笑）も兼ねて共有します。

世界遺産ウォーキング
[iOS](https://apps.apple.com/jp/app/id1591767928) 
[Android](https://play.google.com/store/apps/details?id=app.product.sekaiisanwalking)

# 作ったパッケージ

[background_task](https://pub.dev/packages/background_task)

アプリがバックグラウンド状態でも位置情報を検知して処理できるパッケージです。位置情報更新が検知されると、Stream経由で伝搬されるので、その中でやりたい事を実装します。

以下のような機能を実現したい際に利用できます。

- 歩いた距離や歩数を監視して通知を出したい
- 位置情報（緯度と経度）から目的地に到着したことを通知したい
- 位置情報をトラッキングしたい（サーバーへ送るなど）

弊アプリは、歩数からバーチャルマップの目的地に到達したことを通知できれば良いので、サーバーへは送らずにローカル上で完結させています。

ローカル通知を送ることで、ユーザーに対してアプリを開く事を促し、アプリを開いたタイミングで目的地到達時の処理をするといった流れです。

既に同じようなパッケージは存在していましたが、Stream経由で伝搬されないことや位置情報検知で設定できるパラメータが不足していたこと等があり、好みのパッケージがなかったので作りました。

:::note info
アプリがタスクキルされた状態での位置情報を検知できるよう対応しました。詳しくは[README](https://github.com/never-inc/flutter_background_task?tab=readme-ov-file#usage)をご確認ください。
:::

# 実装方法

iOSとAndroidにそれぞれ権限を付与してください。

```xml:Info.plist
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>Used to monitor location in the background and notify to app.</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>Used to monitor location in the background and notify to app.</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>Used to monitor location in the background and notify to app.</string>
<key>UIBackgroundModes</key>
<array>
    <string>fetch</string>
    <string>location</string>
</array>
```

```xml:AndroidManifest.xml
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION"/>
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
```

実装はとてもシンプルです。

```dart
// 位置情報が検知されると発火する
BackgroundTask.instance.stream.listen((event) {
    // ここでやりたいことをする
});

// バックグラウンドで位置情報の使用を開始
await BackgroundTask.instance.start();

// 停止
await BackgroundTask.instance.stop();
```

詳しくは[example](https://github.com/never-inc/flutter_background_task/blob/main/example/lib/main.dart)をご確認ください。

# 端末の電池消費量

感覚値になりますが、Androidはそこまで気にならなかったです。

iOSはまぁまぁ消費している実感があったので、パラメータで位置情報検知の精度を調整できるようにしました。[DesiredAccuracy](https://github.com/never-inc/flutter_background_task/blob/main/lib/background_task.dart#L26)を使って精度を緩くして電池消費を抑えることができますが、位置情報の検知の頻度も少なくなり、Stream経由の伝搬が期待通りに来ない場合がありますので注意してください。

# アプリの審査を通す

バックグラウンドで位置情報を使用する際の審査対応が必要です。

ユーザーに対して位置情報の使用許可のリクエストをする前に機能の説明をする必要がありました。弊アプリでは事前に機能の説明ダイアログを表示し、ユーザーは説明ダイアログを確認して、機能のON/OFFができます。


審査時はiOSとAndroidともにこの機能を利用したデモ動画が求められますので準備します。注意点は以下の通りです。

- 実機を用いて端末の画面録画から撮影
- 画面録画で必要なシーン
    - アプリでユーザーに対する機能の説明後、位置情報使用を許可してバックグラウンド処理が開始するシーン
    - アプリをバックグラウンド状態にしても位置情報が使用されてるシーン
    - 【念の為】アプリのバックグラウンド状態で目的地の到達通知が来てそれをタップしてアプリを開いて到達したことを確認するまでのシーン
    - 【念の為】アプリから位置情報の検知をOFFにするシーン

## iOS
「App Reviewに関する情報」の入力欄にデモ動画のリンクを記載します。Googleドライブに動画を保存して、そのリンクを記載しました。

あとは、**アプリのプライバシー**に位置情報の内容を追加しました。

## Android

「アプリのコンテンツ」 → 「位置情報の利用許可」のフォームに必要事項を入力します。

|質問  |回答例  |
|---|---|
| アプリの目的 | 歩いた距離（歩数）を監視して目的地の到達を通知する機能です。アプリを閉じた状態で到達通知を受けれるようにします。 |
| 位置情報へのアクセス| 緯度と経度、歩数 |
|動画での手順の説明|※YouTubeへのリンクを記載 |

動画での手順の説明では、YouTubeへのリンク（昔YouTubeやってた経験が活k🚧🚧🚧）を求められるので撮影した動画のリンクを記載しました。

あとは、**データセーフティー**に位置情報の内容を追加しました。


# 最後に

[background_task](https://pub.dev/packages/background_task)は突貫で作ったので、もしバグがあったらPRお願いします。

このパッケージが誰かの役の立てれば幸いです。