diff --git a/.github/workflows/run-unit-tests.yml b/.github/workflows/run-unit-tests.yml
index c91eb2b..e9bcdb1 100644
--- a/.github/workflows/run-unit-tests.yml
+++ b/.github/workflows/run-unit-tests.yml
@@ -22,6 +22,11 @@ jobs:
distribution: 'zulu'
java-version: '17'
+ - name: Setup Ruby
+ uses: ruby/setup-ruby@v1
+ with:
+ ruby-version: '3.3'
+
- name: Bundle Install
run: bundle install
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c4e6003..ff5e130 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+## [2.2.2]
+
+### 2026-08-04
+
+- Fix: The result of the enable-location dialog was delivered only to the most recent request, leaving earlier concurrent requests waiting forever [RMET-5383](https://outsystemsrd.atlassian.net/browse/RMET-5383).
+
## [2.2.1]
### 2026-06-15
diff --git a/README.md b/README.md
index 79247fd..5aed36e 100644
--- a/README.md
+++ b/README.md
@@ -31,7 +31,7 @@ In your app-level gradle file, import the `ion-android-geolocation` library like
```
dependencies {
- implementation("io.ionic.libs:iongeolocation-android:2.2.1")
+ implementation("io.ionic.libs:iongeolocation-android:2.2.2")
}
```
diff --git a/build.gradle b/build.gradle
index c0e3f23..22db03a 100644
--- a/build.gradle
+++ b/build.gradle
@@ -46,8 +46,8 @@ android {
defaultConfig {
minSdk 23
targetSdk 36
- versionCode 4
- versionName "2.2.1"
+ versionCode 5
+ versionName "2.2.2"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
diff --git a/pom.xml b/pom.xml
index f95b78a..11665ef 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,5 +6,5 @@
4.0.0
io.ionic.libs
iongeolocation-android
- 2.2.1
+ 2.2.2
\ No newline at end of file
diff --git a/src/main/kotlin/io/ionic/libs/iongeolocationlib/controller/IONGLOCController.kt b/src/main/kotlin/io/ionic/libs/iongeolocationlib/controller/IONGLOCController.kt
index 160ed46..663610b 100644
--- a/src/main/kotlin/io/ionic/libs/iongeolocationlib/controller/IONGLOCController.kt
+++ b/src/main/kotlin/io/ionic/libs/iongeolocationlib/controller/IONGLOCController.kt
@@ -70,7 +70,7 @@ class IONGLOCController internal constructor(
sensorHandler = IONGLOCSensorHandler(context)
)
- private lateinit var resolveLocationSettingsResultFlow: MutableSharedFlow>
+ private val resolveLocationSettingsResultFlow = MutableSharedFlow>()
private val watchLocationHandlers: MutableMap = mutableMapOf()
private val watchIdsBlacklist: MutableList = mutableListOf()
@@ -275,7 +275,6 @@ class IONGLOCController internal constructor(
return Result.failure(playServicesResult.exceptionOrNull() ?: NullPointerException())
}
- resolveLocationSettingsResultFlow = MutableSharedFlow()
val locationSettingsResult = googleServicesHelper.checkLocationSettings(
activity,
options.copy(timeout = if (isSingleLocationRequest) 0 else options.timeout),
diff --git a/src/test/java/io/ionic/libs/iongeolocationlib/controller/IONGLOCControllerTest.kt b/src/test/java/io/ionic/libs/iongeolocationlib/controller/IONGLOCControllerTest.kt
index aa665c0..36b1636 100644
--- a/src/test/java/io/ionic/libs/iongeolocationlib/controller/IONGLOCControllerTest.kt
+++ b/src/test/java/io/ionic/libs/iongeolocationlib/controller/IONGLOCControllerTest.kt
@@ -272,6 +272,74 @@ class IONGLOCControllerTest {
}
}
+ @Test
+ fun `given two concurrent getCurrentPosition calls both need location settings resolved, when user accepts, both calls receive the location`() =
+ runTest {
+ givenSuccessConditions()
+ coEvery { locationSettingsTask.await() } throws mockk {
+ every { resolution } returns mockk(relaxed = true)
+ }
+ // Simulate Android behaviour: only one activity result callback fires regardless of
+ // how many times the launcher is invoked (e.g. a second overlapping call).
+ var launched = false
+ val testScope = this
+ coEvery { activityResultLauncher.launch(any()) } coAnswers {
+ if (!launched) {
+ launched = true
+ testScope.launch {
+ delay(DELAY)
+ sut.onResolvableExceptionResult(Activity.RESULT_OK)
+ }
+ }
+ }
+
+ val deferred1 = async { sut.getCurrentPosition(mockk(), locationOptions) }
+ val deferred2 = async { sut.getCurrentPosition(mockk(), locationOptions) }
+ runCurrent() // let both coroutines start and suspend on resolveLocationSettingsResultFlow.first()
+ advanceTimeBy(DELAY) // fire onResolvableExceptionResult; shared flow notifies both
+
+ val result1 = deferred1.await()
+ val result2 = deferred2.await()
+
+ assertTrue(result1.isSuccess)
+ assertEquals(locationResult, result1.getOrNull())
+ assertTrue(result2.isSuccess)
+ assertEquals(locationResult, result2.getOrNull())
+ }
+
+ @Test
+ fun `given two concurrent getCurrentPosition calls both need location settings resolved, when user denies, both calls receive IONGLOCRequestDeniedException`() =
+ runTest {
+ givenSuccessConditions()
+ coEvery { locationSettingsTask.await() } throws mockk {
+ every { resolution } returns mockk(relaxed = true)
+ }
+ var launched = false
+ val testScope = this
+ coEvery { activityResultLauncher.launch(any()) } coAnswers {
+ if (!launched) {
+ launched = true
+ testScope.launch {
+ delay(DELAY)
+ sut.onResolvableExceptionResult(Activity.RESULT_CANCELED)
+ }
+ }
+ }
+
+ val deferred1 = async { sut.getCurrentPosition(mockk(), locationOptions) }
+ val deferred2 = async { sut.getCurrentPosition(mockk(), locationOptions) }
+ runCurrent()
+ advanceTimeBy(DELAY)
+
+ val result1 = deferred1.await()
+ val result2 = deferred2.await()
+
+ assertTrue(result1.isFailure)
+ assertTrue(result1.exceptionOrNull() is IONGLOCException.IONGLOCRequestDeniedException)
+ assertTrue(result2.isFailure)
+ assertTrue(result2.exceptionOrNull() is IONGLOCException.IONGLOCRequestDeniedException)
+ }
+
// endregion getCurrentLocation tests
// region addWatch tests