@inoxialabs/react-native-nitro-location-geocoder 0.1.0 → 1.0.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.
@@ -1,6 +1,6 @@
1
- #include <jni.h>
2
- #include "NitroLocationGeocoderOnLoad.hpp"
3
-
4
- JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void*) {
5
- return margelo::nitro::locationgeocoder::initialize(vm);
6
- }
1
+ #include <jni.h>
2
+ #include "NitroLocationGeocoderOnLoad.hpp"
3
+
4
+ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void*) {
5
+ return margelo::nitro::locationgeocoder::initialize(vm);
6
+ }
@@ -1,95 +1,123 @@
1
- package com.margelo.nitro.locationgeocoder
2
-
3
- import android.location.Address
4
- import android.location.Geocoder
5
- import android.os.Build
6
- import androidx.annotation.RequiresApi
7
- import com.facebook.proguard.annotations.DoNotStrip
8
- import com.margelo.nitro.NitroModules
9
- import com.margelo.nitro.core.Promise
10
- import java.util.Locale
11
- import java.util.concurrent.CountDownLatch
12
- import java.util.concurrent.TimeUnit
13
- import java.util.concurrent.atomic.AtomicReference
14
-
15
- @DoNotStrip
16
- class HybridLocationGeocoder : HybridLocationGeocoderSpec() {
17
-
18
- private val context
19
- get() = NitroModules.applicationContext
20
- ?: throw RuntimeException("Application context not available")
21
-
22
- private fun getLocale(locale: String): Locale {
23
- val normalized = locale.trim()
24
- if (normalized.isEmpty()) return Locale.getDefault()
25
- return Locale.forLanguageTag(normalized)
26
- }
27
-
28
- private fun createGeocoder(locale: String): Geocoder {
29
- return Geocoder(context, getLocale(locale))
30
- }
31
-
32
- override fun reverseGeocode(
33
- latitude: Double,
34
- longitude: Double,
35
- locale: String
36
- ): Promise<LocationGeocoderResult> {
37
- return Promise.async {
38
- if (!Geocoder.isPresent()) {
39
- throw Exception("UNAVAILABLE")
40
- }
41
-
42
- val address = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
43
- reverseGeocodeApi33(latitude, longitude, locale)
44
- } else {
45
- reverseGeocodeLegacy(latitude, longitude, locale)
46
- } ?: throw Exception("NO_RESULTS")
47
-
48
- mapAddress(address)
49
- }
50
- }
51
-
52
- @RequiresApi(Build.VERSION_CODES.TIRAMISU)
53
- private fun reverseGeocodeApi33(latitude: Double, longitude: Double, locale: String): Address? {
54
- val geocoder = createGeocoder(locale)
55
- val resultRef = AtomicReference<Address?>()
56
- val errorRef = AtomicReference<Exception?>()
57
- val latch = CountDownLatch(1)
58
-
59
- geocoder.getFromLocation(latitude, longitude, 1, object : Geocoder.GeocodeListener {
60
- override fun onGeocode(addresses: MutableList<Address>) {
61
- resultRef.set(addresses.firstOrNull())
62
- latch.countDown()
63
- }
64
-
65
- override fun onError(errorMessage: String?) {
66
- errorRef.set(Exception(errorMessage ?: "GEOCODER_ERROR"))
67
- latch.countDown()
68
- }
69
- })
70
-
71
- val completed = latch.await(15, TimeUnit.SECONDS)
72
- if (!completed) {
73
- throw Exception("GEOCODER_TIMEOUT")
74
- }
75
-
76
- errorRef.get()?.let { throw it }
77
- return resultRef.get()
78
- }
79
-
80
- @Suppress("DEPRECATION")
81
- private fun reverseGeocodeLegacy(latitude: Double, longitude: Double, locale: String): Address? {
82
- return createGeocoder(locale).getFromLocation(latitude, longitude, 1)?.firstOrNull()
83
- }
84
-
85
- private fun mapAddress(address: Address): LocationGeocoderResult {
86
- return LocationGeocoderResult(
87
- countryCode = address.countryCode ?: "",
88
- country = address.countryName ?: "",
89
- locality = address.locality ?: "",
90
- administrativeArea = address.adminArea ?: "",
91
- subAdministrativeArea = address.subAdminArea ?: "",
92
- subLocality = address.subLocality ?: ""
93
- )
94
- }
95
- }
1
+ package com.margelo.nitro.locationgeocoder
2
+
3
+ import android.location.Address
4
+ import android.location.Geocoder
5
+ import android.os.Build
6
+ import androidx.annotation.RequiresApi
7
+ import com.facebook.proguard.annotations.DoNotStrip
8
+ import com.margelo.nitro.NitroModules
9
+ import com.margelo.nitro.core.Promise
10
+ import java.io.IOException
11
+ import java.util.Locale
12
+ import java.util.concurrent.CountDownLatch
13
+ import java.util.concurrent.TimeUnit
14
+ import java.util.concurrent.atomic.AtomicReference
15
+
16
+ @DoNotStrip
17
+ class HybridLocationGeocoder : HybridLocationGeocoderSpec() {
18
+
19
+ private val context
20
+ get() = NitroModules.applicationContext
21
+ ?: throw RuntimeException("Application context not available")
22
+
23
+ private fun getLocale(locale: String): Locale {
24
+ val normalized = locale.trim()
25
+ if (normalized.isEmpty()) return Locale.getDefault()
26
+ return Locale.forLanguageTag(normalized)
27
+ }
28
+
29
+ private fun createGeocoder(locale: String): Geocoder {
30
+ return Geocoder(context, getLocale(locale))
31
+ }
32
+
33
+ private fun isValidCoordinate(latitude: Double, longitude: Double): Boolean {
34
+ return latitude.isFinite() &&
35
+ longitude.isFinite() &&
36
+ latitude >= -90.0 &&
37
+ latitude <= 90.0 &&
38
+ longitude >= -180.0 &&
39
+ longitude <= 180.0
40
+ }
41
+
42
+ private fun geocoderFailed(message: String?): Exception {
43
+ val normalized = message?.trim()?.takeIf { it.isNotEmpty() }
44
+ return Exception(if (normalized == null) "GEOCODER_FAILED" else "GEOCODER_FAILED: $normalized")
45
+ }
46
+
47
+ override fun reverseGeocode(
48
+ latitude: Double,
49
+ longitude: Double,
50
+ locale: String
51
+ ): Promise<LocationGeocoderResult> {
52
+ return Promise.async {
53
+ if (!isValidCoordinate(latitude, longitude)) {
54
+ throw Exception("INVALID_COORDINATES")
55
+ }
56
+
57
+ if (!Geocoder.isPresent()) {
58
+ throw Exception("UNAVAILABLE")
59
+ }
60
+
61
+ val address = try {
62
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
63
+ reverseGeocodeApi33(latitude, longitude, locale)
64
+ } else {
65
+ reverseGeocodeLegacy(latitude, longitude, locale)
66
+ }
67
+ } catch (error: IllegalArgumentException) {
68
+ throw Exception("INVALID_COORDINATES")
69
+ } catch (error: IOException) {
70
+ throw geocoderFailed(error.message)
71
+ } catch (error: InterruptedException) {
72
+ Thread.currentThread().interrupt()
73
+ throw geocoderFailed(error.message)
74
+ } ?: throw Exception("NO_RESULTS")
75
+
76
+ mapAddress(address)
77
+ }
78
+ }
79
+
80
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
81
+ private fun reverseGeocodeApi33(latitude: Double, longitude: Double, locale: String): Address? {
82
+ val geocoder = createGeocoder(locale)
83
+ val resultRef = AtomicReference<Address?>()
84
+ val errorRef = AtomicReference<Exception?>()
85
+ val latch = CountDownLatch(1)
86
+
87
+ geocoder.getFromLocation(latitude, longitude, 1, object : Geocoder.GeocodeListener {
88
+ override fun onGeocode(addresses: MutableList<Address>) {
89
+ resultRef.set(addresses.firstOrNull())
90
+ latch.countDown()
91
+ }
92
+
93
+ override fun onError(errorMessage: String?) {
94
+ errorRef.set(geocoderFailed(errorMessage))
95
+ latch.countDown()
96
+ }
97
+ })
98
+
99
+ val completed = latch.await(10, TimeUnit.SECONDS)
100
+ if (!completed) {
101
+ throw Exception("GEOCODER_TIMEOUT")
102
+ }
103
+
104
+ errorRef.get()?.let { throw it }
105
+ return resultRef.get()
106
+ }
107
+
108
+ @Suppress("DEPRECATION")
109
+ private fun reverseGeocodeLegacy(latitude: Double, longitude: Double, locale: String): Address? {
110
+ return createGeocoder(locale).getFromLocation(latitude, longitude, 1)?.firstOrNull()
111
+ }
112
+
113
+ private fun mapAddress(address: Address): LocationGeocoderResult {
114
+ return LocationGeocoderResult(
115
+ countryCode = address.countryCode ?: "",
116
+ country = address.countryName ?: "",
117
+ locality = address.locality ?: "",
118
+ administrativeArea = address.adminArea ?: "",
119
+ subAdministrativeArea = address.subAdminArea ?: "",
120
+ subLocality = address.subLocality ?: ""
121
+ )
122
+ }
123
+ }
@@ -1,18 +1,18 @@
1
- package com.margelo.nitro.locationgeocoder
2
-
3
- import com.facebook.react.BaseReactPackage
4
- import com.facebook.react.bridge.NativeModule
5
- import com.facebook.react.bridge.ReactApplicationContext
6
- import com.facebook.react.module.model.ReactModuleInfoProvider
7
-
8
- class NitroLocationGeocoderPackage : BaseReactPackage() {
9
- override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? = null
10
-
11
- override fun getReactModuleInfoProvider(): ReactModuleInfoProvider = ReactModuleInfoProvider { HashMap() }
12
-
13
- companion object {
14
- init {
15
- NitroLocationGeocoderOnLoad.initializeNative()
16
- }
17
- }
18
- }
1
+ package com.margelo.nitro.locationgeocoder
2
+
3
+ import com.facebook.react.BaseReactPackage
4
+ import com.facebook.react.bridge.NativeModule
5
+ import com.facebook.react.bridge.ReactApplicationContext
6
+ import com.facebook.react.module.model.ReactModuleInfoProvider
7
+
8
+ class NitroLocationGeocoderPackage : BaseReactPackage() {
9
+ override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? = null
10
+
11
+ override fun getReactModuleInfoProvider(): ReactModuleInfoProvider = ReactModuleInfoProvider { HashMap() }
12
+
13
+ companion object {
14
+ init {
15
+ NitroLocationGeocoderOnLoad.initializeNative()
16
+ }
17
+ }
18
+ }
package/ios/Bridge.h CHANGED
@@ -1 +1 @@
1
- #pragma once
1
+ #pragma once
@@ -1,49 +1,92 @@
1
- import Foundation
2
- import CoreLocation
3
- import NitroModules
4
-
5
- class HybridLocationGeocoder: HybridLocationGeocoderSpec {
6
-
7
- private let geocoder = CLGeocoder()
8
-
9
- private func preferredLocale(_ locale: String) -> Locale? {
10
- let normalized = locale.trimmingCharacters(in: .whitespacesAndNewlines)
11
- if normalized.isEmpty {
12
- return nil
13
- }
14
- return Locale(identifier: normalized)
15
- }
16
-
17
- func reverseGeocode(latitude: Double, longitude: Double, locale: String) throws -> Promise<LocationGeocoderResult> {
18
- let promise = Promise<LocationGeocoderResult>()
19
-
20
- if geocoder.isGeocoding {
21
- geocoder.cancelGeocode()
22
- }
23
-
24
- let location = CLLocation(latitude: latitude, longitude: longitude)
25
- geocoder.reverseGeocodeLocation(location, preferredLocale: preferredLocale(locale)) { placemarks, error in
26
- if let error = error {
27
- promise.reject(withError: RuntimeError.error(withMessage: "GEOCODER_FAILED: \(error.localizedDescription)"))
28
- return
29
- }
30
-
31
- guard let placemark = placemarks?.first else {
32
- promise.reject(withError: RuntimeError.error(withMessage: "NO_RESULTS"))
33
- return
34
- }
35
-
36
- let result = LocationGeocoderResult(
37
- countryCode: placemark.isoCountryCode ?? "",
38
- country: placemark.country ?? "",
39
- locality: placemark.locality ?? "",
40
- administrativeArea: placemark.administrativeArea ?? "",
41
- subAdministrativeArea: placemark.subAdministrativeArea ?? "",
42
- subLocality: placemark.subLocality ?? ""
43
- )
44
- promise.resolve(withResult: result)
45
- }
46
-
47
- return promise
48
- }
49
- }
1
+ import Foundation
2
+ import CoreLocation
3
+ import NitroModules
4
+
5
+ class HybridLocationGeocoder: HybridLocationGeocoderSpec {
6
+
7
+ private let timeoutSeconds: TimeInterval = 10
8
+
9
+ private func preferredLocale(_ locale: String) -> Locale? {
10
+ let normalized = locale.trimmingCharacters(in: .whitespacesAndNewlines)
11
+ if normalized.isEmpty {
12
+ return nil
13
+ }
14
+ return Locale(identifier: normalized)
15
+ }
16
+
17
+ private func isValidCoordinate(latitude: Double, longitude: Double) -> Bool {
18
+ latitude.isFinite &&
19
+ longitude.isFinite &&
20
+ latitude >= -90 &&
21
+ latitude <= 90 &&
22
+ longitude >= -180 &&
23
+ longitude <= 180
24
+ }
25
+
26
+ func reverseGeocode(latitude: Double, longitude: Double, locale: String) throws -> Promise<LocationGeocoderResult> {
27
+ let promise = Promise<LocationGeocoderResult>()
28
+
29
+ guard isValidCoordinate(latitude: latitude, longitude: longitude) else {
30
+ promise.reject(withError: RuntimeError.error(withMessage: "INVALID_COORDINATES"))
31
+ return promise
32
+ }
33
+
34
+ let geocoder = CLGeocoder()
35
+ var didComplete = false
36
+ var timeoutWorkItem: DispatchWorkItem?
37
+
38
+ func reject(_ message: String) {
39
+ if didComplete {
40
+ return
41
+ }
42
+ didComplete = true
43
+ timeoutWorkItem?.cancel()
44
+ promise.reject(withError: RuntimeError.error(withMessage: message))
45
+ }
46
+
47
+ func resolve(_ result: LocationGeocoderResult) {
48
+ if didComplete {
49
+ return
50
+ }
51
+ didComplete = true
52
+ timeoutWorkItem?.cancel()
53
+ promise.resolve(withResult: result)
54
+ }
55
+
56
+ timeoutWorkItem = DispatchWorkItem {
57
+ reject("GEOCODER_TIMEOUT")
58
+ geocoder.cancelGeocode()
59
+ }
60
+ if let timeoutWorkItem = timeoutWorkItem {
61
+ DispatchQueue.main.asyncAfter(
62
+ deadline: .now() + .milliseconds(Int(timeoutSeconds * 1000)),
63
+ execute: timeoutWorkItem
64
+ )
65
+ }
66
+
67
+ let location = CLLocation(latitude: latitude, longitude: longitude)
68
+ geocoder.reverseGeocodeLocation(location, preferredLocale: preferredLocale(locale)) { placemarks, error in
69
+ if let error = error {
70
+ reject("GEOCODER_FAILED: \(error.localizedDescription)")
71
+ return
72
+ }
73
+
74
+ guard let placemark = placemarks?.first else {
75
+ reject("NO_RESULTS")
76
+ return
77
+ }
78
+
79
+ let result = LocationGeocoderResult(
80
+ countryCode: placemark.isoCountryCode ?? "",
81
+ country: placemark.country ?? "",
82
+ locality: placemark.locality ?? "",
83
+ administrativeArea: placemark.administrativeArea ?? "",
84
+ subAdministrativeArea: placemark.subAdministrativeArea ?? "",
85
+ subLocality: placemark.subLocality ?? ""
86
+ )
87
+ resolve(result)
88
+ }
89
+
90
+ return promise
91
+ }
92
+ }
package/nitro.json CHANGED
@@ -1,24 +1,24 @@
1
- {
2
- "$schema": "https://nitro.margelo.com/nitro.schema.json",
3
- "cxxNamespace": [
4
- "locationgeocoder"
5
- ],
6
- "ios": {
7
- "iosModuleName": "NitroLocationGeocoder"
8
- },
9
- "android": {
10
- "androidNamespace": [
11
- "locationgeocoder"
12
- ],
13
- "androidCxxLibName": "NitroLocationGeocoder"
14
- },
15
- "autolinking": {
16
- "LocationGeocoder": {
17
- "swift": "HybridLocationGeocoder",
18
- "kotlin": "HybridLocationGeocoder"
19
- }
20
- },
21
- "ignorePaths": [
22
- "**/node_modules"
23
- ]
24
- }
1
+ {
2
+ "$schema": "https://nitro.margelo.com/nitro.schema.json",
3
+ "cxxNamespace": [
4
+ "locationgeocoder"
5
+ ],
6
+ "ios": {
7
+ "iosModuleName": "NitroLocationGeocoder"
8
+ },
9
+ "android": {
10
+ "androidNamespace": [
11
+ "locationgeocoder"
12
+ ],
13
+ "androidCxxLibName": "NitroLocationGeocoder"
14
+ },
15
+ "autolinking": {
16
+ "LocationGeocoder": {
17
+ "swift": "HybridLocationGeocoder",
18
+ "kotlin": "HybridLocationGeocoder"
19
+ }
20
+ },
21
+ "ignorePaths": [
22
+ "**/node_modules"
23
+ ]
24
+ }
@@ -27,9 +27,9 @@ int initialize(JavaVM* vm) {
27
27
  }
