الحصول على موقع الجهاز باستخدام geolocator
الحصول على موقع الجهاز باستخدام geolocator
حزمة geolocator هي الإضافة الأكثر استخدامًا في Flutter للوصول إلى نظام تحديد المواقع العالمي (GPS) للجهاز ونظام التموضع القائم على الشبكة. توفر واجهة Dart نظيفة ومتعددة المنصات لطلبات الموقع الفردية وتدفقات المواقع المستمرة، وتُعيد كائن Position غنيًا يتضمن خط العرض وخط الطول والارتفاع والدقة والسرعة والاتجاه.
AndroidManifest.xml وInfo.plist.إعداد الحزمة
أضف التبعية إلى pubspec.yaml ثم شغّل flutter pub get:
pubspec.yaml
dependencies:
flutter:
sdk: flutter
geolocator: ^13.0.0
لنظام Android، أضف الأذونات التالية داخل وسم <manifest> في ملف android/app/src/main/AndroidManifest.xml:
أذونات AndroidManifest.xml
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<!-- فقط إذا احتجت موقعًا في الخلفية -->
<!-- <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" /> -->
لنظام iOS، أضف المفاتيح التالية إلى ios/Runner/Info.plist:
مفاتيح Info.plist
<key>NSLocationWhenInUseUsageDescription</key>
<string>يستخدم هذا التطبيق موقعك لعرض الأماكن القريبة.</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>يحتاج هذا التطبيق إلى موقع الخلفية للتتبع.</string>
التحقق من الأذونات وطلبها
قبل طلب بيانات الموقع يجب التحقق من أن خدمات الموقع مفعّلة على الجهاز وأن تطبيقك يمتلك الإذن اللازم. توفر واجهة geolocator مساعدين مخصصين لكلا الفحصين:
فحص الإذن وطلبه
import 'package:geolocator/geolocator.dart';
Future<void> _checkPermissions() async {
// 1. هل خدمة الموقع مفعّلة في إعدادات الجهاز؟
bool serviceEnabled = await Geolocator.isLocationServiceEnabled();
if (!serviceEnabled) {
throw Exception('خدمات الموقع معطّلة.');
}
// 2. هل منح المستخدم الإذن لهذا التطبيق؟
LocationPermission permission = await Geolocator.checkPermission();
if (permission == LocationPermission.denied) {
// أول مرة أو رفض سابق من المستخدم
permission = await Geolocator.requestPermission();
if (permission == LocationPermission.denied) {
throw Exception('تم رفض إذن الموقع.');
}
}
if (permission == LocationPermission.deniedForever) {
// اختار المستخدم "عدم السماح أبدًا" – يجب فتح إعدادات التطبيق يدويًا
throw Exception('تم رفض إذن الموقع نهائيًا.');
}
// الإذن هو whileInUse أو always – يمكن المتابعة بأمان
}
طلب الموقع مرة واحدة
استخدم Geolocator.getCurrentPosition() لجلب لقطة Position واحدة. هذا مثالي للميزات مثل "اعثر على مطاعم قريبة" حيث تحتاج الموقع مرة واحدة فقط. يمكنك ضبط الدقة في مقابل استهلاك البطارية باستخدام تعداد LocationAccuracy:
LocationAccuracy.lowest— دقة ~3 كم، أدنى استهلاك للبطاريةLocationAccuracy.low— دقة ~1 كمLocationAccuracy.medium— دقة ~100 مLocationAccuracy.high— دقة ~10 م (GPS مفعّل)LocationAccuracy.best— أقصى دقة، أعلى استهلاك للبطاريةLocationAccuracy.bestForNavigation— مثل best لكن يستخدم أيضًا أجهزة الاستشعار الحركية
مثال getCurrentPosition
import 'package:geolocator/geolocator.dart';
class LocationService {
/// تُعيد الموقع الحالي للجهاز بعد التحقق من الأذونات.
static Future<Position> determinePosition() async {
bool serviceEnabled = await Geolocator.isLocationServiceEnabled();
if (!serviceEnabled) {
return Future.error('خدمات الموقع معطّلة.');
}
LocationPermission permission = await Geolocator.checkPermission();
if (permission == LocationPermission.denied) {
permission = await Geolocator.requestPermission();
if (permission == LocationPermission.denied) {
return Future.error('تم رفض أذونات الموقع');
}
}
if (permission == LocationPermission.deniedForever) {
return Future.error(
'تم رفض أذونات الموقع نهائيًا، '
'لا يمكننا طلب الأذونات.',
);
}
// في هذه النقطة الأذونات ممنوحة ويمكننا
// الاستمرار في الوصول إلى موقع الجهاز.
return await Geolocator.getCurrentPosition(
locationSettings: const LocationSettings(
accuracy: LocationAccuracy.high,
timeLimit: Duration(seconds: 15),
),
);
}
}
// الاستخدام داخل StatefulWidget
Future<void> _fetchLocation() async {
try {
final Position position = await LocationService.determinePosition();
print('خط العرض: ${position.latitude}');
print('خط الطول: ${position.longitude}');
print('الدقة: ${position.accuracy} م');
print('الارتفاع: ${position.altitude} م');
print('السرعة: ${position.speed} م/ث');
} catch (e) {
print('خطأ: $e');
}
}
timeLimit في getCurrentPosition(). بدونه يمكن أن يتعلق الـ Future إلى أجل غير مسمى إذا لم يتمكن GPS من الحصول على إصلاح (مثلًا في الأماكن المغلقة أو الأنفاق). مهلة من 10 إلى 15 ثانية مع تراجع سلس توفر تجربة مستخدم أفضل بكثير.تدفق الموقع المستمر
استخدم Geolocator.getPositionStream() لحالات الاستخدام التي تتطلب تتبعًا فوريًا مثل التنقل وتطبيقات اللياقة وتتبع التوصيل المباشر. يُعيد Stream<Position> يُصدر قيمًا جديدة كلما تحرك الجهاز أكثر من مسافة الفلتر المُهيأة أو الفاصل الزمني. احرص دائمًا على إلغاء اشتراك التدفق عند التخلص من الودجت لتجنب تسريب الذاكرة:
getPositionStream مع StreamSubscription
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:geolocator/geolocator.dart';
class LiveLocationWidget extends StatefulWidget {
const LiveLocationWidget({super.key});
@override
State<LiveLocationWidget> createState() => _LiveLocationWidgetState();
}
class _LiveLocationWidgetState extends State<LiveLocationWidget> {
StreamSubscription<Position>? _positionSubscription;
Position? _currentPosition;
String _statusMessage = 'في انتظار الموقع...';
@override
void initState() {
super.initState();
_startLocationStream();
}
void _startLocationStream() {
const locationSettings = LocationSettings(
accuracy: LocationAccuracy.high,
distanceFilter: 10, // يُصدر فقط عند التحرك >= 10 أمتار
);
_positionSubscription =
Geolocator.getPositionStream(locationSettings: locationSettings)
.listen(
(Position position) {
setState(() {
_currentPosition = position;
_statusMessage =
'خط العرض: ${position.latitude.toStringAsFixed(6)}, '
'خط الطول: ${position.longitude.toStringAsFixed(6)}';
});
},
onError: (Object e) {
setState(() {
_statusMessage = 'خطأ في التدفق: $e';
});
},
);
}
@override
void dispose() {
// ضروري: إلغاء الاشتراك لإيقاف عتاد GPS
_positionSubscription?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(_statusMessage, textAlign: TextAlign.center),
if (_currentPosition != null) ...[
Text('الدقة: ${_currentPosition!.accuracy.toStringAsFixed(1)} م'),
Text('الارتفاع: ${_currentPosition!.altitude.toStringAsFixed(1)} م'),
Text('السرعة: ${_currentPosition!.speed.toStringAsFixed(2)} م/ث'),
],
],
);
}
}
_positionSubscription?.cancel() في dispose() يُبقي GPS نشطًا بعد إزالة الودجت من الشجرة. هذا يُفرغ البطارية ويمكن أن يسبب استدعاءات setState على ودجت تم التخلص منه مما يؤدي إلى أخطاء في وقت التشغيل.كائن Position
كل من getCurrentPosition() وgetPositionStream() يُنتجان كائن Position بالخصائص الرئيسية التالية:
latitude/longitude— الإحداثيات بالدرجات العشرية (WGS-84)accuracy— الدقة الأفقية المقدّرة بالأمتارaltitude— الارتفاع فوق مستوى البحر بالأمتارaltitudeAccuracy— الدقة الرأسية المقدّرة بالأمتار (iOS / Android 13+)speed— السرعة الأرضية بالأمتار في الثانيةspeedAccuracy— دقة السرعة المقدّرة بالأمتار في الثانيةheading— اتجاه السير بالدرجات (0 = شمال، في اتجاه عقارب الساعة)timestamp—DateTimeوقت الحصول على الإصلاح
خلاصة
توفر حزمة geolocator كل ما تحتاجه للعمل مع موقع الجهاز في Flutter. استخدم isLocationServiceEnabled() وcheckPermission() / requestPermission() قبل الوصول إلى أي بيانات موقع. استدعِ getCurrentPosition() للحصول على إصلاح فردي مع timeLimit مناسب، واستخدم getPositionStream() مع distanceFilter مناسب للتتبع المباشر. احرص دائمًا على إلغاء StreamSubscription في dispose() لحماية عمر البطارية ومنع أخطاء دورة حياة الودجت.