Skip to content

Commit 1687a43

Browse files
feat(android): Location Manager fallback option (#15)
* feat: Fallback for no network or Play Services Via a future version of native library * chore: Add additional error code For network and location turned off Referneces: https://outsystemsrd.atlassian.net/browse/RMET-2991 * chore(web): Add `enableLocationFallback` param to interface * chore(outsystems-wrapper): Remove .gitignore The reason being that dist/outsystems.js is used directly in OutSystems Wrapper, and as such should be in source control. Plus, it can be helpful to automate updating the wrapper * docs: Document new `enableLocationFallback` parameter * chore(android): update native library * chore(web): run build script * chore(release): prepare to release plugin version 1.1.0
1 parent 62b282e commit 1687a43

File tree

19 files changed

+883
-69
lines changed

19 files changed

+883
-69
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,11 @@ All notable changes to this project will be documented in this file.
44
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
55
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
66

7+
## [1.1.0]
78

9+
## 2025-10-03
10+
11+
- Feature(android): Location fallback in case of Play Services failure or airplane mode.
812

913
## [1.0.3]
1014

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "com.outsystems.plugins.geolocation",
3-
"version": "1.0.3",
3+
"version": "1.1.0",
44
"private": true,
55
"dependencies": {},
66
"scripts": {

packages/cordova-plugin/README.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ cordova plugin add <path-to-repo-local-clone>
1515
* [`getCurrentPosition(...)`](#getcurrentposition)
1616
* [`watchPosition(...)`](#watchposition)
1717
* [`clearWatch(...)`](#clearwatch)
18+
* [Interfaces](#interfaces)
1819
* [Type Aliases](#type-aliases)
1920

2021
</docgen-index>
@@ -80,12 +81,23 @@ Clear a given watch
8081
--------------------
8182

8283

84+
### Interfaces
85+
86+
87+
#### Position
88+
89+
| Prop | Type | Description |
90+
| ------------ | ------------------- | ----------- |
91+
| **`line`** | <code>number</code> | &gt;= 1 |
92+
| **`column`** | <code>number</code> | &gt;= 0 |
93+
94+
8395
### Type Aliases
8496

8597

8698
#### CurrentPositionOptions
8799

88-
<code>{ /** * High accuracy mode (such as GPS, if available) * * On Android 12+ devices it will be ignored if users didn't grant * ACCESS_FINE_LOCATION permissions (can be checked with location alias). * * @default false * @since 1.0.0 */ enableHighAccuracy?: boolean; /** * The maximum wait time in milliseconds for location updates. * * In Android, since version 4.0.0 of the plugin, timeout gets ignored for getCurrentPosition. * * @default 10000 * @since 1.0.0 */ timeout?: number; /** * The maximum age in milliseconds of a possible cached position that is acceptable to return * * @default 0 * @since 1.0.0 */ maximumAge?: number; /** * The minumum update interval for location updates. * * If location updates are available faster than this interval then an update * will only occur if the minimum update interval has expired since the last location update. * * This parameter is only available for Android. It has no effect on iOS or Web platforms. * * @default 5000 * @since 6.1.0 */ minimumUpdateInterval?: number; }</code>
100+
<code>{ /** * High accuracy mode (such as GPS, if available) * * On Android 12+ devices it will be ignored if users didn't grant * ACCESS_FINE_LOCATION permissions (can be checked with location alias). * * @default false * @since 1.0.0 */ enableHighAccuracy?: boolean; /** * The maximum wait time in milliseconds for location updates. * * In Android, since version 4.0.0 of the plugin, timeout gets ignored for getCurrentPosition. * * @default 10000 * @since 1.0.0 */ timeout?: number; /** * The maximum age in milliseconds of a possible cached position that is acceptable to return * * @default 0 * @since 1.0.0 */ maximumAge?: number; /** * The minumum update interval for location updates. * * If location updates are available faster than this interval then an update * will only occur if the minimum update interval has expired since the last location update. * * This parameter is only available for Android. It has no effect on iOS or Web platforms. * * @default 5000 * @since 1.0.0 */ minimumUpdateInterval?: number; /** * This option applies to Android only. * * Whether to fall back to the Android framework's `LocationManager` in case Google Play Service's location settings checks fail. * This can happen for multiple reasons - e.g. device has no Play Services or device has no network connection (Airplane Mode) * If set to `false`, failures are propagated to the caller. * Note that `LocationManager` may not be as effective as Google Play Services implementation. * If the device's in airplane mode, only the GPS provider is used, which may take longer to return a location, depending on GPS signal. * This means that to receive location in such circumstances, you may need to provide a higher timeout. * * @default true * @since 1.1.0 */ enableLocationFallback?: boolean }</code>
89101

90102

91103
#### Position

packages/cordova-plugin/android/OSGeolocation.kt

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,16 @@
11
package com.outsystems.plugins.geolocation
22

3-
import com.google.android.gms.location.LocationServices
3+
import android.Manifest
4+
import android.content.pm.PackageManager
5+
import androidx.activity.result.contract.ActivityResultContracts
46
import com.google.gson.Gson
7+
import io.ionic.libs.iongeolocationlib.controller.IONGLOCController
8+
import io.ionic.libs.iongeolocationlib.model.IONGLOCException
9+
import io.ionic.libs.iongeolocationlib.model.IONGLOCLocationOptions
510
import kotlinx.coroutines.CoroutineScope
611
import kotlinx.coroutines.Dispatchers
12+
import kotlinx.coroutines.cancel
13+
import kotlinx.coroutines.flow.MutableSharedFlow
714
import kotlinx.coroutines.launch
815
import org.apache.cordova.CallbackContext
916
import org.apache.cordova.CordovaInterface
@@ -13,14 +20,6 @@ import org.apache.cordova.PermissionHelper
1320
import org.apache.cordova.PluginResult
1421
import org.json.JSONArray
1522
import org.json.JSONObject
16-
import android.Manifest
17-
import android.content.pm.PackageManager
18-
import androidx.activity.result.contract.ActivityResultContracts
19-
import io.ionic.libs.iongeolocationlib.controller.IONGLOCController
20-
import io.ionic.libs.iongeolocationlib.model.IONGLOCException
21-
import io.ionic.libs.iongeolocationlib.model.IONGLOCLocationOptions
22-
import kotlinx.coroutines.cancel
23-
import kotlinx.coroutines.flow.MutableSharedFlow
2423

2524
/**
2625
* Cordova bridge, inherits from CordovaPlugin
@@ -40,6 +39,7 @@ class OSGeolocation : CordovaPlugin() {
4039
private const val TIMEOUT = "timeout"
4140
private const val MAXIMUM_AGE = "maximumAge"
4241
private const val ENABLE_HIGH_ACCURACY = "enableHighAccuracy"
42+
private const val ENABLE_FALLBACK = "enableLocationFallback"
4343
}
4444

4545
override fun initialize(cordova: CordovaInterface, webView: CordovaWebView) {
@@ -54,10 +54,7 @@ class OSGeolocation : CordovaPlugin() {
5454
}
5555
}
5656

57-
this.controller = IONGLOCController(
58-
LocationServices.getFusedLocationProviderClient(cordova.context),
59-
activityLauncher
60-
)
57+
this.controller = IONGLOCController(cordova.context, activityLauncher)
6158
}
6259

6360
override fun onDestroy() {
@@ -103,7 +100,9 @@ class OSGeolocation : CordovaPlugin() {
103100
val locationOptions = IONGLOCLocationOptions(
104101
options.getLong(TIMEOUT),
105102
options.getLong(MAXIMUM_AGE),
106-
options.getBoolean(ENABLE_HIGH_ACCURACY))
103+
options.getBoolean(ENABLE_HIGH_ACCURACY),
104+
enableLocationManagerFallback = options.optBoolean(ENABLE_FALLBACK, true)
105+
)
107106

108107
val locationResult = controller.getCurrentPosition(cordova.activity, locationOptions)
109108

@@ -137,6 +136,7 @@ class OSGeolocation : CordovaPlugin() {
137136
timeout = options.getLong(TIMEOUT),
138137
maximumAge = options.getLong(MAXIMUM_AGE),
139138
enableHighAccuracy = options.getBoolean(ENABLE_HIGH_ACCURACY),
139+
enableLocationManagerFallback = options.optBoolean(ENABLE_FALLBACK, true)
140140
)
141141

142142
controller.addWatch(cordova.activity, locationOptions, watchId).collect { result ->
@@ -175,6 +175,10 @@ class OSGeolocation : CordovaPlugin() {
175175
callbackContext.sendError(OSGeolocationErrors.LOCATION_SETTINGS_ERROR)
176176
}
177177

178+
is IONGLOCException.IONGLOCLocationAndNetworkDisabledException -> {
179+
callbackContext.sendError(OSGeolocationErrors.NETWORK_LOCATION_DISABLED_ERROR)
180+
}
181+
178182
is IONGLOCException.IONGLOCInvalidTimeoutException -> {
179183
callbackContext.sendError(OSGeolocationErrors.INVALID_TIMEOUT)
180184
}

packages/cordova-plugin/android/OSGeolocationErrors.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,4 +78,9 @@ object OSGeolocationErrors {
7878
code = formatErrorCode(16),
7979
message = "Location settings error."
8080
)
81+
82+
val NETWORK_LOCATION_DISABLED_ERROR = ErrorInfo(
83+
code = formatErrorCode(17),
84+
message = "Unable to retrieve location because device has both Network and Location turned off."
85+
)
8186
}

packages/cordova-plugin/android/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ allprojects {
1717
}
1818

1919
dependencies{
20-
implementation("io.ionic.libs:iongeolocation-android:1.0.0")
20+
implementation("io.ionic.libs:iongeolocation-android:2.0.0")
2121
implementation("androidx.browser:browser:1.8.0")
2222
implementation("com.google.android.gms:play-services-auth:21.2.0")
2323
implementation("com.google.android.gms:play-services-location:21.3.0")

packages/cordova-plugin/dist/definitions.d.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,23 @@ export type CurrentPositionOptions = {
7474
* This parameter is only available for Android. It has no effect on iOS or Web platforms.
7575
*
7676
* @default 5000
77-
* @since 6.1.0
77+
* @since 1.0.0
7878
*/
7979
minimumUpdateInterval?: number;
80+
/**
81+
* This option applies to Android only.
82+
*
83+
* Whether to fall back to the Android framework's `LocationManager` in case Google Play Service's location settings checks fail.
84+
* This can happen for multiple reasons - e.g. device has no Play Services or device has no network connection (Airplane Mode)
85+
* If set to `false`, failures are propagated to the caller.
86+
* Note that `LocationManager` may not be as effective as Google Play Services implementation.
87+
* If the device's in airplane mode, only the GPS provider is used, which may take longer to return a location, depending on GPS signal.
88+
* This means that to receive location in such circumstances, you may need to provide a higher timeout.
89+
*
90+
* @default true
91+
* @since 1.1.0
92+
*/
93+
enableLocationFallback?: boolean;
8094
};
8195
export type ClearWatchOptions = {
8296
id: string;

packages/cordova-plugin/dist/plugin.cjs

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,25 +4,25 @@ function s(t) {
44
t.CapacitorUtils.Synapse = new Proxy(
55
{},
66
{
7-
get(e, o) {
7+
get(e, n) {
88
return new Proxy({}, {
9-
get(w, r) {
10-
return (c, p, n) => {
11-
const i = t.Capacitor.Plugins[o];
9+
get(w, o) {
10+
return (c, p, r) => {
11+
const i = t.Capacitor.Plugins[n];
1212
if (i === void 0) {
13-
n(new Error(`Capacitor plugin ${o} not found`));
13+
r(new Error(`Capacitor plugin ${n} not found`));
1414
return;
1515
}
16-
if (typeof i[r] != "function") {
17-
n(new Error(`Method ${r} not found in Capacitor plugin ${o}`));
16+
if (typeof i[o] != "function") {
17+
r(new Error(`Method ${o} not found in Capacitor plugin ${n}`));
1818
return;
1919
}
2020
(async () => {
2121
try {
22-
const a = await i[r](c);
22+
const a = await i[o](c);
2323
p(a);
2424
} catch (a) {
25-
n(a);
25+
r(a);
2626
}
2727
})();
2828
};
@@ -36,20 +36,21 @@ function u(t) {
3636
t.CapacitorUtils.Synapse = new Proxy(
3737
{},
3838
{
39-
get(e, o) {
40-
return t.cordova.plugins[o];
39+
get(e, n) {
40+
return t.cordova.plugins[n];
4141
}
4242
}
4343
);
4444
}
45-
function y(t = false) {
46-
window.CapacitorUtils = window.CapacitorUtils || {}, window.Capacitor !== void 0 && !t ? s(window) : window.cordova !== void 0 && u(window);
45+
function f(t = false) {
46+
typeof window > "u" || (window.CapacitorUtils = window.CapacitorUtils || {}, window.Capacitor !== void 0 && !t ? s(window) : window.cordova !== void 0 && u(window));
4747
}
4848
const CurrentPositionOptionsDefault = {
4949
enableHighAccuracy: false,
5050
timeout: 1e3,
5151
maximumAge: 0,
52-
minimumUpdateInterval: 5e3
52+
minimumUpdateInterval: 5e3,
53+
enableLocationFallback: true
5354
};
5455
const ClearWatchOptionsDefault = {
5556
id: "-1"
@@ -106,4 +107,4 @@ module.exports = {
106107
watchPosition,
107108
clearWatch
108109
};
109-
y(true);
110+
f(true);

packages/cordova-plugin/dist/plugin.js

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,25 +6,25 @@
66
t.CapacitorUtils.Synapse = new Proxy(
77
{},
88
{
9-
get(e, o) {
9+
get(e, n) {
1010
return new Proxy({}, {
11-
get(w, r) {
12-
return (c, p, n) => {
13-
const i = t.Capacitor.Plugins[o];
11+
get(w, o) {
12+
return (c, p, r) => {
13+
const i = t.Capacitor.Plugins[n];
1414
if (i === void 0) {
15-
n(new Error(`Capacitor plugin ${o} not found`));
15+
r(new Error(`Capacitor plugin ${n} not found`));
1616
return;
1717
}
18-
if (typeof i[r] != "function") {
19-
n(new Error(`Method ${r} not found in Capacitor plugin ${o}`));
18+
if (typeof i[o] != "function") {
19+
r(new Error(`Method ${o} not found in Capacitor plugin ${n}`));
2020
return;
2121
}
2222
(async () => {
2323
try {
24-
const a = await i[r](c);
24+
const a = await i[o](c);
2525
p(a);
2626
} catch (a) {
27-
n(a);
27+
r(a);
2828
}
2929
})();
3030
};
@@ -38,20 +38,21 @@
3838
t.CapacitorUtils.Synapse = new Proxy(
3939
{},
4040
{
41-
get(e, o) {
42-
return t.cordova.plugins[o];
41+
get(e, n) {
42+
return t.cordova.plugins[n];
4343
}
4444
}
4545
);
4646
}
47-
function y(t = false) {
48-
window.CapacitorUtils = window.CapacitorUtils || {}, window.Capacitor !== void 0 && !t ? s(window) : window.cordova !== void 0 && u(window);
47+
function f(t = false) {
48+
typeof window > "u" || (window.CapacitorUtils = window.CapacitorUtils || {}, window.Capacitor !== void 0 && !t ? s(window) : window.cordova !== void 0 && u(window));
4949
}
5050
const CurrentPositionOptionsDefault = {
5151
enableHighAccuracy: false,
5252
timeout: 1e3,
5353
maximumAge: 0,
54-
minimumUpdateInterval: 5e3
54+
minimumUpdateInterval: 5e3,
55+
enableLocationFallback: true
5556
};
5657
const ClearWatchOptionsDefault = {
5758
id: "-1"
@@ -108,5 +109,5 @@
108109
watchPosition,
109110
clearWatch
110111
};
111-
y(true);
112+
f(true);
112113
});

packages/cordova-plugin/dist/plugin.mjs

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,25 @@ function s(t) {
33
t.CapacitorUtils.Synapse = new Proxy(
44
{},
55
{
6-
get(e, o) {
6+
get(e, n) {
77
return new Proxy({}, {
8-
get(w, r) {
9-
return (c, p, n) => {
10-
const i = t.Capacitor.Plugins[o];
8+
get(w, o) {
9+
return (c, p, r) => {
10+
const i = t.Capacitor.Plugins[n];
1111
if (i === void 0) {
12-
n(new Error(`Capacitor plugin ${o} not found`));
12+
r(new Error(`Capacitor plugin ${n} not found`));
1313
return;
1414
}
15-
if (typeof i[r] != "function") {
16-
n(new Error(`Method ${r} not found in Capacitor plugin ${o}`));
15+
if (typeof i[o] != "function") {
16+
r(new Error(`Method ${o} not found in Capacitor plugin ${n}`));
1717
return;
1818
}
1919
(async () => {
2020
try {
21-
const a = await i[r](c);
21+
const a = await i[o](c);
2222
p(a);
2323
} catch (a) {
24-
n(a);
24+
r(a);
2525
}
2626
})();
2727
};
@@ -35,20 +35,21 @@ function u(t) {
3535
t.CapacitorUtils.Synapse = new Proxy(
3636
{},
3737
{
38-
get(e, o) {
39-
return t.cordova.plugins[o];
38+
get(e, n) {
39+
return t.cordova.plugins[n];
4040
}
4141
}
4242
);
4343
}
44-
function y(t = false) {
45-
window.CapacitorUtils = window.CapacitorUtils || {}, window.Capacitor !== void 0 && !t ? s(window) : window.cordova !== void 0 && u(window);
44+
function f(t = false) {
45+
typeof window > "u" || (window.CapacitorUtils = window.CapacitorUtils || {}, window.Capacitor !== void 0 && !t ? s(window) : window.cordova !== void 0 && u(window));
4646
}
4747
const CurrentPositionOptionsDefault = {
4848
enableHighAccuracy: false,
4949
timeout: 1e3,
5050
maximumAge: 0,
51-
minimumUpdateInterval: 5e3
51+
minimumUpdateInterval: 5e3,
52+
enableLocationFallback: true
5253
};
5354
const ClearWatchOptionsDefault = {
5455
id: "-1"
@@ -105,4 +106,4 @@ module.exports = {
105106
watchPosition,
106107
clearWatch
107108
};
108-
y(true);
109+
f(true);

0 commit comments

Comments
 (0)