QuasarScaffold
HOW TO SETUP A QUASAR-VUE CAPACITOR ANDROID PROJECT
Feb 2025
Introduction
There are a number of steps you need to perform to properly scaffold and configure your Quasar-Capacitor project for creating Android apps using Vue.js.
This guide assumes, You Are:
- Running VS Code IDE (though in general this should work with other IDEs).
- Familiar with Vue.js, as it is a foundation for Quasar.
- A beginner.
Capacitor V7
Supposedly Quasar is now supporting Capacitor v7 (this tutorial is based on Capacitor v6). Note my initial tests trying to upgrade this project to Capacitor v7 failed in the thicket of versioning purgatory. Probably more my problem than anything else (read my rants).
GitHub Repo
For the truly lazy (like me) here's a link to my GitHub repository of this project (plus a bit of extras), so you don't have to code a thing.
https://github.com/tenace2/ScaffoldQuasarCapacitorV3
Claude AI notes
As a salute to laziness I actually had Copilot Agent mode (using Claude Sonnet 4) follow this tutorial-blog to create the GitHub project. It was a bit of sausage making, messy and you may not like all the ingredients, such as:
- Claude gobbled tokens attempting to fight with ESLint: so I had to turn off lint.
- Claude gobbled even more tokens fighting with Typescript: so I dumped it as well.
Note I had to create a copilot-instructions.md
file to provide guidance to the AI agent in order to follow this tutorial to actually code the project. Getting Copilot Agent mode going was an inspiration from Burke Holland, suggest following video:
https://www.youtube.com/watch?v=dutyOc_cAEU
.
Google Maps Failure
Note that while this geolocate tutorial app worked fine, I was never able to successfully implement the @capacitor/google-maps
plugin.
It would seem be a short walk to add a Google Maps page to this app, but alas, after weeks of work, it came to nada, nothing.
Just can't get it to behave either on my old Pixel 2 phone, or even in an emulator within Android Studio. Just clicking on the map would crash the app. etc, etc. Especially annoying was the behavior of the Chrome Device inspect tool, which would not display the map, or it would ghost. Or even more vexing: even just installing the plugin, without even invoking or using the plugin in a page component, it would throw a myriad of problems. I guess I'll use Leaflet?
.
About This Guide
I have attempted to show Information that has been directly gleaned from the internet and then "cut-n-pasted" incorporated into this guide between sets of lines and "Per research".
This guide is intended for someone who is starting out on their journey (like me). Experienced developers will likely find most of the information, well, superfluous.
Disclaimer
There are very probably egregious errors, omissions, and mistakes in this guide. If you find them, blame a couple things:
-The existing documentation on the Quasar and Capacitor websites (as of Feb 2025).
--The Quasar web site does have info about Capacitor, but it's not very extensive, and it's difficult find any comprehensive implementations of a capacitor plugin out there on the internet.
. https://quasar.dev/quasar-cli-vite/developing-capacitor-apps/introduction
--And the Capacitor website really doesn't have much to say about Quasar.
If there was more documentation on how to properly integrate these two frameworks, I would not have found putting this guide together worth the effort in the first place.
-Oh, and also blame the fact there is so much to learn about both Quasar, Capacitor and Vue!...and I'm just learning.
The following image pretty much captures the experience of trying to get a clean install of a Quasar-Capacitor-Android project: What a headache.
QUASAR IS AN AWESOME FRAMEWORK!
Quasar and Capacitor are truly wonderful, but I've found it a bit difficult to get them to behave, so hence this guide. At the end of all this you should behold the following desktop configuration!
Here's a few links to relevant information
https://quasar.dev/
.
. https://quasar.dev/quasar-cli-vite/developing-capacitor-apps/introduction
Note of Why Quasar?
Take the notes below with a grain-of-salt...some of it is pure opinion. Do your own reasearch!
There are other frameworks out there besides Quasar that will help develop cross platform apps with one code base to target multiple platforms, such as Flutter, React Native, and Xamarin, and Ionic.
Flutter is a Google thing...and Google simply abandons products without a second thought. I've been burned so many times relying on a Google platform or framework that simply disappeared, that I would never adopt anything they make unless forced too by their market monopoly power, such as Google Maps.
React Native...well you have to learn React. But it supposedly renders native UI components instead of using WebView12. This results in apps that are supposedly indistinguishable from those built using native languages like Objective-C or Java.
.Xamarin... this is a Microsoft framework, requires C# and .NET. Yuck. I'd probably have to buy a windows machine. More Yuck. But, It compiles directly to native code, avoiding the use of WebView.
Ionic looks pretty good, but it's just a javascript/typescript thing? I don't really know, but as of 2025 it's more popular than Quasar, so that does say something.
Quasar - so far, given that I'm primarily developing with Vue.js, I haven't found a better framework to develop in.
Some more notes Per research further discussion:
Ionic and Quasar are both popular frameworks for cross-platform app development, but they have some key differences:
Performance and Rendering
Ionic relies on WebView for app rendering, which can sometimes result in performance issues1. Quasar, on the other hand, takes advantage of platform-specific features, potentially offering better performance and a more native feel1.
Challenges of WebView-Based Frameworks
-
Performance Limitations: WebViews may not match the speed or responsiveness of fully native apps due to additional rendering layers24.
-
Device Compatibility Issues: Variations in WebView implementations across operating systems can lead to inconsistent behavior4.
-
Limited Access to Native Features: While plugins can bridge some gaps, certain advanced device functionalities may not be accessible26.
-
User Experience Concerns: Apps relying heavily on WebViews may feel less fluid compared to native applications7.
Platform Support
While Ionic primarily focuses on hybrid mobile applications, Quasar targets a broader range of platforms including mobile (iOS, Android) using Capacitor, desktop (Windows, Mac, Linux) using Electron, and web applications including SPA and SSR 12.
UI Components and Design
Ionic offers pre-built UI components following Material Design and Apple's Human Interface Guidelines1. (Ionic seems to have an Apple bias)
Quasar provides a more extensive set of components and styling options, offering both Material Design and iOS-style components12.
Framework Base
Ionic can be used with various JavaScript frameworks, while Quasar is built specifically on Vue.js, offering seamless integration with Vue's ecosystem13.
Development Experience
Quasar is often praised for its comprehensive toolset, which includes a wide range of UI components, layout elements, and helpers2. It also offers features like code splitting, lazy loading, and server-side rendering out of the box2.
Community and Ecosystem
Ionic has a larger, more established community with extensive documentation and a broad range of plugins1.
Quasar, while newer, has a growing community and offers a collection of ready-to-use plugins and integrations13.
Performance Optimization
Quasar emphasizes performance with built-in features like code minification, source mapping, and tree shaking2.
Customization and Theming
Quasar offers more extensive theming capabilities, including support for right-to-left (RTL) languages2.
In summary, while various frameworks have their strengths, Quasar seems to offer more flexibility in terms of platform support and potentially better performance, especially for Vue.js developers. However, Ionic's larger community and ecosystem might be advantageous for some projects.
Guide Structure
This guide is divided into seven major parts:
-
Prerequisites for a Quasar / Vue project
Setting up your development environment -
Project Planning link
What you should consider before starting -
Project Setup & Scaffold Steps link
Detailed configuration steps with thorough explanations
Launch as SPA web app link -
Launching Your Android Application in Dev link
Getting your development environment running -
Installing Capacitor Plugins link
Installing the geolocation plugin -
Creating a Hello World page link
-
Creating a page that uses the plugin link
A very basic geolocation plugin page
Oh, and some credits are at the end...
PART 1: Prerequisites for a Quasar Project
Prerequisites Checks
There are 3 steps:
-have Node, nvm and npm installed
-verify Android Studio install
-set android environment variables
Part 1 - Step 1. Check Node.js, npm, and nvm versions:
First, let's verify the development environment
==Important DO NOT IGNORE: ==
As of Feb 2025, default Quasar & Capacitor (v6) required Node v18.
This will probably change, hence why having node version manager (nvm) is essential.
Terminal commands:
node --version # Should be >=18.0.0
npm --version # Should be >=6.13.4
nvm --version # Check if nvm is installed
If nvm is not installed:
# For macOS using brew
brew install nvm
After installation, add these lines to your ~/.zshrc or ~/.bash_profile:
export NVM_DIR="$HOME/.nvm"
[ -s "/opt/homebrew/opt/nvm/nvm.sh" ] && \. "/opt/homebrew/opt/nvm/nvm.sh"
[ -s "/opt/homebrew/opt/nvm/etc/bash_completion.d/nvm" ] && \. "/opt/homebrew/opt/nvm/etc/bash_completion.d/nvm"
Reload your terminal configuration:
source ~/.zshrc # or source ~/.bash_profile
Verify nvm installation:
nvm --version
If Node.js version is not 18:
# Install Node.js 18
nvm install 18
# Use Node.js 18
nvm use 18
# Verify the version
node --version # Should show v18.x.x (for Capacitor v6)
To use a specific version directly, use the terminal command:
nvm use <version>
npm and npx are both tools used in Node.js development, but they serve different purposes:
npm (Node Package Manager)
- Used for installing and managing packages in Node.js projects
- Primarily handles package installation, either locally or globally
- Manages dependencies listed in the package.json file
- Requires packages to be installed before they can be used
- Usage:
npm install <package>
npx (Node Package Execute)
- Executes Node.js packages without requiring installation
- Allows running packages directly from the npm registry
- Ideal for one-time or infrequent package usage18
- Can run different versions of packages without conflicts
- Usage:
npx <package>
I have more notes in some section [[#Part 3F - Sync Your Project]] on npx
The node_modules folder plays a crucial role in your Quasar and Capacitor project, as it does in most Node.js-based applications:
Dependency Storage:
node_modules is where all the external libraries and packages that your project depends on are stored15. This includes Quasar, Capacitor, and any other packages you've installed using npm or yarn.
Local Package Access:
When you import a package in your code, Node.js looks for it in the node_modules folder1. This allows your application to use these dependencies without needing to specify their full path.
Project-Specific Dependencies:
Each project has its own node_modules folder, ensuring that different projects can use different versions of the same package without conflicts.
Automatic Management:
When you run npm install or yarn install, the package manager reads your package.json file and downloads all the required dependencies into the node_modules folder.
Exclusion from Version Control:
The node_modules folder is typically not included in version control systems like Git due to its large size35. Instead, developers share the package.json file, which lists all the dependencies.
Recreating Dependencies:
Other developers can recreate the node_modules folder on their machines by running npm install or yarn install, using the package.json as a reference.
For Quasar and Capacitor specifically:
- These frameworks, along with their dependencies, are stored in node_modules when you set up your project
- When you add Capacitor plugins to your Quasar project, they are installed into the node_modules folder
- Remember, if you're having issues with missing modules after cloning a repository or switching branches, try deleting the node_modules folder and package-lock.json file, then running npm install again to ensure all dependencies are correctly installed4
Part 1 - Step 2. Verify Android Studio installation:
Open Android Studio and verify the following:
Go to Tools > SDK Manager
Under "SDK Platforms" ensure you have:
- Android 14.0 (API 34) or latest stable
- Android 13.0 (API 33)
Under "SDK Tools" verify you have:
- Android SDK Build-Tools
- Android SDK Command-line Tools
- Android Emulator
- Android SDK Platform-Tools
Part 1 - Step 3. Set up android environment variables
Add to ~/.zshrc or ~/.bash_profile:
export ANDROID_HOME=$HOME/Library/Android/sdk
export PATH=$PATH:$ANDROID_HOME/tools
export PATH=$PATH:$ANDROID_HOME/platform-tools
Following is on a MacBook
Here’s how to do it:
1. Open Terminal.
2. Open your .zshrc
file in a text editor.
You can use nano
, which is simple and built-in:
nano ~/.zshrc
.
3. Add the lines shown above to the end of the file
4. Save and close the file.
If using nano
, press Control + X
, then Y
to confirm, and Enter
to save.
.
To apply these changes immediately after editing .zshrc, run:
source ~/.zshrc
This will reload your shell configuration and make the environment variables available in your current terminal session.
What These Lines Do:
export ANDROID_HOME=$HOME/Library/Android/sdk
:
- Sets the ANDROID_HOME environment variable to the directory where the Android SDK is installed
- The ANDROID_HOME variable is used by many Android tools to locate the SDK
- Tools like Gradle, Android Studio, and command-line utilities rely on this variable
export PATH=$PATH:$ANDROID_HOME/tools
:
- Appends the tools directory inside the Android SDK to your system's PATH
- Allows you to run commands like android from any location without typing the full path
export PATH=$PATH:$ANDROID_HOME/platform-tools
:
- Appends the platform-tools directory to your system's PATH
- Contains essential utilities like adb (Android Debug Bridge) and fastboot
Why This Is Important:
- Makes Android development tools easily accessible from the command line
- Eliminates need to type full paths to tools
- Essential for tasks like building apps, managing emulators, or debugging devices
How It Works in Practice:
After adding these lines to your .zshrc, you can:
- Use commands like adb devices or emulator -avd <avd_name> directly
- Run Gradle builds or other tasks that depend on the Android SDK
PART 2: Project Planning
Before you begin scaffolding Quasar, think carefully about the following names:
- Directory & Name
- Product Name
- App ID
- Capacitor AppName
Part 2 - Note 1. Directory & Name
Plan the naming of your project name-directory-folder.
- You will be using cd a lot, so don't get too carried away: keep it simple
- This will also be the "name" in your root package.json file
- This is typically lowercase, no spaces
- Used for the project directory and npm package name
- Best practice: Use kebab-case (e.g., "my-map-app") or single words
Part 2 - Note 2. Product Name
Product Name ("productName" in root package.json):
- This is the user-friendly display name
- Can include spaces and proper capitalization
- This often appears in the app launcher, title bar, etc.
Part 2 - Note 3. App ID
Capacitor App ID (appId in capacitor.config.json):
- Should follow reverse domain name notation
- Format:
com.company.appname
- In my case I used a GitHub Pages domain: "io.github.abcdefg.googlemap"
- Do Not Use Dashes in the app ID !!!
- Best practice: Use a domain you control
- This is important for app stores and must be unique
- Even if you are creating an Android app, you could still launch a test as a spa
Part 2 - Note 4 Capacitor AppName
Capacitor App Name (appName in capacitor.config.json):
- Usually matches the productName in package.json
- This is what shows under the app icon
PART 3: Quasar Project Setup Steps
This section provides detailed instructions for setting up your Quasar project with Capacitor for Android development. Follow these steps in order:
Part 3 Overview
A - Install Quasar CLI
Installation and configuration of the Quasar Command Line Interface link
B. Scaffold Quasar / Vue
Creating the base Quasar & Vue project structure link
C. Add Capacitor to Quasar
Integrating Capacitor framework with your Quasar project link
D. Add Capacitor/android to project
Setting up the Android platform configuration link
E. Create /dist directory
Preparing the distribution directory for your application link
F. Sync the project
Synchronizing Quasar and Capacitor configurations link
Each step will be covered in detail in its own section. Follow the steps sequentially to ensure proper setup of your development environment.
Part 3A - Install Quasar CLI
In your project directory, first check the install of the Quasar CLI:
quasar --version
The above command will return the CLI version if installed, something like:
@quasar/cli 2.4.1
Run the Quasar CLI install command if not already installed:
npm i -g @quasar/cli
Note also that you can run the following command:
quasar info
This will show a complete report of your Quasar project (if the Quasar framework project has been scaffolded).
Part 3B - Scaffold Quasar / Vue
To initialize and scaffold the Quasar and Vue.js project:
-Create Quasar project
-Install and verify
-Test initial build
P3B Step 1: Create new Quasar project
Run this command:
npm init quasar@latest
Or run this command:
npm create quasar
The above command seems to take a long time to run...you just have to wait.
As of Feb 2025, I can't determine definitively which of the above commands is better. Either command will then prompt the same set of selection options: see note below.
The command "npm init quasar@latest"
will often hang on this last step:
✔ Install project dependencies? (recommended) › Yes, use npm
It may fail with this message:
npm error code UNABLE_TO_GET_ISSUER_CERT_LOCALLY
npm error errno UNABLE_TO_GET_ISSUER_CERT_LOCALLY
npm error request to https://registry.npmjs.org/@eslint%2fjs failed, reason: unable to get local issuer certificate
When this happens, you'll see:
Quasar • ⚠️ Could not auto install dependencies. Probably a temporary npm registry issue?
Quasar • Initialized Git repository 🚀
To get started:
`cd yourprojectfolder`
yarn #or:` `npm install`
yarn lint --fix # or: `npm run lint -- --fix`
quasar dev # or: yarn quasar dev # or: `npx quasar dev`
Documentation can be found at: https://quasar.dev
If it simply hangs, you can Cntrl-C to end the terminal command, then cd into your project and run npm install
. Note there doesn't seem to be any harm in running the command multiple times. Below is a screen shot of what this looks like:
- ? What would you like to build?
- Select: App with Quasar CLI, let's go! - spa/pwa/ssr/bex/electron/capacitor/cordova
- Project folder:
<your project folder name>
- Select: Quasar v2 (Vue 3) latest and greatest - recommended
- Choose: Javascript or TypeScript (Javascript is easier for beginners)
- Select: Quasar App CLI with Vite 6 (v2) - recommended (Important!)
- Package name:
<whatever you need-could be project folder name>
- Project product name:
Must start with letter if building mobile apps
(e.g., My Quasar App) - Project description: A Quasar test
- Select: Composition API with
<script setup>
- Pick your CSS preprocessor: Sass with SCSS syntax
- Check the features needed for your project:
- Vue Router (Yes)
- Pinia (Yes)
- ESLint (Yes)
- Install dependencies (Yes)
After running npm init quasar@latest
your project structure should look something like this:
your-project/
├── .quasar <-quasar
├── node_modules
├── src/ <- src/ folder Vue project.
│ ├── assets
│ ├── components/
│ │ └── EssentialLink.vue
│ ├── css
│ ├──layouts/
│ │ └── MainLayout.vue
│ ├── pages/
│ │ ├── IndexPage.vue
│ │ └── ErrorNotFound.vue
│ ├── router/
│ │ ├── index.js
│ │ └── routes.js
│ └── App.vue
├── index.html
├── package-lock.json
├── package.json <- Pay close attention to dependency versions
└── quasar.config.js <-quasar
The above /src
folder should look familiar to a Vue.js developer.
The package.json in the project root
will have a dependency list similar to the following:
{
"dependencies": {
"@quasar/extras": "^1.16.4", <-quasar
"axios": "^1.2.1",
"pinia": "^3.0.1",
"quasar": "^2.16.0", <-quasar
"vue": "^3.4.18",
"vue-router": "^4.0.0"
},
"devDependencies": {
"@eslint/js": "^9.14.0",
"@quasar/app-vite": "^2.1.0", <-quasar
"autoprefixer": "^10.4.2",
"eslint": "^9.14.0",
"eslint-plugin-vue": "^9.30.0",
"globals": "^15.12.0",
"postcss": "^8.4.14",
"vite-plugin-checker": "^0.8.0"
}
}
Note there are no Capacitor-related elements yet.
P3B Step 2: Install & Verify
Change directories into the project root:
cd <your project folder>
Then run:
npm install
Verify the terminal output resulting from the install
Running "npm install" performs the following actions:
Installs Dependencies:
- Reads the package.json file to identify all dependencies
- Downloads dependencies from npm registry
- Installs them into the node_modules folder
Creates or Updates package-lock.json:
- Uses existing package-lock.json if it exists
- Generates new one if it doesn't exist
- Locks dependency versions for consistency
Also run in your project root:
quasar info
- Check Quasar installation
- Verify Vite is listed as the build tool
following is when my project was pretty much fully scaffoled/configured
Operating System - Darwin(23.5.0) - darwin/arm64
NodeJs - 18.20.6
Global packages
NPM - 10.8.2
yarn - Not installed
pnpm - Not installed
bun - Not installed
@quasar/cli - 2.4.1
@quasar/icongenie - Not installed
cordova - Not installed
Important local packages
quasar - 2.17.7 -- Build high-performance VueJS user interfaces (SPA, PWA, SSR, Mobile and Desktop) in record time
@quasar/app-vite - 2.1.0-- Quasar Framework App CLI with Vite
@quasar/extras - 1.16.17 -- Quasar Framework fonts, icons and animations
eslint-plugin-quasar - Not installed
vue - 3.5.13 -- The progressive JavaScript framework for building modern web UI.
vue-router - 4.5.0
pinia - 3.0.1 -- Intuitive, type safe and flexible Store for Vue
vite - 6.1.0 -- Native-ESM powered web dev build tool
vite-plugin-checker - Not installed
eslint - 9.20.1 -- An AST-based pattern checker for JavaScript.
esbuild - 0.24.2 -- An extremely fast JavaScript and CSS bundler and minifier.
typescript - Not installed
workbox-build - Not installed
register-service-worker - Not installed
electron - Not installed
@electron/packager - Not installed
electron-builder - Not installed
@capacitor/core - 6.0.0-- Capacitor: Cross-platform apps with JavaScript and the web
@capacitor/cli - 6.2.0-- Capacitor: Cross-platform apps with JavaScript and the web
@capacitor/android - 6.0.0-- Capacitor: Cross-platform apps with JavaScript and the web
@capacitor/ios - Not installed
Quasar App Extensions
None installed
Networking
Host - abcdefgh-MacBook-Air.local
en0 - 10.0.0.125
P3B Step 3: Test initial build - SPA
You will now want to make sure Quasar (and Vue) are working as a plain SPA web app.
npm run dev # Should open development server
You should launch a spa web page similar to below...be sure to enable dev tools and look for any problems.
Part 3B Summary of scaffolding Quasar
So, in summary to Part 3B, to initially scaffold the Quasar project, run these 3 commands:
npm i -g @quasar/cli
npm init quasar@latest
npm install
The above 3 commands will:
- Allow you to run Quasar CLI commands
- Create an initially scaffolded Quasar-Vue project
Part 3C - Add Capacitor to Quasar
There are a couple steps to install capacitor to a Quasar project.
-Add capacitor structures to project using Quasar command
-Add capacitor config to project root
using npm commands
-Verify installation
P3C - Step 1: Add Capacitor to Quasar
Run the following Quasar command in your project root
folder:
quasar mode add capacitor
The above Quasar command will prompt for:
What is the Capacitor app id? (org.capacitor.quasar.app)
The app id is reverse domain name notation (ref project planning notes)
The folder /src-capcitor
will then be created in your project and the app id will go into the capacitor.config.json
file.
The newly created src-capacitor
structure will also hold the android structures in subsequent steps.
P3C Step 1 Notes
I recommend you review each of the notes below after running quasar mode add capacitor
.
Running quasar mode add capacitor
will probably default in older versions of capacitor (v6), as shown in the package.json
in /src-capacitor
folder (as of Feb 2025). But by the time you read this, who knows? You will just have to fight the version dependency battle.
Research indicates the Quasar team have the command quasar mode add capacitor
default in a supposedly more stable versions of capacitor at v6 (but Capacitor plugins, as of Feb 2025, are at v7)
The version is crucial!
Subsequent steps to install @capacitor/android
and other capacitor plugins can run into version issues, because version 7 of @capacitor/android
will default in, which will conflict with @capacitor/core at v6.
You may see an error msg such as this:
[warn] @capacitor/core@6.2.0 version doesn't match @capacitor/android@7.0.1 version.
Consider updating to a matching version, e.g. w/ npm install @capacitor/core@7.0.1
Attempts to upgrade capacitor/core might drag you down a rabbit hole of chained updates to your entire project, including Node.js.
It appears that (as of Feb 2025) Capacitor v6 requires Node v18.
-But Capacitor v7 requires Node v20.
-But Node v20 doesn't apparently work with the Quasar version that is defaulted in my npm create quasar.
So I recommend avoiding to attempts to upgrade individually. And note: I was never able to land a clean working v7 Capacitor into my Quasar project. Again: there seems to be no comprehensive versioning guide to install a Node-Vue-Quasar-Capacitor-Android-plugin stack.
here's some links to other notes ranting about version dependencies:
[[#P3D Step 1 Notes]]
[[#P3G Step 1 Note 1 Versioning Challenges read]]
Running command quasar mode add capacitor
in project root does of course negate the need to run specific NPM commands in a /src-capacitor
folder:
npm install @capacitor/core
- and
npm install @capacitor/cli
Per research:
Running "quasar mode add capacitor"
does more than just installing the Capacitor core package.
Here's why it's important:
- It sets up the Capacitor integration within your Quasar project structure.
- It creates a dedicated
"/src-capacitor"
folder in your Quasar project, which contains the necessary configuration files for Capacitor. - It automatically adds platform-specific folders (ios and android) to your project root when you add those platforms.
- It configures your Quasar project to work seamlessly with Capacitor, including setting up build processes and development scripts.
- It ensures compatibility between Quasar and Capacitor versions, reducing potential conflicts.
Simply running "npm install @capacitor/core"
(in project root) would only install the core package without integrating it properly into your Quasar project structure or setting up the necessary configurations. The "quasar mode add capacitor"
command provides a more comprehensive and streamlined setup for using Capacitor with Quasar
Rant Alert: But, why running "quasar mode add capacitor" also does not update the package.json
and node_mooules in the Quasar project root is simply inexplicable and exasperating. It should, as this just creates extra steps and opportunities for error and confusion.
If your floundering around the internet attempting to figure out how to land a clean install of Quasar-Capacitor-Android, you will probably come across the cmnd:
npx cap init
This command npx cap init
will create a completely new scaffold of your project right on-top of everything you've done with "quasar mode add capacitor"
. Thus you will either duplicate files and directories, or over-write files and directories...either way it's kind-of-mess. If using quasar to install capacitor, I recommend avoiding also using "npx cap init".
At the end of scaffolding capacitor when using the Quasar command quasar mode add capacitor
, the project structure should look something like this:
your-project/
├── .quasar
├── node_modules
├── src/
├── src-capacitor/ <- new folder created
│ ├── node_modules <- more node_modules
│ ├── www
│ ├── capacitor.config.json
│ ├── package-lock.json
│ └── package.json <- Yet another package.json file!
├── index.html
├── package-lock.json
├── package.json
└── quasar.config.js
The package.json
file under /src-capactor
folder should look something like this:
{
"name": "gmap9",
"version": "0.0.1",
"description": "A Quasar Google Map test",
"author": "abcdefg2 <abcdefg2@icloud.com>",
"private": true,
"dependencies": {
"@capacitor/app": "^6.0.0", <- v6!!!
"@capacitor/cli": "^6.0.0", <- v6!!!
"@capacitor/core": "^6.0.0". <- v6!!!
}
}
Special Note: Notice above that Quasar defaulted in v6 capacitor!!!
Each of the files perform different function (duh)
@capacitor/app
This is a specific Capacitor plugin that provides functionality related to the app itself.
It offers methods for handling app-level events and information, such as:
- Detecting when the app is put into the background or brought to the foreground
- Accessing app information like build and version numbers
- Handling deep links
- Managing app exit events
It's an optional plugin that you can add to your project for additional app-specific functionality.
NOTE: @capacitor/app does not need a separate install step in theroot
package.json.
@capacitor/core:
This is the main package that provides the core functionality and APIs for Capacitor.
It contains the JavaScript runtime that allows web apps to interact with native platform features.
It's used in your web application code to access Capacitor's APIs and plugins.
It provides a consistent API layer that works across all platforms (iOS, Android, and web).
NOTE: @capacitor/core ** does need ** a separate step to install in the root
package.json
@capacitor/cli
The @capacitor/cli is the Command Line Interface for Capacitor, a crucial tool for managing Capacitor projects. It provides several key functionalities:
Project initialization: The CLI allows you to initialize new Capacitor projects with the "npx cap init" command5.
. Platform management: It enables adding native platforms to your project, such as iOS and Android, using commands like "npx cap add android" or "npx cap add ios"5.
. Project synchronization: The CLI facilitates syncing your web app with native projects using "npx cap sync"1.
Plugin management: It helps in adding, removing, and updating Capacitor plugins3.
Build and run: The CLI assists in building and running your app on different platforms3.
Project configuration: It allows you to manage and update your project's configuration settings3.
Code copying: The CLI handles copying your web app's code to native platforms and updating plugin code in native projects3.
NOTE: @capacitor/cli ** does need ** a separate step to install in the root
package.json
The Capacitor CLI is typically installed as a development dependency in your project, ensuring that different projects can use different CLI versions if needed**
The capacitor.config.json
file will probably initially look something like this:
{
"appId": "com.abcdefg2.org",
"appName": "My Gmap9 Test",
"webDir": "www"
}
The webDir
will probably need to be changed at some point to "dist/spa"
.
The dist/spa
directory is created in a following step.
As far as I can determine, the "www"
is a default webpack configuration,
and "dist/spa"
is a Quasar thing.
If you don't change the webDir
, you will might get a common bug that results in a white-screen on android.
There is a specific section, Part 3 E Create /dist directory which will discuss the webDir
in more detail.
P3C - Step 2: Add Capacitor config In Project Root
You MUST manually install the @capacitor/cli
and the @capacitor/core
modules in the project root! ***Make sure root install matches the version in /src-capacitor
Change directories to your project root.
In your project root directory first run:
npm install @capacitor/cli@6
Then run:
npm install @capacitor/core@6
CRUCIAL!!! DO NOT IGNORE THIS NOTE!!!
Add Capacitor cli and core to the package.json file in Root Directory for Vite web build.
Running "quasar mode add capacitor"
in the root directory only creates src-capacitor folder and the capacitor related files configurations in the /src-capcitor
directory.
The command "quasar mode add capacitor"
does not however update the package.json
file or update the node_modules in the project root, which turns out to be (as of Feb 2025) absolutely crucial.
Per research:
Point 1
Why@capacitor/core
Must Be Installed in the Root of the Quasar Project.
Ensures Compatibility with Quasar's Web Build System
- Quasar's frontend relies on Webpack (or Vite) for bundling the application.
- The Capacitor plugins and API calls (import { Camera } from '@capacitor/camera') are part of this web build process.
- If
@capacitor/core
is not in root node_modules, Quasar's Webpack build may not resolve, leading to "module not found" errors.
Point 2: Install in Project Root? Or just in /src-capacitor
?
Which capacitor plugins features-plugins-components-api (what ever you want to call them) do you need to install in the project root so it just updates the root package.json
and the node_modules?
And which capacitor plugins to only install into the /src-capacitor
folder so it only updates the package.json
file and node_modules in the /src-capacitor
folder?
It turns out this is nearly pure guess-work.
There is no definitive chart or guide. As of Feb 2025, here's a little bit of what I've learned through arduous trial and error and endless floundering around on the internet.
The heuristic rule of thumb seems to be:
- Native? or not Native?
If the capacitor feature is going to exercise native phone features, such as haptics or camera, only install in just/src-capacitor
.
Examples:
Install in both project root and src-capacitor:
@capacitor/core
@capacitor/geolocation
Install in just /src-capacitor
:
@capacitor/android
The package.json
file dependencies in the project root should subsequently look something like the json file below. Note the addition of the capacitor cli and core.
{
"dependencies": {
"@capacitor/cli": "^6.2.0", <- match package.json in src-capacitor
"@capacitor/core": "^6.2.0", <- match package.json in src-capacitor
"@quasar/extras": "^1.16.4",
"axios": "^1.2.1",
"pinia": "^3.0.1",
"quasar": "^2.16.0",
"vue": "^3.4.18",
"vue-router": "^4.0.0"
},
"devDependencies": {
"@eslint/js": "^9.14.0",
"@quasar/app-vite": "^2.1.0",
"autoprefixer": "^10.4.2",
"eslint": "^9.14.0",
"eslint-plugin-vue": "^9.30.0",
"globals": "^15.12.0",
"postcss": "^8.4.14",
"vite-plugin-checker": "^0.8.0"
}
}
P3C - Step 3: Verify installation
Check the capacitor CLI version in project root
:
npx cap --version
Change directories into the /src-capacitor folder:
cd src-capacitor
Then run to check capacitor in /src-capacitor
:
npm list @capacitor/core
The capacitor core version, which should match package.json.
Part 3D - Add Capacitor/android to project
Now we can start configuring things to actually create an Android app. These steps will allow Android Studio to launch.
NOTE: The @capacitor/android
is native and only needs installed in the /src-capacitor
folder.
The install of capacitor android is a two step process. As an analogy: think about this as repairing your car:
- First go to the parts store and get the car parts (install android).
- And then you actually bolt the parts to the car (create android).
P3D - Step 1: Install Android Platform Package
The step is like going to the automotive store to get your car parts.
Be sure and change directories into the /src-capacitor
directory-folder:
cd src-capacitor
then
npm install @capacitor/android@6.0.0
The package.json
file in /src-capacitor
folder should then look something like this:
{
"name": "gmap9",
"version": "0.0.1",
"description": "A Quasar Google Map test",
"author": "abcdefg2 <abcdefg2@icloud.com>",
"private": true,
"dependencies": {
"@capacitor/android": "^6.0.0", <- android added
"@capacitor/app": "^6.0.0",
"@capacitor/cli": "^6.0.0",
"@capacitor/core": "^6.0.0"
}
}
P3D Step 1 Notes
Warning Note:
Just running the following will default in android v7.
npm install @capacitor/android
This v7 android will conflict with the the v6 capacitor/core that Quasar defaulted.
Check your package.json
file in /src-capacitor
Run following command to stay compatible with package.json versions in src-capacitor folder, if of course your cli
and core
are at v6:
npm install @capacitor/android@6.0.0
Link to other notes & rants about versioning issues:
[[#P3C Step 1 Notes]]
[[#P3G Step 1 Note 1 Versioning Challenges read]]
Here is a link to the official Capacitor documentation on the @capacitor/android
package, (and it really doesn't tell you much about versioning):
https://capacitorjs.com/docs/android#adding-the-android-platform
/src-capacitor
Warning: Why Not Install @capacitor/android in the Project Root?
Per research:
Unlike @capacitor/core, which is needed in both the web and native contexts, @capacitor/android is a native platform package that is only needed by the Android project inside src-capacitor.
🚨 Problems If Installed in the Root:
- Not Needed for Web Builds: The root Quasar project does not need @capacitor/android, as it does not run any Android-specific code.
- Potential Dependency Conflicts: Installing @capacitor/android at the root can sometimes cause conflicts when running npx cap sync, as Capacitor expects platform dependencies inside src-capacitor.
Per research:
Why @capacitor/whateverplugin
usually needs to be Installed In just /src-capacitor
:
(a) Native Runtime Dependency for Capacitor's Native Code
- The src-capacitor directory is where Capacitor's native project (Android/iOS) is initialized.
- The native build system (gradle for Android, CocoaPods for iOS) may require @capacitor/core to be present.
- When you run npx cap sync, it scans src-capacitor to gather necessary dependencies for native builds.
(b) Ensures Proper Dependency Resolution for Native Plugins
Some Capacitor plugins rely on @capacitor/core being locally available inside src-capacitor to properly initialize.
- If @capacitor/core missing, commands like
npx cap sync
ornpx cap run
could have errors where native plugins cannot find Capacitor core runtime
(c) There are exceptions (of course) to installing plugins in the project root: native platform package. (meaning: if the plugin is native android, probably don't install in project root).
P3D - Step 2: Create the Android Project Structure
This step is like actually bolting the car parts to your car.
Run the following in the /src-capacitor
folder:
npx cap add android
The above command npx cap add android
will actually create the /android
folder-directory within the /src-capacitor
folder.
When you run npx cap add android
, Capacitor is looking at the capacitor.config.json
file.
your-project/
├── .quasar
├── node_modules
├── src/
├── src-capacitor/
│ ├── node_modules
│ ├── www
│ ├── android/ <- location of android
│ ├── capacitor.config.json
│ ├── package-lock.json
│ └── package.json
├── index.html
├── package-lock.json
├── package.json
└── quasar.config.js
P3D Step 2 Android Notes
Note 1: Android Project Structure
Inside the /src-capacitor
folder:
After running npx cap add android
, the generated /android
folder follows a standard Android project structure that should look something like this:
android/
├── app/
│ ├── src/
│ │ ├── main/
│ │ │ ├── java/com/example/app/
│ │ │ │ ├── MainActivity.kt
│ │ │ ├── AndroidManifest.xml <- update capacitor plugins
│ │ │ ├── res/
│ │ │ │ ├── drawable/
│ │ │ │ ├── layout/
│ │ │ │ ├── mipmap/
│ │ │ │ ├── values/
├── build.gradle
├── settings.gradle
When you install a capacitor plugin you will probably need to manually update permission within the `AndroidManifest.xml` file.
Note 2: The MainActivity.kt File (Entry Point)
This is the Kotlin (kt) language for Android...you might default in Java. Either will work.
Located at:
src-capacitor/android/app/src/main/java/com/example/app/MainActivity.kt
This file extends BridgeActivity, which is provided by Capacitor:
package com.example.app
import com.getcapacitor.BridgeActivity
class MainActivity : BridgeActivity() {
}
What The above Means:
- BridgeActivity is a Capacitor-provided class that acts as the bridge between the web code (your Quasar app) and the native Android layer.
- This is where Capacitor loads the web content (index.html from your Quasar build).
- You can extend this class to register additional native plugins.
Note 3: AndroidManifest.xml (Native Configuration)
Located at:
/src-capacitor/android/app/src/main/AndroidManifest.xml
This file configures important settings like:
- Permissions (e.g., Camera, Geolocation)
- App name, package name
- Main activity that launches on startup
Example snippet below (please excuse the tick mark escape characters):
Note again when adding plugins, you will probably need to update permissions:
`<uses-permission...>
`<manifest xmlns:android="http://schemas.android.com/apk/res/android">
`<package="com.example.app">`
`<uses-permission android:name="android.permission.INTERNET" />`
`<application
android:allowBackup="true"
android:theme="@style/AppTheme">
`<activity
android:name=".MainActivity"
android:launchMode="singleTask"
android:theme="@style/AppTheme.NoActionBar">
`<intent-filter>
`<action android:name="android.intent.action.MAIN" />
</intent-filter>
`</activity>
`</application>
`</manifest>`
P3D Step 2 Note 4: Capacitor Plugins and Kotlin
My understanding of how Java is integrated into this architecture is sketchy at best. You do need to be sensitive to the versions of the JDK, JVM and Kotlin. Things can go wrong and you might be forced to fiddle directly with gradle files, which are way down inside the node modules. Do your own research!
Per some of my research:
If you install native plugins (e.g., Camera, Filesystem), Capacitor will automatically integrate them.
For example, if you install the capacitor camera plugin:
npm install @capacitor/camera
npx cap sync
It will register in Kotlin automatically. If needed, you can manually add them to MainActivity.kt:
import com.getcapacitor.BridgeActivity
import com.getcapacitor.Plugin
import com.getcapacitor.community.camera.CameraPlugin
class MainActivity : BridgeActivity() {
init {
registerPluginclass.java
}
}
DO NOT upgrade Gradle when prompted
DO NOT upgrade other Android dependencies automatically
Use "File > Invalidate caches and restart" if encountering IDE errors
P3D Step 3 : Verify android installation
Verify installation in the /src-capacitor
folder:
npm list @capacitor/android
ls android/ # Should show complete Android project structure
-
Your Quasar app runs inside a WebView on the Android.
-
Capacitor provides a JavaScript-to-Native bridge, allowing Quasar to interact with the native Android layer.
-
Note: some developers do not like WebView for various reasons, such as performance.
-
Kotlin is only used when you need to modify native functionality (e.g., adding custom plugins or tweaking the Android behavior).
Part 3E - Create Dist/Spa Directory
Additionally, there is yet another step:
- Create a
/dist
directory
To build your Quasar app for production generate the /dist directory with terminal command in the root directory:
quasar build
This will create a new /dist directory:
your-project/
├── .quasar
├── dist/spa <- new dist directory
├── node_modules
├── src/
├── src-capacitor/
├── index.html
├── package-lock.json
├── package.json
└── quasar.config.js
Then Very Important!
Update the capacitor.config.json
file!!!
In your /src-capacitor
folder, it must to point to the correct web directory:
"webDir": "../dist/spa"
The capacitor.config.json
file should look something like this:
{
"appId": "com.yoururl.gmap",
"appName": "quasargooglemap",
"webDir": "../dist/spa"
}
Notes: per my research here's an explanation for the above step regarding dist/spa
:
When you scaffolded your project using quasar mode add capacitor
, it defaulted to "www"
instead of "dist/spa"
for historical and compatibility reasons.
The "www"
directory is a common default for many web-based mobile frameworks, including Cordova, which Capacitor was designed to be compatible with. This default setting allows for easier migration from Cordova projects and maintains consistency with other hybrid mobile development tools.
However, Quasar's build process typically outputs to the "dist/spa"
directory for Single Page Applications. This mismatch between Quasar's output and Capacitor's default expectation is why you need to manually update the webDir
in the capacitor.config.json
file.
To align Capacitor with Quasar's build output, you should change the webDir
setting in your capacitor.config.json
If you do not run "quasar build", and just launch a dev server locally, you can probably keep "webDir": "www"
in capacitor.config.json
file.
Part 3F - Sync Your Project
Now sync your project with Capacitor to ensure everything is up-to-date run command in the src-capacitor directory:
cd src-capacitor
npx cap sync
P3F Note 1: Sync Process Details
Run npx cap sync
in the /src-capacitor
folder to synchronize your web app with the Capacitor project.
This command does two things:
- Copies your web assets to the native platforms
- Updates native plugins and dependencies
The npx cap sync command relies on the capacitor.config.json
file (in /src-capaitor
folder), which contains the following crucial piece of information:
"webDir": "dist/spa"
The npx cap sync
command is crucial because it:
-
Copies the latest web assets from your specified webDir to the native platform folders.
-
Updates Capacitor and Cordova plugins, ensuring all native dependencies are in place.
Do Not Run
npx cap sync
In Project root.
Note that if you run npx cap sync in the projectroot
directory, you will get an error, because the root directory does not have acapacitor.config.json
file.The error message seems rather confusing,
It should probably say something like:
*"Yo dummy, I couldn't find the capacitor.config.json file so I couldn't do anything. *
But if you read the error msg carefully it has a clue.
error Could not find the web assets directory: ./www. Please create it and make sure it has an index.html file. You can change the path of this directory in capacitor.config.json (webDir option). You may need to compile the web assets for your app (typically npm run build). More info: https://capacitorjs.com/docs/basics/workflow#sync-your-project
The clue in the above error is the command npx cap sync
looking for the webDir
(aka web assets directory) in the capacitor.config.json
file, which is only in the /src-capacitor
folder to create the web assets (for use in the WebView)
** Per Research: More tedious details**:
The command npx cap sync
is responsible for synchronizing your web app with the native platforms (Android/iOS). Specifically, it performs the following steps:
-
Copies Web Assets (
dist/spa
) to Native Platforms- It then copies the built web files (
index.html
, CSS, JavaScript, etc.) into each native platform’s web runtime directory:- For Android →
android/app/src/main/assets/public/
- For iOS →
ios/App/public/
- For Android →
- It then copies the built web files (
-
Updates Dependencies for Installed Plugins
- It looks at
package.json
to find any installed Capacitor plugins (e.g.,@capacitor/camera
,@capacitor/geolocation
). - Then, it ensures that the native Android/iOS projects include those plugins.
- It looks at
-
Runs
npx cap copy
Automatically- This is the same as running
npx cap copy
manually, meaning it updates the web assets in the native platforms.
- This is the same as running
-
Runs
npx cap update
Automatically- It checks for any new Capacitor dependencies and updates the Android/iOS native projects accordingly.
-
Ensures Android/iOS Project Files are in Sync
- It updates native platform files, such as
AndroidManifest.xml
andInfo.plist
, with any configuration changes from your Capacitor plugins.
Optionally, you can runnpx cap sync android
in the/src-capacitor
folder to specifically sync the Android platform.
- It updates native platform files, such as
**How capacitor.config.json
is Used
Your capacitor.config.json
file contains essential project settings:
-
appId
:"com.abcdefg.gmap"
- This is the unique application identifier (package name for Android, bundle ID for iOS).
-
appName
:"quasargooglemap"
- The display name of your app.
-
webDir
:"dist/spa"
- This tells Capacitor where to find the built web files.
- Since you're using Quasar,
"dist/spa"
is the directory where Quasar compiles the production build of your app.
Why is this important?
npx cap sync
relies onwebDir
to copy files to the native platforms.- If your Quasar build output changes (e.g., if you're using
dist/pwa
instead ofdist/spa
), you'd need to updatewebDir
.
/src-capacitor
importance
P3F Note 2: Directory Location Importance
Running npx cap sync
in the root directory of your Quasar-Capacitor project doesn't work because Quasar manages the Capacitor integration differently from a standard Capacitor project. Here's why:
-
Quasar's project structure: Quasar isolates Capacitor-related files in the
/src-capacitor
directory, including the relevantcapacitor.config.json
file. -
Capacitor configuration: The capacitor.config.json file in the
/src-capacitor
folder is the one Capacitor uses directly when building and running our app. -
Quasar CLI integration: Quasar CLI handles the Capacitor sync process when you run commands like
quasar dev -m capacitor
orquasar build -m capacitor
.
Core Capacitor Commands
These are the main commands used for managing Capacitor in your project:
-
npx cap init [appName] [appId]
Initializes Capacitor in your project by creating acapacitor.config.ts/json
file. -
npx cap add [platform]
Adds a Capacitor platform (ios
,android
,web
) to your project.
Example:
npx cap add android
-
npx cap update
Updates Capacitor and all installed plugins to the latest compatible version. -
npx cap sync
Copies web assets (dist
folder) to the native platform directories and updates dependencies.- This should be run after making changes to web assets or Capacitor plugins.
-
npx cap copy
Only copies web assets to the native platforms but does not update dependencies.- Use this when you update frontend code but don’t need to reinstall plugins.
-
npx cap open [platform]
Opens the native IDE for the given platform.-
Example:
sh
CopyEdit
npx cap open android # Opens Android Studio npx cap open ios # Opens Xcode
-
Development & Debugging Commands
These help with debugging and running your app:
-
npx cap doctor
Checks for common issues in your Capacitor project setup. -
npx cap run [platform]
Builds the app and runs it on a connected device or emulator.-
Example:
sh
CopyEdit
npx cap run android
-
-
npx cap serve
Serves the web app locally for testing (useful forcapacitor://
orhttp://
debugging).
Plugin & Configuration Management
Useful for managing Capacitor plugins and settings:
-
npx cap ls
Lists installed Capacitor platforms and plugins. -
npx cap plugin add [plugin-name]
Installs a Capacitor plugin.
Example:sh
CopyEdit
npx cap plugin add @capacitor/geolocation
-
npx cap plugin rm [plugin-name]
Removes a Capacitor plugin. -
npx cap migrate
Migrates Capacitor config files from JSON to TypeScript (if needed).
Platform-Specific Commands
These commands apply to a specific platform:
-
npx cap copy [platform]
Copies only web assets to the specified platform. -
npx cap sync [platform]
Syncs web assets and updates dependencies for a single platform.
Example:sh
CopyEdit
npx cap sync android
-
npx cap open [platform]
Opens the corresponding native project in its IDE.
Troubleshooting Tip
If you are facing issues with Capacitor commands, try:
npx cap doctor
This will scan for common misconfigurations.
P3F Note 1: Inspect your package.json files
At this point I highly recommend you inspect both package.json file, in project root
and in /src-capacitor
to insure the plugin is in fact installed.
They should look similar to what's below. (Note: a slight variation in capacitor versions between root
and /src-capacitor
didn't seem to cause issues).
Package.json in project root
:
Package.json in /src-capacitor
:
Conclusion to Part 3: Installing Quasar and Capacitor
And finally, always after installing plugins or making other updates, be sure and remember run in your /src-capacitor
folder:
npx cap sync
For notes about the npx cap sync
command, reference [[#Part 3F - Sync Your Project]] again for notes on "What npx cap sync
does".
Wasn't that a journey! And now, finally you might actually be able to do some actual development.
Part 4: Launch Android App
P4 Step 1 - Launching Your Application in Dev
To finally launch the app in development, run:
quasar dev -m capacitor -T android
The above command translates/parses roughly as follows:
quasar
: Hey Quasar,dev
: please launch a development server,-m capacitor
: in mode capacitor,-T android
: targeting android.
When you run the above command, a session of Android Studio should automagically launch.
You should be able to interact with your app in Android Studio, and on the test Android phone at the same time.
P4 Note A - Important Android phone Setup Notes
When setting up your development environment:
- You will plug-tether your test Android phone into your development machine via USB (MacBook, etc).
- Make sure your test Android phone has debug enabled.
- Make sure that wifi is enabled on your test Android phone.
- Make sure that your development machine (MacBook, etc) and the Android phone are on the same wifi network.
- Note that wifi in coffee shops, gyms, airports, etc often won't work!...some sort of port-forwarding security issue (TLDR...outside the scope of this document).
- Note that on the Android Studio you usually have to unplug the USB to your test phone and plug it back in to get it to hot-up and be recognized.
P4 Note B - Chrome Inspect Devices Tool
Also: HIGHLY RECOMMEND: use the chrome inspect devices tool!
Open a browser tab, put in:
chrome://inspect/#devices
When you launch the app you will be able to select your test Android phone and interact with it and show console log and review error messages from the WebView! This feature is so awesome!
But, just a note: the inspect tool can be a bit balky...and it sometimes lags up to a few minutes before it brings up the device list.
===The Chrome Inspect tool is crucial. ===
Per research: Your Quasar + Capacitor app is basically a web app running inside a native WebView. Chrome inspect is the tool designed to inspect WebViews. It’s basically poking inside the WebView as if it were a regular browser tab.
Android Studio is great at debugging native Android code (Java/Kotlin). It can see the device, it can launch and deploy your app, and it gives you full access to Logcat, which catches:
- Native logs (Log.d, Log.e, etc.)
- console.log() output only if it’s bridged properly via Capacitor/Cordova
Sometimes your JavaScript console.log messages do show up in Logcat — but not always clearly, and they can get buried in the noise.
Part 5 - Install Capacitor Plugins
And finally, now we can start adding Capacitor plugins that can work on a phone!
There are so many cool features that Capacitor has to offer. Here's a link: https://capacitorjs.com/docs/plugins
Each time you add/install a capacitor plugin:
- You may need to run an
npm install
in two places! - Once in
/src-capacitor
, then again in theroot
. - And you should carefully inspect both
package.json
files to confirm versions. - And run
npx cap sync
in thesrc-capacitor
directory/folder.
P5 - Step 1: Install Proper Plugin Versions
Example: @capacitor/geolocation
Let's use the geolocation plugin as an example for how to go about installing a plugin into your scaffolded Quasar project (which again in Feb 2025: Quasar defaulted in cli and core at v6).
.
https://capacitorjs.com/docs/apis/geolocation
The Capacitor geolocation plugin does not explicitly provide any map rendering or map visualization capabilities.
The @capacitor/geolocation
plugin uses the native features on your phone, but on your desktop machine running as a SPA, it is the browser that will determine lat-long location data, permissions, etc and "the browser will prompt the user".
.
This plugins "dual use" of native and not-native-browser-SPA capability makes it a good example to get your head wrapped around Capacitor.
.
A specific example is permissions:
Per research:
The reason why Geolocation.requestPermissions()
works on Android but not on the web in Capacitor.js is due to platform-specific implementation differences in the Capacitor Geolocation API.
Platform Differences in Permission Handling
Android (and iOS):
-
On native platforms like Android,
Geolocation.requestPermissions()
is implemented and used to explicitly request location permissions from the user at runtime35. -
This method interacts with the native permission system to prompt the user for access, which is required by Android's security model.
Web:
-
On the web,
Geolocation.requestPermissions()
is not implemented in the Capacitor Geolocation plugin5. -
The official Capacitor documentation explicitly states:
"Request location permissions. Will throw if system location services are disabled. Not availableon web.5
-
Instead, the browser's own Geolocation API manages permissions. When you call
getCurrentPosition()
orwatchPosition()
, the browser will automatically prompt the user for permission if needed, and there is no separate API to pre-request or check permissions in advance.
What Happens If You Call requestPermissions()
on the Web?
-
If you attempt to call
Geolocation.requestPermissions()
on the web, it will either do nothing or throw an error, because the method is not implemented for the browser25. -
Developers have reported that calling this method on the web leads to an error, and the correct behavior is to simply use
getCurrentPosition()
orwatchPosition()
and let the browser handle the permission prompt2.
Recommended Approach
-
On Android/iOS: Use
Geolocation.requestPermissions()
if you want to request permissions explicitly. -
On Web: Do not call
requestPermissions()
. Instead, callgetCurrentPosition()
orwatchPosition()
directly; the browser will handle permissions automatically.
Summary Table
Platform | Geolocation.requestPermissions() |
How to Request Permission |
---|---|---|
Android | Implemented | Use requestPermissions() or call getCurrentPosition() (auto-prompts) |
iOS | Implemented | Use requestPermissions() or call getCurrentPosition() (auto-prompts) |
Web | Not implemented | Call getCurrentPosition() or watchPosition() ; browser prompts user |
Key Takeaway
You see different behavior because the web platform relies on the browser's built-in permission prompts, and the Capacitor Geolocation plugin does not expose a manual permission request method for the web5. On Android, the plugin bridges to the native permission system, making requestPermissions()
functional and necessary35.
Google Geolocation API
Don't get the capacitor geolocation mixed up with the Google Geolocation API. Per research:
The Google Geolocation API is a completely separate web service (part of Maps Platform) used for:
- Server-side location processing
- Cell tower/Wi-Fi triangulation without GPS
- Requires API keys and billing
Capacitor's plugin bypasses this Google Geolocation API by using direct device capabilities.
cd to your project root
, and run:
npm install @capacitor/geolocation@6.x
run in /src-capacitor
folder:
cd src-capacitor
npm install @capacitor/geolocation@6.x
.
The same pattern (appending @6.x) should work for all the capacitor plugins.
P5 Step 1 Note 1: Versioning Challenges: read:
⚠️ Warning: Just blindly following the Capacitor documentation might get you into a versioning conflict.
Just running npm install @capacitor/geolocation
will default in v7 (as of Feb 2025).
This v7 will conflict with the capacitor core defaulted in with quasar mode add capacitor
command, which defaults in capacitor-core at v6 (as of Feb 2025).
Oh, and good luck trying to find a comprehensive guide to a proper versioning for the software stack of:
...Node,
.....Vite,
........Quasar,
................Vue,
....................Capacitor,
..................... Capacitor-Android
.........................Android-Studio
............................Android SDK
...............................Gradle
.................................JDK
.....................................Java & Jetbrains
......................................JVM
.........................................Kotlin
... there simply isn't any comprehensive guide as far as I could discern after many hours of flogging the internet.
It's mostly trial and error. The documentation IMO is lacking. Or it's just proof that I don't really know what I'm doing? More probable: All programming is stuck in this version dependency purgatory...and we just have to suffer, because that is after all, according to Buddha, what life is about.
This guide represents literally weeks of many, many scaffolding attempts to land a project that works (as of Feb 2025).
You're Welcome.
link to other rants about versioning dependencies:
[[#P3C Step 1 Notes]]
[[#P3D Step 1 Notes]]
P5 Step 2: Add Permissions in AndroidManifest.xml file
Yes folks, yet more manual steps! Who said this would be easy?
For Android, you sometimes, but not always, have to manually update the AndroidManifest.xml
file.
Do your own research!
Example:
Per Capacitor Documentation at following link: https://capacitorjs.com/docs/apis/geolocation
Find your AndroidManifest.xml
file, which should be way down under:
/src-capacitor
/android
/app
/src
/main
For the geolocation plugin, per capacitor, put the following into the AndroidManifest.xml
file:
<!-- Geolocation Plugin -->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-feature android:name="android.hardware.location.gps" />
Part 6 - Create a Hello World Page
First let's just get a hello world MapPageGeoLocate.vue
wired in,
...we will replace the hello world with code that uses the geolocation plugin in the next step.
IndexPage.vue
Note that for dev & testing I often create several versions of any given component...some times a dozen. So to facilitate that I use the IndexPage.vue
as a default landing page, then use buttons to route to my actual pages. Don't judge...it's just for testing.
Lets create an initial Hello World page under /src/pages
folder
...just to make sure the basic page, router and button work first.
P6 step A - Create a CapacitorGeoLocate.vue
- Hello World
Below is code for creating the CapacitorGeoLocate.vue
in the /src/pages
folder...again, we are just getting things wired up.
<script setup>
// No setup needed for this simple example
</script>
<template>
<div>
<h1>Hello, World!</h1>
</div>
</template>
<style scoped>
/* Add styles here if needed */
</style>
P6 step B - Create route in route.js
Add the following to the routes.js
under the /src/router
folder.
{
path: '/geolocation',
name: 'geolocation',
component: () => import('pages/CapacitorGeoLocate.vue'),
},
route.js
file should look like below:
P6 step C - Add button to IndexPage.vue
Add following to IndexPage.vue
under /src/pages
folder
<q-btn
color="primary"
label="Go to just Geolocate Page"
@click="$router.push('/geolocation')"
/>
IndexPage.vue
file should look like below:
P6 step D - Test CapacitorGeoLocate.vue page, button & route
Test as both a SPA web page and in Android Studio
SPA - In the root
directory run following terminal command.
npm run dev
You should see a web page with the button. The button should take you to the Hello World page.
!spaPage-Button.png
Android Studio - In the root
directory run the following terminal command.
quasar dev -m capacitor -T android
You should be able to launch as an Android app in Android Studio. The button should take you to the Hello World page.
!AllScreensHelloWorld.png
Part 7 - Update the Geolocation Page
Now that we have:
- a working app (done as SPA in part 3, and as Android in part 4),
- and the capacitor geolocation plugin installed (part 5),
- and a
MapPageGeoLocate.vue
page that properly routes (previous part 6),
lets create an actual Vue.js page that uses all this work.
The @capacitor/geolocation
plugin is fairly basic so it easier to implement than lets say the google-maps plugin (subject of another blog...tbd).
Full disclosure: It's just a hack page created by Claude...don't hate me.
<template>
<div class="geolocation-demo">
<h1>@capacitor/geolocation Demo</h1>
<!-- Platform Check -->
<div class="feature-section">
<div class="feature-card">
<h3>Platform Check</h3>
<p>Check which platform the app is running on (Native or Web).</p>
<button @click="checkPlatform">Check Platform</button>
<div class="result-box">
<pre>{{ platformInfo }}</pre>
</div>
</div>
</div>
<!-- Permissions Section -->
<div class="feature-section">
<h2>Permissions</h2>
<div class="feature-card">
<h3>Check Permissions</h3>
<p>Check current location permissions status on the device.</p>
<button @click="checkPermissions">Check Permissions</button>
<div class="result-box">
<pre>{{ permissionsStatus }}</pre>
</div>
</div>
<div class="feature-card">
<h3>Request Permissions</h3>
<p>Request location permissions from the user. Not available on web.</p>
<button @click="requestPermissions">Request Permissions</button>
<div class="result-box">
<pre>{{ permissionsRequestResult }}</pre>
</div>
<div v-if="requestPermissionsError" class="error-box request-error">
<p><strong>Code:</strong> {{ requestPermissionsError.code }}</p>
<p><strong>Message:</strong> {{ requestPermissionsError.message }}</p>
</div>
</div>
</div>
<!-- Current Position Section -->
<div class="feature-section">
<h2>Get Current Position</h2>
<div class="feature-card">
<h3>Get Current Position</h3>
<p>Get the current GPS location of the device.</p>
<div class="options-form">
<div class="form-group">
<label>
<input
type="checkbox"
v-model="currentPosOptions.enableHighAccuracy"
/>
Enable High Accuracy
</label>
<p class="option-description">
Uses GPS if available (may consume more battery).
</p>
</div>
<div class="form-group">
<label
>Timeout (ms):
<input
type="number"
v-model.number="currentPosOptions.timeout"
min="0"
/>
</label>
<p class="option-description">
Maximum wait time for location data.
</p>
</div>
<div class="form-group">
<label
>Maximum Age (ms):
<input
type="number"
v-model.number="currentPosOptions.maximumAge"
min="0"
/>
</label>
<p class="option-description">
Maximum age of cached position that is acceptable.
</p>
</div>
</div>
<button @click="getCurrentPosition">Get Current Position</button>
<div class="result-box">
<pre>{{ currentPosition }}</pre>
</div>
</div>
</div>
<!-- Watch Position Section -->
<div class="feature-section">
<h2>Watch Position</h2>
<div class="feature-card">
<h3>Watch Position</h3>
<p>
Set up a watch for location changes. Note that watching for location
can consume significant energy.
</p>
<div class="options-form">
<div class="form-group">
<label>
<input
type="checkbox"
v-model="watchPosOptions.enableHighAccuracy"
/>
Enable High Accuracy
</label>
</div>
<div class="form-group">
<label
>Timeout (ms):
<input
type="number"
v-model.number="watchPosOptions.timeout"
min="0"
/>
</label>
</div>
<div class="form-group">
<label
>Maximum Age (ms):
<input
type="number"
v-model.number="watchPosOptions.maximumAge"
min="0"
/>
</label>
</div>
<div class="form-group">
<label
>Minimum Update Interval (ms):
<input
type="number"
v-model.number="watchPosOptions.minimumUpdateInterval"
min="0"
/>
</label>
<p class="option-description">
Android only: Minimum time between updates.
</p>
</div>
</div>
<button @click="startWatchPosition" :disabled="isWatching">
Start Watching
</button>
<button @click="stopWatchPosition" :disabled="!isWatching">
Stop Watching
</button>
<div class="result-box">
<p>Watch ID: {{ watchId || 'Not watching' }}</p>
<p>Updates received: {{ watchUpdatesCount }}</p>
<pre>{{ watchPosition }}</pre>
</div>
</div>
</div>
<!-- Error Display -->
<div v-if="error" class="error-section">
<h3>General Error</h3>
<div class="error-box">
<p><strong>Code:</strong> {{ error.code }}</p>
<p><strong>Message:</strong> {{ error.message }}</p>
</div>
</div>
</div>
</template>
<script setup>
import { ref, reactive } from 'vue';
import { Geolocation } from '@capacitor/geolocation';
import { Capacitor } from '@capacitor/core';
// Platform info
const platformInfo = ref('');
// Permission states
const permissionsStatus = ref('');
const permissionsRequestResult = ref('');
const requestPermissionsError = ref(null);
// Current position
const currentPosition = ref('');
const currentPosOptions = reactive({
enableHighAccuracy: false,
timeout: 10000,
maximumAge: 0,
});
// Watch position
const watchPosition = ref('');
const watchPosOptions = reactive({
enableHighAccuracy: false,
timeout: 10000,
maximumAge: 0,
minimumUpdateInterval: 5000,
});
const isWatching = ref(false);
const watchId = ref('');
const watchUpdatesCount = ref(0);
// Error handling
const error = ref(null);
// Check platform
const checkPlatform = () => {
const isNative = Capacitor.isNativePlatform();
const platformName = Capacitor.getPlatform();
const platformData = {
isNativePlatform: isNative,
platform: platformName,
description: isNative
? `Running on native ${platformName} platform`
: 'Running as a web application',
};
platformInfo.value = JSON.stringify(platformData, null, 2);
console.log('Platform check:', platformData);
};
// Helper function to format position data for display
const formatPosition = (position) => {
if (!position) return 'No position data available';
const { coords, timestamp } = position;
return JSON.stringify(
{
timestamp,
coords: {
latitude: coords.latitude,
longitude: coords.longitude,
accuracy: coords.accuracy,
altitude: coords.altitude,
altitudeAccuracy: coords.altitudeAccuracy,
heading: coords.heading,
speed: coords.speed,
},
},
null,
2
);
};
// Check permissions
const checkPermissions = async () => {
try {
error.value = null;
console.log('Checking permissions...');
const status = await Geolocation.checkPermissions();
console.log('Permissions status:', status);
permissionsStatus.value = JSON.stringify(status, null, 2);
} catch (err) {
console.error('Error checking permissions:', err);
error.value = {
code: err.code || 'UNKNOWN',
message: err.message || 'An unknown error occurred',
};
permissionsStatus.value = 'Error checking permissions';
}
};
// Request permissions
const requestPermissions = async () => {
try {
error.value = null;
requestPermissionsError.value = null;
console.log('Requesting permissions...');
const status = await Geolocation.requestPermissions({
permissions: ['location'],
});
console.log('Permissions request result:', status);
permissionsRequestResult.value = JSON.stringify(status, null, 2);
} catch (err) {
console.error('Error requesting permissions:', err);
requestPermissionsError.value = {
code: err.code || 'UNKNOWN',
message: err.message || 'An unknown error occurred',
};
permissionsRequestResult.value = 'Error requesting permissions';
}
};
// Get current position
const getCurrentPosition = async () => {
try {
error.value = null;
currentPosition.value = 'Loading...';
console.log('Getting current position with options:', currentPosOptions);
const position = await Geolocation.getCurrentPosition(currentPosOptions);
console.log('Current position result:', position);
currentPosition.value = formatPosition(position);
} catch (err) {
console.error('Error getting current position:', err);
error.value = {
code: err.code || 'UNKNOWN',
message: err.message || 'An unknown error occurred',
};
currentPosition.value = 'Error getting current position';
}
};
// Watch position
const startWatchPosition = async () => {
try {
error.value = null;
if (isWatching.value) return;
watchPosition.value = 'Waiting for position updates...';
watchUpdatesCount.value = 0;
console.log('Starting position watch with options:', watchPosOptions);
watchId.value = await Geolocation.watchPosition(
watchPosOptions,
(position, err) => {
if (err) {
console.error('Error in watch callback:', err);
error.value = {
code: err.code || 'UNKNOWN',
message: err.message || 'An unknown error occurred during watch',
};
return;
}
console.log('Watch update received:', position);
watchUpdatesCount.value++;
watchPosition.value = formatPosition(position);
}
);
console.log('Watch started with ID:', watchId.value);
isWatching.value = true;
} catch (err) {
console.error('Error starting position watch:', err);
error.value = {
code: err.code || 'UNKNOWN',
message: err.message || 'An unknown error occurred',
};
watchPosition.value = 'Error starting position watch';
isWatching.value = false;
}
};
// Clear watch
const stopWatchPosition = async () => {
try {
error.value = null;
if (!isWatching.value || !watchId.value) return;
console.log('Stopping watch with ID:', watchId.value);
await Geolocation.clearWatch({
id: watchId.value,
});
console.log('Watch stopped successfully');
isWatching.value = false;
watchId.value = '';
watchPosition.value += '\n\nWatch stopped.';
} catch (err) {
console.error('Error stopping watch:', err);
error.value = {
code: err.code || 'UNKNOWN',
message: err.message || 'An unknown error occurred',
};
}
};
</script>
<style scoped>
.geolocation-demo {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
h1 {
text-align: center;
margin-bottom: 30px;
}
h2 {
margin-top: 30px;
border-bottom: 1px solid #ccc;
padding-bottom: 10px;
}
.feature-section {
margin-bottom: 30px;
}
.feature-card {
border: 1px solid #ddd;
border-radius: 8px;
padding: 15px;
margin-bottom: 20px;
background-color: #f9f9f9;
}
.feature-card h3 {
margin-top: 0;
}
.options-form {
margin: 15px 0;
padding: 10px;
background-color: #f0f0f0;
border-radius: 5px;
}
.form-group {
margin-bottom: 10px;
}
.option-description {
margin: 0;
font-size: 0.8em;
color: #666;
}
button {
background-color: #4caf50;
color: white;
border: none;
padding: 10px 15px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 14px;
margin: 4px 2px;
cursor: pointer;
border-radius: 5px;
}
button:disabled {
background-color: #cccccc;
cursor: not-allowed;
}
.result-box {
margin-top: 10px;
padding: 10px;
background-color: #f5f5f5;
border: 1px solid #ddd;
border-radius: 4px;
min-height: 50px;
max-height: 300px;
overflow: auto;
}
.result-box pre {
margin: 0;
white-space: pre-wrap;
}
.error-section {
margin-top: 30px;
padding: 15px;
background-color: #ffebee;
border: 1px solid #ffcdd2;
border-radius: 8px;
}
.error-box {
margin-top: 10px;
background-color: #ffebee;
border: 1px solid #ffcdd2;
border-radius: 8px;
padding: 10px;
}
.request-error {
margin-top: 15px;
}
</style>
After updating your code (and saving), test as both a SPA web page and in Android Studio
SPA - In the root
directory run following terminal command.
npm run dev
You should see a web page with the button,
...clicking the button should take you to the CapacitorGeoLocate.vue page:
Android Studio - In the root
directory run the following terminal command.
quasar dev -m capacitor -T android
You should be able to launch as an Android app in Android Studio.
...on Android you may probably see a permissions pop-up screen:
Credits
Let me thanks Vueschool.io (link: https://vueschool.io) for their excellent video tutorials. They have a great product that really helped getting ramped up on Vue.js.
Let me thank Luke Diebold in particular for his excellent video tutorials, as they gave a huge head start. Luke is just an outstanding educator and Quasar evangelist.
https://www.youtube.com/@LukeDiebold
Also a shout out goes to all my AI friends, especially Perplexity (which offer links to it's sources!) and Claude which can produce fairly decent code.
- Not once did these AI's ever roll their eyes and give back some sort of snarky answer to my endless questions about-the-same-dang-thing.
- If I had to manually research each technical issue or question on Stackoverflow, or any of the other blogs, forums, etc, I could have been reading them till my eye-balls fell out of my skull and never emerged from the research rabbit-hole with any answers at all.
*So, apologies to all the kind people of the internet who selflessly contribute to the technical zeitgeist. * But without these AI's ability to distill their knowledge into specific answers, this project would have been absolutely impossible. Although the AI's are often frustratingly dead wrong, they also often nail the answers, and even the wrong answers sometimes offer important clues. In fact, I fully expect this blog too will be consumed into the AI blackhole.
Oh, but I found Copilot in VS Code kinda useless...just say'n. This is as of Feb 2025, so I'm sure things will improve.
And let's give a thanks to Obsidian!
https://obsidian.md/
I recently discovered this app, and it sure beats any of the other apps I've used over the years for taking notes.
Be sure and install the Obsidian webclipper extension to your browser! Wow...it is sooooo useful. When you find a great web resource, just clip into Obsidian and it's there for you to research and link (tags) into all your other notes. Also, in fact, this blog was produced in Obsidian. Obsidian is so useful, and it's free! Unbelievable.