Fixed Location Permission issue on Android - 1

This commit is contained in:
2024-05-24 07:21:28 +09:00
parent 74f6a79a36
commit e55674e1b9
19 changed files with 376 additions and 330 deletions

View File

@ -9,6 +9,7 @@
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
<application <application
android:label="岐阜ナビ" android:label="岐阜ナビ"
android:name="${applicationName}" android:name="${applicationName}"

View File

@ -20,6 +20,7 @@ import com.google.android.gms.location.LocationCallback
import com.google.android.gms.location.LocationRequest import com.google.android.gms.location.LocationRequest
import com.google.android.gms.location.LocationResult import com.google.android.gms.location.LocationResult
import com.google.android.gms.location.LocationServices import com.google.android.gms.location.LocationServices
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
@ -27,6 +28,7 @@ import java.util.Date
import java.util.Locale import java.util.Locale
import android.app.Notification import android.app.Notification
data class GpsData( data class GpsData(
val id: Int, val id: Int,
val team_name: String, val team_name: String,
@ -54,6 +56,7 @@ class GpsDatabaseHelper(private val context: Context) {
class LocationService : Service() { class LocationService : Service() {
private lateinit var fusedLocationClient: FusedLocationProviderClient private lateinit var fusedLocationClient: FusedLocationProviderClient
private lateinit var gpsDatabaseHelper: GpsDatabaseHelper
override fun onBind(intent: Intent?): IBinder? { override fun onBind(intent: Intent?): IBinder? {
return null return null
@ -61,12 +64,13 @@ class LocationService : Service() {
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
Log.d("LocationService", "Android: onCreate.")
fusedLocationClient = LocationServices.getFusedLocationProviderClient(this) fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)
gpsDatabaseHelper = GpsDatabaseHelper.getInstance(applicationContext)
// 位置情報の権限チェックとGPS有効化の確認を行う // 位置情報の権限チェックとGPS有効化の確認を行う
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) { if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED &&
ContextCompat.checkSelfPermission(this, Manifest.permission.FOREGROUND_SERVICE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
val locationManager = getSystemService(Context.LOCATION_SERVICE) as LocationManager val locationManager = getSystemService(Context.LOCATION_SERVICE) as LocationManager
if (locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) { if (locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
val locationRequest = LocationRequest.create().apply { val locationRequest = LocationRequest.create().apply {
@ -75,45 +79,37 @@ class LocationService : Service() {
fastestInterval = 5000 fastestInterval = 5000
} }
fusedLocationClient.requestLocationUpdates(locationRequest, locationCallback, null) fusedLocationClient.requestLocationUpdates(locationRequest, locationCallback, null)
} else {
Log.d("LocationService", "GPS is disabled.")
// GPSが無効の場合の処理を追加する例: ユーザーにGPSを有効にするように促すなど
}
} else {
Log.d("LocationService", "Location permission is not granted.")
// 位置情報の権限が許可されていない場合の処理を追加する
}
/*
// GPSデバイスが有効になっているか確認する
val locationManager = getSystemService(Context.LOCATION_SERVICE) as LocationManager
if (!locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
Log.d("LocationService", "GPS is disabled.")
// GPSが無効の場合の処理を追加する例: ユーザーにGPSを有効にするように促すなど
}else{
Log.d("LocationService", "GPS is enabled.")
}
*/
// フォアグラウンドサービスの設定 // フォアグラウンドサービスの設定
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val CHANNEL_ID = "location"
val channel = NotificationChannel(CHANNEL_ID, "Location", NotificationManager.IMPORTANCE_DEFAULT) val channel = NotificationChannel(CHANNEL_ID, "Location", NotificationManager.IMPORTANCE_DEFAULT)
val notificationManager = getSystemService(NotificationManager::class.java) // この行を追加 val notificationManager = getSystemService(NotificationManager::class.java)
Log.d("LocationService", "Android: Foreground service.")
notificationManager?.createNotificationChannel(channel) notificationManager?.createNotificationChannel(channel)
} }
val notification = NotificationCompat.Builder(this, "location") val notification = NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("Tracking location...") .setContentTitle("Tracking location...")
.setContentText("Location: null") .setContentText("Location: null")
.setSmallIcon(android.R.drawable.ic_menu_mylocation) // リソースが存在しないため0を設定 .setSmallIcon(android.R.drawable.ic_menu_mylocation)
.setOngoing(true) .setOngoing(true)
.build() .build()
Log.d("LocationService", "Android: Foreground service 2.")
startForeground(1, notification) startForeground(NOTIFICATION_ID, notification)
} else {
Log.d("LocationService", "GPS is disabled.")
// GPSが無効の場合の処理を追加する例: ユーザーにGPSを有効にするように促すなど
stopSelf() // サービスを停止する
}
} else {
Log.d("LocationService", "Location permission or Foreground service location permission is not granted.")
// 位置情報の権限またはフォアグラウンドサービスの位置情報の権限が許可されていない場合の処理を追加する
stopSelf() // サービスを停止する
}
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
return START_STICKY
} }
override fun onDestroy() { override fun onDestroy() {
@ -170,8 +166,6 @@ class LocationService : Service() {
private val locationCallback = object : LocationCallback() { private val locationCallback = object : LocationCallback() {
override fun onLocationResult(locationResult: LocationResult) { override fun onLocationResult(locationResult: LocationResult) {
val currentLocation = locationResult.lastLocation val currentLocation = locationResult.lastLocation
//Log.d("LocationService", "Android: onLocationResult.")
if (currentLocation != null) { if (currentLocation != null) {
val accuracy = currentLocation.accuracy val accuracy = currentLocation.accuracy
if (accuracy <= 30) { if (accuracy <= 30) {
@ -181,13 +175,12 @@ class LocationService : Service() {
val lon = currentLocation.longitude val lon = currentLocation.longitude
val currentTime = System.currentTimeMillis() val currentTime = System.currentTimeMillis()
// GPS データをデバッグ用に表示 // GPSデータをデバッグ用に表示
val sdf = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()) val sdf = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault())
val formattedTime = sdf.format(Date(currentTime)) val formattedTime = sdf.format(Date(currentTime))
//Log.d("LocationService", "Latitude: $lat, Longitude: $lon, Time: $formattedTime, Accuracy: $accuracy")
// GPS データをデータベースに保存 // GPSデータをデータベースに保存
GlobalScope.launch { GlobalScope.launch(Dispatchers.IO) {
addGPStoDB(lat, lon, currentTime) addGPStoDB(lat, lon, currentTime)
} }
@ -203,36 +196,16 @@ class LocationService : Service() {
} }
/*
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Log.d("LocationService", "Android: onStartCommand.") Log.d("LocationService", "Android: onStartCommand.")
/* onCreate でやってるので除外。
// 位置情報の権限チェックとGPS有効化の確認を行う
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
val locationManager = getSystemService(Context.LOCATION_SERVICE) as LocationManager
if (locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
val locationRequest = LocationRequest.create().apply {
priority = LocationRequest.PRIORITY_HIGH_ACCURACY
interval = 10000
fastestInterval = 5000
}
fusedLocationClient.requestLocationUpdates(locationRequest, locationCallback, null)
} else {
Log.d("LocationService", "GPS is disabled.")
// GPSが無効の場合の処理を追加する例: ユーザーにGPSを有効にするように促すなど
}
} else {
Log.d("LocationService", "Location permission is not granted.")
// 位置情報の権限が許可されていない場合の処理を追加する
}
*/
// Foregroundサービスを開始 // Foregroundサービスを開始
startForeground(NOTIFICATION_ID, createNotification()) startForeground(NOTIFICATION_ID, createNotification())
return START_STICKY return START_STICKY
} }
*/
private suspend fun addGPStoDB(lat: Double, lng: Double, timestamp: Long, isCheckin: Int = 0) { private suspend fun addGPStoDB(lat: Double, lng: Double, timestamp: Long, isCheckin: Int = 0) {
try { try {
@ -241,7 +214,6 @@ class LocationService : Service() {
val teamName = preferences.getString("team_name", "") ?: "" val teamName = preferences.getString("team_name", "") ?: ""
val eventCode = preferences.getString("event_code", "") ?: "" val eventCode = preferences.getString("event_code", "") ?: ""
if (teamName.isNotEmpty() && eventCode.isNotEmpty()) { if (teamName.isNotEmpty() && eventCode.isNotEmpty()) {
val gpsData = GpsData( val gpsData = GpsData(
id = 0, id = 0,
@ -253,13 +225,12 @@ class LocationService : Service() {
created_at = timestamp created_at = timestamp
) )
val db = GpsDatabaseHelper.getInstance(context) gpsDatabaseHelper.insertGps(gpsData)
db.insertGps(gpsData)
Log.d("LocationService", "Android: addGPStoDB.") Log.d("LocationService", "Android: addGPStoDB.")
} }
} catch (e: Exception) { } catch (e: Exception) {
Log.e("LocationService", "Error adding GPS data to DB", e) Log.e("LocationService", "Error adding GPS data to DB", e)
// エラーメッセージをユーザーに表示するなどの処理を追加
} }
} }
} }

View File

@ -36,6 +36,11 @@ class MainActivity: FlutterActivity() {
stopLocationService() stopLocationService()
result.success(null) result.success(null)
} }
"isLocationServiceRunning" -> {
Log.d("MainActivity", "Android: called isLocationServiceRunnung.")
val isRunning = isServiceRunning(LocationService::class.java)
result.success(isRunning)
}
else -> { else -> {
result.notImplemented() result.notImplemented()
} }
@ -75,9 +80,8 @@ class MainActivity: FlutterActivity() {
} }
private fun startLocationService() { private fun startLocationService() {
Log.d("MainActivity", "Android: startLocationService.") if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED &&
ContextCompat.checkSelfPermission(this, Manifest.permission.FOREGROUND_SERVICE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
if (!isServiceRunning(LocationService::class.java)) { if (!isServiceRunning(LocationService::class.java)) {
val intent = Intent(this, LocationService::class.java) val intent = Intent(this, LocationService::class.java)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
@ -91,16 +95,19 @@ class MainActivity: FlutterActivity() {
Log.d("MainActivity", "Location service is already running.") Log.d("MainActivity", "Location service is already running.")
} }
} else { } else {
Log.d("MainActivity", "Location permission is not granted.") Log.d("MainActivity", "Location permission or Foreground service location permission is not granted.")
// 位置情報の権限が許可されていない場合の処理を追加する // 位置情報の権限またはフォアグラウンドサービスの位置情報の権限が許可されていない場合の処理を追加する
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.ACCESS_FINE_LOCATION), PERMISSION_REQUEST_CODE) // 例: ユーザーに権限の必要性を説明し、許可を求めるダイアログを表示するなど
} }
} }
private fun stopLocationService() { private fun stopLocationService() {
Log.d("MainActivity", "Android: stopLocationService.") if (isServiceRunning(LocationService::class.java)) {
val intent = Intent(this, LocationService::class.java) val intent = Intent(this, LocationService::class.java)
stopService(intent) stopService(intent)
} else {
Log.d("MainActivity", "Location service is not running.")
}
} }
private fun isServiceRunning(serviceClass: Class<*>): Boolean { private fun isServiceRunning(serviceClass: Class<*>): Boolean {

View File

@ -8,6 +8,32 @@ import Flutter
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool { ) -> Bool {
GeneratedPluginRegistrant.register(with: self) GeneratedPluginRegistrant.register(with: self)
let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
let locationServiceChannel = FlutterMethodChannel(name: "location",
binaryMessenger: controller.binaryMessenger)
locationServiceChannel.setMethodCallHandler { (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
if call.method == "isLocationServiceRunning" {
result(self.isLocationServiceRunning())
} else {
result(FlutterMethodNotImplemented)
}
}
locationManager = CLLocationManager()
locationManager?.delegate = self
locationManager?.requestAlwaysAuthorization()
locationManager?.startUpdatingLocation()
return super.application(application, didFinishLaunchingWithOptions: launchOptions) return super.application(application, didFinishLaunchingWithOptions: launchOptions)
} }
private func isLocationServiceRunning() -> Bool {
guard let locationManager = locationManager else {
return false
}
let isRunning = locationManager.monitoredRegions.count > 0 || locationManager.location != nil
return isRunning
}
} }

View File

@ -3,6 +3,7 @@ import 'dart:io';
//import 'dart:convert'; //import 'dart:convert';
//import 'dart:developer'; //import 'dart:developer';
import 'package:rogapp/model/gps_data.dart'; import 'package:rogapp/model/gps_data.dart';
import 'package:rogapp/pages/home/home_page.dart';
import 'package:rogapp/utils/database_gps.dart'; import 'package:rogapp/utils/database_gps.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_map_tile_caching/flutter_map_tile_caching.dart'; import 'package:flutter_map_tile_caching/flutter_map_tile_caching.dart';
@ -31,6 +32,8 @@ import 'package:flutter/services.dart';
import 'package:permission_handler/permission_handler.dart'; import 'package:permission_handler/permission_handler.dart';
import 'pages/permission/permission.dart';
Map<String, dynamic> deviceInfo = {}; Map<String, dynamic> deviceInfo = {};
@ -129,7 +132,10 @@ void main() async {
//Get.put(LocationController()); //Get.put(LocationController());
requestLocationPermission(); //await PermissionController.checkAndRequestPermissions();
//requestLocationPermission();
// startMemoryMonitoring(); // 2024-4-8 Akira: メモリ使用量のチェックを開始 See #2810 // startMemoryMonitoring(); // 2024-4-8 Akira: メモリ使用量のチェックを開始 See #2810
Get.put(SettingsController()); // これを追加 Get.put(SettingsController()); // これを追加
@ -143,10 +149,11 @@ void main() async {
*/ */
runApp(const ProviderScope(child: MyApp())); runApp(const ProviderScope(child: MyApp()));
//runApp(HomePage()); // MyApp()からHomePage()に変更
//runApp(const MyApp()); //runApp(const MyApp());
} }
/*
Future<void> requestLocationPermission() async { Future<void> requestLocationPermission() async {
try { try {
final status = await Permission.locationAlways.request(); final status = await Permission.locationAlways.request();
@ -160,6 +167,7 @@ Future<void> requestLocationPermission() async {
print('Error requesting location permission: $e'); print('Error requesting location permission: $e');
} }
} }
*/
// メモリ使用量の解説https://qiita.com/hukusuke1007/items/e4e987836412e9bc73b9 // メモリ使用量の解説https://qiita.com/hukusuke1007/items/e4e987836412e9bc73b9
@ -289,6 +297,16 @@ Future<void> startBackgroundTracking() async {
} catch (e) { } catch (e) {
print('Error starting background tracking: $e'); print('Error starting background tracking: $e');
} }
}else if (Platform.isAndroid && background == false) {
background = true;
debugPrint("バックグラウンド処理を開始しました。");
try {
// 位置情報の権限が許可されているかを確認
await PermissionController.checkAndRequestPermissions();
}catch(e){
print('Error starting background tracking: $e');
}
} }
} }
@ -321,8 +339,12 @@ Future<void> stopBackgroundTracking() async {
}else if(Platform.isAndroid && background==true){ }else if(Platform.isAndroid && background==true){
background=false; background=false;
debugPrint("バックグラウンド処理:停止しました。"); debugPrint("バックグラウンド処理:停止しました。");
await positionStream?.cancel(); const platform = MethodChannel('location');
positionStream = null; try {
await platform.invokeMethod('stopLocationService');
} on PlatformException catch (e) {
print("Failed to stop location service: '${e.message}'.");
}
} }
} }
@ -344,6 +366,12 @@ class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
} }
WidgetsBinding.instance.addObserver(this); WidgetsBinding.instance.addObserver(this);
/*
WidgetsBinding.instance.addPostFrameCallback((_) async {
await PermissionController.checkAndRequestPermissions();
});
*/
debugPrint("Start MyAppState..."); debugPrint("Start MyAppState...");
} }
@ -458,6 +486,7 @@ class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return GetMaterialApp( return GetMaterialApp(
translations: StringValues(), translations: StringValues(),
locale: const Locale('ja', 'JP'), locale: const Locale('ja', 'JP'),
@ -480,4 +509,7 @@ class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
enableLog: true, enableLog: true,
); );
} }
} }

