As web developers, we often sing the praise of the web and talk about how great of a platform it is. But as it turns out, the web is still limited in terms of compelling features. For instance, if you want to have access to a device’s File System, Push Notifications, or Bluetooth capabilities, you more than likely need to have a native app to accompany your web app.
Thankfully, web developers can take their web apps and add a native target without having to change anything.
Introducing: Capacitor.
Capacitor can be thought of as a ponyfill for native apps as well as a runtime for web apps. Capacitor provides an API that allows developers to call native APIs through JavaScript and deploy their web app through the iOS App Stores and Google Play.
For example, if we wanted to schedule a local notification in an app, with Capacitor it would be a matter of calling LocalNotifications.schedule
and passing an array of notifications.
import { Plugins } from '@capacitor/core'; const { LocalNotifications } = Plugins; LocalNotifications.schedule({ notifications: [ { title: "On sale", body: "Widgets are 10% off. Act fast!", id: 1, schedule: { at: new Date(Date.now() + 1000 * 5) }, sound: null, attachments: null, actionTypeId: "", extra: null } ] });
This is possible because Capacitor sits between the various APIs from the Browser, native iOS, and native Android. When we call a Capacitor API, we’re actually calling proxied API.
So when we call our local notification example on iOS, we’ll actually call the native notification API and handle things there. On the web, we can also call the Notification API, so why not just use the existing API instead?
If we change our example from Notifications to Background Tasks, which has no web equivalent, the benefit becomes pretty clear. By providing it’s own API, Capacitor let’s you safely call APIs, and provide graceful fall-backs when features are not available. Much better than having a giant error in an app.
Since Capacitor itself is mainly focused on the device features, we still need to build out an app. In this case, we’ll build a small Vue app and use the library Vuetify for our components. I’ll assume you know how to create a Vue project and how to Vuetify to it, but the TL;DR is:
npm i -g @vue/cli vue create my-project cd my-project vue add vuetify
Minus a few prompts here and there, that’s about it.
With the project created, let’s also install Capacitor Core and it’s CLI to our project:
npm install @capacitor/core @capacitor/cli
We’ll use these in a bit, so let’s take a look at our component. In our app, we should have a Home route that points to a Home.vue component.
Let’s start to scaffold out the structure for this:
<template> <div> <h1>Geolocation</h1> <p>Your location is:</p> <p> Latitude: <span v-if="loc">{{ loc.coords.latitude }}</span> </p> <p> Longitude: <span v-if="loc">{{ loc.coords.longitude }}</span> </p> <v-btn block elevation="2" @click="getCurrentPosition">Get Current Location</v-btn> <v-btn block elevation="2" @click="scheduleNotification">Local Notifications</v-btn> </div> </template> <script> import Vue from "vue"; export default Vue.extend({ name: "Home", data: function() { return { loc: null }; }, methods: { getCurrentPosition: function() { }, scheduleNotification: async function() { }, } }); </script>
With this in place, we can start to build out the logic to get our current location and to schedule a notification. Let’s start with the geolocation portion first.
To get started, we first need to import the Plugins object from @capacitor/core
. This will allow us to call various device features through Capacitor:
import Vue from "vue"; import { Plugins } from "@capacitor/core"; export default Vue.extend({ name: "Home", data: function() { return { loc: null }; }, methods: { getCurrentPosition: function() { }, scheduleNotification: async function() { }, } });
In our getCurrentPosition
function, we can get a hold of the Geolocation object and start to call the various functions on it. Here we’ll call the getCurrentPosition
function which will return a promise.
getCurrentPosition: function() { const { Geolocation } = Plugins; Geolocation.getCurrentPosition().then( loc => (this.loc = loc), e => console.log("there was an error", e) ); },
Once the location data has been retrieved, we can set the loc binding to the location data.
When testing this in the browser, test in Chrome or Firefox as Safari will block access to geolocation if the connection is not secure. Chrome and Firefox will also block non-secure access, but give localhost domains special access.
And that’s it!
Geolocation data can now be displayed in our app. Let’s move on to Notifications via the Local Notification API.
Local Notifications often get confused with Push Notifications, but the two are very different. Push Notifications come from a central hub, either Google or Apple and their various services. Local Notifications are, as the name hints at, local to the app itself.
Like the Geolocation example, we first want to get access the LocalNotifications
object:
scheduleNotification: async function() { const { LocalNotifications } = Plugins; },
Notifications are a bit more involved and require you to get permission first before being able to show them.
So, we’ll make use of async/await here and request permission:
scheduleNotification: async function() { const { LocalNotifications } = Plugins; const canSend = await LocalNotifications.requestPermission(); if (canSend) { } },
Now if we can send a notification, we can do so inside our if statement. If users click deny, then our function will just end.
Inside of the “if” statement, we can schedule a notification like so:
if (canSend) { await LocalNotifications.schedule({ notifications: [ { title: "On sale", body: "Widgets are 10% off. Act fast!", id: 1, schedule: { at: new Date(Date.now() + 1000 * 5) }, actionTypeId: "", extra: null } ] }); }
We can actually schedule multiple notifications here, but one will do for our demo. There’s some more entries we can set for our notification, so I’ll encourage you to take a look at the documentation here.
All together, our function should look something like this:
scheduleNotification: async function() { const { LocalNotifications } = Plugins; const canSend = await LocalNotifications.requestPermission(); if (canSend) { await LocalNotifications.schedule({ notifications: [ { title: "On sale", body: "Widgets are 10% off. Act fast!", id: 1, schedule: { at: new Date(Date.now() + 1000 * 5) }, actionTypeId: "", extra: null } ] }); } },
And all together, our component logic looks like this:
import Vue from "vue"; import { Plugins } from "@capacitor/core"; export default Vue.extend({ name: "Home", data: function(): { loc: GeolocationPosition | null } { return { loc: null }; }, methods: { scheduleNotification: async function() { const { LocalNotifications } = Plugins; const canSend = await LocalNotifications.requestPermission(); if (canSend) { await LocalNotifications.schedule({ notifications: [ { title: "On sale", body: "Widgets are 10% off. Act fast!", id: 1, schedule: { at: new Date(Date.now() + 1000 * 5) }, actionTypeId: "", extra: null } ] }); } }, getCurrentPosition: function() { const { Geolocation } = Plugins; Geolocation.getCurrentPosition().then( loc => (this.loc = loc), e => console.log("there was an error", e) ); } } });
With our features added, let’s do a build of our app and start adding the native platforms.
npm run build
To add the native platforms, we first need to enable Capacitor in our project by call the Capacitor CLI:
npx cap init MyVueApp --webDir=dist
All this does is let Capacitor know what our App name should be and where the web assets are located.
Once done, we can add iOS or Android deploy targets to our project:
npx cap add ios *# or* npx cap add android
Both of these actions will require you to have either native SDK setup on your machine. For iOS, this means installing Xcode and Cocoapods, and for Android you’ll need to install Android Studio.
Once the native platform have been added, all that’s left to do is to run the apps on a simulator or real device:
With it’s minimal CLI and Web-focused approach, Capacitor is really a drop-in tool for deploying your app to native.
Here we looked at Vue, but Capacitor itself is Framework-agnostic, and could be used with Angular, React, Preact, or no framework at all.
If you are interested in learning more about Capacitor, be sure to check out the Capacitor Docs as well as the documentation for the various Plugins/APIs available.
Debugging Vue.js applications can be difficult, especially when there are dozens, if not hundreds of mutations during a user session. If you’re interested in monitoring and tracking Vue mutations for all of your users in production, try LogRocket.
LogRocket is like a DVR for web and mobile apps, recording literally everything that happens in your Vue apps, including network requests, JavaScript errors, performance problems, and much more. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred.
The LogRocket Vuex plugin logs Vuex mutations to the LogRocket console, giving you context around what led to an error and what state the application was in when an issue occurred.
Modernize how you debug your Vue apps — start monitoring for free.
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowLearn how to manage memory leaks in Rust, avoid unsafe behavior, and use tools like weak references to ensure efficient programs.
Bypass anti-bot measures in Node.js with curl-impersonate. Learn how it mimics browsers to overcome bot detection for web scraping.
Handle frontend data discrepancies with eventual consistency using WebSockets, Docker Compose, and practical code examples.
Efficient initializing is crucial to smooth-running websites. One way to optimize that process is through lazy initialization in Rust 1.80.