28
28
 
29
29
  struct JHybridLocationGeocoderSpecImpl: public jni::JavaClass<JHybridLocationGeocoderSpecImpl, JHybridLocationGeocoderSpec::JavaPart> {
30
- static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/locationgeocoder/HybridLocationGeocoder;";
30
+ static constexpr auto kJavaDescriptor = "Lcom/margelo/nitro/locationgeocoder/HybridLocationGeocoder;";
31
31
  static std::shared_ptr<JHybridLocationGeocoderSpec> create() {
32
- static auto constructorFn = javaClassStatic()->getConstructor<JHybridLocationGeocoderSpecImpl::javaobject()>();
32
+ static const auto constructorFn = javaClassStatic()->getConstructor<JHybridLocationGeocoderSpecImpl::javaobject()>();
33
33
  jni::local_ref<JHybridLocationGeocoderSpec::JavaPart> javaPart = javaClassStatic()->newObject(constructorFn);
34
34
  return javaPart->getJHybridLocationGeocoderSpec();
35
35
  }
@@ -21,11 +21,11 @@ namespace margelo::nitro::locationgeocoder {
21
21
  class JHybridLocationGeocoderSpec: public virtual HybridLocationGeocoderSpec, public virtual JHybridObject {
22
22
  public:
23
23
  struct JavaPart: public jni::JavaClass<JavaPart, JHybridObject::JavaPart> {
24
- static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/locationgeocoder/HybridLocationGeocoderSpec;";
24
+ static constexpr auto kJavaDescriptor = "Lcom/margelo/nitro/locationgeocoder/HybridLocationGeocoderSpec;";
25
25
  std::shared_ptr<JHybridLocationGeocoderSpec> getJHybridLocationGeocoderSpec();
26
26
  };
27
27
  struct CxxPart: public jni::HybridClass<CxxPart, JHybridObject::CxxPart> {
28
- static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/locationgeocoder/HybridLocationGeocoderSpec$CxxPart;";
28
+ static constexpr auto kJavaDescriptor = "Lcom/margelo/nitro/locationgeocoder/HybridLocationGeocoderSpec$CxxPart;";
29
29
  static jni::local_ref<jhybriddata> initHybrid(jni::alias_ref<jhybridobject> jThis);
30
30
  static void registerNatives();
31
31
  using HybridBase::HybridBase;
@@ -17,11 +17,11 @@ namespace margelo::nitro::locationgeocoder {
17
17
  using namespace facebook;
18
18
 
19
19
  /**
20
- * The C++ JNI bridge between the C++ struct "LocationGeocoderResult" and the the Kotlin data class "LocationGeocoderResult".
20
+ * The C++ JNI bridge between the C++ struct "LocationGeocoderResult" and the Kotlin data class "LocationGeocoderResult".
21
21
  */
22
22
  struct JLocationGeocoderResult final: public jni::JavaClass<JLocationGeocoderResult> {
23
23
  public:
24
- static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/locationgeocoder/LocationGeocoderResult;";
24
+ static constexpr auto kJavaDescriptor = "Lcom/margelo/nitro/locationgeocoder/LocationGeocoderResult;";
25
25
 
26
26
  public:
27
27
  /**
@@ -9,6 +9,7 @@ package com.margelo.nitro.locationgeocoder
9
9
 
10
10
  import androidx.annotation.Keep
11
11
  import com.facebook.proguard.annotations.DoNotStrip
12
+ import java.util.Objects
12
13
 
13
14
 
14
15
  /**
@@ -38,6 +39,28 @@ data class LocationGeocoderResult(
38
39
  ) {
39
40
  /* primary constructor */
40
41
 
42
+ override fun equals(other: Any?): Boolean {
43
+ if (this === other) return true
44
+ if (other !is LocationGeocoderResult) return false
45
+ return Objects.deepEquals(this.countryCode, other.countryCode)
46
+ && Objects.deepEquals(this.country, other.country)
47
+ && Objects.deepEquals(this.locality, other.locality)
48
+ && Objects.deepEquals(this.administrativeArea, other.administrativeArea)
49
+ && Objects.deepEquals(this.subAdministrativeArea, other.subAdministrativeArea)
50
+ && Objects.deepEquals(this.subLocality, other.subLocality)
51
+ }
52
+
53
+ override fun hashCode(): Int {
54
+ return arrayOf<Any?>(
55
+ countryCode,
56
+ country,
57
+ locality,
58
+ administrativeArea,
59
+ subAdministrativeArea,
60
+ subLocality
61
+ ).contentDeepHashCode()
62
+ }
63
+
41
64
  companion object {
42
65
  /**
43
66
  * Constructor called from C++
@@ -56,5 +56,7 @@ def add_nitrogen_files(spec)
56
56
  "SWIFT_OBJC_INTEROP_MODE" => "objcxx",
57
57
  # Enables stricter modular headers
58
58
  "DEFINES_MODULE" => "YES",
59
+ # Disable auto-generated ObjC header for Swift (Static linkage on Xcode 26.4 breaks here)
60
+ "SWIFT_INSTALL_OBJC_HEADER" => "NO",
59
61
  })
60
62
  end