View File

@ -36,6 +36,8 @@ import 'package:image_gallery_saver/image_gallery_saver.dart';
import 'package:rogapp/utils/const.dart'; import 'package:rogapp/utils/const.dart';
import 'package:logger/logger.dart'; import 'package:logger/logger.dart';
import 'package:rogapp/pages/permission/permission.dart';
// 目的地に関連する状態管理とロジックを担当するクラスです。 // 目的地に関連する状態管理とロジックを担当するクラスです。
// //
class DestinationController extends GetxController { class DestinationController extends GetxController {
@ -1265,6 +1267,12 @@ class DestinationController extends GetxController {
void onInit() async { void onInit() async {
super.onInit(); super.onInit();
/*
WidgetsBinding.instance.addPostFrameCallback((_) async {
await PermissionController.checkAndRequestPermissions();
});
*/
startGPSCheckTimer(); startGPSCheckTimer();
// MapControllerの初期化完了を待機するフラグを設定 // MapControllerの初期化完了を待機するフラグを設定
@ -1715,6 +1723,7 @@ class DestinationController extends GetxController {
} }
} }
/*
// 位置情報の許可を確認する関数です。 // 位置情報の許可を確認する関数です。
// //
void checkPermission() async { void checkPermission() async {
@ -1726,6 +1735,7 @@ class DestinationController extends GetxController {
permission = await Geolocator.requestPermission(); permission = await Geolocator.requestPermission();
} }
} }
*/
// IDに基づいて目的地を取得する関数です。 // IDに基づいて目的地を取得する関数です。
// //

View File

@ -15,6 +15,11 @@ class _HomePageState extends State<HomePage> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
/*
WidgetsBinding.instance.addPostFrameCallback((_) {
_checkLocationService(); // 非同期的に呼び出す
});
*/
_checkLocationService(); _checkLocationService();
} }

