From b45dbf348fd37742ba53b4b491278fdfb451479e Mon Sep 17 00:00:00 2001 From: stelselim Date: Thu, 2 Jul 2026 10:40:05 +0200 Subject: [PATCH 1/4] migrate geolocation to react native nitro geolocation --- android/app/src/main/AndroidManifest.xml | 1 + ios/Podfile.lock | 91 ++++++++++++++++-------- package-lock.json | 37 ++++++---- package.json | 3 +- 4 files changed, 86 insertions(+), 46 deletions(-) diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 7de895d..81c1dae 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -20,6 +20,7 @@ + diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 2cd1632..3c86669 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -198,6 +198,53 @@ PODS: - nanopb/encode (= 2.30910.0) - nanopb/decode (2.30910.0) - nanopb/encode (2.30910.0) + - NitroGeolocation (1.4.2): + - hermes-engine + - NitroModules + - RCTRequired + - RCTTypeSafety + - React-callinvoker + - React-Core + - React-Core-prebuilt + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - NitroModules (0.36.1): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-callinvoker + - React-Core + - React-Core-prebuilt + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga - op-sqlite (15.2.5): - hermes-engine - OpenSSL-Universal @@ -221,7 +268,7 @@ PODS: - ReactCommon/turbomodule/core - ReactNativeDependencies - Yoga - - OpenSSL-Universal (3.3.3001) + - OpenSSL-Universal (3.6.2000) - PromisesObjC (2.4.0) - PromisesSwift (2.4.0): - PromisesObjC (= 2.4.0) @@ -1714,28 +1761,6 @@ PODS: - Yoga - react-native-geocoder (0.5.0): - React - - react-native-geolocation (3.4.0): - - hermes-engine - - RCTRequired - - RCTTypeSafety - - React-Core - - React-Core-prebuilt - - React-debug - - React-Fabric - - React-featureflags - - React-graphics - - React-ImageManager - - React-jsi - - React-NativeModulesApple - - React-RCTFabric - - React-renderercss - - React-rendererdebug - - React-utils - - ReactCodegen - - ReactCommon/turbomodule/bridging - - ReactCommon/turbomodule/core - - ReactNativeDependencies - - Yoga - react-native-google-maps (1.20.1): - Google-Maps-iOS-Utils (= 5.0.0) - GoogleMaps (= 8.4.0) @@ -2963,6 +2988,8 @@ DEPENDENCIES: - IQKeyboardManager - MendixNative (from `../node_modules/mendix-native`) - nanopb + - NitroGeolocation (from `../node_modules/react-native-nitro-geolocation`) + - NitroModules (from `../node_modules/react-native-nitro-modules`) - "op-sqlite (from `../node_modules/@op-engineering/op-sqlite`)" - QRCodeReader.swift (~> 10.1.0) - RCTDeprecation (from `../node_modules/react-native/ReactApple/Libraries/RCTFoundation/RCTDeprecation`) @@ -3007,7 +3034,6 @@ DEPENDENCIES: - "react-native-cameraroll (from `../node_modules/@react-native-camera-roll/camera-roll`)" - react-native-file-viewer-turbo (from `../node_modules/react-native-file-viewer-turbo`) - react-native-geocoder (from `../node_modules/react-native-geocoder`) - - "react-native-geolocation (from `../node_modules/@react-native-community/geolocation`)" - react-native-google-maps (from `../node_modules/react-native-maps`) - react-native-image-picker (from `../node_modules/react-native-image-picker`) - react-native-maps (from `../node_modules/react-native-maps`) @@ -3122,6 +3148,10 @@ EXTERNAL SOURCES: :tag: hermes-v0.15.1 MendixNative: :path: "../node_modules/mendix-native" + NitroGeolocation: + :path: "../node_modules/react-native-nitro-geolocation" + NitroModules: + :path: "../node_modules/react-native-nitro-modules" op-sqlite: :path: "../node_modules/@op-engineering/op-sqlite" RCTDeprecation: @@ -3206,8 +3236,6 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native-file-viewer-turbo" react-native-geocoder: :path: "../node_modules/react-native-geocoder" - react-native-geolocation: - :path: "../node_modules/@react-native-community/geolocation" react-native-google-maps: :path: "../node_modules/react-native-maps" react-native-image-picker: @@ -3371,14 +3399,16 @@ SPEC CHECKSUMS: GoogleDataTransport: 6c09b596d841063d76d4288cc2d2f42cc36e1e2a GoogleMaps: 8939898920281c649150e0af74aa291c60f2e77d GoogleUtilities: ea963c370a38a8069cc5f7ba4ca849a60b6d7d15 - hermes-engine: 2ece5229b4460076597f50050f4fb14074f8f714 + hermes-engine: dd25b752a961795fcef9d248218ede66437847d4 IQKeyboardManager: c8665b3396bd0b79402b4c573eac345a31c7d485 libwebp: 02b23773aedb6ff1fd38cec7a77b81414c6842a8 MendixNative: 7e5ed081dfc0cbe76d8d81b76efafba9f3dd6a4e MultiplatformBleAdapter: b1fddd0d499b96b607e00f0faa8e60648343dc1d nanopb: 438bc412db1928dac798aa6fd75726007be04262 + NitroGeolocation: 647199e12e95fa2e4030e872ede9a3598fd379d3 + NitroModules: 4d19ff59969b2c67a19175fb17b66917af6708e0 op-sqlite: e9ef65bcf95a97863874cee87841425bb71c8396 - OpenSSL-Universal: 6082b0bf950e5636fe0d78def171184e2b3899c2 + OpenSSL-Universal: ecee7b138fa75a74ecf00d7ffd248fb584739b9e PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 PromisesSwift: 9d77319bbe72ebf6d872900551f7eeba9bce2851 QRCodeReader.swift: 373a389fe9a22d513c879a32a6f647c58f4ef572 @@ -3390,7 +3420,7 @@ SPEC CHECKSUMS: React: 1ba7d364ade7d883a1ec055bfc3606f35fdee17b React-callinvoker: bc2a26f8d84fb01f003fc6de6c9337b64715f95b React-Core: 7840d3a80b43a95c5e80ef75146bd70925ebab0f - React-Core-prebuilt: a54db89db007bc34115efc5abe10e05c699797da + React-Core-prebuilt: 2439654d53489a5609231fe950d7f4e4c641ad52 React-CoreModules: 2eb010400b63b89e53a324ffb3c112e4c7c3ce42 React-cxxreact: a558e92199d26f145afa9e62c4233cf8e7950efe React-debug: 755200a6e7f5e6e0a40ff8d215493d43cce285fc @@ -3423,7 +3453,6 @@ SPEC CHECKSUMS: react-native-cameraroll: bd1a177463acbba89365d09e81057294eba1e2f6 react-native-file-viewer-turbo: 0da5fe2575b1c2e43ce8246fcd0fa9c3b097a72d react-native-geocoder: bc977303c9c53cce36bcdb3c6f17e6832f86beb6 - react-native-geolocation: cfc39041210bd9f591ec2beb7d417291251da8e5 react-native-google-maps: c4f5c5b2dda17e7cb2cb2b37a81f140b039b3e7e react-native-image-picker: 168d6972e50ea848defe4ac4c222faa9bc52aea0 react-native-maps: 9febd31278b35cd21e4fad2cf6fa708993be5dab @@ -3470,7 +3499,7 @@ SPEC CHECKSUMS: ReactCodegen: 797de5178718324c6eba3327b07f9a423fbd5787 ReactCommon: 07572bf9e687c8a52fbe4a3641e9e3a1a477c78e ReactNativeBiometrics: 9ead8d633833fd13e5a79564af8ba7064c2a58c6 - ReactNativeDependencies: e709c771664b1e4e11acf00936c61a975577bf9c + ReactNativeDependencies: 14132e559d3008cd261e3c00859a58fd4f7fa42c RNBootSplash: 13b7612788960d0c5446e68489ac2509e506cfcd RNCalendarEvents: f90f73666b6bcbb3cc8a491ffbb5e48c0db3de37 RNCAsyncStorage: 3a4f5e2777dae1688b781a487923a08569e27fe4 diff --git a/package-lock.json b/package-lock.json index 978bd7e..06357f2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,7 +19,6 @@ "@react-native-community/cli-platform-android": "20.1.3", "@react-native-community/cli-platform-ios": "20.1.3", "@react-native-community/datetimepicker": "8.6.0", - "@react-native-community/geolocation": "3.4.0", "@react-native-community/netinfo": "11.5.2", "@react-native-firebase/analytics": "20.1.0", "@react-native-firebase/app": "20.1.0", @@ -50,6 +49,8 @@ "react-native-linear-gradient": "2.8.3", "react-native-localize": "3.7.0", "react-native-maps": "1.20.1", + "react-native-nitro-geolocation": "1.4.2", + "react-native-nitro-modules": "0.36.1", "react-native-notify-kit": "10.4.7", "react-native-pager-view": "6.9.1", "react-native-permissions": "5.5.1", @@ -2264,19 +2265,6 @@ } } }, - "node_modules/@react-native-community/geolocation": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@react-native-community/geolocation/-/geolocation-3.4.0.tgz", - "integrity": "sha512-bzZH89/cwmpkPMKKveoC72C4JH0yF4St5Ceg/ZM9pA1SqX9MlRIrIrrOGZ/+yi++xAvFDiYfihtn9TvXWU9/rA==", - "license": "MIT", - "engines": { - "node": ">=18.0.0" - }, - "peerDependencies": { - "react": "*", - "react-native": "*" - } - }, "node_modules/@react-native-community/netinfo": { "version": "11.5.2", "resolved": "https://registry.npmjs.org/@react-native-community/netinfo/-/netinfo-11.5.2.tgz", @@ -8020,6 +8008,27 @@ } } }, + "node_modules/react-native-nitro-geolocation": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/react-native-nitro-geolocation/-/react-native-nitro-geolocation-1.4.2.tgz", + "integrity": "sha512-woUfdQgzWeTFrluUkfLyHIAPCJFCX00BPWDC+1w6c0eLkkr9SNFX0UFANdEP5aOBw+LGhb4utH/LmeVl3X3MmQ==", + "license": "MIT", + "peerDependencies": { + "react": ">=18.0.0", + "react-native": ">=0.75.0", + "react-native-nitro-modules": "*" + } + }, + "node_modules/react-native-nitro-modules": { + "version": "0.36.1", + "resolved": "https://registry.npmjs.org/react-native-nitro-modules/-/react-native-nitro-modules-0.36.1.tgz", + "integrity": "sha512-kBv/VvKqAmkXAvP1DxJMC9b/fRhh7JdSO4EUnPP46hJjrIFeFR8AwKm8mYaKZEuF014M/TVdv2vomVUW0umsQQ==", + "license": "MIT", + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, "node_modules/react-native-notify-kit": { "version": "10.4.7", "resolved": "https://registry.npmjs.org/react-native-notify-kit/-/react-native-notify-kit-10.4.7.tgz", diff --git a/package.json b/package.json index 82d7e59..04e2b3b 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,6 @@ "@react-native-community/cli-platform-android": "20.1.3", "@react-native-community/cli-platform-ios": "20.1.3", "@react-native-community/datetimepicker": "8.6.0", - "@react-native-community/geolocation": "3.4.0", "@react-native-community/netinfo": "11.5.2", "@react-native-firebase/analytics": "20.1.0", "@react-native-firebase/app": "20.1.0", @@ -49,6 +48,8 @@ "react-native-fast-image": "8.6.3", "react-native-file-viewer-turbo": "0.7.4", "react-native-geocoder": "0.5.0", + "react-native-nitro-geolocation": "1.4.2", + "react-native-nitro-modules": "0.36.1", "react-native-gesture-handler": "2.31.2", "react-native-image-picker": "7.2.3", "react-native-inappbrowser-reborn": "3.7.0", From eff05404cfcc31cc18be3004da7b862c22af2568 Mon Sep 17 00:00:00 2001 From: stelselim Date: Wed, 8 Jul 2026 11:11:28 +0200 Subject: [PATCH 2/4] chore: add previous patcheds to Android --- ...react-native-nitro-geolocation+1.4.2.patch | 315 ++++++++++++++++++ 1 file changed, 315 insertions(+) create mode 100644 patches/react-native-nitro-geolocation+1.4.2.patch diff --git a/patches/react-native-nitro-geolocation+1.4.2.patch b/patches/react-native-nitro-geolocation+1.4.2.patch new file mode 100644 index 0000000..ad03173 --- /dev/null +++ b/patches/react-native-nitro-geolocation+1.4.2.patch @@ -0,0 +1,315 @@ +diff --git a/node_modules/react-native-nitro-geolocation/android/src/main/java/com/margelo/nitro/nitrogeolocation/GetCurrentPosition.kt b/node_modules/react-native-nitro-geolocation/android/src/main/java/com/margelo/nitro/nitrogeolocation/GetCurrentPosition.kt +index e3124f0..527e0bb 100644 +--- a/node_modules/react-native-nitro-geolocation/android/src/main/java/com/margelo/nitro/nitrogeolocation/GetCurrentPosition.kt ++++ b/node_modules/react-native-nitro-geolocation/android/src/main/java/com/margelo/nitro/nitrogeolocation/GetCurrentPosition.kt +@@ -12,6 +12,15 @@ import android.os.Looper + import android.util.Log + import androidx.core.content.ContextCompat + import com.facebook.react.bridge.ReactApplicationContext ++import com.google.android.gms.common.ConnectionResult ++import com.google.android.gms.common.GoogleApiAvailability ++import com.google.android.gms.location.FusedLocationProviderClient ++import com.google.android.gms.location.LocationCallback ++import com.google.android.gms.location.LocationRequest ++import com.google.android.gms.location.LocationResult ++import com.google.android.gms.location.LocationServices ++import com.google.android.gms.location.Priority ++import com.google.android.gms.location.LocationAvailability + + class GetCurrentPosition(private val reactContext: ReactApplicationContext) { + +@@ -19,6 +28,121 @@ class GetCurrentPosition(private val reactContext: ReactApplicationContext) { + success: (position: CompatGeolocationResponse) -> Unit, + error: ((error: CompatGeolocationError) -> Unit)?, + options: CompatGeolocationOptions? ++ ) { ++ val opts = parseOptions(options) ++ ++ // Use FusedLocationProviderClient when Play Services are available ++ if (isGooglePlayServicesAvailable()) { ++ getCurrentPositionWithFused(success, error, opts) ++ return ++ } ++ ++ // Fallback to basic LocationManager ++ getCurrentPositionWithLocationManager(success, error, opts) ++ } ++ ++ // ===== Fused Location Provider (Play Services) Path ===== ++ ++ private fun getCurrentPositionWithFused( ++ success: (CompatGeolocationResponse) -> Unit, ++ error: ((CompatGeolocationError) -> Unit)?, ++ options: ParsedOptions ++ ) { ++ val fusedLocationClient = LocationServices.getFusedLocationProviderClient(reactContext) ++ ++ try { ++ fusedLocationClient.lastLocation ++ .addOnSuccessListener { location -> ++ if (location != null && isCachedLocationValid(location, options)) { ++ success(locationToPosition(location)) ++ } else { ++ requestFreshFusedLocation(fusedLocationClient, success, error, options) ++ } ++ } ++ .addOnFailureListener { ++ requestFreshFusedLocation(fusedLocationClient, success, error, options) ++ } ++ } catch (e: SecurityException) { ++ Log.e(TAG, "Security exception: ${e.message}") ++ error?.invoke( ++ createError(PERMISSION_DENIED, "Location permission denied: ${e.message}") ++ ) ++ } ++ } ++ ++ private fun requestFreshFusedLocation( ++ fusedLocationClient: FusedLocationProviderClient, ++ success: (CompatGeolocationResponse) -> Unit, ++ error: ((CompatGeolocationError) -> Unit)?, ++ options: ParsedOptions ++ ) { ++ val handler = Handler(Looper.getMainLooper()) ++ var isResolved = false ++ ++ val locationRequest = LocationRequest.Builder(0) ++ .setPriority( ++ if (options.androidAccuracy.mode == AndroidAccuracyMode.HIGH) ++ Priority.PRIORITY_HIGH_ACCURACY ++ else ++ Priority.PRIORITY_BALANCED_POWER_ACCURACY ++ ) ++ .setMaxUpdateAgeMillis(options.maximumAge.toLong().coerceAtLeast(0L)) ++ .setMinUpdateDistanceMeters(0f) ++ .setMaxUpdates(1) ++ .build() ++ ++ val locationCallback = object : LocationCallback() { ++ override fun onLocationResult(locationResult: LocationResult) { ++ val location = locationResult.lastLocation ++ ++ if (location == null) { ++ // Don't error on null location - wait for timeout or next callback ++ return ++ } ++ ++ if (!isResolved) { ++ isResolved = true ++ handler.removeCallbacksAndMessages(null) ++ fusedLocationClient.removeLocationUpdates(this) ++ success(locationToPosition(location)) ++ } ++ } ++ ++ override fun onLocationAvailability(locationAvailability: LocationAvailability) { ++ if (!locationAvailability.isLocationAvailable) { ++ // Don't error immediately - wait for timeout or a location update ++ return ++ } ++ } ++ } ++ ++ val timeoutRunnable = Runnable { ++ if (!isResolved) { ++ isResolved = true ++ fusedLocationClient.removeLocationUpdates(locationCallback) ++ error?.invoke(createError(TIMEOUT, "Location request timed out")) ++ } ++ } ++ ++ try { ++ fusedLocationClient.requestLocationUpdates( ++ locationRequest, ++ locationCallback, ++ Looper.getMainLooper() ++ ) ++ handler.postDelayed(timeoutRunnable, options.timeout.toLong()) ++ } catch (e: SecurityException) { ++ Log.e(TAG, "SecurityException in fused request: ${e.message}") ++ error?.invoke(createError(PERMISSION_DENIED, "Permission denied: ${e.message}")) ++ } ++ } ++ ++ // ===== Basic LocationManager Path (Fallback) ===== ++ ++ private fun getCurrentPositionWithLocationManager( ++ success: (CompatGeolocationResponse) -> Unit, ++ error: ((CompatGeolocationError) -> Unit)?, ++ options: ParsedOptions + ) { + val locationManager = + reactContext.getSystemService(Context.LOCATION_SERVICE) as? LocationManager +@@ -28,8 +152,7 @@ class GetCurrentPosition(private val reactContext: ReactApplicationContext) { + return + } + +- val opts = parseOptions(options) +- val provider = getValidProvider(locationManager, opts.androidAccuracy) ++ val provider = getValidProvider(locationManager, options.androidAccuracy) + + if (provider == null) { + Log.e(TAG, "No location provider available") +@@ -41,19 +164,19 @@ class GetCurrentPosition(private val reactContext: ReactApplicationContext) { + val lastKnownLocation = locationManager.getLastKnownLocation(provider) + + // Check if cached location is fresh enough +- if (lastKnownLocation != null && isCachedLocationValid(lastKnownLocation, opts)) { ++ if (lastKnownLocation != null && isCachedLocationValid(lastKnownLocation, options)) { + success(locationToPosition(lastKnownLocation)) + return + } + + // If maximumAge is Infinity and we have a last known location, use it +- if (lastKnownLocation != null && opts.maximumAge == Double.POSITIVE_INFINITY) { ++ if (lastKnownLocation != null && options.maximumAge == Double.POSITIVE_INFINITY) { + success(locationToPosition(lastKnownLocation)) + return + } + + // Request fresh location +- requestFreshLocation(locationManager, provider, opts, success, error, lastKnownLocation) ++ requestFreshLocation(locationManager, provider, options, success, error, lastKnownLocation) + } catch (e: SecurityException) { + Log.e(TAG, "Security exception: ${e.message}") + error?.invoke( +@@ -64,6 +187,11 @@ class GetCurrentPosition(private val reactContext: ReactApplicationContext) { + + // ===== Helper Functions ===== + ++ private fun isGooglePlayServicesAvailable(): Boolean { ++ return GoogleApiAvailability.getInstance() ++ .isGooglePlayServicesAvailable(reactContext.applicationContext) == ConnectionResult.SUCCESS ++ } ++ + private fun parseOptions(options: CompatGeolocationOptions?): ParsedOptions { + return ParsedOptions( + timeout = options?.timeout ?: DEFAULT_TIMEOUT, +diff --git a/node_modules/react-native-nitro-geolocation/android/src/main/java/com/margelo/nitro/nitrogeolocation/WatchPosition.kt b/node_modules/react-native-nitro-geolocation/android/src/main/java/com/margelo/nitro/nitrogeolocation/WatchPosition.kt +index e0cbe24..4f29d39 100644 +--- a/node_modules/react-native-nitro-geolocation/android/src/main/java/com/margelo/nitro/nitrogeolocation/WatchPosition.kt ++++ b/node_modules/react-native-nitro-geolocation/android/src/main/java/com/margelo/nitro/nitrogeolocation/WatchPosition.kt +@@ -11,6 +11,14 @@ import android.os.Looper + import android.util.Log + import androidx.core.content.ContextCompat + import com.facebook.react.bridge.ReactApplicationContext ++import com.google.android.gms.common.ConnectionResult ++import com.google.android.gms.common.GoogleApiAvailability ++import com.google.android.gms.location.FusedLocationProviderClient ++import com.google.android.gms.location.LocationCallback ++import com.google.android.gms.location.LocationRequest ++import com.google.android.gms.location.LocationResult ++import com.google.android.gms.location.LocationServices ++import com.google.android.gms.location.Priority + import java.util.concurrent.ConcurrentHashMap + import java.util.concurrent.atomic.AtomicInteger + +@@ -19,8 +27,11 @@ class WatchPosition(private val reactContext: ReactApplicationContext) { + private val watchCallbacks = ConcurrentHashMap() + private val watchIdGenerator = AtomicInteger(0) + private var locationListener: LocationListener? = null ++ private var fusedLocationCallback: LocationCallback? = null ++ private var fusedLocationClient: FusedLocationProviderClient? = null + private var watchedProvider: String? = null + private var currentOptions: CompatGeolocationOptions? = null ++ private var usingFusedProvider = false + + data class WatchCallback( + val success: (CompatGeolocationResponse) -> Unit, +@@ -54,18 +65,80 @@ class WatchPosition(private val reactContext: ReactApplicationContext) { + } + + fun stopObserving() { +- val locationManager = +- reactContext.getSystemService(Context.LOCATION_SERVICE) as? LocationManager +- +- locationListener?.let { listener -> locationManager?.removeUpdates(listener) } ++ if (usingFusedProvider) { ++ fusedLocationCallback?.let { callback -> ++ fusedLocationClient?.removeLocationUpdates(callback) ++ } ++ fusedLocationCallback = null ++ fusedLocationClient = null ++ } else { ++ val locationManager = ++ reactContext.getSystemService(Context.LOCATION_SERVICE) as? LocationManager ++ locationListener?.let { listener -> locationManager?.removeUpdates(listener) } ++ } + + locationListener = null + watchedProvider = null + currentOptions = null ++ usingFusedProvider = false + watchCallbacks.clear() + } + + private fun startObserving(options: CompatGeolocationOptions?) { ++ // Use FusedLocationProviderClient when Play Services are available ++ if (isGooglePlayServicesAvailable()) { ++ startObservingWithFused(options) ++ return ++ } ++ ++ // Fallback to basic LocationManager ++ startObservingWithLocationManager(options) ++ } ++ ++ private fun startObservingWithFused(options: CompatGeolocationOptions?) { ++ val opts = parseOptions(options) ++ val client = LocationServices.getFusedLocationProviderClient(reactContext) ++ ++ val locationRequest = LocationRequest.Builder( ++ if (opts.androidAccuracy.mode == AndroidAccuracyMode.HIGH) ++ Priority.PRIORITY_HIGH_ACCURACY ++ else ++ Priority.PRIORITY_BALANCED_POWER_ACCURACY, ++ opts.interval.toLong() ++ ) ++ .setMinUpdateDistanceMeters(opts.distanceFilter.toFloat()) ++ .build() ++ ++ val callback = object : LocationCallback() { ++ override fun onLocationResult(locationResult: LocationResult) { ++ val location = locationResult.lastLocation ?: return ++ val position = locationToPosition(location) ++ watchCallbacks.values.forEach { watchCallback -> watchCallback.success(position) } ++ } ++ } ++ ++ try { ++ client.requestLocationUpdates( ++ locationRequest, ++ callback, ++ Looper.getMainLooper() ++ ) ++ fusedLocationClient = client ++ fusedLocationCallback = callback ++ usingFusedProvider = true ++ currentOptions = options ++ } catch (e: SecurityException) { ++ Log.e(TAG, "SecurityException: ${e.message}") ++ emitErrorToAll( ++ createError( ++ GetCurrentPosition.PERMISSION_DENIED, ++ "Location permission denied: ${e.message}" ++ ) ++ ) ++ } ++ } ++ ++ private fun startObservingWithLocationManager(options: CompatGeolocationOptions?) { + val locationManager = + reactContext.getSystemService(Context.LOCATION_SERVICE) as? LocationManager + +@@ -143,6 +216,11 @@ class WatchPosition(private val reactContext: ReactApplicationContext) { + } + } + ++ private fun isGooglePlayServicesAvailable(): Boolean { ++ return GoogleApiAvailability.getInstance() ++ .isGooglePlayServicesAvailable(reactContext.applicationContext) == ConnectionResult.SUCCESS ++ } ++ + private fun emitErrorToAll(error: CompatGeolocationError) { + watchCallbacks.values.forEach { callback -> callback.error?.invoke(error) } + } From 8c8ed3bcc0753a19f2a53ecd76c6694058999a11 Mon Sep 17 00:00:00 2001 From: stelselim Date: Tue, 21 Jul 2026 10:19:24 +0200 Subject: [PATCH 3/4] chore: update react-native-nitro-geolocation to version 1.4.3 and remove obsolete patch --- package.json | 2 +- ...react-native-nitro-geolocation+1.4.2.patch | 315 ------------------ 2 files changed, 1 insertion(+), 316 deletions(-) delete mode 100644 patches/react-native-nitro-geolocation+1.4.2.patch diff --git a/package.json b/package.json index 04e2b3b..90a12b5 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,7 @@ "react-native-fast-image": "8.6.3", "react-native-file-viewer-turbo": "0.7.4", "react-native-geocoder": "0.5.0", - "react-native-nitro-geolocation": "1.4.2", + "react-native-nitro-geolocation": "1.4.3", "react-native-nitro-modules": "0.36.1", "react-native-gesture-handler": "2.31.2", "react-native-image-picker": "7.2.3", diff --git a/patches/react-native-nitro-geolocation+1.4.2.patch b/patches/react-native-nitro-geolocation+1.4.2.patch deleted file mode 100644 index ad03173..0000000 --- a/patches/react-native-nitro-geolocation+1.4.2.patch +++ /dev/null @@ -1,315 +0,0 @@ -diff --git a/node_modules/react-native-nitro-geolocation/android/src/main/java/com/margelo/nitro/nitrogeolocation/GetCurrentPosition.kt b/node_modules/react-native-nitro-geolocation/android/src/main/java/com/margelo/nitro/nitrogeolocation/GetCurrentPosition.kt -index e3124f0..527e0bb 100644 ---- a/node_modules/react-native-nitro-geolocation/android/src/main/java/com/margelo/nitro/nitrogeolocation/GetCurrentPosition.kt -+++ b/node_modules/react-native-nitro-geolocation/android/src/main/java/com/margelo/nitro/nitrogeolocation/GetCurrentPosition.kt -@@ -12,6 +12,15 @@ import android.os.Looper - import android.util.Log - import androidx.core.content.ContextCompat - import com.facebook.react.bridge.ReactApplicationContext -+import com.google.android.gms.common.ConnectionResult -+import com.google.android.gms.common.GoogleApiAvailability -+import com.google.android.gms.location.FusedLocationProviderClient -+import com.google.android.gms.location.LocationCallback -+import com.google.android.gms.location.LocationRequest -+import com.google.android.gms.location.LocationResult -+import com.google.android.gms.location.LocationServices -+import com.google.android.gms.location.Priority -+import com.google.android.gms.location.LocationAvailability - - class GetCurrentPosition(private val reactContext: ReactApplicationContext) { - -@@ -19,6 +28,121 @@ class GetCurrentPosition(private val reactContext: ReactApplicationContext) { - success: (position: CompatGeolocationResponse) -> Unit, - error: ((error: CompatGeolocationError) -> Unit)?, - options: CompatGeolocationOptions? -+ ) { -+ val opts = parseOptions(options) -+ -+ // Use FusedLocationProviderClient when Play Services are available -+ if (isGooglePlayServicesAvailable()) { -+ getCurrentPositionWithFused(success, error, opts) -+ return -+ } -+ -+ // Fallback to basic LocationManager -+ getCurrentPositionWithLocationManager(success, error, opts) -+ } -+ -+ // ===== Fused Location Provider (Play Services) Path ===== -+ -+ private fun getCurrentPositionWithFused( -+ success: (CompatGeolocationResponse) -> Unit, -+ error: ((CompatGeolocationError) -> Unit)?, -+ options: ParsedOptions -+ ) { -+ val fusedLocationClient = LocationServices.getFusedLocationProviderClient(reactContext) -+ -+ try { -+ fusedLocationClient.lastLocation -+ .addOnSuccessListener { location -> -+ if (location != null && isCachedLocationValid(location, options)) { -+ success(locationToPosition(location)) -+ } else { -+ requestFreshFusedLocation(fusedLocationClient, success, error, options) -+ } -+ } -+ .addOnFailureListener { -+ requestFreshFusedLocation(fusedLocationClient, success, error, options) -+ } -+ } catch (e: SecurityException) { -+ Log.e(TAG, "Security exception: ${e.message}") -+ error?.invoke( -+ createError(PERMISSION_DENIED, "Location permission denied: ${e.message}") -+ ) -+ } -+ } -+ -+ private fun requestFreshFusedLocation( -+ fusedLocationClient: FusedLocationProviderClient, -+ success: (CompatGeolocationResponse) -> Unit, -+ error: ((CompatGeolocationError) -> Unit)?, -+ options: ParsedOptions -+ ) { -+ val handler = Handler(Looper.getMainLooper()) -+ var isResolved = false -+ -+ val locationRequest = LocationRequest.Builder(0) -+ .setPriority( -+ if (options.androidAccuracy.mode == AndroidAccuracyMode.HIGH) -+ Priority.PRIORITY_HIGH_ACCURACY -+ else -+ Priority.PRIORITY_BALANCED_POWER_ACCURACY -+ ) -+ .setMaxUpdateAgeMillis(options.maximumAge.toLong().coerceAtLeast(0L)) -+ .setMinUpdateDistanceMeters(0f) -+ .setMaxUpdates(1) -+ .build() -+ -+ val locationCallback = object : LocationCallback() { -+ override fun onLocationResult(locationResult: LocationResult) { -+ val location = locationResult.lastLocation -+ -+ if (location == null) { -+ // Don't error on null location - wait for timeout or next callback -+ return -+ } -+ -+ if (!isResolved) { -+ isResolved = true -+ handler.removeCallbacksAndMessages(null) -+ fusedLocationClient.removeLocationUpdates(this) -+ success(locationToPosition(location)) -+ } -+ } -+ -+ override fun onLocationAvailability(locationAvailability: LocationAvailability) { -+ if (!locationAvailability.isLocationAvailable) { -+ // Don't error immediately - wait for timeout or a location update -+ return -+ } -+ } -+ } -+ -+ val timeoutRunnable = Runnable { -+ if (!isResolved) { -+ isResolved = true -+ fusedLocationClient.removeLocationUpdates(locationCallback) -+ error?.invoke(createError(TIMEOUT, "Location request timed out")) -+ } -+ } -+ -+ try { -+ fusedLocationClient.requestLocationUpdates( -+ locationRequest, -+ locationCallback, -+ Looper.getMainLooper() -+ ) -+ handler.postDelayed(timeoutRunnable, options.timeout.toLong()) -+ } catch (e: SecurityException) { -+ Log.e(TAG, "SecurityException in fused request: ${e.message}") -+ error?.invoke(createError(PERMISSION_DENIED, "Permission denied: ${e.message}")) -+ } -+ } -+ -+ // ===== Basic LocationManager Path (Fallback) ===== -+ -+ private fun getCurrentPositionWithLocationManager( -+ success: (CompatGeolocationResponse) -> Unit, -+ error: ((CompatGeolocationError) -> Unit)?, -+ options: ParsedOptions - ) { - val locationManager = - reactContext.getSystemService(Context.LOCATION_SERVICE) as? LocationManager -@@ -28,8 +152,7 @@ class GetCurrentPosition(private val reactContext: ReactApplicationContext) { - return - } - -- val opts = parseOptions(options) -- val provider = getValidProvider(locationManager, opts.androidAccuracy) -+ val provider = getValidProvider(locationManager, options.androidAccuracy) - - if (provider == null) { - Log.e(TAG, "No location provider available") -@@ -41,19 +164,19 @@ class GetCurrentPosition(private val reactContext: ReactApplicationContext) { - val lastKnownLocation = locationManager.getLastKnownLocation(provider) - - // Check if cached location is fresh enough -- if (lastKnownLocation != null && isCachedLocationValid(lastKnownLocation, opts)) { -+ if (lastKnownLocation != null && isCachedLocationValid(lastKnownLocation, options)) { - success(locationToPosition(lastKnownLocation)) - return - } - - // If maximumAge is Infinity and we have a last known location, use it -- if (lastKnownLocation != null && opts.maximumAge == Double.POSITIVE_INFINITY) { -+ if (lastKnownLocation != null && options.maximumAge == Double.POSITIVE_INFINITY) { - success(locationToPosition(lastKnownLocation)) - return - } - - // Request fresh location -- requestFreshLocation(locationManager, provider, opts, success, error, lastKnownLocation) -+ requestFreshLocation(locationManager, provider, options, success, error, lastKnownLocation) - } catch (e: SecurityException) { - Log.e(TAG, "Security exception: ${e.message}") - error?.invoke( -@@ -64,6 +187,11 @@ class GetCurrentPosition(private val reactContext: ReactApplicationContext) { - - // ===== Helper Functions ===== - -+ private fun isGooglePlayServicesAvailable(): Boolean { -+ return GoogleApiAvailability.getInstance() -+ .isGooglePlayServicesAvailable(reactContext.applicationContext) == ConnectionResult.SUCCESS -+ } -+ - private fun parseOptions(options: CompatGeolocationOptions?): ParsedOptions { - return ParsedOptions( - timeout = options?.timeout ?: DEFAULT_TIMEOUT, -diff --git a/node_modules/react-native-nitro-geolocation/android/src/main/java/com/margelo/nitro/nitrogeolocation/WatchPosition.kt b/node_modules/react-native-nitro-geolocation/android/src/main/java/com/margelo/nitro/nitrogeolocation/WatchPosition.kt -index e0cbe24..4f29d39 100644 ---- a/node_modules/react-native-nitro-geolocation/android/src/main/java/com/margelo/nitro/nitrogeolocation/WatchPosition.kt -+++ b/node_modules/react-native-nitro-geolocation/android/src/main/java/com/margelo/nitro/nitrogeolocation/WatchPosition.kt -@@ -11,6 +11,14 @@ import android.os.Looper - import android.util.Log - import androidx.core.content.ContextCompat - import com.facebook.react.bridge.ReactApplicationContext -+import com.google.android.gms.common.ConnectionResult -+import com.google.android.gms.common.GoogleApiAvailability -+import com.google.android.gms.location.FusedLocationProviderClient -+import com.google.android.gms.location.LocationCallback -+import com.google.android.gms.location.LocationRequest -+import com.google.android.gms.location.LocationResult -+import com.google.android.gms.location.LocationServices -+import com.google.android.gms.location.Priority - import java.util.concurrent.ConcurrentHashMap - import java.util.concurrent.atomic.AtomicInteger - -@@ -19,8 +27,11 @@ class WatchPosition(private val reactContext: ReactApplicationContext) { - private val watchCallbacks = ConcurrentHashMap() - private val watchIdGenerator = AtomicInteger(0) - private var locationListener: LocationListener? = null -+ private var fusedLocationCallback: LocationCallback? = null -+ private var fusedLocationClient: FusedLocationProviderClient? = null - private var watchedProvider: String? = null - private var currentOptions: CompatGeolocationOptions? = null -+ private var usingFusedProvider = false - - data class WatchCallback( - val success: (CompatGeolocationResponse) -> Unit, -@@ -54,18 +65,80 @@ class WatchPosition(private val reactContext: ReactApplicationContext) { - } - - fun stopObserving() { -- val locationManager = -- reactContext.getSystemService(Context.LOCATION_SERVICE) as? LocationManager -- -- locationListener?.let { listener -> locationManager?.removeUpdates(listener) } -+ if (usingFusedProvider) { -+ fusedLocationCallback?.let { callback -> -+ fusedLocationClient?.removeLocationUpdates(callback) -+ } -+ fusedLocationCallback = null -+ fusedLocationClient = null -+ } else { -+ val locationManager = -+ reactContext.getSystemService(Context.LOCATION_SERVICE) as? LocationManager -+ locationListener?.let { listener -> locationManager?.removeUpdates(listener) } -+ } - - locationListener = null - watchedProvider = null - currentOptions = null -+ usingFusedProvider = false - watchCallbacks.clear() - } - - private fun startObserving(options: CompatGeolocationOptions?) { -+ // Use FusedLocationProviderClient when Play Services are available -+ if (isGooglePlayServicesAvailable()) { -+ startObservingWithFused(options) -+ return -+ } -+ -+ // Fallback to basic LocationManager -+ startObservingWithLocationManager(options) -+ } -+ -+ private fun startObservingWithFused(options: CompatGeolocationOptions?) { -+ val opts = parseOptions(options) -+ val client = LocationServices.getFusedLocationProviderClient(reactContext) -+ -+ val locationRequest = LocationRequest.Builder( -+ if (opts.androidAccuracy.mode == AndroidAccuracyMode.HIGH) -+ Priority.PRIORITY_HIGH_ACCURACY -+ else -+ Priority.PRIORITY_BALANCED_POWER_ACCURACY, -+ opts.interval.toLong() -+ ) -+ .setMinUpdateDistanceMeters(opts.distanceFilter.toFloat()) -+ .build() -+ -+ val callback = object : LocationCallback() { -+ override fun onLocationResult(locationResult: LocationResult) { -+ val location = locationResult.lastLocation ?: return -+ val position = locationToPosition(location) -+ watchCallbacks.values.forEach { watchCallback -> watchCallback.success(position) } -+ } -+ } -+ -+ try { -+ client.requestLocationUpdates( -+ locationRequest, -+ callback, -+ Looper.getMainLooper() -+ ) -+ fusedLocationClient = client -+ fusedLocationCallback = callback -+ usingFusedProvider = true -+ currentOptions = options -+ } catch (e: SecurityException) { -+ Log.e(TAG, "SecurityException: ${e.message}") -+ emitErrorToAll( -+ createError( -+ GetCurrentPosition.PERMISSION_DENIED, -+ "Location permission denied: ${e.message}" -+ ) -+ ) -+ } -+ } -+ -+ private fun startObservingWithLocationManager(options: CompatGeolocationOptions?) { - val locationManager = - reactContext.getSystemService(Context.LOCATION_SERVICE) as? LocationManager - -@@ -143,6 +216,11 @@ class WatchPosition(private val reactContext: ReactApplicationContext) { - } - } - -+ private fun isGooglePlayServicesAvailable(): Boolean { -+ return GoogleApiAvailability.getInstance() -+ .isGooglePlayServicesAvailable(reactContext.applicationContext) == ConnectionResult.SUCCESS -+ } -+ - private fun emitErrorToAll(error: CompatGeolocationError) { - watchCallbacks.values.forEach { callback -> callback.error?.invoke(error) } - } From 6f0e1fc987800e49c18abd430c62fda389914352 Mon Sep 17 00:00:00 2001 From: stelselim Date: Tue, 21 Jul 2026 15:28:47 +0200 Subject: [PATCH 4/4] chore: update react-native-nitro-geolocation to version 1.4.3 --- ios/Podfile.lock | 4 ++-- package-lock.json | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 3c86669..0fc165b 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -198,7 +198,7 @@ PODS: - nanopb/encode (= 2.30910.0) - nanopb/decode (2.30910.0) - nanopb/encode (2.30910.0) - - NitroGeolocation (1.4.2): + - NitroGeolocation (1.4.3): - hermes-engine - NitroModules - RCTRequired @@ -3405,7 +3405,7 @@ SPEC CHECKSUMS: MendixNative: 7e5ed081dfc0cbe76d8d81b76efafba9f3dd6a4e MultiplatformBleAdapter: b1fddd0d499b96b607e00f0faa8e60648343dc1d nanopb: 438bc412db1928dac798aa6fd75726007be04262 - NitroGeolocation: 647199e12e95fa2e4030e872ede9a3598fd379d3 + NitroGeolocation: 688a2b889656060dafe9d83dc62fc0513b954a97 NitroModules: 4d19ff59969b2c67a19175fb17b66917af6708e0 op-sqlite: e9ef65bcf95a97863874cee87841425bb71c8396 OpenSSL-Universal: ecee7b138fa75a74ecf00d7ffd248fb584739b9e diff --git a/package-lock.json b/package-lock.json index 06357f2..2be9bdd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -49,7 +49,7 @@ "react-native-linear-gradient": "2.8.3", "react-native-localize": "3.7.0", "react-native-maps": "1.20.1", - "react-native-nitro-geolocation": "1.4.2", + "react-native-nitro-geolocation": "1.4.3", "react-native-nitro-modules": "0.36.1", "react-native-notify-kit": "10.4.7", "react-native-pager-view": "6.9.1", @@ -8009,9 +8009,9 @@ } }, "node_modules/react-native-nitro-geolocation": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/react-native-nitro-geolocation/-/react-native-nitro-geolocation-1.4.2.tgz", - "integrity": "sha512-woUfdQgzWeTFrluUkfLyHIAPCJFCX00BPWDC+1w6c0eLkkr9SNFX0UFANdEP5aOBw+LGhb4utH/LmeVl3X3MmQ==", + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/react-native-nitro-geolocation/-/react-native-nitro-geolocation-1.4.3.tgz", + "integrity": "sha512-8r09M6fjf7krCXRuF3RskuJpcxE8dvwQtRJnlrk+U3tO4v+ltyXhRRGmAMlO2Y/PEv1II8sgnIgil58RaLPI4A==", "license": "MIT", "peerDependencies": { "react": ">=18.0.0",