diff --git a/package.json b/package.json index 9ed4ad87..30eedfb2 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "generate-plugin-docs": "node scripts/api.mjs", "generate-v6-plugin-docs": "node scripts/api-v6.mjs", "generate-v7-plugin-docs": "node scripts/api-v7.mjs", + "generate-v8-plugin-docs": "node scripts/api-v8.mjs", "serve": "docusaurus serve", "prestart": "npm run generate-plugin-docs", "start": "docusaurus start", diff --git a/scripts/api-v8.mjs b/scripts/api-v8.mjs new file mode 100644 index 00000000..b5e087a0 --- /dev/null +++ b/scripts/api-v8.mjs @@ -0,0 +1,418 @@ +// @ts-check +import fs from 'fs'; +import fetch from 'node-fetch'; + +// @ts-ignore +const API_DIR = new URL('../versioned_docs/version-v8/apis/', import.meta.url); + +const tag = 'latest'; + +/** + * @typedef {Object} PluginApi + * @property {string} id + * @property {string} [title] + * @property {boolean} isCore + * @property {boolean} isExperimental + * @property {string} npmScope + * @property {string} [description] + * @property {string} editUrl + * @property {string} editApiUrl + * @property {string} [tag] + */ +const pluginApis = [ + { + id: 'action-sheet', + isCore: false, + isExperimental: false, + npmScope: '@capacitor', + editUrl: 'https://github.com/ionic-team/capacitor-plugins/blob/main/action-sheet/README.md', + editApiUrl: 'https://github.com/ionic-team/capacitor-plugins/blob/main/action-sheet/src/definitions.ts', + }, + { + id: 'app', + isCore: false, + isExperimental: false, + npmScope: '@capacitor', + editUrl: 'https://github.com/ionic-team/capacitor-plugins/blob/main/app/README.md', + editApiUrl: 'https://github.com/ionic-team/capacitor-plugins/blob/main/app/src/definitions.ts', + }, + { + id: 'app-launcher', + isCore: false, + isExperimental: false, + npmScope: '@capacitor', + editUrl: 'https://github.com/ionic-team/capacitor-plugins/blob/main/app-launcher/README.md', + editApiUrl: 'https://github.com/ionic-team/capacitor-plugins/blob/main/app-launcher/src/definitions.ts', + }, + { + id: 'background-runner', + isCore: false, + isExperimental: false, + npmScope: '@capacitor', + editUrl: 'https://github.com/ionic-team/capacitor-background-runner/blob/main/packages/capacitor-plugin/README.md', + editApiUrl: + 'https://github.com/ionic-team/capacitor-background-runner/blob/main/packages/capacitor-plugin/src/definitions.ts', + }, + { + id: 'barcode-scanner', + isCore: false, + isExperimental: false, + npmScope: '@capacitor', + editUrl: 'https://github.com/ionic-team/capacitor-barcode-scanner/blob/main/plugin/README.md', + editApiUrl: 'https://github.com/ionic-team/capacitor-barcode-scanner/blob/main/plugin/src/definitions.ts', + }, + { + id: 'browser', + isCore: false, + isExperimental: false, + npmScope: '@capacitor', + editUrl: 'https://github.com/ionic-team/capacitor-plugins/blob/main/browser/README.md', + editApiUrl: 'https://github.com/ionic-team/capacitor-plugins/blob/main/browser/src/definitions.ts', + }, + { + id: 'calendar', + isCore: false, + isExperimental: false, + npmScope: '@capacitor', + editUrl: 'https://github.com/ionic-team/capacitor-calendar/blob/main/README.md', + editApiUrl: 'https://github.com/ionic-team/capacitor-calendar/blob/main/src/definitions.ts', + }, + { + id: 'camera', + isCore: false, + isExperimental: false, + npmScope: '@capacitor', + editUrl: 'https://github.com/ionic-team/capacitor-camera/blob/main/README.md', + editApiUrl: 'https://github.com/ionic-team/capacitor-camera/blob/main/src/definitions.ts', + }, + { + id: 'clipboard', + isCore: false, + isExperimental: false, + npmScope: '@capacitor', + editUrl: 'https://github.com/ionic-team/capacitor-plugins/blob/main/clipboard/README.md', + editApiUrl: 'https://github.com/ionic-team/capacitor-plugins/blob/main/clipboard/src/definitions.ts', + }, + { + id: 'contacts', + isCore: false, + isExperimental: false, + npmScope: '@capacitor', + editUrl: 'https://github.com/ionic-team/capacitor-contacts/blob/main/README.md', + editApiUrl: 'https://github.com/ionic-team/capacitor-contacts/blob/main/src/definitions.ts', + }, + { + id: 'cookies', + isCore: true, + isExperimental: false, + npmScope: '@capacitor', + description: 'The Capacitor Cookies API provides native cookie support via patching `document.cookie` to use native libraries.', + editUrl: 'https://github.com/ionic-team/capacitor/blob/main/core/cookies.md', + editApiUrl: 'https://github.com/ionic-team/capacitor/blob/main/core/src/core-plugins.ts', + }, + { + id: 'device', + isCore: false, + isExperimental: false, + npmScope: '@capacitor', + editUrl: 'https://github.com/ionic-team/capacitor-plugins/blob/main/device/README.md', + editApiUrl: 'https://github.com/ionic-team/capacitor-plugins/blob/main/device/src/definitions.ts', + }, + { + id: 'dialog', + isCore: false, + isExperimental: false, + npmScope: '@capacitor', + editUrl: 'https://github.com/ionic-team/capacitor-plugins/blob/main/dialog/README.md', + editApiUrl: 'https://github.com/ionic-team/capacitor-plugins/blob/main/dialog/src/definitions.ts', + }, + { + id: 'filesystem', + isCore: false, + isExperimental: false, + npmScope: '@capacitor', + editUrl: 'https://github.com/ionic-team/capacitor-filesystem/blob/main/README.md', + editApiUrl: 'https://github.com/ionic-team/capacitor-filesystem/blob/main/src/definitions.ts', + }, + { + id: 'file-transfer', + isCore: false, + isExperimental: false, + npmScope: '@capacitor', + editUrl: 'https://github.com/ionic-team/capacitor-file-transfer/blob/main/packages/capacitor-plugin/README.md', + editApiUrl: 'https://github.com/ionic-team/capacitor-file-transfer/blob/main/packages/capacitor-plugin/src/definitions.ts', + }, + { + id: 'file-viewer', + isCore: false, + isExperimental: false, + npmScope: '@capacitor', + editUrl: 'https://github.com/ionic-team/capacitor-file-viewer/blob/main/packages/capacitor-plugin/README.md', + editApiUrl: 'https://github.com/ionic-team/capacitor-file-viewer/blob/main/packages/capacitor-plugin/src/definitions.ts', + }, + { + id: 'geolocation', + isCore: false, + isExperimental: false, + npmScope: '@capacitor', + description: 'The Geolocation API provides simple methods for getting and tracking the current position of the device using GPS, along with altitude, heading, and speed information if available.', + editUrl: 'https://github.com/ionic-team/capacitor-geolocation/blob/main/README.md', + editApiUrl: 'https://github.com/ionic-team/capacitor-geolocation/blob/main/src/definitions.ts', + }, + { + id: 'google-maps', + isCore: false, + isExperimental: false, + npmScope: '@capacitor', + editUrl: 'https://github.com/ionic-team/capacitor-google-maps/blob/main/plugin/README.md', + editApiUrl: 'https://github.com/ionic-team/capacitor-google-maps/blob/main/plugin/src/definitions.ts', + }, + { + id: 'haptics', + isCore: false, + isExperimental: false, + npmScope: '@capacitor', + editUrl: 'https://github.com/ionic-team/capacitor-haptics/blob/main/README.md', + editApiUrl: 'https://github.com/ionic-team/capacitor-haptics/blob/main/src/definitions.ts', + }, + { + id: 'http', + isCore: true, + isExperimental: false, + npmScope: '@capacitor', + description: 'The Capacitor Http API provides native http support via patching `fetch` and `XMLHttpRequest` to use native libraries.', + editUrl: 'https://github.com/ionic-team/capacitor/blob/main/core/http.md', + editApiUrl: 'https://github.com/ionic-team/capacitor/blob/main/core/src/core-plugins.ts', + }, + { + id: 'inappbrowser', + title: 'InAppBrowser', + isCore: false, + isExperimental: false, + npmScope: '@capacitor', + editUrl: 'https://github.com/ionic-team/capacitor-os-inappbrowser/blob/main/README.md', + editApiUrl: 'https://github.com/ionic-team/capacitor-os-inappbrowser/blob/main/src/definitions.ts', + }, + { + id: 'keyboard', + isCore: false, + isExperimental: false, + npmScope: '@capacitor', + editUrl: 'https://github.com/ionic-team/capacitor-keyboard/blob/main/README.md', + editApiUrl: 'https://github.com/ionic-team/capacitor-keyboard/blob/main/src/definitions.ts', + }, + { + id: 'local-notifications', + isCore: false, + isExperimental: false, + npmScope: '@capacitor', + editUrl: 'https://github.com/ionic-team/capacitor-plugins/blob/main/local-notifications/README.md', + editApiUrl: 'https://github.com/ionic-team/capacitor-plugins/blob/main/local-notifications/src/definitions.ts', + }, + { + id: 'motion', + isCore: false, + isExperimental: false, + npmScope: '@capacitor', + editUrl: 'https://github.com/ionic-team/capacitor-plugins/blob/main/motion/README.md', + editApiUrl: 'https://github.com/ionic-team/capacitor-plugins/blob/main/motion/src/definitions.ts', + }, + { + id: 'network', + isCore: false, + isExperimental: false, + npmScope: '@capacitor', + editUrl: 'https://github.com/ionic-team/capacitor-plugins/blob/main/network/README.md', + editApiUrl: 'https://github.com/ionic-team/capacitor-plugins/blob/main/network/src/definitions.ts', + }, + { + id: 'preferences', + isCore: false, + isExperimental: false, + npmScope: '@capacitor', + editUrl: 'https://github.com/ionic-team/capacitor-plugins/blob/main/preferences/README.md', + editApiUrl: 'https://github.com/ionic-team/capacitor-plugins/blob/main/preferences/src/definitions.ts', + }, + { + id: 'privacy-screen', + isCore: false, + isExperimental: false, + npmScope: '@capacitor', + editUrl: 'https://github.com/ionic-team/capacitor-privacy-screen/blob/main/README.md', + editApiUrl: 'https://github.com/ionic-team/capacitor-privacy-screen/blob/main/src/definitions.ts', + }, + { + id: 'push-notifications', + isCore: false, + isExperimental: false, + npmScope: '@capacitor', + editUrl: 'https://github.com/ionic-team/capacitor-plugins/blob/main/push-notifications/README.md', + editApiUrl: 'https://github.com/ionic-team/capacitor-plugins/blob/main/push-notifications/src/definitions.ts', + }, + { + id: 'screen-orientation', + isCore: false, + isExperimental: false, + npmScope: '@capacitor', + editUrl: 'https://github.com/ionic-team/capacitor-plugins/blob/main/screen-orientation/README.md', + editApiUrl: 'https://github.com/ionic-team/capacitor-plugins/blob/main/screen-orientation/src/definitions.ts', + }, + { + id: 'screen-reader', + isCore: false, + isExperimental: false, + npmScope: '@capacitor', + editUrl: 'https://github.com/ionic-team/capacitor-plugins/blob/main/screen-reader/README.md', + editApiUrl: 'https://github.com/ionic-team/capacitor-plugins/blob/main/screen-reader/src/definitions.ts', + }, + { + id: 'share', + isCore: false, + isExperimental: false, + npmScope: '@capacitor', + editUrl: 'https://github.com/ionic-team/capacitor-plugins/blob/main/share/README.md', + editApiUrl: 'https://github.com/ionic-team/capacitor-plugins/blob/main/share/src/definitions.ts', + }, + { + id: 'splash-screen', + isCore: false, + isExperimental: false, + npmScope: '@capacitor', + editUrl: 'https://github.com/ionic-team/capacitor-plugins/blob/main/splash-screen/README.md', + editApiUrl: 'https://github.com/ionic-team/capacitor-plugins/blob/main/splash-screen/src/definitions.ts', + }, + { + id: 'status-bar', + isCore: false, + isExperimental: false, + npmScope: '@capacitor', + editUrl: 'https://github.com/ionic-team/capacitor-plugins/blob/main/status-bar/README.md', + editApiUrl: 'https://github.com/ionic-team/capacitor-plugins/blob/main/status-bar/src/definitions.ts', + }, + { + id: 'system-bars', + isCore: true, + isExperimental: false, + npmScope: '@capacitor', + description: 'The System Bars API provides methods for configuring the style and visibility of the device System Bars / Status Bar.', + editUrl: 'https://github.com/ionic-team/capacitor/blob/main/core/system-bars.md', + editApiUrl: 'https://github.com/ionic-team/capacitor/blob/main/core/src/core-plugins.ts', + }, + { + id: 'text-zoom', + isCore: false, + isExperimental: false, + npmScope: '@capacitor', + editUrl: 'https://github.com/ionic-team/capacitor-plugins/blob/main/text-zoom/README.md', + editApiUrl: 'https://github.com/ionic-team/capacitor-plugins/blob/main/text-zoom/src/definitions.ts', + }, + { + id: 'toast', + isCore: false, + isExperimental: false, + npmScope: '@capacitor', + editUrl: 'https://github.com/ionic-team/capacitor-plugins/blob/main/toast/README.md', + editApiUrl: 'https://github.com/ionic-team/capacitor-plugins/blob/main/toast/src/definitions.ts', + }, + { + id: 'local-llm', + title: "Local LLM", + isCore: false, + isExperimental: true, + npmScope: '@capacitor', + editUrl: 'https://github.com/ionic-team/capacitor-local-llm/blob/main/README.md', + editApiUrl: 'https://github.com/ionic-team/capacitor-local-llm/blob/main/src/definitions.ts', + } +]; + +/** + * @param {PluginApi} plugin + */ +async function buildPluginApiDocs(plugin) { + const [readme, pkgJson] = await Promise.all([getReadme(plugin), getPkgJsonData(plugin)]); + + const apiContent = createApiPage(plugin, readme, pkgJson); + const fileName = `${plugin.id}.md`; + const filePath = new URL(fileName, API_DIR); + fs.writeFileSync(filePath, apiContent); +} + +/** + * @param {PluginApi} plugin + * @param {string} readme + * @param {any} pkgJson + * @returns {string} + */ +function createApiPage(plugin, readme, pkgJson) { + const title = `${plugin.title ?? toTitleCase(plugin.id)} Capacitor Plugin API`; + const desc = plugin.description ? plugin.description : pkgJson.description ? pkgJson.description.replace(/\n/g, ' ') : title; + const editUrl = plugin.editUrl; + const editApiUrl = plugin.editApiUrl; + const sidebarLabel = plugin.title ?? toTitleCase(plugin.id); + return ` +--- +title: ${title} +description: ${desc} +custom_edit_url: ${editUrl} +editApiUrl: ${editApiUrl} +sidebar_label: ${sidebarLabel}${plugin.isExperimental ? ' 🧪' : ''} +--- + +${readme}`.trim(); +} + +async function invalidateJSDELIVRCache(url) { + const rsp = await fetch(url.replace('cdn', 'purge'), { method: 'GET' }); + let err = null; + let rspData = null; + try { + rspData = await rsp.json(); + } catch (e) { + err = e; + } + // @ts-ignore + if (err !== null || rspData.status !== 'finished') { + console.error(err); + throw new Error("Failed to invalidate JSDELIVR cache for " + url); + } +} + +/** + * @param {PluginApi} plugin + * @returns {Promise} + */ +async function getReadme(plugin) { + const url = `https://cdn.jsdelivr.net/npm/${plugin.npmScope}/${!plugin.isCore ? plugin.id : 'core'}@${plugin.tag ?? tag}/${plugin.isCore ? `${plugin.id}.md` : 'README.md'}`; + await invalidateJSDELIVRCache(url); + const rsp = await fetch(url); + return rsp.text(); +} + +/** + * @param {PluginApi} plugin + * @returns {Promise} + */ +async function getPkgJsonData(plugin) { + const url = `https://cdn.jsdelivr.net/npm/${plugin.npmScope}/${!plugin.isCore ? plugin.id : 'core'}@${plugin.tag ?? tag}/package.json`; + await invalidateJSDELIVRCache(url); + const rsp = await fetch(url); + return rsp.json(); +} + +async function main() { + console.log("Updating Plugin API Files..."); + await Promise.all(pluginApis.map(buildPluginApiDocs)); + console.log(`Plugin API Files Updated 🎸`); +} + +/** + * @param {string} str + * @returns {string} + */ +function toTitleCase(str) { + return str.replace(/(^\w|-\w)/g, (s) => { + return s.replace(/-/, ' ').toUpperCase(); + }); +} + +main(); diff --git a/vercel.json b/vercel.json index 070f532f..669fd1a3 100644 --- a/vercel.json +++ b/vercel.json @@ -13,7 +13,9 @@ "redirects": [ { "source": "/", "destination": "/docs" }, { "source": "/docs/apis/storage", "destination": "/docs/apis/preferences" }, - { "source": "/docs/cli/commands/list", "destination": "/docs/cli/commands/ls" } + { "source": "/docs/cli/commands/list", "destination": "/docs/cli/commands/ls" }, + { "source": "/docs/v8", "destination": "/docs" }, + { "source": "/docs/v8/:match*", "destination": "/docs/:match*" } ], "rewrites": [ { "source": "/docs", "destination": "/" }, diff --git a/versioned_docs/version-v8/apis/background-runner.md b/versioned_docs/version-v8/apis/background-runner.md index 925c37c6..32491d96 100644 --- a/versioned_docs/version-v8/apis/background-runner.md +++ b/versioned_docs/version-v8/apis/background-runner.md @@ -1,7 +1,7 @@ --- title: Background Runner Capacitor Plugin API description: Capacitor Background Runner -custom_edit_url: https://github.com/ionic-team/capacitor-background-runner/blob/main/README.md +custom_edit_url: https://github.com/ionic-team/capacitor-background-runner/blob/main/packages/capacitor-plugin/README.md editApiUrl: https://github.com/ionic-team/capacitor-background-runner/blob/main/packages/capacitor-plugin/src/definitions.ts sidebar_label: Background Runner --- diff --git a/versioned_docs/version-v8/apis/filesystem.md b/versioned_docs/version-v8/apis/filesystem.md index 9aeee1fc..916f1541 100644 --- a/versioned_docs/version-v8/apis/filesystem.md +++ b/versioned_docs/version-v8/apis/filesystem.md @@ -1,8 +1,8 @@ --- title: Filesystem Capacitor Plugin API description: The Filesystem API provides a NodeJS-like API for working with files on the device. -custom_edit_url: https://github.com/ionic-team/capacitor-filesystem/blob/main/packages/capacitor-plugin/README.md -editApiUrl: https://github.com/ionic-team/capacitor-filesystem/blob/main/packages/capacitor-plugin/src/definitions.ts +custom_edit_url: https://github.com/ionic-team/capacitor-filesystem/blob/main/README.md +editApiUrl: https://github.com/ionic-team/capacitor-filesystem/blob/main/src/definitions.ts sidebar_label: Filesystem --- diff --git a/versioned_docs/version-v8/apis/geolocation.md b/versioned_docs/version-v8/apis/geolocation.md index 05bbe3fe..4b3e13b9 100644 --- a/versioned_docs/version-v8/apis/geolocation.md +++ b/versioned_docs/version-v8/apis/geolocation.md @@ -1,8 +1,8 @@ --- title: Geolocation Capacitor Plugin API description: The Geolocation API provides simple methods for getting and tracking the current position of the device using GPS, along with altitude, heading, and speed information if available. -custom_edit_url: https://github.com/ionic-team/capacitor-geolocation/blob/main/packages/capacitor-plugin/README.md -editApiUrl: https://github.com/ionic-team/capacitor-geolocation/blob/main/packages/capacitor-plugin/src/definitions.ts +custom_edit_url: https://github.com/ionic-team/capacitor-geolocation/blob/main/README.md +editApiUrl: https://github.com/ionic-team/capacitor-geolocation/blob/main/src/definitions.ts sidebar_label: Geolocation --- @@ -24,13 +24,16 @@ Apple requires privacy descriptions to be specified in `Info.plist` for location - `NSLocationAlwaysAndWhenInUseUsageDescription` (`Privacy - Location Always and When In Use Usage Description`) - `NSLocationWhenInUseUsageDescription` (`Privacy - Location When In Use Usage Description`) -> [!NOTE] -> This Capacitor plugin does not support background geolocation directly. However, it relies on -> [`ion-ios-geolocation`](https://github.com/ionic-team/ion-ios-geolocation), which can report -> location in the background. As a result, Apple requires you to include a -> `NSLocationAlwaysAndWhenInUseUsageDescription` entry in your `Info.plist`. Since this permission -> prompt won’t appear to users, you can safely use the same description string as for -> `NSLocationWhenInUseUsageDescription`. +:::info[Background Location Usage Strings] + +This Capacitor plugin does not support background geolocation directly. However, it relies on +[`ion-ios-geolocation`](https://github.com/ionic-team/ion-ios-geolocation), which can report +location in the background. As a result, Apple requires you to include a +`NSLocationAlwaysAndWhenInUseUsageDescription` entry in your `Info.plist`. Since this permission +prompt won’t appear to users, you can safely use the same description string as for +`NSLocationWhenInUseUsageDescription`. + +:::info Read about [Configuring `Info.plist`](https://capacitorjs.com/docs/ios/configuration#configuring-infoplist) in the [iOS Guide](https://capacitorjs.com/docs/ios) for more information on setting iOS permissions in Xcode @@ -47,6 +50,12 @@ This plugin requires the following permissions be added to your `AndroidManifest The first two permissions ask for location data, both fine and coarse, and the last line is optional but necessary if your app _requires_ GPS to function. You may leave it out, though keep in mind that this may mean your app is installed on devices lacking GPS hardware. +:::note + +If you only require approximate location (variable accuracy but usually around 2 kilometers), you may just declare `ACCESS_COARSE_LOCATION` and `number | The maximum wait time in milliseconds for location updates. | 10000 | 1.0.0 | | **`maximumAge`** | number | The maximum age in milliseconds of a possible cached position that is acceptable to return | 0 | 1.0.0 | | **`minimumUpdateInterval`** | number | The minimum update interval for `watchPosition`. Not to be confused with `interval`. 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. | 5000 | 6.1.0 | -| **`interval`** | number | Desired interval in milliseconds to receive location updates in `watchPosition`. For very low values of `interval` (a couple seconds or less), the platform may not guarantee timely location updates - they may take longer than specified. The platform may also be able to provide location updates faster than `interval`. You may use `minimumUpdateInterval` to control that behavior. For backwards compatiblity with version 7.1.x, if no value is passed, the default value of this parameter is that of `timeout`. This parameter is only available for Android. It has no effect on iOS or Web platforms. | `timeout` | 8.0.0 | +| **`interval`** | number | Desired interval in milliseconds to receive location updates in `watchPosition`. For very low values of `interval` (a couple seconds or less), the platform may not guarantee timely location updates - they may take longer than specified. The platform may also be able to provide location updates faster than `interval`. You may use `minimumUpdateInterval` to control that behavior. For backwards compatibility with version 7.1.x, if no value is passed, the default value of this parameter is that of `timeout`. This parameter is only available for Android. It has no effect on iOS or Web platforms. | `timeout` | 8.0.0 | | **`enableLocationFallback`** | boolean | 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. This parameter is only available for Android. It has no effect on iOS or Web platforms. | true | 8.0.0 | @@ -254,4 +263,5 @@ The following table list all the plugin errors: | OS-PLUG-GLOC-0014 | Android | Google Play Services error user resolvable. | | OS-PLUG-GLOC-0015 | Android | Google Play Services error. | | OS-PLUG-GLOC-0016 | Android | Location settings error. | -| OS-PLUG-GLOC-0017 | Android | Unable to retrieve location because device has both Network and Location turned off. | \ No newline at end of file +| OS-PLUG-GLOC-0017 | Android | Unable to retrieve location because device has both Network and Location turned off. | +| OS-PLUG-GLOC-0018 | Android | Location permissions are not declared in manifest. Make sure at least ACCESS_COARSE_LOCATION is declared in AndroidManifest.xml, and optionally ACCESS_FINE_LOCATION if you require precise location access. | \ No newline at end of file