View File

@ -190,11 +190,15 @@ class IndexController extends GetxController with WidgetsBindingObserver {
} }
/*
void checkPermission() void checkPermission()
{ {
debugPrint("MapControllerの初期化が完了したら、位置情報の許可をチェックする"); debugPrint("MapControllerの初期化が完了したら、位置情報の許可をチェックする");
_checkLocationPermission(); _checkLocationPermission();
} }
*/
@override @override
void onClose() { void onClose() {

View File

@ -1,78 +1,156 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:permission_handler/permission_handler.dart'; import 'package:permission_handler/permission_handler.dart';
import 'package:rogapp/routes/app_pages.dart'; import 'package:rogapp/services/location_service.dart';
import 'dart:developer' as developer;
class PermissionHandlerScreen extends StatefulWidget {
const PermissionHandlerScreen({Key? key}) : super(key: key);
@override class PermissionController {
_PermissionHandlerScreenState createState() =>
_PermissionHandlerScreenState();
}
class _PermissionHandlerScreenState extends State<PermissionHandlerScreen> { static Future<bool> checkLocationPermissions() async {
@override debugPrint("(gifunavi)== checkLocationPermissions ==");
void initState() { final alwaysPermission = await Permission.locationAlways.status;
super.initState(); final whenInUsePermission = await Permission.locationWhenInUse.status;
WidgetsBinding.instance.addPostFrameCallback((_) { final locationPermission = await Permission.location.status;
_checkPermissionStatus();
}); return (alwaysPermission == PermissionStatus.granted || whenInUsePermission == PermissionStatus.granted) &&
(locationPermission == PermissionStatus.granted);
} }
Future<void> _checkPermissionStatus() async { static Future<bool> checkLocationBasicPermission() async {
PermissionStatus status = await Permission.location.status; debugPrint("(gifunavi)== checkLocationBasicPermission ==");
final locationPermission = await Permission.location.status;
if (status.isGranted) { return locationPermission == PermissionStatus.granted;
if (context.mounted) {
Get.offNamed(AppPages.LOGIN);
} }
} else {
if (context.mounted) { static Future<bool> checkLocationWhenInUsePermission() async {
_showPermissionRequestDialog(); debugPrint("(gifunavi)== checkLocationWhenInUsePermission ==");
final whenInUsePermission = await Permission.locationWhenInUse.status;
return whenInUsePermission == PermissionStatus.granted;
}
static Future<bool> checkLocationAlwaysPermission() async {
debugPrint("(gifunavi)== checkLocationAlwaysPermission ==");
final alwaysPermission = await Permission.locationAlways.status;
return alwaysPermission == PermissionStatus.granted;
}
static bool isBasicPermission=false;
static Future<void> requestLocationBasicPermissions() async {
debugPrint("(gifunavi)== requestLocationBasicPermissions ==");
try{
if(!isBasicPermission){
isBasicPermission=true;
final locationStatus = await Permission.location.request();
if (locationStatus != PermissionStatus.granted) {
showPermissionDeniedDialog();
}
}
}catch (e, stackTrace){
print('Exception: $e');
print('Stack trace: $stackTrace');
debugPrintStack(label: 'Exception occurred', stackTrace: stackTrace);
}
}
static bool isLocationServiceRunning = false;
static bool isRequestedWhenInUsePermission = false;
static Future<void> requestLocationWhenInUsePermissions() async {
debugPrint("(gifunavi)== requestLocationWhenInUsePermissions ==");
try{
if(!isRequestedWhenInUsePermission){
isRequestedWhenInUsePermission=true;
final whenInUseStatus = await Permission.locationWhenInUse.request();
if (whenInUseStatus != PermissionStatus.granted) {
showPermissionDeniedDialog();
}else{
if( !isLocationServiceRunning ){
isLocationServiceRunning=true;
const platform = MethodChannel('location');
try {
await platform.invokeMethod('startLocationService'); // Location Service を開始する。
} on PlatformException catch (e) {
debugPrint("Failed to start location service: '${e.message}'.");
} }
} }
} }
}
}catch (e, stackTrace){
debugPrint('Exception: $e');
debugPrint('Stack trace: $stackTrace');
debugPrintStack(label: 'Exception occurred', stackTrace: stackTrace);
}
void _showPermissionRequestDialog() { }
showDialog(
context: context, static bool isRequestedAlwaysPermission = false;
builder: (BuildContext context) {
return AlertDialog( static Future<void> requestLocationAlwaysPermissions() async {
title: Text('location_permission_required_title'.tr), debugPrint("(gifunavi)== requestLocationAlwaysPermissions ==");
content: Text('location_permission_required_message'.tr),
try {
if( !isRequestedAlwaysPermission ){
isRequestedAlwaysPermission=true;
final alwaysStatus = await Permission.locationAlways.request();
if (alwaysStatus != PermissionStatus.granted) {
showPermissionDeniedDialog();
}
}
}catch (e, stackTrace){
print('Exception: $e');
print('Stack trace: $stackTrace');
debugPrintStack(label: 'Exception occurred', stackTrace: stackTrace);
}
}
static Future<void> checkAndRequestPermissions() async {
final hasPermissions = await checkLocationBasicPermission();
if (!hasPermissions) {
await requestLocationBasicPermissions();
}
final hasWIUPermissions = await checkLocationWhenInUsePermission();
if (!hasWIUPermissions) {
await requestLocationWhenInUsePermissions();
}
final hasAlwaysPermissions = await checkLocationAlwaysPermission();
if (!hasAlwaysPermissions) {
await requestLocationAlwaysPermissions();
}
}
static void showPermissionDeniedDialog() {
Get.dialog(
AlertDialog(
title: Text('location_permission_needed_title'.tr),
// 位置情報への許可が必要です
content: Text('location_permission_needed_main'.tr),
// 岐阜ロゲでは、位置情報を使用してスタート・チェックイン・ゴール等の通過照明及び移動手段の記録のために、位置情報のトラッキングを行なっています。このためバックグラウンドでもトラッキングができるように位置情報の権限が必要です。
// 設定画面で、「岐阜ナビ」に対して、常に位置情報を許可するように設定してください。
actions: [ actions: [
TextButton( TextButton(
child: Text('cancel'.tr), child: Text('キャンセル'),
onPressed: () { onPressed: () => Get.back(),
Navigator.of(context).pop();
Get.offNamed(AppPages.HOME);
},
), ),
TextButton( TextButton(
child: Text('ok'.tr), child: Text('設定'),
onPressed: () { onPressed: () {
Navigator.of(context).pop(); Get.back();
_requestLocationPermission(); openAppSettings();
}, },
), ),
], ],
); ),
},
); );
} }
void _requestLocationPermission() async {
final status = await Permission.location.request();
if (status.isGranted) {
Get.offNamed(AppPages.LOGIN);
} else {
Get.offNamed(AppPages.HOME);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(body: Container());
}
} }

View File

@ -1,188 +0,0 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:rogapp/routes/app_pages.dart';
import 'dart:io';
class PermissionHandlerScreen extends StatefulWidget {
const PermissionHandlerScreen({Key? key}) : super(key: key);
@override
State<PermissionHandlerScreen> createState() =>
_PermissionHandlerScreenState();
}
class _PermissionHandlerScreenState extends State<PermissionHandlerScreen> {
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
_checkPermissionStatus();
});
}
Future<void> _checkPermissionStatus() async {
PermissionStatus status = await Permission.location.status;
if (status.isGranted) {
if (context.mounted) {
Get.toNamed(AppPages.LOGIN);
}
} else {
if (context.mounted) {
Get.toNamed(AppPages.HOME);
}
}
}
/*
_checkPermissionStatus() async {
PermissionStatus status = await Permission.location.status;
if (status.isGranted == false) {
if (context.mounted) {
showAlert(context);
}
} else if (status.isPermanentlyDenied) {
await requestPermission();
} else {
if (mounted) {
Get.toNamed(AppPages.LOGIN);
}
}
}
*/
void showAlert(BuildContext context) {
showDialog(
context: context,
builder: (_) => AlertDialog(
title: Text('location_permission_title'.tr),
content: SingleChildScrollView(
child: Text('location_permission_content'.tr),
),
actions: <Widget>[
ElevatedButton(
child: const Text('OK'),
onPressed: () {
Get.back();
requestPermission();
},
),
],
));
}
// 要検討:位置情報の許可が拒否された場合、適切なエラーメッセージを表示することを検討してください。
//
/*
Future<void> requestPermission() async {
PermissionStatus permission = await Permission.location.status;
if (permission == PermissionStatus.permanentlyDenied) {
showPermanentAlert();
} else {
PermissionStatus newPermission = await Permission.location.request();
if (newPermission != PermissionStatus.granted) {
exit(0);
} else {
if (context.mounted) {
Get.toNamed(AppPages.LOGIN);
}
}
}
}
*/
Future<void> requestPermission() async {
PermissionStatus permission = await Permission.location.request();
if (permission == PermissionStatus.granted) {
if (context.mounted) {
Get.toNamed(AppPages.LOGIN);
}
} else if (permission == PermissionStatus.denied) {
await showLocationPermissionDeniedDialog();
} else if (permission == PermissionStatus.permanentlyDenied) {
await showPermanentlyDeniedDialog();
}
}
Future<void> showLocationPermissionDeniedDialog() async {
await showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text('location_permission_denied_title'.tr),
content: Text('location_permission_denied_message'.tr),
actions: [
TextButton(
child: Text('ok'.tr),
onPressed: () => Navigator.of(context).pop(),
),
],
);
},
);
}
Future<void> showPermanentlyDeniedDialog() async {
await showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text('location_permission_permanently_denied_title'.tr),
content: Text('location_permission_permanently_denied_message'.tr),
actions: [
TextButton(
child: Text('open_settings'.tr),
onPressed: () {
Navigator.of(context).pop();
openAppSettings();
},
),
],
);
},
);
}
@override
Widget build(BuildContext context) {
return const Scaffold(
body: Text(""),
);
}
// 要検討:ユーザーが位置情報の許可を拒否し続けた場合の対処方法を明確にすることをお勧めします。
//
void showPermanentAlert() {
showDialog(
context: context,
builder: (_) => AlertDialog(
title: Text('location_disabled_title'.tr),
content: SingleChildScrollView(
child: Text('location_disabled_content'.tr),
),
actions: <Widget>[
ElevatedButton(
child: const Text('OK'),
onPressed: () async {
await openAppSettings().then(
(value) async {
if (value) {
if (await Permission
.location.status.isPermanentlyDenied ==
true &&
await Permission.location.status.isGranted ==
false) {
requestPermission(); /* opens app settings until permission is granted */
}
}
},
);
},
),
],
));
}
}

