@capawesome/capacitor-flic
v0.1.1
Published
Unofficial Capacitor plugin for Flic smart buttons on Android and iOS.
Maintainers
Readme
Capacitor Flic Plugin
Unofficial Capacitor plugin for Flic smart buttons.[^1]
Features
- 🔍 Pairing wizard: Pair new Flic buttons with a single call using the integrated scan wizard.
- 🔗 Connection management: Connect, disconnect and forget buttons at any time.
- 🖱️ Button events: Listen for single click, double click, hold, down and up events.
- 📥 Queued events: Receive events that occurred while the button was disconnected.
- 🔋 Battery level: Read the last known battery voltage of each button.
- 🔐 Permissions: Check and request the required permissions with a single call.
- 📦 CocoaPods & SPM: Supports CocoaPods and Swift Package Manager for iOS.
- 🔁 Up-to-date: Always supports the latest Capacitor version.
Missing a feature? Just open an issue and we'll take a look!
Use Cases
The Flic plugin is typically used to trigger app actions with a physical button, for example:
- Smart home control: Toggle lights, scenes or other devices with a single press.
- Safety buttons: Send an alert or start a call when the user presses the button.
- Time tracking: Start and stop timers without opening the app.
- Accessibility: Provide a simple physical trigger for essential app actions.
Compatibility
| Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 0.x.x | >=8.x.x | Active support |
Installation
You can use our AI-Assisted Setup to install the plugin. Add the Capawesome Skills to your AI tool using the following command:
npx skills add capawesome-team/skills --skill capacitor-pluginsThen use the following prompt:
Use the `capacitor-plugins` skill from `capawesome-team/skills` to install the `@capawesome/capacitor-flic` plugin in my project.If you prefer Manual Setup, install the plugin by running the following commands and follow the platform-specific instructions below:
npm install @capawesome/capacitor-flic
npx cap syncAndroid
The plugin already declares all required Bluetooth permissions in its manifest, so no additional configuration is required.
The flic2lib-android library is resolved from JitPack. The plugin already declares the JitPack repository in its build.gradle file. If your project restricts repository declarations to the settings file (e.g. via dependencyResolutionManagement with FAIL_ON_PROJECT_REPOS), add the JitPack repository to your settings.gradle file:
dependencyResolutionManagement {
repositories {
maven { url 'https://jitpack.io' }
}
}Variables
This plugin will use the following project variables (defined in your app’s variables.gradle file):
$flic2libVersionversion ofcom.github.50ButtonsEach:flic2lib-android(default:2.0.1)
iOS
Add the NSBluetoothAlwaysUsageDescription and NSBluetoothPeripheralUsageDescription keys to the ios/App/App/Info.plist file, which tells the user why the app needs access to Bluetooth:
<key>NSBluetoothAlwaysUsageDescription</key>
<string>The app needs access to Bluetooth to communicate with your Flic buttons.</string>
<key>NSBluetoothPeripheralUsageDescription</key>
<string>The app needs access to Bluetooth to communicate with your Flic buttons.</string>If you want to receive button events while the app is in the background, you must also enable the Uses Bluetooth LE accessories background mode in the Signing & Capabilities section of your Xcode project and call initialize(...) with the iosBackground option set to true.
Configuration
No configuration required for this plugin.
Usage
Initialize the plugin
Call initialize() as soon as possible after the app has launched to minimize the delay of any pending button events:
import { Flic } from '@capawesome/capacitor-flic';
const initialize = async () => {
await Flic.initialize({ iosBackground: true });
};Pair a new button
To pair a new button, start a scan and press and hold the button for at least 6 seconds:
import { Flic, ScanStatus } from '@capawesome/capacitor-flic';
const pairButton = async () => {
await Flic.addListener('scanStatusChanged', (event) => {
if (event.status === ScanStatus.Discovered) {
console.log('Button discovered. Keep holding it...');
}
});
const { button } = await Flic.startScan();
return button;
};List paired buttons
import { Flic } from '@capawesome/capacitor-flic';
const getButtons = async () => {
const { buttons } = await Flic.getButtons();
return buttons;
};Connect a button
After an app restart, each paired button must be connected again:
import { Flic } from '@capawesome/capacitor-flic';
const connectButtons = async () => {
const { buttons } = await Flic.getButtons();
for (const button of buttons) {
await Flic.connectButtonById({ id: button.id });
}
};Listen for button events
import { Flic } from '@capawesome/capacitor-flic';
const addListeners = async () => {
await Flic.addListener('buttonSingleClick', (event) => {
console.log('Button was clicked', event);
});
await Flic.addListener('buttonDoubleClick', (event) => {
console.log('Button was double clicked', event);
});
await Flic.addListener('buttonHold', (event) => {
console.log('Button was held down', event);
});
};Forget a button
import { Flic } from '@capawesome/capacitor-flic';
const forgetButton = async (id: string) => {
await Flic.forgetButtonById({ id });
};Check and request permissions
import { Flic } from '@capawesome/capacitor-flic';
const checkPermissions = async () => {
const permissionStatus = await Flic.checkPermissions();
return permissionStatus;
};
const requestPermissions = async () => {
const permissionStatus = await Flic.requestPermissions();
return permissionStatus;
};API
checkPermissions()connectButtonById(...)disconnectButtonById(...)forgetButtonById(...)getButtons()initialize(...)requestPermissions()startScan()stopScan()addListener('buttonConnected', ...)addListener('buttonConnectionFailed', ...)addListener('buttonDisconnected', ...)addListener('buttonDoubleClick', ...)addListener('buttonDown', ...)addListener('buttonHold', ...)addListener('buttonReady', ...)addListener('buttonSingleClick', ...)addListener('buttonUnpaired', ...)addListener('buttonUp', ...)addListener('scanStatusChanged', ...)removeAllListeners()- Interfaces
- Type Aliases
- Enums
checkPermissions()
checkPermissions() => Promise<PermissionStatus>Check the status of the permissions that are required to use Flic buttons.
On iOS, the Bluetooth permission is requested when initialize(...) is called.
Only available on Android and iOS.
Returns: Promise<PermissionStatus>
Since: 0.1.0
connectButtonById(...)
connectButtonById(options: ConnectButtonByIdOptions) => Promise<void>Connect a button.
The returned promise resolves immediately. The connection is established
as soon as the button is available and does not time out. Listen to the
buttonConnected and buttonReady events to know when the button is
ready to be used.
Only available on Android and iOS.
| Param | Type |
| ------------- | ----------------------------------------------------------------------------- |
| options | ConnectButtonByIdOptions |
Since: 0.1.0
disconnectButtonById(...)
disconnectButtonById(options: DisconnectButtonByIdOptions) => Promise<void>Disconnect a button or cancel a pending connection.
Only available on Android and iOS.
| Param | Type |
| ------------- | ----------------------------------------------------------------------------------- |
| options | DisconnectButtonByIdOptions |
Since: 0.1.0
forgetButtonById(...)
forgetButtonById(options: ForgetButtonByIdOptions) => Promise<void>Forget a button.
This removes the pairing with the button. To use the button again,
it must be paired again using startScan().
Only available on Android and iOS.
| Param | Type |
| ------------- | --------------------------------------------------------------------------- |
| options | ForgetButtonByIdOptions |
Since: 0.1.0
getButtons()
getButtons() => Promise<GetButtonsResult>Get all buttons that are currently paired with the app.
Only available on Android and iOS.
Returns: Promise<GetButtonsResult>
Since: 0.1.0
initialize(...)
initialize(options?: InitializeOptions | undefined) => Promise<void>Initialize the plugin.
This method must be called before any other method. It is recommended to call this method as soon as possible after the app has launched to minimize the delay of any pending button events.
On iOS, this method triggers the Bluetooth permission prompt if the permission has not yet been granted.
Only available on Android and iOS.
| Param | Type |
| ------------- | --------------------------------------------------------------- |
| options | InitializeOptions |
Since: 0.1.0
requestPermissions()
requestPermissions() => Promise<PermissionStatus>Request the permissions that are required to use Flic buttons.
On iOS, this method only returns the current permission status since
the Bluetooth permission is requested when initialize(...) is called.
Only available on Android and iOS.
Returns: Promise<PermissionStatus>
Since: 0.1.0
startScan()
startScan() => Promise<StartScanResult>Start scanning for new buttons to pair.
To pair a button, press and hold it for at least 6 seconds while scanning.
The returned promise resolves with the paired button once the pairing
has completed. Listen to the scanStatusChanged event to keep the user
informed about the scan progress.
Only one scan can be running at a time.
Only available on Android and iOS.
Returns: Promise<StartScanResult>
Since: 0.1.0
stopScan()
stopScan() => Promise<void>Stop an ongoing scan.
This rejects the pending startScan() call.
Only available on Android and iOS.
Since: 0.1.0
addListener('buttonConnected', ...)
addListener(eventName: 'buttonConnected', listenerFunc: (event: ButtonConnectedEvent) => void) => Promise<PluginListenerHandle>Called when a button establishes a Bluetooth connection.
The button is not ready to be used until the buttonReady event is emitted.
Only available on Android and iOS.
| Param | Type |
| ------------------ | ----------------------------------------------------------------------------------------- |
| eventName | 'buttonConnected' |
| listenerFunc | (event: ButtonConnectedEvent) => void |
Returns: Promise<PluginListenerHandle>
Since: 0.1.0
addListener('buttonConnectionFailed', ...)
addListener(eventName: 'buttonConnectionFailed', listenerFunc: (event: ButtonConnectionFailedEvent) => void) => Promise<PluginListenerHandle>Called when a connection attempt to a button fails.
Only available on Android and iOS.
| Param | Type |
| ------------------ | ------------------------------------------------------------------------------------------------------- |
| eventName | 'buttonConnectionFailed' |
| listenerFunc | (event: ButtonConnectionFailedEvent) => void |
Returns: Promise<PluginListenerHandle>
Since: 0.1.0
addListener('buttonDisconnected', ...)
addListener(eventName: 'buttonDisconnected', listenerFunc: (event: ButtonDisconnectedEvent) => void) => Promise<PluginListenerHandle>Called when the Bluetooth connection with a button is lost.
Only available on Android and iOS.
| Param | Type |
| ------------------ | ----------------------------------------------------------------------------------------------- |
| eventName | 'buttonDisconnected' |
| listenerFunc | (event: ButtonDisconnectedEvent) => void |
Returns: Promise<PluginListenerHandle>
Since: 0.1.0
addListener('buttonDoubleClick', ...)
addListener(eventName: 'buttonDoubleClick', listenerFunc: (event: ButtonEvent) => void) => Promise<PluginListenerHandle>Called when a button is double clicked.
Only available on Android and iOS.
| Param | Type |
| ------------------ | ----------------------------------------------------------------------- |
| eventName | 'buttonDoubleClick' |
| listenerFunc | (event: ButtonEvent) => void |
Returns: Promise<PluginListenerHandle>
Since: 0.1.0
addListener('buttonDown', ...)
addListener(eventName: 'buttonDown', listenerFunc: (event: ButtonEvent) => void) => Promise<PluginListenerHandle>Called when a button is pressed down.
Only available on Android and iOS.
| Param | Type |
| ------------------ | ----------------------------------------------------------------------- |
| eventName | 'buttonDown' |
| listenerFunc | (event: ButtonEvent) => void |
Returns: Promise<PluginListenerHandle>
Since: 0.1.0
addListener('buttonHold', ...)
addListener(eventName: 'buttonHold', listenerFunc: (event: ButtonEvent) => void) => Promise<PluginListenerHandle>Called when a button is held down.
Only available on Android and iOS.
| Param | Type |
| ------------------ | ----------------------------------------------------------------------- |
| eventName | 'buttonHold' |
| listenerFunc | (event: ButtonEvent) => void |
Returns: Promise<PluginListenerHandle>
Since: 0.1.0
addListener('buttonReady', ...)
addListener(eventName: 'buttonReady', listenerFunc: (event: ButtonReadyEvent) => void) => Promise<PluginListenerHandle>Called when a button has been cryptographically verified after a connection and is ready to be used.
Only available on Android and iOS.
| Param | Type |
| ------------------ | --------------------------------------------------------------------------------- |
| eventName | 'buttonReady' |
| listenerFunc | (event: ButtonReadyEvent) => void |
Returns: Promise<PluginListenerHandle>
Since: 0.1.0
addListener('buttonSingleClick', ...)
addListener(eventName: 'buttonSingleClick', listenerFunc: (event: ButtonEvent) => void) => Promise<PluginListenerHandle>Called when a button is clicked once.
Only available on Android and iOS.
| Param | Type |
| ------------------ | ----------------------------------------------------------------------- |
| eventName | 'buttonSingleClick' |
| listenerFunc | (event: ButtonEvent) => void |
Returns: Promise<PluginListenerHandle>
Since: 0.1.0
addListener('buttonUnpaired', ...)
addListener(eventName: 'buttonUnpaired', listenerFunc: (event: ButtonUnpairedEvent) => void) => Promise<PluginListenerHandle>Called when the pairing with a button is no longer valid, for example
because the button has been factory reset. In this case, the button must
be forgotten using forgetButtonById(...) and then paired again.
Only available on Android and iOS.
| Param | Type |
| ------------------ | --------------------------------------------------------------------------------------- |
| eventName | 'buttonUnpaired' |
| listenerFunc | (event: ButtonUnpairedEvent) => void |
Returns: Promise<PluginListenerHandle>
Since: 0.1.0
addListener('buttonUp', ...)
addListener(eventName: 'buttonUp', listenerFunc: (event: ButtonEvent) => void) => Promise<PluginListenerHandle>Called when a button is released.
Only available on Android and iOS.
| Param | Type |
| ------------------ | ----------------------------------------------------------------------- |
| eventName | 'buttonUp' |
| listenerFunc | (event: ButtonEvent) => void |
Returns: Promise<PluginListenerHandle>
Since: 0.1.0
addListener('scanStatusChanged', ...)
addListener(eventName: 'scanStatusChanged', listenerFunc: (event: ScanStatusChangedEvent) => void) => Promise<PluginListenerHandle>Called when the status of an ongoing scan changes.
Only available on Android and iOS.
| Param | Type |
| ------------------ | --------------------------------------------------------------------------------------------- |
| eventName | 'scanStatusChanged' |
| listenerFunc | (event: ScanStatusChangedEvent) => void |
Returns: Promise<PluginListenerHandle>
Since: 0.1.0
removeAllListeners()
removeAllListeners() => Promise<void>Remove all listeners for this plugin.
Since: 0.1.0
Interfaces
PermissionStatus
| Prop | Type | Description | Since |
| ---------------------- | ----------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | ----- |
| bluetooth | PermissionState | The permission state of the Bluetooth permission. Only available on iOS. | 0.1.0 |
| bluetoothConnect | PermissionState | The permission state of the BLUETOOTH_CONNECT permission. Only required on Android 12 and later. Only available on Android. | 0.1.0 |
| bluetoothScan | PermissionState | The permission state of the BLUETOOTH_SCAN permission. Only required on Android 12 and later. Only available on Android. | 0.1.0 |
| location | PermissionState | The permission state of the ACCESS_FINE_LOCATION permission. Only required on Android 11 and earlier. Only available on Android. | 0.1.0 |
ConnectButtonByIdOptions
| Prop | Type | Description | Since |
| -------- | ------------------- | ----------------------------- | ----- |
| id | string | The identifier of the button. | 0.1.0 |
DisconnectButtonByIdOptions
| Prop | Type | Description | Since |
| -------- | ------------------- | ----------------------------- | ----- |
| id | string | The identifier of the button. | 0.1.0 |
ForgetButtonByIdOptions
| Prop | Type | Description | Since |
| -------- | ------------------- | ----------------------------- | ----- |
| id | string | The identifier of the button. | 0.1.0 |
GetButtonsResult
| Prop | Type | Description | Since |
| ------------- | --------------------- | --------------------------------------------------- | ----- |
| buttons | Button[] | The buttons that are currently paired with the app. | 0.1.0 |
Button
| Prop | Type | Description | Since |
| --------------------- | ----------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----- |
| batteryVoltage | number | The last known battery voltage of the button in volts. If no battery sample has been taken yet, this property is not available. It is recommended to show a "change the battery soon" hint in your app once the voltage goes below 2.65. | 0.1.0 |
| connectionState | ButtonConnectionState | The connection state of the button. | 0.1.0 |
| firmwareVersion | number | The revision of the firmware currently running on the button. | 0.1.0 |
| id | string | The identifier of the button on this device. On Android, this is the Bluetooth address of the button. On iOS, this is an identifier that is guaranteed to be the same for each button paired to a particular device. | 0.1.0 |
| isReady | boolean | Whether or not the button has been cryptographically verified after a connection and is ready to be used. | 0.1.0 |
| isUnpaired | boolean | Whether or not the pairing with the button is no longer valid, for example because the button has been factory reset. In this case, the button must be forgotten using forgetButtonById(...) and then paired again. | 0.1.0 |
| name | string | The human readable name of the button that the user may change in the official Flic app. | 0.1.0 |
| pressCount | number | The number of times the button has been clicked since it last booted. | 0.1.0 |
| serialNumber | string | The serial number of the button that is printed on the backside of the button inside the battery hatch. | 0.1.0 |
| uuid | string | The unique identifier of the button that is the same across devices and apps. | 0.1.0 |
InitializeOptions
| Prop | Type | Description | Default | Since |
| ------------------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------ | ----- |
| iosBackground | boolean | Whether or not you intend to use the buttons while the app is in the background. If set to true, the Uses Bluetooth LE accessories background mode must be enabled in the app's capabilities. Only available on iOS. | false | 0.1.0 |
StartScanResult
| Prop | Type | Description | Since |
| ------------ | ----------------------------------------- | --------------------------- | ----- |
| button | Button | The button that was paired. | 0.1.0 |
PluginListenerHandle
| Prop | Type |
| ------------ | ----------------------------------------- |
| remove | () => Promise<void> |
ButtonConnectedEvent
| Prop | Type | Description | Since |
| -------------- | ------------------- | ----------------------------- | ----- |
| buttonId | string | The identifier of the button. | 0.1.0 |
ButtonConnectionFailedEvent
| Prop | Type | Description | Since |
| -------------- | ------------------- | ------------------------------------------------------------- | ----- |
| buttonId | string | The identifier of the button. | 0.1.0 |
| message | string | The message that describes why the connection attempt failed. | 0.1.0 |
ButtonDisconnectedEvent
| Prop | Type | Description | Since |
| -------------- | ------------------- | ----------------------------- | ----- |
| buttonId | string | The identifier of the button. | 0.1.0 |
ButtonEvent
| Prop | Type | Description | Since |
| --------------- | -------------------- | ---------------------------------------------------------------------------------------- | ----- |
| buttonId | string | The identifier of the button. | 0.1.0 |
| timestamp | number | The timestamp of the event in milliseconds since the Unix epoch. | 0.1.0 |
| wasQueued | boolean | Whether or not the event was queued because it occurred before the button was connected. | 0.1.0 |
ButtonReadyEvent
| Prop | Type | Description | Since |
| -------------- | ------------------- | ----------------------------- | ----- |
| buttonId | string | The identifier of the button. | 0.1.0 |
ButtonUnpairedEvent
| Prop | Type | Description | Since |
| -------------- | ------------------- | ----------------------------- | ----- |
| buttonId | string | The identifier of the button. | 0.1.0 |
ScanStatusChangedEvent
| Prop | Type | Description | Since |
| ------------ | ------------------------------------------------- | ----------------------- | ----- |
| status | ScanStatus | The status of the scan. | 0.1.0 |
Type Aliases
PermissionState
'prompt' | 'prompt-with-rationale' | 'granted' | 'denied'
Enums
ButtonConnectionState
| Members | Value | Description | Since |
| ------------------- | ---------------------------- | -------------------------------------------------------------------------------------------------------------------- | ----- |
| Connected | 'CONNECTED' | The button is connected. | 0.1.0 |
| Connecting | 'CONNECTING' | The button is disconnected but a pending connection is set. The button will connect as soon as it becomes available. | 0.1.0 |
| Disconnected | 'DISCONNECTED' | The button is disconnected and no pending connection is set. | 0.1.0 |
| Disconnecting | 'DISCONNECTING' | The button is connected but is attempting to disconnect. Only available on iOS. | 0.1.0 |
ScanStatus
| Members | Value | Description | Since |
| ---------------------------- | ----------------------------------------- | -------------------------------------------------------------------------------------- | ----- |
| AskToAcceptPairRequest | 'ASK_TO_ACCEPT_PAIR_REQUEST' | The user must accept the system pairing dialog to continue. Only available on Android. | 0.1.0 |
| Connected | 'CONNECTED' | A button was found and a connection is being established. | 0.1.0 |
| Discovered | 'DISCOVERED' | A button was discovered. | 0.1.0 |
| Verified | 'VERIFIED' | The button has been cryptographically verified. Only available on iOS. | 0.1.0 |
FAQ
Which Flic buttons are supported?
The plugin supports Flic 2 buttons. For the Flic Duo, only the events of the big button are currently delivered.
Does the plugin receive button events while the app is in the background?
On Android, events are delivered as long as the app process is alive. Consider using the Android Foreground Service plugin to keep the app process alive. On iOS, enable the Uses Bluetooth LE accessories background mode and call initialize(...) with the iosBackground option set to true (see Installation).
Can I use this plugin with Ionic, React, Vue or Angular?
Yes, the plugin is framework-agnostic. It works in any Capacitor app regardless of the web framework, including Ionic with Angular, React, or Vue, as well as plain JavaScript projects.
Related Plugins
- Android Foreground Service: Keep the app process alive to receive button events in the background on Android.
- Bluetooth Low Energy: Communicate with other Bluetooth Low Energy devices.
Newsletter
Stay up to date with the latest news and updates about the Capawesome, Capacitor, and Ionic ecosystem by subscribing to our Capawesome Newsletter.
Changelog
See CHANGELOG.md.
License
See LICENSE.
The bundled flic2lib binary is distributed under its own license from Shortcut Labs AB (see flic2lib-LICENCE.txt).
[^1]: This project is not affiliated with, endorsed by, sponsored by, or approved by Shortcut Labs AB or any of its affiliates or subsidiaries. "Flic" is a trademark of Shortcut Labs AB.
