Lokasyon Servisleri
Mobil uygulamalarda lokasyon servisleri, kullanıcı deneyimini zenginleştiren önemli bir bileşendir. Bu bölümde, lokasyon tabanlı servislerin güvenli, verimli ve kullanıcı dostu şekilde nasıl implement edileceği ele alınacaktır.
Hızlı Karar
| İhtiyaç | Yaklaşım | Dikkat |
|---|---|---|
| Yaklaşık konum yeterli | Balanced/low accuracy | Daha az batarya tüketir |
| Arka plan takip | Geofence/significant change | İzin ve açıklama kritik |
| Navigasyon | High accuracy foreground | Batarya maliyeti yüksek |
| Konum bazlı içerik | Cached last location | Stale konum kontrol edilmeli |
Üretim Kontrol Listesi
- Problem: Özellik hangi hassasiyet ve süre boyunca konum istiyor?
- Çözüm: Permission flow, accuracy, update interval, background mode ve fallback net mi?
- Trade-off: Hassas konum daha iyi deneyim sağlar; batarya ve gizlilik maliyeti büyür.
- Hata durumu: Permission denied, stale location, GPS unavailable ve background restriction ele alınmalı.
- Ölçüm: Permission opt-in, location latency, battery drain, update frequency ve error rate izlenmeli.
- Güvenlik/maliyet: Konum PII kabul edilmeli; retention ve paylaşım minimum tutulmalı.
Temel Kavramlar
1. Lokasyon Hassasiyeti
- Yüksek Hassasiyet: GPS + Ağ + Pasif
- Dengeli Güç: Ağ + Pasif
- Düşük Güç: Sadece Pasif
- Güç Yok: Sadece Önbellek Konumları
2. Lokasyon Türleri
- GPS: En hassas, en yüksek batarya tüketimi
- Network: Orta hassasiyet, orta batarya tüketimi
- Passive: Düşük hassasiyet, düşük batarya tüketimi
Platform-Spesifik Implementasyonlar
iOS Core Location
Temel Kurulum
swift
import CoreLocation
class LocationManager: NSObject, CLLocationManagerDelegate {
private let locationManager = CLLocationManager()
private var locationUpdateHandler: ((CLLocation) -> Void)?
override init() {
super.init()
setupLocationManager()
}
private func setupLocationManager() {
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.distanceFilter = 10.0 // 10 metre minimum hareket
}
func requestLocationPermission() {
switch locationManager.authorizationStatus {
case .notDetermined:
locationManager.requestWhenInUseAuthorization()
case .denied, .restricted:
// Kullanıcıyı ayarlara yönlendir
showLocationSettingsAlert()
case .authorizedWhenInUse:
// İzin var, lokasyon isteyebiliriz
break
case .authorizedAlways:
// Tam izin var
break
@unknown default:
break
}
}
func startLocationUpdates(updateHandler: @escaping (CLLocation) -> Void) {
self.locationUpdateHandler = updateHandler
locationManager.startUpdatingLocation()
}
func stopLocationUpdates() {
locationManager.stopUpdatingLocation()
locationUpdateHandler = nil
}
// MARK: - CLLocationManagerDelegate
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.last else { return }
locationUpdateHandler?(location)
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print("Location error: \\(error.localizedDescription)")
}
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
switch status {
case .authorizedWhenInUse, .authorizedAlways:
startLocationUpdates { location in
// Handle location update
}
case .denied, .restricted:
showLocationSettingsAlert()
default:
break
}
}
}Background Location
swift
// Background location için gerekli konfigürasyon
class BackgroundLocationManager: NSObject {
private let locationManager = CLLocationManager()
func enableBackgroundLocationUpdates() {
// Info.plist'e eklenmiş olmalı:
// <key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
// <key>UIBackgroundModes</key>
// <array><string>location</string></array>
locationManager.requestAlwaysAuthorization()
locationManager.allowsBackgroundLocationUpdates = true
locationManager.pausesLocationUpdatesAutomatically = false
}
func optimizeForBattery() {
// Batarya optimizasyonu için
locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters
locationManager.distanceFilter = 100.0
locationManager.pausesLocationUpdatesAutomatically = true
}
}Android Location Services
Modern Location API
kotlin
import com.google.android.gms.location.*
import com.google.android.gms.tasks.Task
class LocationService {
private lateinit var fusedLocationClient: FusedLocationProviderClient
private lateinit var locationRequest: LocationRequest
private lateinit var locationCallback: LocationCallback
fun initializeLocationServices(context: Context) {
fusedLocationClient = LocationServices.getFusedLocationProviderClient(context)
createLocationRequest()
createLocationCallback()
}
private fun createLocationRequest() {
locationRequest = LocationRequest.create().apply {
interval = 10000 // 10 saniye
fastestInterval = 5000 // 5 saniye
priority = LocationRequest.PRIORITY_HIGH_ACCURACY
smallestDisplacement = 10f // 10 metre
}
}
private fun createLocationCallback() {
locationCallback = object : LocationCallback() {
override fun onLocationResult(locationResult: LocationResult) {
locationResult.lastLocation?.let { location ->
handleLocationUpdate(location)
}
}
override fun onLocationAvailability(locationAvailability: LocationAvailability) {
if (!locationAvailability.isLocationAvailable) {
// Lokasyon servisi mevcut değil
handleLocationUnavailable()
}
}
}
}
@SuppressLint("MissingPermission")
fun startLocationUpdates() {
fusedLocationClient.requestLocationUpdates(
locationRequest,
locationCallback,
Looper.getMainLooper()
)
}
fun stopLocationUpdates() {
fusedLocationClient.removeLocationUpdates(locationCallback)
}
@SuppressLint("MissingPermission")
fun getLastKnownLocation(): Task<Location> {
return fusedLocationClient.lastLocation
}
private fun handleLocationUpdate(location: Location) {
// Lokasyon güncellemesini işle
val latitude = location.latitude
val longitude = location.longitude
val accuracy = location.accuracy
// Lokasyonu kaydet veya kullan
saveLocationToDatabase(latitude, longitude, accuracy)
}
}Permission Handling
kotlin
class LocationPermissionManager {
companion object {
const val LOCATION_PERMISSION_REQUEST_CODE = 1001
fun hasLocationPermission(context: Context): Boolean {
return ContextCompat.checkSelfPermission(
context,
Manifest.permission.ACCESS_FINE_LOCATION
) == PackageManager.PERMISSION_GRANTED
}
fun requestLocationPermission(activity: Activity) {
ActivityCompat.requestPermissions(
activity,
arrayOf(
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION
),
LOCATION_PERMISSION_REQUEST_CODE
)
}
fun shouldShowRationale(activity: Activity): Boolean {
return ActivityCompat.shouldShowRequestPermissionRationale(
activity,
Manifest.permission.ACCESS_FINE_LOCATION
)
}
}
}
// Activity'de kullanım
class MainActivity : AppCompatActivity() {
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
when (requestCode) {
LocationPermissionManager.LOCATION_PERMISSION_REQUEST_CODE -> {
if (grantResults.isNotEmpty() &&
grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// İzin verildi
startLocationServices()
} else {
// İzin reddedildi
showPermissionDeniedMessage()
}
}
}
}
}React Native Lokasyon Servisleri
Geolocation API
javascript
import Geolocation from '@react-native-community/geolocation';
import {PermissionsAndroid, Platform} from 'react-native';
class LocationService {
constructor() {
this.watchId = null;
this.currentLocation = null;
this.isTracking = false;
}
async requestLocationPermission() {
if (Platform.OS === 'android') {
try {
const granted = await PermissionsAndroid.request(
PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION,
{
title: 'Lokasyon İzni',
message: 'Uygulamanın lokasyonunuza erişmesi gerekiyor',
buttonNeutral: 'Daha Sonra Sor',
buttonNegative: 'İptal',
buttonPositive: 'Tamam',
}
);
return granted === PermissionsAndroid.RESULTS.GRANTED;
} catch (err) {
console.warn(err);
return false;
}
}
return true; // iOS için permission Info.plist'te tanımlı
}
getCurrentLocation() {
return new Promise((resolve, reject) => {
Geolocation.getCurrentPosition(
position => {
this.currentLocation = position;
resolve(position);
},
error => reject(error),
{
enableHighAccuracy: true,
timeout: 15000,
maximumAge: 10000,
}
);
});
}
startTracking(onLocationUpdate) {
this.watchId = Geolocation.watchPosition(
position => {
this.currentLocation = position;
onLocationUpdate(position);
},
error => {
console.error('Location tracking error:', error);
},
{
enableHighAccuracy: true,
distanceFilter: 10, // 10 metre minimum hareket
interval: 10000, // 10 saniye interval (Android)
fastestInterval: 5000, // 5 saniye minimum interval (Android)
}
);
this.isTracking = true;
}
stopTracking() {
if (this.watchId !== null) {
Geolocation.clearWatch(this.watchId);
this.watchId = null;
this.isTracking = false;
}
}
// Batarya optimizasyonu için düşük hassasiyet modu
startLowPowerTracking(onLocationUpdate) {
this.watchId = Geolocation.watchPosition(
position => {
this.currentLocation = position;
onLocationUpdate(position);
},
error => {
console.error('Location tracking error:', error);
},
{
enableHighAccuracy: false, // Düşük hassasiyet
distanceFilter: 100, // 100 metre minimum hareket
interval: 60000, // 1 dakika interval
fastestInterval: 30000, // 30 saniye minimum interval
}
);
}
}
// Hook kullanımı
import {useState, useEffect} from 'react';
export const useLocation = () => {
const [location, setLocation] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const locationService = new LocationService();
const getCurrentLocation = async () => {
setLoading(true);
setError(null);
try {
const hasPermission = await locationService.requestLocationPermission();
if (!hasPermission) {
throw new Error('Lokasyon izni reddedildi');
}
const position = await locationService.getCurrentLocation();
setLocation(position);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
const startTracking = async (callback) => {
const hasPermission = await locationService.requestLocationPermission();
if (hasPermission) {
locationService.startTracking(callback);
}
};
const stopTracking = () => {
locationService.stopTracking();
};
return {
location,
loading,
error,
getCurrentLocation,
startTracking,
stopTracking,
};
};Flutter Location Services
Geolocator Package
dart
import 'package:geolocator/geolocator.dart';
import 'package:permission_handler/permission_handler.dart';
class LocationService {
static const LocationSettings _locationSettings = LocationSettings(
accuracy: LocationAccuracy.high,
distanceFilter: 10,
);
Future<bool> requestLocationPermission() async {
PermissionStatus permission = await Permission.locationWhenInUse.status;
if (permission.isDenied) {
permission = await Permission.locationWhenInUse.request();
}
if (permission.isPermanentlyDenied) {
// Kullanıcıyı ayarlara yönlendir
openAppSettings();
return false;
}
return permission.isGranted;
}
Future<Position?> getCurrentLocation() async {
try {
bool hasPermission = await requestLocationPermission();
if (!hasPermission) return null;
bool serviceEnabled = await Geolocator.isLocationServiceEnabled();
if (!serviceEnabled) {
throw Exception('Lokasyon servisi kapalı');
}
Position position = await Geolocator.getCurrentPosition(
desiredAccuracy: LocationAccuracy.high,
);
return position;
} catch (e) {
print('Lokasyon alınamadı: $e');
return null;
}
}
Stream<Position> getLocationStream() {
return Geolocator.getPositionStream(
locationSettings: _locationSettings,
);
}
// Mesafe hesaplama
double calculateDistance(Position start, Position end) {
return Geolocator.distanceBetween(
start.latitude,
start.longitude,
end.latitude,
end.longitude,
);
}
// Batarya dostu tracking
Stream<Position> getBatteryOptimizedLocationStream() {
const LocationSettings batteryOptimizedSettings = LocationSettings(
accuracy: LocationAccuracy.low,
distanceFilter: 100, // 100 metre
);
return Geolocator.getPositionStream(
locationSettings: batteryOptimizedSettings,
);
}
}
// Provider kullanımı
class LocationProvider extends ChangeNotifier {
Position? _currentPosition;
bool _isTracking = false;
StreamSubscription<Position>? _positionSubscription;
Position? get currentPosition => _currentPosition;
bool get isTracking => _isTracking;
final LocationService _locationService = LocationService();
Future<void> getCurrentLocation() async {
Position? position = await _locationService.getCurrentLocation();
if (position != null) {
_currentPosition = position;
notifyListeners();
}
}
void startTracking() {
if (_isTracking) return;
_positionSubscription = _locationService.getLocationStream().listen(
(Position position) {
_currentPosition = position;
notifyListeners();
},
onError: (error) {
print('Location tracking error: $error');
},
);
_isTracking = true;
notifyListeners();
}
void stopTracking() {
_positionSubscription?.cancel();
_positionSubscription = null;
_isTracking = false;
notifyListeners();
}
@override
void dispose() {
stopTracking();
super.dispose();
}
}Geofencing
iOS Geofencing
swift
import CoreLocation
class GeofenceManager: NSObject, CLLocationManagerDelegate {
private let locationManager = CLLocationManager()
private var geofences: [CLCircularRegion] = []
override init() {
super.init()
locationManager.delegate = self
}
func addGeofence(identifier: String, center: CLLocationCoordinate2D, radius: CLLocationDistance) {
let geofence = CLCircularRegion(
center: center,
radius: radius,
identifier: identifier
)
geofence.notifyOnEntry = true
geofence.notifyOnExit = true
locationManager.startMonitoring(for: geofence)
geofences.append(geofence)
}
func removeGeofence(identifier: String) {
if let region = locationManager.monitoredRegions.first(where: { $0.identifier == identifier }) {
locationManager.stopMonitoring(for: region)
}
geofences.removeAll { $0.identifier == identifier }
}
// MARK: - CLLocationManagerDelegate
func locationManager(_ manager: CLLocationManager, didEnterRegion region: CLRegion) {
print("Geofence'e girildi: \\(region.identifier)")
handleGeofenceEntry(region.identifier)
}
func locationManager(_ manager: CLLocationManager, didExitRegion region: CLRegion) {
print("Geofence'den çıkıldı: \\(region.identifier)")
handleGeofenceExit(region.identifier)
}
private func handleGeofenceEntry(_ identifier: String) {
// Geofence'e girildiğinde yapılacak işlemler
sendLocalNotification(title: "Bölgeye Girildi", body: "\\(identifier) bölgesine girdiniz")
}
private func handleGeofenceExit(_ identifier: String) {
// Geofence'den çıkıldığında yapılacak işlemler
sendLocalNotification(title: "Bölgeden Çıkıldı", body: "\\(identifier) bölgesinden çıktınız")
}
}Android Geofencing
kotlin
import com.google.android.gms.location.*
class GeofenceManager(private val context: Context) {
private lateinit var geofencingClient: GeofencingClient
private val geofenceList = mutableListOf<Geofence>()
init {
geofencingClient = LocationServices.getGeofencingClient(context)
}
fun addGeofence(id: String, latitude: Double, longitude: Double, radius: Float) {
val geofence = Geofence.Builder()
.setRequestId(id)
.setCircularRegion(latitude, longitude, radius)
.setExpirationDuration(Geofence.NEVER_EXPIRE)
.setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER or Geofence.GEOFENCE_TRANSITION_EXIT)
.build()
geofenceList.add(geofence)
addGeofences()
}
@SuppressLint("MissingPermission")
private fun addGeofences() {
val geofencingRequest = GeofencingRequest.Builder().apply {
setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER)
addGeofences(geofenceList)
}.build()
geofencingClient.addGeofences(geofencingRequest, geofencePendingIntent)
}
private val geofencePendingIntent: PendingIntent by lazy {
val intent = Intent(context, GeofenceBroadcastReceiver::class.java)
PendingIntent.getBroadcast(
context,
0,
intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
}
}
class GeofenceBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val geofencingEvent = GeofencingEvent.fromIntent(intent)
if (geofencingEvent?.hasError() == true) {
return
}
val geofenceTransition = geofencingEvent?.geofenceTransition
when (geofenceTransition) {
Geofence.GEOFENCE_TRANSITION_ENTER -> {
// Geofence'e girildi
handleGeofenceEntry(geofencingEvent.triggeringGeofences)
}
Geofence.GEOFENCE_TRANSITION_EXIT -> {
// Geofence'den çıkıldı
handleGeofenceExit(geofencingEvent.triggeringGeofences)
}
}
}
private fun handleGeofenceEntry(geofences: List<Geofence>?) {
geofences?.forEach { geofence ->
// Notification gönder
sendNotification("Bölgeye Girildi", "${geofence.requestId} bölgesine girdiniz")
}
}
private fun handleGeofenceExit(geofences: List<Geofence>?) {
geofences?.forEach { geofence ->
// Notification gönder
sendNotification("Bölgeden Çıkıldı", "${geofence.requestId} bölgesinden çıktınız")
}
}
}En İyi Uygulamalar
1. Batarya Optimizasyonu
- Gerekli olmadığında lokasyon servisini durdurun
- Düşük hassasiyet modunu kullanın
- Minimum hareket mesafesi belirleyin
2. Güvenlik ve Gizlilik
- Kullanıcıdan açık izin alın
- Lokasyon verilerini güvenli şekilde saklayın
- Gereksiz lokasyon verisi toplamayın
3. Kullanıcı Deneyimi
- İzin isteme nedenini açık şekilde belirtin
- Lokasyon servisi kapalıysa alternatif sunun
- Loading state'leri gösterin
4. Error Handling
- Lokasyon bulunamadığında fallback plan hazırlayın
- Timeout durumlarını handle edin
- Kullanıcıya anlaşılır hata mesajları verin
Bu implementasyonlar ile güçlü, verimli ve kullanıcı dostu lokasyon servisleri geliştirebilirsiniz.