View File

@ -26,6 +26,7 @@ import 'package:rogapp/pages/debug/debug_binding.dart';
import 'package:rogapp/pages/subperf/subperf_page.dart'; import 'package:rogapp/pages/subperf/subperf_page.dart';
import 'package:rogapp/spa/spa_binding.dart'; import 'package:rogapp/spa/spa_binding.dart';
import 'package:rogapp/spa/spa_page.dart'; import 'package:rogapp/spa/spa_page.dart';
import 'package:rogapp/widgets/permission_handler_screen.dart';
part 'app_routes.dart'; part 'app_routes.dart';
@ -42,6 +43,7 @@ class AppPages {
static const DESTINATION_MAP = Routes.DESTINATION_MAP; static const DESTINATION_MAP = Routes.DESTINATION_MAP;
static const HOME = Routes.HOME; static const HOME = Routes.HOME;
static const PERMISSION = Routes.PERMISSION; static const PERMISSION = Routes.PERMISSION;
//static const PERMISSION = Routes.HOME;
static const SEARCH = Routes.SEARCH; static const SEARCH = Routes.SEARCH;
static const MAINPERF = Routes.MAINPERF; static const MAINPERF = Routes.MAINPERF;
static const SUBPERF = Routes.SUBPERF; static const SUBPERF = Routes.SUBPERF;
@ -88,7 +90,7 @@ class AppPages {
), ),
GetPage( GetPage(
name: Routes.PERMISSION, name: Routes.PERMISSION,
page: () => const PermissionHandlerScreen(), page: () => PermissionHandlerScreen(),
), ),
GetPage( GetPage(
name: Routes.SEARCH, name: Routes.SEARCH,
@ -127,4 +129,5 @@ class AppPages {
), ),
]; ];
} }

