Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ at the time; the last full run on the Simulator is under
| [`sensors_plus_watchos`](packages/sensors_plus_watchos) [![pub](https://img.shields.io/pub/v/sensors_plus_watchos.svg)](https://pub.dev/packages/sensors_plus_watchos) | [`sensors_plus`](https://pub.dev/packages/sensors_plus) | CoreMotion (`CMMotionManager`) |
| [`local_auth_watchos`](packages/local_auth_watchos) [![pub](https://img.shields.io/pub/v/local_auth_watchos.svg)](https://pub.dev/packages/local_auth_watchos) | [`local_auth`](https://pub.dev/packages/local_auth) | LocalAuthentication (passcode) |
| [`geolocator_watchos`](packages/geolocator_watchos) [![pub](https://img.shields.io/pub/v/geolocator_watchos.svg)](https://pub.dev/packages/geolocator_watchos) | [`geolocator`](https://pub.dev/packages/geolocator) | CoreLocation (`CLLocationManager`) |
| [`permission_handler_watchos`](packages/permission_handler_watchos) | [`permission_handler`](https://pub.dev/packages/permission_handler) | UserNotifications, and CoreLocation, CoreMotion, AVFAudio, Contacts, EventKit, CoreBluetooth and Intents loaded at runtime |
| [`sqflite_watchos`](packages/sqflite_watchos) | [`sqflite`](https://pub.dev/packages/sqflite) | The system SQLite (`libsqlite3`), its C API called from Dart |
| [`video_player_watchos`](packages/video_player_watchos) [![pub](https://img.shields.io/pub/v/video_player_watchos.svg)](https://pub.dev/packages/video_player_watchos) | [`video_player`](https://pub.dev/packages/video_player) | AVFoundation + AVKit platform view |
| [`audioplayers_watchos`](packages/audioplayers_watchos) [![pub](https://img.shields.io/pub/v/audioplayers_watchos.svg)](https://pub.dev/packages/audioplayers_watchos) | [`audioplayers`](https://pub.dev/packages/audioplayers) | AVFoundation (`AVPlayer`) |
Expand Down
23 changes: 23 additions & 0 deletions packages/permission_handler_watchos/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Dart / Flutter
.dart_tool/
build/
.flutter-plugins
.flutter-plugins-dependencies
.packages
.pub/

# Xcode / SwiftPM (per-user, generated when watchos/Package.swift is opened)
**/.swiftpm/
**/.build/
**/xcuserdata/

# flutter-watchos app wiring (generated in an app's watchos/, never a plugin's)
watchos/Flutter/

# IDE
.idea/
.vscode/
*.iml

# macOS
.DS_Store
29 changes: 29 additions & 0 deletions packages/permission_handler_watchos/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
## 0.0.1

First version, staged: checked on the watchOS 27.0 Simulator, not yet on a
physical watch.

* `PermissionHandlerWatchos` answers `permission_handler` on watchOS over
dart:ffi: status, service status and requests for location (when in use
and always), notifications, critical alerts, sensors, the microphone,
contacts, the calendar (full access and write only), reminders, Bluetooth
and Siri (`assistant`, for an app that sets `PermissionHandlerWatchosSiri`
in its Info.plist), with the iOS implementation's statuses and error
codes. `storage` is granted, as on iOS.
* Each framework but UserNotifications is loaded at runtime instead of
linked. Every group's class names and selectors are still in the binary,
unlike the iOS implementation, whose build macros leave a group out;
whether App Store Connect's usage-description check reads them is not yet
known (see the README).
* The location strategy keeps one flag in `NSUserDefaults`, so apps declare
`NSPrivacyAccessedAPICategoryUserDefaults` (`CA92.1`) in their privacy
manifest.
* A request whose usage description is missing fails with
`MISSING_USAGE_DESCRIPTION` instead of the app being terminated. A request
while another waits fails with `ERROR_ALREADY_REQUESTING_PERMISSIONS`, as
on iOS. A location prompt closed without an answer answers denied, as on
iOS; watchOS sends no delegate call then, so the app becoming active
again is the answer.
* The example's `lib/` is permission_handler_apple's own example,
unchanged, with `url_launcher_watchos` for its template's links; a
watchOS integration test checks the answers that need no prompt.
21 changes: 21 additions & 0 deletions packages/permission_handler_watchos/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2018 Baseflow

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
157 changes: 157 additions & 0 deletions packages/permission_handler_watchos/PORTING_REPORT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
# permission_handler_watchos — porting report

## Verification status

| Aspect | Result |
|---|---|
| Implementation | ✅ Working (FFI; one strategy per permission group, as in the iOS implementation) |
| watchOS capability | ◐ The permissions watchOS has: location (when in use and always), notifications, critical alerts, sensors, microphone, contacts, calendar (full access and write only), reminders, Bluetooth, Siri; storage is granted as on iOS. The others answer as iOS answers for a permission it does not know |
| Host unit tests (`flutter test`) | ✅ 8 pass |
| Upstream integration test | ○ none upstream (Baseflow's example is a manual demo) |
| watchOS integration test (`example/integration_test/permission_handler_test.dart`) | ✅ 5 pass on the watchOS 27.0 Simulator |
| System prompts | ✅ asked for on the Simulator, each answered as on iOS: notification granted; location when in use granted; location always kept at "while using" → permanentlyDenied; sensors granted; microphone granted; contacts refused → permanentlyDenied; reminders granted; calendar write only granted. Bluetooth reads granted on the Simulator, which has no Bluetooth. After the review fixes, location when in use again: the prompt closed with the crown → answered, and a second request prompted again; allowed → granted; "always" asked for right after → its prompt stayed up until answered, kept at "while using" → permanentlyDenied. In upstream's example: calendar (full access) allowed → granted |
| Critical alerts and Siri prompts | not yet asked for: each needs an entitlement the example does not have |
| App Store Connect upload | not yet done: whether ITMS-90683 reads the class names of groups an app does not use is unchecked (see "Design") |
| Example deviations | `example/pubspec.yaml` adds `url_launcher_watchos`, for the links of `baseflow_plugin_template`, which go through `url_launcher`. `example/lib/main.dart` is otherwise upstream's, formatted as this repo's CI checks |
| Physical watch | not yet checked |

Marking: ✅ full / passes · ◐ partial — reason given · ○ not applicable (no upstream test) · ✗ unsupported on watchOS.

> This scaffold has been **finished by hand** into a working FFI package; see
> the status above. The generated notes below are kept for provenance.

## Design

The iOS implementation compiles each permission group in or out with a
preprocessor macro (`PERMISSION_CONTACTS=1` and so on, all off by default), so
that an app does not reference an API it never asks for: App Store Connect
warns about a missing usage description (ITMS-90683) for the privacy APIs a
binary references. A watch app has no Podfile to set those macros, and the
flutter-watchos CLI compiles plugin sources with fixed flags, so every group
is compiled into every app. Each framework is loaded with `dlopen` and its
classes looked up by name the first time a permission of its group is asked
about, and only messaged; of the frameworks, only UserNotifications, which
needs no usage description, is linked. `nm -u` on the compiled objects shows
no class symbol of CoreLocation, CoreMotion, AVFAudio, Contacts, EventKit,
CoreBluetooth or Intents. That is not the iOS result: the class names,
selectors and delegate protocol metadata are still in the binary, and Apple
does not document whether its check reads those. Until an App Store Connect
upload of an app that uses only notifications shows no ITMS-90683, the README
tells apps to expect to need every group's usage description. Leaving groups
out at compile time needs a CLI feature.

The location strategy keeps one flag in `NSUserDefaults` (whether "always"
was asked for), a required-reason API; the iOS implementation ships a
privacy manifest for the same reason. The README shows the entry an app's
manifest needs.

watchOS terminates an app that asks for a permission without its usage
description. Before each prompt this package checks the Info.plist key the
prompt needs and fails the request with a `PlatformException` whose code is
`MISSING_USAGE_DESCRIPTION`, the code the iOS implementation uses for
location, instead.

Each call returns a request id at once; the strategy answers on its own
queue, native keeps the answer as JSON, and wakes Dart through one
`NativeCallable.listener` that carries a kind, never a payload. Dart asks for
the permissions of `requestPermissions` one at a time, as the watch shows one
prompt at a time.

A location prompt closed without an answer, with the crown, sends no
delegate call on watchOS, not even the second "not determined" report the
iOS implementation takes as that (seen on the watchOS 27.0 Simulator). The
app becoming active again answers then, if it resigned active since the
request (the prompt taking the screen does that): with the status at that
moment, or, if it is still "not determined" a second later, denied. An
answer from the delegate in that second stands the re-read down, so a
request made right after, such as "always" after "when in use", is not
answered by it.

An "always" location request kept at "while using" changes nothing, so
CoreLocation sends no delegate call. As in the iOS implementation, the app
becoming active again after the prompt (`WKApplicationDidBecomeActiveNotification`)
is the answer then, and a second request answers without a prompt that
watchOS would not show again.

## Permission coverage

| Permission | watchOS |
|---|---|
| `notification` | ✅ UNUserNotificationCenter: alert, sound and badge, as on iOS; provisional is reported. The iOS implementation also registers for remote notifications after a grant; this package does not, which is firebase_messaging_watchos's job on the watch |
| `location`, `locationWhenInUse`, `locationAlways` | ✅ CLLocationManager; service status is `locationServicesEnabled` |
| `sensors` | ✅ CMMotionActivityManager, asked for with an activity query as on iOS; service status is `isActivityAvailable` |
| `microphone` | ✅ AVAudioApplication's record permission (watchOS 10 and later) |
| `contacts` | ✅ CNContactStore; the watchOS SDK has no limited access |
| `calendarFullAccess`, `calendarWriteOnly`, `calendar`, `reminders` | ✅ EKEventStore, with the full-access and write-only requests |
| `bluetooth` | ✅ CBCentralManager: creating it asks, its first state update answers; service status is whether it is powered on |
| `criticalAlerts` | ✅ UNUserNotificationCenter's critical alert setting and request; needs the Critical Alerts entitlement |
| `assistant` | ◐ INPreferences (watchOS 6+), Siri, for an app that opts in; needs the Siri capability. INPreferences throws in an app without it (seen on the Simulator), so the Info.plist key `PermissionHandlerWatchosSiri` (`YES`) is the opt-in, in place of the iOS implementation's `PERMISSION_ASSISTANT` macro; `NSSiriUsageDescription`, which apps add with every other group's, does not switch it on. Without the opt-in the status is `denied` and so is a request; with it and no `NSSiriUsageDescription`, a request fails with `MISSING_USAGE_DESCRIPTION`. A per-app compile-time opt-in is #19 |
| `storage` | ✅ granted, service not applicable, as on iOS |
| `camera`, `photos`, `photosAddOnly`, `mediaLibrary`, `speech`, `appTrackingTransparency`, `backgroundRefresh`, `phone` | ✗ not on watchOS: `denied`, a request `permanentlyDenied`, service `disabled`, as iOS answers for an unknown permission |
| The Android-only permissions | answered as an unknown permission, as on iOS |

## Interface coverage

| Member | watchOS |
|---|---|
| `checkPermissionStatus` | ✅ |
| `checkServiceStatus` | ✅ |
| `requestPermissions` | ✅ one at a time; another call while one waits fails with `ERROR_ALREADY_REQUESTING_PERMISSIONS`, as on iOS |
| `shouldShowRequestPermissionRationale` | ✅ false, as on iOS |
| `openAppSettings` | ✗ false: watchOS has no settings page an app can open |

## Example

`example/lib/main.dart` is permission_handler_apple 9.6.3's own example,
unchanged but for the formatting this repo's CI checks: a phone app on `baseflow_plugin_template` that lists every
permission, shows its status and asks for it on a tap. It runs on the watch,
cramped but usable (on the watchOS 27.0 Simulator a calendar request from it
prompted and answered granted). The example's pubspec adds what it imports,
`baseflow_plugin_template` and `permission_handler_platform_interface`, and
`url_launcher_watchos` for the template's links, which call url_launcher only
when tapped; `res/images/` is upstream's. On the Simulator the template's
info page opened, but its link buttons sit below a scroll area too small to
reach there, so the links were not tapped. The Info.plist has the usage
description of each permission. Upstream has no integration test, so
`integration_test/permission_handler_test.dart` is this package's own; it
imports only `permission_handler`.

Generated by `flutter-watchos plugin port` on 2026-10-21.

Source: `permission_handler_apple` 9.6.2 (path: `/Users/iamoguzhanarslantas/.pub-cache/hosted/pub.dev/permission_handler_apple-9.6.2`)
Base platform: ios (Objective-C)
Output: `./permission_handler_watchos`

## APIs the source plugin used

### Not available on watchOS

These iOS/macOS APIs have **no watchOS equivalent** — the capability must be omitted or redesigned (often via the paired iPhone):

| API | Used at | Why / watchOS note |
|---|---|---|
| AVCaptureCamera | `ios/permission_handler_apple/Sources/permission_handler_apple/strategies/AudioVideoPermissionStrategy.m:58` (+1 more) | AVFoundation exists on watchOS but the capture stack does not — there is no camera. Camera features must be omitted; the rest of the plugin's AVFoundation use (players, audio) can stay. |
| CoreTelephony | `ios/permission_handler_apple/Sources/permission_handler_apple/strategies/PhonePermissionStrategy.m:8` (+6 more) | CoreTelephony is not available on watchOS — even cellular watch models do not expose carrier/cellular-plan APIs to apps. There is no watchOS replacement; the feature must be omitted. |
| Photos | `ios/permission_handler_apple/Sources/permission_handler_apple/strategies/PhotoPermissionStrategy.h:11` (+4 more) | The Photos library framework is not available on watchOS. |
| SpeechRecognition | `ios/permission_handler_apple/Sources/permission_handler_apple/strategies/SpeechPermissionStrategy.m:27` (+2 more) | The Speech framework is not available on watchOS. System dictation is reachable only through WatchKit text input (`presentTextInputController`), which the Flutter watchOS engine already drives for text fields — a custom speech pipeline has no watchOS port. |
| UIApplication | `ios/permission_handler_apple/Sources/permission_handler_apple/strategies/BackgroundRefreshStrategy.m:25` (+4 more) | UIApplication does not exist on watchOS — the app object is WKApplication (watchOS 7+) / WKExtension. For opening URLs, use `openSystemURL(_:)`. It reliably handles tel: and sms:, and — despite the documentation listing only those — it also accepts http/https, presenting the system web sheet. Verified on a physical watch (watchOS 26.5): for a third-party app that sheet declines to render and shows "URL failed to load — this url can be viewed on your iPhone", even for a trivial page. Pair it with an NSUserActivity (NSUserActivityTypeBrowsingWeb) so the phone can actually pick the link up — see url_launcher_watchos. |

### Available, but review

These work on watchOS (often differently than iOS) — implement them, checking the note:

| API | Used at | watchOS note |
|---|---|---|
| CoreLocation | `ios/permission_handler_apple/Sources/permission_handler_apple/strategies/LocationPermissionStrategy.m:17` (+9 more) | CoreLocation IS available on watchOS: requestLocation, continuous updates, and heading (watchOS 6+) work, with GPS on cellular/GPS models and phone-assisted fixes otherwise. Differences to review: authorization is shared with the paired iPhone app when one exists, region monitoring is limited, and background location needs a workout or background-refresh context. |

## Checklist

- [x] Declare your C functions in `watchos/Classes/permission_handler_watchos_ffi.h` and implement them in the `.m`, one per platform-interface method you support.
- [x] List every exported symbol under `ffiSymbols` in `pubspec.yaml`, and the frameworks you link in `Package.swift`.
- [x] Add a `lookupFunction` binding per symbol in the Dart `Bindings` class and override the platform-interface methods.
- [x] Add the package to a watchOS app (`flutter-watchos create` one if needed), build for `watchsimulator`, then `nm` the binary to confirm your `ffiSymbols` are present (type `T`).
- [x] Bump the version and update `CHANGELOG.md` before publishing.

---

See the finished plugins in the `flutterwatch/plugins` repo (e.g. `path_provider_watchos`, `battery_plus_watchos`) for worked examples of this exact shape.
Loading
Loading