flutterライブラリのlocationを使用して、デバイスの位置情報を取得するための方法を記載する。

開発環境と検証端末

  • 開発環境
    • MacOS Sonoma 14.1
    • Xcode – develop for iOS and macOS (Xcode 15.0.1)
    • Android Studio 17.0.6
    • Flutter 3.13.6
  • 検証端末
    • iPhone8+ iOS16.7.2
    • Google Pixel 6a Android13

使用するライブラリ

location 5.0.3: https://pub.dev/packages/location

設定

Android

以下のファイルに追記

android/app/src/main/AndroidManifest.xml

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />

記載場所については、以下の公式ドキュメントを参照。

https://developer.android.com/guide/topics/manifest/uses-permission-element

iOS

以下のファイルに追記

ios/Runner/Info.plist

<key>NSLocationWhenInUseUsageDescription</key>
<string>このアプリがバックグラウンドで位置情報を使用する理由を説明するテキスト</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>アプリが常に位置情報を使用する理由を説明するテキスト</string>

実装

アプリで位置情報を使用できるようにするためには、位置情報サービスの状態と権限状態を確認する処理を実装する必要がある。以下の手順で実装する。

  1. 位置情報のロジックを別ファイルで定義する。
  2. main.dartなどのファイルで呼び出す

1. 位置情報のロジックを別ファイルで定義する

location_manager.dartという新しいファイルを作成し、以下のコードを追加。処理の詳細についてはコメントを参照。

import 'package:amplify_flutter/amplify_flutter.dart';
import 'package:location/location.dart';

class LocationManager {
  final Location location = Location();
  // 位置情報サービスが現在有効かどうか
  late bool _serviceEnabled;
  // アプリに位置情報へのアクセス権限が与えられているか
  late PermissionStatus _permissionGranted;
  // 現在の位置情報
  LocationData? _currentLocationData;

  // LocationManagerのシングルトンインスタンスを作成
  static final LocationManager _instance = LocationManager._internal();

  factory LocationManager() {
    return _instance;
  }

  // privateなコンストラクタ
  LocationManager._internal();

  /*
    位置情報サービスを初期化する処理
  */
  Future<void> initLocationService() async {
    try {
      // 位置情報サービスが現在有効かどうかを確認
      _serviceEnabled = await location.serviceEnabled();
      if (!_serviceEnabled) {
        // サービスが無効の場合、ユーザーに位置情報サービスを有効にするように要求
        _serviceEnabled = await location.requestService();
        if (!_serviceEnabled) {
          throw Exception('Location services are disabled.');
        }
      }

      // アプリに位置情報へのアクセス権限が与えられているかをチェック
      _permissionGranted = await location.hasPermission();
      if (_permissionGranted == PermissionStatus.denied) {
        // 権限が拒否されている場合、ユーザーに位置情報へのアクセス権限を要求
        _permissionGranted = await location.requestPermission();
        if (_permissionGranted != PermissionStatus.granted) {
          throw Exception('Location permissions are denied');
        }
      }

      // 現在の位置情報を取得
      _currentLocationData = await location.getLocation();
    } catch (e) {
      // 例外をキャッチしてログに記録するか、エラーハンドリングを行う
      safePrint('An error occurred while initializing location services: $e');
      // 必要に応じてユーザーにエラーを通知
    }
  }

  /* 
    現在の位置情報を取得
    @return _currentLocationData: latitude longitude
  */
  Future<LocationData?> getLocation() async {
    if (_permissionGranted != PermissionStatus.granted) {
      throw Exception('Location permissions are not granted');
    }
    _currentLocationData = await location.getLocation();
    return _currentLocationData;
  }

  /*
    位置情報の変更をリッスンする
  */
  Stream<LocationData> get onLocationChanged {
    return location.onLocationChanged;
  }

  /*
    バックグラウンドでの位置情報取得を有効にする
    @param enable バックグラウンドでの位置情報取得を有効にするかどうか
  */
  Future<void> enableBackgroundMode(bool enable) async {
    await location.enableBackgroundMode(enable: enable);
  }
}

2. main.dartなどのファイルで呼び出す

@override
  void initState() {
    super.initState();
    // 位置情報関連の非同期処理をinitStateで安全に呼び出す
    WidgetsBinding.instance.addPostFrameCallback((_) async {
      // LocationManagerのインスタンスを取得し初期化
      var locationManager = LocationManager();
      await locationManager.initLocationService();
      // 現在の位置情報を取得
      var currentLocation = await locationManager.getLocation();
      safePrint(
          'Current Location: ${currentLocation?.latitude}, ${currentLocation?.longitude}');
      // アプリがバックグラウンドにあるときも位置情報を取得する
      await locationManager.enableBackgroundMode(true);
      // 位置情報が変わった時の処理を設定
      locationManager.onLocationChanged.listen((LocationData currentLocation) {
        safePrint(
            'New Location: ${currentLocation.latitude}, ${currentLocation.longitude}');
      });
    });
  }

上記の実装により、以下のような位置情報を確認するためのホップアップが表示されるようになる。アプリの使用時のみを選択した場合には、上記の`safePrint(‘Current Location: ${currentLocation?.latitude}, ${currentLocation?.longitude}’);`で現在の位置情報が出力されるようになる。

iPhoneでの表示

Androidでの表示

発生したエラー

locationプラグイン設定後のAndroidビルドエラー

私の環境下でlocationプラグイン設定後に以下のエラーが発生した。

Run with --info or --debug option to get more log output.
> Run with --scan to get full insights.

* Get more help at https://help.gradle.org

BUILD FAILED in 40s

┌─ Flutter Fix ──────────────────────────────────────────────────────────────────────────────┐
│ [!] Your project requires a newer version of the Kotlin Gradle plugin.                     │
│ Find the latest version on https://kotlinlang.org/docs/releases.html#release-details, then │
│ update /Users/waibandl321/onishi/code/Flutter/reboot/android/build.gradle:                 │
│ ext.kotlin_version = '<latest-version>'                                                    │
└────────────────────────────────────────────────────────────────────────────────────────────┘
Exception: Gradle task assembleDebug failed with exit code 1

Kotlin Gradleプラグインのバージョンが古いことが原因のよう。以下の手順で解消した。

  • Kotlinの最新バージョンを確認するために、提供されたリンク Kotlin Releases にアクセス
  • プロジェクトのandroid/build.gradleファイルを開き、ext.kotlin_version の値を最新バージョンに更新
  • 再度ビルドする
カテゴリー: Flutter