View File

@ -1,4 +1,5 @@
import 'dart:convert'; import 'dart:convert';
import 'package:flutter/services.dart';
import 'package:geojson_vi/geojson_vi.dart'; import 'package:geojson_vi/geojson_vi.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
@ -9,6 +10,7 @@ import 'package:rogapp/utils/const.dart';
class LocationService { class LocationService {
static Future<GeoJSONFeatureCollection?> loadLocationsFor( static Future<GeoJSONFeatureCollection?> loadLocationsFor(
String perfecture, String cat) async { String perfecture, String cat) async {
final IndexController indexController = Get.find<IndexController>(); final IndexController indexController = Get.find<IndexController>();
@ -187,4 +189,16 @@ class LocationService {
} }
return null; return null;
} }
static const platform = MethodChannel('location');
static Future<bool> isLocationServiceRunning() async {
try {
final bool isRunning = await platform.invokeMethod('isLocationServiceRunning');
return isRunning;
} catch (e) {
print("Failed to check if location service is running: $e");
return false;
}
}
} }

View File

@ -6,6 +6,7 @@ import 'package:latlong2/latlong.dart';
//import 'package:rogapp/widgets/debug_widget.dart'; //import 'package:rogapp/widgets/debug_widget.dart';
import 'package:flutter_map_location_marker/flutter_map_location_marker.dart'; import 'package:flutter_map_location_marker/flutter_map_location_marker.dart';
import 'package:rogapp/pages/destination/destination_controller.dart'; import 'package:rogapp/pages/destination/destination_controller.dart';
import 'package:rogapp/pages/permission/permission.dart';
// LocationControllerクラスは、GetxControllerを継承したクラスであり、位置情報の管理を担当しています。 // LocationControllerクラスは、GetxControllerを継承したクラスであり、位置情報の管理を担当しています。
// LocationControllerは以下の機能を提供しています。 // LocationControllerは以下の機能を提供しています。
@ -188,6 +189,18 @@ class LocationController extends GetxController {
// Check for location service and permissions before starting the stream // Check for location service and permissions before starting the stream
// 位置情報サービスの有効性をチェックし、無効な場合はエラーハンドリングを行います。 // 位置情報サービスの有効性をチェックし、無効な場合はエラーハンドリングを行います。
// //
await PermissionController.checkAndRequestPermissions();
/*
bool isPermissionGranted = await PermissionController.checkLocationPermissions();
if (!isPermissionGranted) {
PermissionController.showPermissionDeniedDialog();
return;
}
*/
/*
bool serviceEnabled = await Geolocator.isLocationServiceEnabled(); bool serviceEnabled = await Geolocator.isLocationServiceEnabled();
if (!serviceEnabled) { if (!serviceEnabled) {
// Use GetX's context to show a dialog // Use GetX's context to show a dialog
@ -262,6 +275,7 @@ class LocationController extends GetxController {
); );
return; return;
} }
*/
// 位置情報の設定を行います。z11 // 位置情報の設定を行います。z11
// Set up the location options // Set up the location options

View File

@ -391,7 +391,7 @@ class StringValues extends Translations{
'location_permission_permanently_denied_message': '位置情報の許可が永久に拒否されました。位置情報の許可を付与するには、アプリ設定を開いてください。', 'location_permission_permanently_denied_message': '位置情報の許可が永久に拒否されました。位置情報の許可を付与するには、アプリ設定を開いてください。',
'open_settings': '設定を開く', 'open_settings': '設定を開く',
'location_permission_needed_title': '位置情報への許可が必要です', 'location_permission_needed_title': '位置情報への許可が必要です',
'location_permission_needed_main': '位置情報への許可が拒否されています。設定を開いて、位置情報の許可を「岐阜ナビ」に与えてください。', 'location_permission_needed_main': '岐阜ロゲでは、位置情報を使用してスタート・チェックイン・ゴール等の通過照明及び移動手段の記録のために、位置情報のトラッキングを行なっています。このためバックグラウンドでもトラッキングができるように位置情報の権限が必要です。設定画面で、「岐阜ナビ」に対して、常に位置情報を許可するように設定してください。',
'open_settings': '設定を開く', 'open_settings': '設定を開く',
'location_permission_denied_title': '位置情報へのアクセスが拒否されています。', 'location_permission_denied_title': '位置情報へのアクセスが拒否されています。',
'location_permission_denied_main': 'この岐阜ナビアプリは正常に動かすには位置情報への許可が必要です。「設定」画面で位置情報の許可を指定してください。', 'location_permission_denied_main': 'この岐阜ナビアプリは正常に動かすには位置情報への許可が必要です。「設定」画面で位置情報の許可を指定してください。',

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:rogapp/pages/destination/destination_controller.dart'; import 'package:rogapp/pages/destination/destination_controller.dart';
import 'package:rogapp/routes/app_pages.dart'; // これを追加 import 'package:rogapp/routes/app_pages.dart'; // これを追加
@ -14,8 +15,34 @@ class _CurrentPositionState extends State<CurrentPosition> {
final DestinationController destinationController = final DestinationController destinationController =
Get.find<DestinationController>(); Get.find<DestinationController>();
void _onLongPress() { void _onLongPress() async {
Get.toNamed(AppPages.SETTINGS); // これを追加 PermissionStatus status = await Permission.location.status;
if (!status.isGranted) {
await showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text('位置情報の許可が必要です'),
content: Text('現在位置を表示するには、位置情報の許可が必要です。「設定」からアプリの権限を許可してください。'),
actions: [
TextButton(
child: Text('キャンセル'),
onPressed: () => Navigator.of(context).pop(),
),
TextButton(
child: Text('設定'),
onPressed: () {
Navigator.of(context).pop();
openAppSettings();
},
),
],
);
},
);
} else {
Get.toNamed(AppPages.SETTINGS);
}
} }
@override @override

