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

@ -3,6 +3,7 @@ import 'dart:io';
//import 'dart:convert';
//import 'dart:developer';
import 'package:rogapp/model/gps_data.dart';
import 'package:rogapp/pages/home/home_page.dart';
import 'package:rogapp/utils/database_gps.dart';
import 'package:flutter/material.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 'pages/permission/permission.dart';
Map<String, dynamic> deviceInfo = {};
@ -129,7 +132,10 @@ void main() async {
//Get.put(LocationController());
requestLocationPermission();
//await PermissionController.checkAndRequestPermissions();
//requestLocationPermission();
// startMemoryMonitoring(); // 2024-4-8 Akira: メモリ使用量のチェックを開始 See #2810
Get.put(SettingsController()); // これを追加
@ -143,10 +149,11 @@ void main() async {
*/
runApp(const ProviderScope(child: MyApp()));
//runApp(HomePage()); // MyApp()からHomePage()に変更
//runApp(const MyApp());
}
/*
Future<void> requestLocationPermission() async {
try {
final status = await Permission.locationAlways.request();
@ -160,6 +167,7 @@ Future<void> requestLocationPermission() async {
print('Error requesting location permission: $e');
}
}
*/
// メモリ使用量の解説https://qiita.com/hukusuke1007/items/e4e987836412e9bc73b9
@ -289,6 +297,16 @@ Future<void> startBackgroundTracking() async {
} catch (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){
background=false;
debugPrint("バックグラウンド処理:停止しました。");
await positionStream?.cancel();
positionStream = null;
const platform = MethodChannel('location');
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.addPostFrameCallback((_) async {
await PermissionController.checkAndRequestPermissions();
});
*/
debugPrint("Start MyAppState...");
}
@ -458,6 +486,7 @@ class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
@override
Widget build(BuildContext context) {
return GetMaterialApp(
translations: StringValues(),
locale: const Locale('ja', 'JP'),
@ -480,4 +509,7 @@ class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
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:logger/logger.dart';
import 'package:rogapp/pages/permission/permission.dart';
// 目的地に関連する状態管理とロジックを担当するクラスです。
//
class DestinationController extends GetxController {
@ -1265,6 +1267,12 @@ class DestinationController extends GetxController {
void onInit() async {
super.onInit();
/*
WidgetsBinding.instance.addPostFrameCallback((_) async {
await PermissionController.checkAndRequestPermissions();
});
*/
startGPSCheckTimer();
// MapControllerの初期化完了を待機するフラグを設定
@ -1715,6 +1723,7 @@ class DestinationController extends GetxController {
}
}
/*
// 位置情報の許可を確認する関数です。
//
void checkPermission() async {
@ -1726,6 +1735,7 @@ class DestinationController extends GetxController {
permission = await Geolocator.requestPermission();
}
}
*/
// IDに基づいて目的地を取得する関数です。
//

View File

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

View File

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

View File

@ -1,78 +1,156 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.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
_PermissionHandlerScreenState createState() =>
_PermissionHandlerScreenState();
}
class PermissionController {
class _PermissionHandlerScreenState extends State<PermissionHandlerScreen> {
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
_checkPermissionStatus();
});
static Future<bool> checkLocationPermissions() async {
debugPrint("(gifunavi)== checkLocationPermissions ==");
final alwaysPermission = await Permission.locationAlways.status;
final whenInUsePermission = await Permission.locationWhenInUse.status;
final locationPermission = await Permission.location.status;
return (alwaysPermission == PermissionStatus.granted || whenInUsePermission == PermissionStatus.granted) &&
(locationPermission == PermissionStatus.granted);
}
Future<void> _checkPermissionStatus() async {
PermissionStatus status = await Permission.location.status;
static Future<bool> checkLocationBasicPermission() async {
debugPrint("(gifunavi)== checkLocationBasicPermission ==");
final locationPermission = await Permission.location.status;
return locationPermission == PermissionStatus.granted;
}
if (status.isGranted) {
if (context.mounted) {
Get.offNamed(AppPages.LOGIN);
static Future<bool> checkLocationWhenInUsePermission() async {
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();
}
}
} else {
if (context.mounted) {
_showPermissionRequestDialog();
}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);
}
}
static bool isRequestedAlwaysPermission = false;
static Future<void> requestLocationAlwaysPermissions() async {
debugPrint("(gifunavi)== requestLocationAlwaysPermissions ==");
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();
}
}
void _showPermissionRequestDialog() {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text('location_permission_required_title'.tr),
content: Text('location_permission_required_message'.tr),
actions: [
TextButton(
child: Text('cancel'.tr),
onPressed: () {
Navigator.of(context).pop();
Get.offNamed(AppPages.HOME);
},
),
TextButton(
child: Text('ok'.tr),
onPressed: () {
Navigator.of(context).pop();
_requestLocationPermission();
},
),
],
);
},
static void showPermissionDeniedDialog() {
Get.dialog(
AlertDialog(
title: Text('location_permission_needed_title'.tr),
// 位置情報への許可が必要です
content: Text('location_permission_needed_main'.tr),
// 岐阜ロゲでは、位置情報を使用してスタート・チェックイン・ゴール等の通過照明及び移動手段の記録のために、位置情報のトラッキングを行なっています。このためバックグラウンドでもトラッキングができるように位置情報の権限が必要です。
// 設定画面で、「岐阜ナビ」に対して、常に位置情報を許可するように設定してください。
actions: [
TextButton(
child: Text('キャンセル'),
onPressed: () => Get.back(),
),
TextButton(
child: Text('設定'),
onPressed: () {
Get.back();
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/spa/spa_binding.dart';
import 'package:rogapp/spa/spa_page.dart';
import 'package:rogapp/widgets/permission_handler_screen.dart';
part 'app_routes.dart';
@ -42,6 +43,7 @@ class AppPages {
static const DESTINATION_MAP = Routes.DESTINATION_MAP;
static const HOME = Routes.HOME;
static const PERMISSION = Routes.PERMISSION;
//static const PERMISSION = Routes.HOME;
static const SEARCH = Routes.SEARCH;
static const MAINPERF = Routes.MAINPERF;
static const SUBPERF = Routes.SUBPERF;
@ -88,7 +90,7 @@ class AppPages {
),
GetPage(
name: Routes.PERMISSION,
page: () => const PermissionHandlerScreen(),
page: () => PermissionHandlerScreen(),
),
GetPage(
name: Routes.SEARCH,
@ -127,4 +129,5 @@ class AppPages {
),
];
}

View File

@ -1,4 +1,5 @@
import 'dart:convert';
import 'package:flutter/services.dart';
import 'package:geojson_vi/geojson_vi.dart';
import 'package:get/get.dart';
import 'package:http/http.dart' as http;
@ -9,6 +10,7 @@ import 'package:rogapp/utils/const.dart';
class LocationService {
static Future<GeoJSONFeatureCollection?> loadLocationsFor(
String perfecture, String cat) async {
final IndexController indexController = Get.find<IndexController>();
@ -187,4 +189,16 @@ class LocationService {
}
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:flutter_map_location_marker/flutter_map_location_marker.dart';
import 'package:rogapp/pages/destination/destination_controller.dart';
import 'package:rogapp/pages/permission/permission.dart';
// LocationControllerクラスは、GetxControllerを継承したクラスであり、位置情報の管理を担当しています。
// LocationControllerは以下の機能を提供しています。
@ -188,6 +189,18 @@ class LocationController extends GetxController {
// 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();
if (!serviceEnabled) {
// Use GetX's context to show a dialog
@ -262,6 +275,7 @@ class LocationController extends GetxController {
);
return;
}
*/
// 位置情報の設定を行います。z11
// Set up the location options

View File

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

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:rogapp/pages/destination/destination_controller.dart';
import 'package:rogapp/routes/app_pages.dart'; // これを追加
@ -14,8 +15,34 @@ class _CurrentPositionState extends State<CurrentPosition> {
final DestinationController destinationController =
Get.find<DestinationController>();
void _onLongPress() {
Get.toNamed(AppPages.SETTINGS); // これを追加
void _onLongPress() async {
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

View File

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