react-native-nitro-geolocation 0.0.1 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/LICENSE +4 -1
  2. package/README.md +318 -0
  3. package/android/src/main/java/com/margelo/nitro/nitrogeolocation/GetCurrentPosition.kt +341 -0
  4. package/android/src/main/java/com/margelo/nitro/nitrogeolocation/NitroGeolocation.kt +76 -5
  5. package/android/src/main/java/com/margelo/nitro/nitrogeolocation/RequestAuthorization.kt +164 -0
  6. package/android/src/main/java/com/margelo/nitro/nitrogeolocation/WatchPosition.kt +228 -0
  7. package/ios/LocationManager.swift +529 -0
  8. package/ios/NitroGeolocation.swift +96 -2
  9. package/nitrogen/generated/.gitattributes +1 -0
  10. package/nitrogen/generated/android/c++/JAuthorizationLevelInternal.hpp +62 -0
  11. package/nitrogen/generated/android/c++/JFunc_void.hpp +74 -0
  12. package/nitrogen/generated/android/c++/JFunc_void_GeolocationError.hpp +77 -0
  13. package/nitrogen/generated/android/c++/JFunc_void_GeolocationResponse.hpp +79 -0
  14. package/nitrogen/generated/android/c++/JGeolocationCoordinates.hpp +77 -0
  15. package/nitrogen/generated/android/c++/JGeolocationError.hpp +69 -0
  16. package/nitrogen/generated/android/c++/JGeolocationOptions.hpp +77 -0
  17. package/nitrogen/generated/android/c++/JGeolocationResponse.hpp +59 -0
  18. package/nitrogen/generated/android/c++/JHybridNitroGeolocationSpec.cpp +98 -0
  19. package/nitrogen/generated/android/c++/JHybridNitroGeolocationSpec.hpp +69 -0
  20. package/nitrogen/generated/android/c++/JLocationProviderInternal.hpp +62 -0
  21. package/nitrogen/generated/android/c++/JRNConfigurationInternal.hpp +69 -0
  22. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/AuthorizationLevelInternal.kt +22 -0
  23. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/Func_void.kt +81 -0
  24. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/Func_void_GeolocationError.kt +81 -0
  25. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/Func_void_GeolocationResponse.kt +81 -0
  26. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/GeolocationCoordinates.kt +47 -0
  27. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/GeolocationError.kt +41 -0
  28. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/GeolocationOptions.kt +47 -0
  29. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/GeolocationResponse.kt +32 -0
  30. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/HybridNitroGeolocationSpec.kt +87 -0
  31. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/LocationProviderInternal.kt +22 -0
  32. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/RNConfigurationInternal.kt +38 -0
  33. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/nitrogeolocationOnLoad.kt +35 -0
  34. package/nitrogen/generated/android/nitrogeolocation+autolinking.cmake +81 -0
  35. package/nitrogen/generated/android/nitrogeolocation+autolinking.gradle +27 -0
  36. package/nitrogen/generated/android/nitrogeolocationOnLoad.cpp +50 -0
  37. package/nitrogen/generated/android/nitrogeolocationOnLoad.hpp +25 -0
  38. package/nitrogen/generated/ios/NitroGeolocation+autolinking.rb +60 -0
  39. package/nitrogen/generated/ios/NitroGeolocation-Swift-Cxx-Bridge.cpp +56 -0
  40. package/nitrogen/generated/ios/NitroGeolocation-Swift-Cxx-Bridge.hpp +252 -0
  41. package/nitrogen/generated/ios/NitroGeolocation-Swift-Cxx-Umbrella.hpp +67 -0
  42. package/nitrogen/generated/ios/NitroGeolocationAutolinking.mm +33 -0
  43. package/nitrogen/generated/ios/NitroGeolocationAutolinking.swift +25 -0
  44. package/nitrogen/generated/ios/c++/HybridNitroGeolocationSpecSwift.cpp +11 -0
  45. package/nitrogen/generated/ios/c++/HybridNitroGeolocationSpecSwift.hpp +125 -0
  46. package/nitrogen/generated/ios/swift/AuthorizationLevelInternal.swift +44 -0
  47. package/nitrogen/generated/ios/swift/Func_void.swift +47 -0
  48. package/nitrogen/generated/ios/swift/Func_void_GeolocationError.swift +47 -0
  49. package/nitrogen/generated/ios/swift/Func_void_GeolocationResponse.swift +47 -0
  50. package/nitrogen/generated/ios/swift/GeolocationCoordinates.swift +149 -0
  51. package/nitrogen/generated/ios/swift/GeolocationError.swift +79 -0
  52. package/nitrogen/generated/ios/swift/GeolocationOptions.swift +185 -0
  53. package/nitrogen/generated/ios/swift/GeolocationResponse.swift +46 -0
  54. package/nitrogen/generated/ios/swift/HybridNitroGeolocationSpec.swift +54 -0
  55. package/nitrogen/generated/ios/swift/HybridNitroGeolocationSpec_cxx.swift +236 -0
  56. package/nitrogen/generated/ios/swift/LocationProviderInternal.swift +44 -0
  57. package/nitrogen/generated/ios/swift/RNConfigurationInternal.swift +104 -0
  58. package/nitrogen/generated/shared/c++/AuthorizationLevelInternal.hpp +80 -0
  59. package/nitrogen/generated/shared/c++/GeolocationCoordinates.hpp +91 -0
  60. package/nitrogen/generated/shared/c++/GeolocationError.hpp +83 -0
  61. package/nitrogen/generated/shared/c++/GeolocationOptions.hpp +91 -0
  62. package/nitrogen/generated/shared/c++/GeolocationResponse.hpp +72 -0
  63. package/nitrogen/generated/shared/c++/HybridNitroGeolocationSpec.cpp +26 -0
  64. package/nitrogen/generated/shared/c++/HybridNitroGeolocationSpec.hpp +79 -0
  65. package/nitrogen/generated/shared/c++/LocationProviderInternal.hpp +80 -0
  66. package/nitrogen/generated/shared/c++/RNConfigurationInternal.hpp +84 -0
  67. package/package.json +34 -10
  68. package/src/NitroGeolocation.nitro.ts +38 -3
  69. package/src/NitroGeolocationModule.ts +5 -0
  70. package/src/clearWatch.ts +13 -0
  71. package/src/getCurrentPosition.ts +14 -0
  72. package/src/index.tsx +32 -7
  73. package/src/requestAuthorization.ts +9 -0
  74. package/src/setRNConfiguration.ts +22 -0
  75. package/src/stopObserving.ts +12 -0
  76. package/src/types.ts +43 -0
  77. package/src/watchPosition.ts +26 -0
  78. package/nitro.json +0 -17
  79. package/turbo.json +0 -42