View File

@ -6,6 +6,7 @@ import 'package:flutter_polyline_points/flutter_polyline_points.dart';
import 'package:geojson_vi/geojson_vi.dart'; import 'package:geojson_vi/geojson_vi.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:latlong2/latlong.dart'; import 'package:latlong2/latlong.dart';
import 'package:rogapp/pages/permission/permission.dart';
import 'package:rogapp/pages/settings/settings_binding.dart'; import 'package:rogapp/pages/settings/settings_binding.dart';
import 'package:rogapp/model/destination.dart'; import 'package:rogapp/model/destination.dart';
import 'package:rogapp/pages/destination/destination_controller.dart'; import 'package:rogapp/pages/destination/destination_controller.dart';
@ -75,7 +76,8 @@ class _MapWidgetState extends State<MapWidget> with WidgetsBindingObserver {
indexController.isMapControllerReady.value = true; indexController.isMapControllerReady.value = true;
}); });
// MapControllerの初期化が完了したら、IndexControllerのonInitを呼び出す // MapControllerの初期化が完了したら、IndexControllerのonInitを呼び出す
indexController.checkPermission(); //indexController.checkPermission();
PermissionController.checkAndRequestPermissions();
}); });
late MapResetController mapResetController = MapResetController(); late MapResetController mapResetController = MapResetController();

View File

@ -0,0 +1,31 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:rogapp/pages/permission/permission.dart';
class PermissionHandlerScreen extends StatefulWidget {
@override
_PermissionHandlerScreenState createState() => _PermissionHandlerScreenState();
}
class _PermissionHandlerScreenState extends State<PermissionHandlerScreen> {
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) async {
await PermissionController.checkAndRequestPermissions();
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('権限の確認'),
),
body: Center(
child: Text('権限の確認中...'),
),
);
}
}

View File

@ -760,6 +760,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.2+1" version: "2.0.2+1"
logging:
dependency: "direct main"
description:
name: logging
sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340"
url: "https://pub.dev"
source: hosted
version: "1.2.0"
matcher: matcher:
dependency: transitive dependency: transitive
description: description:

View File

@ -38,6 +38,7 @@ dependencies:
flutter_map: ^6.0.1 flutter_map: ^6.0.1
geolocator: ^10.1.0 geolocator: ^10.1.0
permission_handler: ^11.3.1 permission_handler: ^11.3.1
logging: ^1.0.2
# flutter_dev_tools: ^0.0.2 # flutter_dev_tools: ^0.0.2
# permission_handler: ^11.1.0 <== older # permission_handler: ^11.1.0 <== older
# permission_handler 11.2.0 (11.3.1 available) # permission_handler 11.2.0 (11.3.1 available)