package/LICENSE CHANGED
@@ -1,6 +1,9 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2022-present, React Native Community
3
+ This project is a Nitro Modules port of @react-native-community/geolocation
4
+
5
+ Original work Copyright (c) 2022-present, React Native Community
6
+ Modified work Copyright (c) 2025, jingjing2222
4
7
 
5
8
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
9
  of this software and associated documentation files (the "Software"), to deal
package/README.md ADDED
@@ -0,0 +1,318 @@
1
+ # react-native-nitro-geolocation
2
+
3
+ [![NPM](https://img.shields.io/npm/v/react-native-nitro-geolocation)](https://www.npmjs.com/package/react-native-nitro-geolocation)
4
+
5
+ [`@react-native-community/geolocation`](https://github.com/michalchudziak/react-native-geolocation)
6
+ for the **React Native New Architecture** — with 100% API compatibility.
7
+
8
+ ![react-native-nitro-geolocation)(https://raw.githubusercontent.com/jingjing2222/react-native-nitro-geolocation/main/demo.gif)
9
+
10
+ ---
11
+
12
+ ## 📘 Documentation
13
+
14
+ Full documentation available at:
15
+ 👉 [https://react-native-nitro-geolocation.pages.dev](https://react-native-nitro-geolocation.pages.dev)
16
+
17
+ ---
18
+
19
+ ## 🧭 Introduction
20
+
21
+ The `@react-native-community/geolocation` package has long been the standard way to access device location in React Native apps.
22
+
23
+ With React Native moving toward **Nitro Modules**, **Fabric**, and **JSI**,
24
+ this project — **React Native Nitro Geolocation** — brings the same familiar API to the new architecture.
25
+
26
+ It provides the **same API surface** with:
27
+
28
+ - 🚀 Faster performance via direct **JSI bindings**
29
+ - 📱 Improved native consistency (Android + iOS)
30
+ - 🔁 Seamless migration from `@react-native-community/geolocation`
31
+ - 🧩 TypeScript-first developer experience
32
+ - 🔄 100% API compatibility (drop-in replacement)
33
+
34
+ Whether upgrading an existing app or building a new one,
35
+ **React Native Nitro Geolocation** keeps the simplicity you know — with modern internals.
36
+
37
+ ## 🏗 Architecture Comparison
38
+
39
+ ### 🧩 Origin: Event-based Architecture (`@react-native-community/geolocation`)
40
+
41
+ ~~~
42
+ JavaScript
43
+ ↓ EventEmitter.addListener('geolocationDidChange', callback)
44
+ ↓ (Callback stored in JS)
45
+ React Native Bridge (JSON serialization)
46
+
47
+ Native Layer (Android/iOS)
48
+ ↓ LocationListener receives updates
49
+ ↓ emit('geolocationDidChange', data)
50
+
51
+ EventEmitter dispatches to all listeners
52
+
53
+ User callback executed
54
+ ~~~
55
+
56
+ **Key traits:**
57
+ - Callbacks stored only in JS
58
+ - Bridge serialization on every update
59
+ - One shared event stream
60
+
61
+ ---
62
+
63
+ ### ⚡ Modern: Direct Callback Architecture (`react-native-nitro-geolocation`)
64
+
65
+ ~~~
66
+ JavaScript
67
+ ↓ Geolocation.watchPosition(success, error)
68
+ ↓ (Callbacks passed directly to native via JSI)
69
+ JSI Layer (No Bridge!)
70
+
71
+ Native Layer (Kotlin/Swift)
72
+ ↓ callback.success(position) → JSI direct call
73
+
74
+ User callback executed immediately
75
+ ~~~
76
+
77
+ **Key traits:**
78
+ - Callbacks passed as native JSI references
79
+ - No Bridge serialization
80
+ - Independent callback per watcher
81
+ - Native → JS communication in real time
82
+
83
+ ---
84
+
85
+ ## ⚡ Quick Start
86
+
87
+ ### 1. Installation
88
+
89
+ ~~~bash
90
+ # Install Nitro core and Geolocation module
91
+ yarn add react-native-nitro-modules react-native-nitro-geolocation
92
+
93
+ # or using npm
94
+ npm install react-native-nitro-modules react-native-nitro-geolocation
95
+ ~~~
96
+
97
+ After installation, rebuild your native app:
98
+
99
+ ~~~bash
100
+ cd ios && pod install
101
+ ~~~
102
+
103
+ ---
104
+
105
+ ### 2. iOS Setup
106
+
107
+ Add the following permissions to your **Info.plist**:
108
+
109
+ ~~~xml
110
+ <key>NSLocationWhenInUseUsageDescription</key>
111
+ <string>This app requires access to your location while it’s in use.</string>
112
+ <key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
113
+ <string>This app requires access to your location at all times.</string>
114
+ ~~~
115
+
116
+ ---
117
+
118
+ ### 3. Android Setup
119
+
120
+ Add these permissions to your **AndroidManifest.xml**:
121
+
122
+ ~~~xml
123
+ <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
124
+ <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
125
+ ~~~
126
+
127
+ Optional (for background access):
128
+
129
+ ~~~xml
130
+ <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
131
+ ~~~
132
+
133
+ ---
134
+
135
+ ### 4. Usage Example
136
+
137
+ ~~~tsx
138
+ import Geolocation from 'react-native-nitro-geolocation';
139
+
140
+ Geolocation.getCurrentPosition(
141
+ (position) => {
142
+ console.log('Latitude:', position.coords.latitude);
143
+ console.log('Longitude:', position.coords.longitude);
144
+ },
145
+ (error) => {
146
+ console.error('Location error:', error);
147
+ },
148
+ { enableHighAccuracy: true, timeout: 15000, maximumAge: 10000 }
149
+ );
150
+
151
+ // Subscribe to continuous updates
152
+ const watchId = Geolocation.watchPosition(
153
+ (position) => {
154
+ console.log('Updated position:', position);
155
+ },
156
+ (error) => console.error(error),
157
+ );
158
+ ~~~
159
+
160
+ ---
161
+
162
+ ### Migrating from `@react-native-community/geolocation`
163
+
164
+ Nitro Geolocation is **100% API-compatible** with the original package.
165
+ You can migrate simply by replacing imports:
166
+
167
+ ~~~diff
168
+ - import Geolocation from '@react-native-community/geolocation';
169
+ + import Geolocation from 'react-native-nitro-geolocation';
170
+ ~~~
171
+
172
+ **Benefits:**
173
+ - Better performance via JSI
174
+ - Reduced bridge overhead
175
+ - Improved permission consistency
176
+ - Built-in TypeScript definitions
177
+
178
+ ---
179
+
180
+ ## 🧠 API Methods
181
+
182
+ ### Summary
183
+ - `setRNConfiguration`
184
+ - `requestAuthorization`
185
+ - `getCurrentPosition`
186
+ - `watchPosition`
187
+ - `clearWatch`
188
+ - `stopObserving`
189
+
190
+ ---
191
+
192
+ ### `setRNConfiguration()`
193
+
194
+ Sets configuration options used for all location requests.
195
+
196
+ ~~~tsx
197
+ import Geolocation from 'react-native-nitro-geolocation';
198
+
199
+ Geolocation.setRNConfiguration({
200
+ skipPermissionRequests: false,
201
+ authorizationLevel: 'auto',
202
+ enableBackgroundLocationUpdates: true,
203
+ locationProvider: 'auto',
204
+ });
205
+
206
+ // recommended
207
+ import { setRNConfiguration } from 'react-native-nitro-geolocation';
208
+
209
+ setRNConfiguration({
210
+ skipPermissionRequests: false,
211
+ authorizationLevel: 'auto',
212
+ enableBackgroundLocationUpdates: true,
213
+ locationProvider: 'auto',
214
+ });
215
+ ~~~
216
+
217
+ **Options:**
218
+ - `skipPermissionRequests` — default `false`
219
+ - `authorizationLevel` — `'always' | 'whenInUse' | 'auto'` *(iOS only)*
220
+ - `enableBackgroundLocationUpdates` — *(iOS only)*
221
+ - `locationProvider` — `'playServices' | 'android' | 'auto'` *(Android only)*
222
+
223
+ ---
224
+
225
+ ### `requestAuthorization()`
226
+
227
+ Requests location permission from the system.
228
+
229
+ ~~~tsx
230
+ import Geolocation from 'react-native-nitro-geolocation';
231
+
232
+ Geolocation.requestAuthorization(
233
+ () => console.log('Permission granted'),
234
+ (error) => console.error('Permission error:', error),
235
+ );
236
+
237
+ // recommended
238
+ import { requestAuthorization } from 'react-native-nitro-geolocation';
239
+
240
+ requestAuthorization(
241
+ () => console.log('Permission granted'),
242
+ (error) => console.error('Permission error:', error),
243
+ );
244
+ ~~~
245
+
246
+ ---
247
+
248
+ ### `getCurrentPosition()`
249
+
250
+ Retrieves the current device location once.
251
+
252
+ ~~~tsx
253
+ import Geolocation from 'react-native-nitro-geolocation';
254
+
255
+ Geolocation.getCurrentPosition(
256
+ (position) => console.log(position),
257
+ (error) => console.error(error),
258
+ { enableHighAccuracy: true, timeout: 15000, maximumAge: 10000 },
259
+ );
260
+
261
+ // recommended
262
+ import { getCurrentPosition } from 'react-native-nitro-geolocation';
263
+
264
+ getCurrentPosition(
265
+ (position) => console.log(position),
266
+ (error) => console.error(error),
267
+ { enableHighAccuracy: true, timeout: 15000, maximumAge: 10000 },
268
+ );
269
+ ~~~
270
+
271
+ ---
272
+
273
+ ### `watchPosition()`
274
+
275
+ Watches location changes and calls the success callback each time.
276
+
277
+ ~~~tsx
278
+ import Geolocation from 'react-native-nitro-geolocation';
279
+
280
+ const id = Geolocation.watchPosition(
281
+ (position) => console.log('Position:', position),
282
+ (error) => console.error(error),
283
+ { interval: 5000, distanceFilter: 10 },
284
+ );
285
+
286
+ // recommended
287
+ import { watchPosition } from 'react-native-nitro-geolocation';
288
+
289
+ const id = watchPosition(
290
+ (position) => console.log('Position:', position),
291
+ (error) => console.error(error),
292
+ { interval: 5000, distanceFilter: 10 },
293
+ );
294
+
295
+ ~~~
296
+
297
+ ---
298
+
299
+ ### `clearWatch()`
300
+
301
+ Stops watching location updates for a given watch ID.
302
+
303
+ ~~~tsx
304
+ import Geolocation from 'react-native-nitro-geolocation';
305
+ Geolocation.clearWatch(id);
306
+
307
+ // recommended
308
+ import { clearWatch } from 'react-native-nitro-geolocation';
309
+ clearWatch(id);
310
+ ~~~
311
+
312
+ ---
313
+
314
+ ## 🧪 Summary
315
+
316
+ **React Native Nitro Geolocation** transforms the geolocation API
317
+ from a bridge-based, event-driven system into a **JSI-powered direct-callback model** —
318
+ delivering native-level performance with **zero API changes** for developers.
@@ -0,0 +1,341 @@
1
+ package com.margelo.nitro.nitrogeolocation
2
+
3
+ import android.Manifest
4
+ import android.content.Context
5
+ import android.content.pm.PackageManager
6
+ import android.location.Location
7
+ import android.location.LocationListener
8
+ import android.location.LocationManager
9
+ import android.os.Bundle
10
+ import android.os.Handler
11
+ import android.os.Looper
12
+ import android.util.Log
13
+ import androidx.core.content.ContextCompat
14
+ import com.facebook.react.bridge.ReactApplicationContext
15
+
16
+ class GetCurrentPosition(private val reactContext: ReactApplicationContext) {
17
+
18
+ fun execute(
19
+ success: (position: GeolocationResponse) -> Unit,
20
+ error: ((error: GeolocationError) -> Unit)?,
21
+ options: GeolocationOptions?
22
+ ) {
23
+ val locationManager =
24
+ reactContext.getSystemService(Context.LOCATION_SERVICE) as? LocationManager
25
+ if (locationManager == null) {
26
+ Log.e(TAG, "LocationManager is not available")
27
+ error?.invoke(createError(POSITION_UNAVAILABLE, "LocationManager is not available"))
28
+ return
29
+ }
30
+
31
+ val opts = parseOptions(options)
32
+ val provider = getValidProvider(locationManager, opts.enableHighAccuracy)
33
+
34
+ if (provider == null) {
35
+ Log.e(TAG, "No location provider available")
36
+ error?.invoke(createError(POSITION_UNAVAILABLE, "No location provider available"))
37
+ return
38
+ }
39
+
40
+ try {
41
+ val lastKnownLocation = locationManager.getLastKnownLocation(provider)
42
+
43
+ // Check if cached location is fresh enough
44
+ if (lastKnownLocation != null && isCachedLocationValid(lastKnownLocation, opts)) {
45
+ success(locationToPosition(lastKnownLocation))
46
+ return
47
+ }
48
+
49
+ // If maximumAge is Infinity and we have a last known location, use it
50
+ if (lastKnownLocation != null && opts.maximumAge == Double.POSITIVE_INFINITY) {
51
+ success(locationToPosition(lastKnownLocation))
52
+ return
53
+ }
54
+
55
+ // Request fresh location
56
+ requestFreshLocation(locationManager, provider, opts, success, error, lastKnownLocation)
57
+ } catch (e: SecurityException) {
58
+ Log.e(TAG, "Security exception: ${e.message}")
59
+ error?.invoke(
60
+ createError(PERMISSION_DENIED, "Location permission denied: ${e.message}")
61
+ )
62
+ }
63
+ }
64
+
65
+ // ===== Helper Functions =====
66
+
67
+ private fun parseOptions(options: GeolocationOptions?): ParsedOptions {
68
+ return ParsedOptions(
69
+ timeout = options?.timeout ?: DEFAULT_TIMEOUT,
70
+ maximumAge = options?.maximumAge ?: DEFAULT_MAXIMUM_AGE,
71
+ enableHighAccuracy = options?.enableHighAccuracy ?: false
72
+ )
73
+ }
74
+
75
+ private fun isCachedLocationValid(location: Location, options: ParsedOptions): Boolean {
76
+ val age = System.currentTimeMillis() - location.time
77
+ return age < options.maximumAge
78
+ }
79
+
80
+ private fun getValidProvider(locationManager: LocationManager, highAccuracy: Boolean): String? {
81
+ val preferredProvider =
82
+ if (highAccuracy) LocationManager.GPS_PROVIDER else LocationManager.NETWORK_PROVIDER
83
+ val fallbackProvider =
84
+ if (highAccuracy) LocationManager.NETWORK_PROVIDER else LocationManager.GPS_PROVIDER
85
+
86
+ return when {
87
+ isProviderValid(locationManager, preferredProvider) -> preferredProvider
88
+ isProviderValid(locationManager, fallbackProvider) -> fallbackProvider
89
+ else -> null
90
+ }
91
+ }
92
+
93
+ private fun isProviderValid(locationManager: LocationManager, provider: String): Boolean {
94
+ if (!locationManager.isProviderEnabled(provider)) return false
95
+
96
+ val permission =
97
+ if (provider == LocationManager.GPS_PROVIDER)
98
+ Manifest.permission.ACCESS_FINE_LOCATION
99
+ else Manifest.permission.ACCESS_COARSE_LOCATION
100
+
101
+ return ContextCompat.checkSelfPermission(reactContext, permission) ==
102
+ PackageManager.PERMISSION_GRANTED
103
+ }
104
+
105
+ private fun requestFreshLocation(
106
+ locationManager: LocationManager,
107
+ provider: String,
108
+ options: ParsedOptions,
109
+ success: (GeolocationResponse) -> Unit,
110
+ error: ((GeolocationError) -> Unit)?,
111
+ fallbackLocation: Location?
112
+ ) {
113
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R) {
114
+ // Android 11+ supports getCurrentLocation
115
+ requestCurrentLocationModern(
116
+ locationManager,
117
+ provider,
118
+ options,
119
+ success,
120
+ error,
121
+ fallbackLocation
122
+ )
123
+ } else {
124
+ // Fallback to requestLocationUpdates
125
+ requestCurrentLocationLegacy(
126
+ locationManager,
127
+ provider,
128
+ options,
129
+ success,
130
+ error,
131
+ fallbackLocation
132
+ )
133
+ }
134
+ }
135
+
136
+ @androidx.annotation.RequiresApi(android.os.Build.VERSION_CODES.R)
137
+ private fun requestCurrentLocationModern(
138
+ locationManager: LocationManager,
139
+ provider: String,
140
+ options: ParsedOptions,
141
+ success: (GeolocationResponse) -> Unit,
142
+ error: ((GeolocationError) -> Unit)?,
143
+ fallbackLocation: Location?
144
+ ) {
145
+ val handler = Handler(Looper.getMainLooper())
146
+ val cancellationSignal = android.os.CancellationSignal()
147
+
148
+ val timeoutRunnable = Runnable {
149
+ cancellationSignal.cancel()
150
+ error?.invoke(createError(TIMEOUT, "Location request timed out"))
151
+ }
152
+
153
+ try {
154
+ locationManager.getCurrentLocation(
155
+ provider,
156
+ cancellationSignal,
157
+ { runnable -> handler.post(runnable) }
158
+ ) { location ->
159
+ handler.removeCallbacks(timeoutRunnable)
160
+
161
+ val bestLocation = selectBestLocation(location, fallbackLocation)
162
+
163
+ if (bestLocation != null) {
164
+ success(locationToPosition(bestLocation))
165
+ } else {
166
+ Log.e(TAG, "No location available")
167
+ error?.invoke(createError(POSITION_UNAVAILABLE, "Unable to get location"))
168
+ }
169
+ }
170
+ handler.postDelayed(timeoutRunnable, options.timeout.toLong())
171
+ } catch (e: SecurityException) {
172
+ Log.e(TAG, "SecurityException: ${e.message}")
173
+ error?.invoke(createError(PERMISSION_DENIED, "Permission denied: ${e.message}"))
174
+ }
175
+ }
176
+
177
+ private fun selectBestLocation(newLocation: Location?, fallbackLocation: Location?): Location? {
178
+ return when {
179
+ newLocation == null -> fallbackLocation
180
+ fallbackLocation == null -> newLocation
181
+ isBetterLocation(newLocation, fallbackLocation) -> newLocation
182
+ else -> fallbackLocation
183
+ }
184
+ }
185
+
186
+ private fun requestCurrentLocationLegacy(
187
+ locationManager: LocationManager,
188
+ provider: String,
189
+ options: ParsedOptions,
190
+ success: (GeolocationResponse) -> Unit,
191
+ error: ((GeolocationError) -> Unit)?,
192
+ fallbackLocation: Location?
193
+ ) {
194
+ val handler = Handler(Looper.getMainLooper())
195
+ var isResolved = false
196
+ var oldLocation = fallbackLocation
197
+ lateinit var listener: LocationListener
198
+
199
+ val timeoutRunnable = Runnable {
200
+ if (!isResolved) {
201
+ isResolved = true
202
+ locationManager.removeUpdates(listener)
203
+ error?.invoke(createError(TIMEOUT, "Location request timed out"))
204
+ }
205
+ }
206
+
207
+ listener =
208
+ object : LocationListener {
209
+ override fun onLocationChanged(location: Location) {
210
+ synchronized(this) {
211
+ if (!isResolved) {
212
+ val bestLocation = selectBestLocation(location, oldLocation)
213
+ if (bestLocation == location) {
214
+ isResolved = true
215
+ handler.removeCallbacks(timeoutRunnable)
216
+ locationManager.removeUpdates(this)
217
+ success(locationToPosition(location))
218
+ }
219
+ oldLocation = location
220
+ }
221
+ }
222
+ }
223
+
224
+ override fun onStatusChanged(provider: String?, status: Int, extras: Bundle?) {}
225
+ override fun onProviderEnabled(provider: String) {}
226
+ override fun onProviderDisabled(provider: String) {}
227
+ }
228
+
229
+ try {
230
+ locationManager.requestLocationUpdates(
231
+ provider,
232
+ 100,
233
+ 1f,
234
+ listener,
235
+ Looper.getMainLooper()
236
+ )
237
+ handler.postDelayed(timeoutRunnable, options.timeout.toLong())
238
+ } catch (e: SecurityException) {
239
+ Log.e(TAG, "SecurityException in requestLocationUpdates: ${e.message}")
240
+ error?.invoke(createError(PERMISSION_DENIED, "Permission denied: ${e.message}"))
241
+ }
242
+ }
243
+
244
+ /**
245
+ * Determines whether one Location reading is better than the current Location fix Taken from
246
+ * Android Examples: https://developer.android.com/guide/topics/location/strategies.html
247
+ */
248
+ private fun isBetterLocation(location: Location, currentBestLocation: Location?): Boolean {
249
+ if (currentBestLocation == null) {
250
+ return true
251
+ }
252
+
253
+ val timeDelta = location.time - currentBestLocation.time
254
+ val isSignificantlyNewer = timeDelta > TWO_MINUTES
255
+ val isSignificantlyOlder = timeDelta < -TWO_MINUTES
256
+ val isNewer = timeDelta > 0
257
+
258
+ if (isSignificantlyNewer) {
259
+ return true
260
+ } else if (isSignificantlyOlder) {
261
+ return false
262
+ }
263
+
264
+ val accuracyDelta = (location.accuracy - currentBestLocation.accuracy).toInt()
265
+ val isLessAccurate = accuracyDelta > 0
266
+ val isMoreAccurate = accuracyDelta < 0
267
+ val isSignificantlyLessAccurate = accuracyDelta > 200
268
+
269
+ val isFromSameProvider = isSameProvider(location.provider, currentBestLocation.provider)
270
+
271
+ return when {
272
+ isMoreAccurate -> true
273
+ isNewer && !isLessAccurate -> true
274
+ isNewer && !isSignificantlyLessAccurate && isFromSameProvider -> true
275
+ else -> false
276
+ }
277
+ }
278
+
279
+ private fun isSameProvider(provider1: String?, provider2: String?): Boolean {
280
+ if (provider1 == null) {
281
+ return provider2 == null
282
+ }
283
+ return provider1 == provider2
284
+ }
285
+
286
+ // ===== Data Conversion =====
287
+
288
+ private fun locationToPosition(location: Location): GeolocationResponse {
289
+ return GeolocationResponse(
290
+ coords =
291
+ GeolocationCoordinates(
292
+ latitude = location.latitude,
293
+ longitude = location.longitude,
294
+ altitude = if (location.hasAltitude()) location.altitude else null,
295
+ accuracy = location.accuracy.toDouble(),
296
+ altitudeAccuracy =
297
+ if (android.os.Build.VERSION.SDK_INT >=
298
+ android.os.Build.VERSION_CODES.O &&
299
+ location.hasVerticalAccuracy()
300
+ )
301
+ location.verticalAccuracyMeters.toDouble()
302
+ else null,
303
+ heading =
304
+ if (location.hasBearing()) location.bearing.toDouble()
305
+ else null,
306
+ speed = if (location.hasSpeed()) location.speed.toDouble() else null
307
+ ),
308
+ timestamp = location.time.toDouble()
309
+ )
310
+ }
311
+
312
+ private fun createError(code: Int, message: String): GeolocationError {
313
+ return GeolocationError(
314
+ code = code.toDouble(),
315
+ message = message,
316
+ PERMISSION_DENIED = PERMISSION_DENIED.toDouble(),
317
+ POSITION_UNAVAILABLE = POSITION_UNAVAILABLE.toDouble(),
318
+ TIMEOUT = TIMEOUT.toDouble()
319
+ )
320
+ }
321
+
322
+ // ===== Data Classes =====
323
+
324
+ private data class ParsedOptions(
325
+ val timeout: Double,
326
+ val maximumAge: Double,
327
+ val enableHighAccuracy: Boolean
328
+ )
329
+
330
+ companion object {
331
+ private const val TAG = "GetCurrentPosition"
332
+ const val PERMISSION_DENIED = 1
333
+ const val POSITION_UNAVAILABLE = 2
334
+ const val TIMEOUT = 3
335
+
336
+ const val DEFAULT_TIMEOUT = 10 * 60 * 1000.0 // 10 minutes
337
+ const val DEFAULT_MAXIMUM_AGE = Double.POSITIVE_INFINITY
338
+
339
+ private const val TWO_MINUTES = 1000 * 60 * 2L // 2 minutes in milliseconds
340
+ }
341
+ }