Skip to content
Merged
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
36 changes: 36 additions & 0 deletions packages/firebase_auth_watchos/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,39 @@
## 0.2.0

* **Breaking:** works with `firebase_auth` 7.x, and needs it. The package now
depends on `firebase_core: ^5.0.0` (was `^4.0.0`) and
`firebase_auth_platform_interface: ^9.1.1` (was `^9.0.5`), the first
version of the platform interface for `firebase_core` 5.x. An app that
stays on `firebase_auth` 6.x keeps 0.1.1.
* The watch still links the Firebase Apple SDK 11.x through
`watchos/Package.swift`. The newer SDK that `firebase_auth` 7.x uses on iOS
and macOS does not apply to it.
* `authStateChanges`, `idTokenChanges` and `userChanges` no longer repeat
their first value for the call the SDK makes to each listener when the
plugin installs it, on the first native call for an app. With 0.1.1 a
stream subscribed before any other auth call could emit the current user,
or null, a second time. The streams now leave that call out when it reports
the value they last emitted, and emit it when the user changed before it
ran: for `authStateChanges` it can be the only report of such a sign-out.
`idTokenChanges` and `userChanges` can still emit a signed-in user twice
when the SDK restores or signs in that user while the plugin installs its
listeners; `firebase_auth` usually emits it once there.
* The same streams report a signed-out state once, as `firebase_auth` 7.0.1
does: a null is not emitted when the last value emitted was null too.
* `User.updateProfile`: `firebase_auth` 7.0.1 passes only the fields it is
given, so an omitted field now keeps its value on the watch; this package
already applied only the fields present in the request. A field given as
`null` is forwarded to the SDK's profile change request. The Firebase Apple
SDK then clears it on the local user and does not send it to the server, as
on iOS. A new test checks that the `null` reaches the native layer.
* README: the install snippet names `firebase_core: ^5.1.0`,
`firebase_auth: ^7.0.1` and this version, and says that
`sendPasswordResetEmail`, `sendEmailVerification` and
`verifyBeforeUpdateEmail` do not pass on their `ActionCodeSettings` yet.
* Example: `pubspec.yaml` names `firebase_auth: ^7.0.1`,
`firebase_core: ^5.1.0` and `firebase_messaging: ^17.0.1`. No native code
changed, and the example was not rebuilt on the Simulator for this version.

## 0.1.1

* API docs and comments say "release" for handing native memory back.
Expand Down
26 changes: 21 additions & 5 deletions packages/firebase_auth_watchos/PORTING_REPORT.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@
Scaffolded by `flutter-watchos plugin port --from-pub firebase_auth
--include-example` on 2026-07-17, then hand-finished.

Source: `firebase_auth` 6.5.6
Source: `firebase_auth` 6.5.6. Since 0.2.0 it implements
`firebase_auth_platform_interface` 9.1.1 and later, for `firebase_auth` 7.x;
read against 7.0.1 and platform interface 9.1.2. The watch still links the
Firebase Apple SDK 11.x (`watchos/Package.resolved` pins 11.15.0); the newer
SDK that `firebase_auth` 7.x uses on iOS and macOS does not apply to it.
Base platform: ios (Objective-C)
Model: **dart:ffi over the Firebase Apple SDK** (`FirebaseAuth`)

Expand All @@ -13,8 +17,8 @@ Model: **dart:ffi over the Firebase Apple SDK** (`FirebaseAuth`)
|---|---|
| Implementation (Dart + FFI + native) | ◐ the watch-viable flows, except the methods the README lists under "Not implemented yet" (reauthentication and linking among them) |
| watchOS capability | ◐ email, anonymous and token sign-in are implemented; provider/OAuth, phone, and MFA flows need UI the watch cannot present |
| Host unit tests | ✅ 13/13 pass (native FirebaseAuth faked) |
| On-simulator build + link + run | ◐ links the Firebase Apple SDK; the example builds and starts on the watchOS 27.0 Simulator. The smoke test was not run for this version |
| Host unit tests | ✅ 23/23 pass (native FirebaseAuth faked) |
| On-simulator build + link + run | ◐ links the Firebase Apple SDK; the example builds and starts on the watchOS 27.0 Simulator (checked with 0.1.1; 0.2.0 changes no native code and was not rebuilt). The smoke test was not run for this version |
| Physical watch hardware | ○ not yet verified |
| Upstream integration test | ○ none in the firebase_auth pub example (smoke test added) |

Expand All @@ -30,7 +34,19 @@ poll-a-generation pattern: the native layer registers the SDK's
auth-state/ID-token listeners once per app and bumps counters that
`firebase_auth_watchos_current_user` reports alongside the user snapshot;
`authStateChanges`/`idTokenChanges`/`userChanges` poll the counters and emit
on change (the poll timer stops when the subscription is cancelled).
on change (the poll timer stops when the subscription is cancelled). The SDK
calls each listener once when it is installed, with the user it has when the
call runs. A counter that is still 0 at a stream's first snapshot is waiting
for that call, and a move that is only that call is left out when it reports
the value the stream last emitted; when the user changed before the call ran,
it is emitted. As in `firebase_auth` 7.x, each stream also reports a
signed-out state once: a null is not emitted after a null. A known
difference: `idTokenChanges` and `userChanges` can still emit a signed-in
user twice when the SDK restores or signs in that user while the listeners
are being installed, because its own change notification then bumps the
ID-token counter as well. Installing the listeners from a later main-queue
block, after reading `currentUser`, would remove it; that is a native change
still to make and check on the Simulator.

Errors carry FlutterFire's string codes: the native layer reads the SDK's
`FIRAuthErrorUserInfoNameKey` (`ERROR_WRONG_PASSWORD`) and converts it to the
Expand Down Expand Up @@ -63,7 +79,7 @@ watchOS).
FirebaseAuthPlatform`, `UserWatchos extends UserPlatform`,
`UserCredentialWatchos`, `MultiFactorWatchos`, over the FFI bindings.
- `test/firebase_auth_watchos_test.dart` — host tests with the native backend
faked (13/13).
faked (23/23).
- `example/` — the upstream firebase_auth example, ported for watchOS, plus
an added `integration_test/firebase_auth_smoke_test.dart` (the pub example
ships no upstream integration_test).
21 changes: 15 additions & 6 deletions packages/firebase_auth_watchos/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@ and the auth-state streams poll the SDK's change notifications.
> Firebase support on watchOS is new.
>
> **What has been checked:** the example app builds and starts on the
> watchOS 27.0 Simulator, and the host unit tests pass. It has not yet been
> run on a physical Apple Watch.
> watchOS 27.0 Simulator, and the host unit tests pass. The Simulator run
> was with 0.1.1 and `firebase_auth` 6.x; 0.2.0 changes no native code, and
> its example has not been rebuilt yet. It has not yet been run on a
> physical Apple Watch.
>
> Requires `firebase_core_watchos` (the initialization + app registry
> foundation) and a `flutter-watchos` CLI with external-SwiftPM-dependency
Expand Down Expand Up @@ -50,6 +52,10 @@ enrollment. These throw `UnimplementedError` or report `unimplemented`.
Without reauthentication, Firebase rejects `updatePassword` and `delete` with
`requires-recent-login` once the last sign-in is too old.

`sendPasswordResetEmail`, `sendEmailVerification` and
`verifyBeforeUpdateEmail` send their email without the `ActionCodeSettings`
they are given: passing those on is not implemented yet.

## Usage

This is a federated plugin implementation. Apps that already depend on
Expand All @@ -58,12 +64,15 @@ This is a federated plugin implementation. Apps that already depend on

```yaml
dependencies:
firebase_core: ^4.0.0
firebase_core_watchos: ^0.1.0
firebase_auth: ^6.0.0
firebase_auth_watchos: ^0.1.0
firebase_core: ^5.1.0
firebase_core_watchos: ^0.1.2
firebase_auth: ^7.0.1
firebase_auth_watchos: ^0.2.0
```

Version 0.2.0 needs `firebase_auth` 7.x and `firebase_core` 5.x. An app that
stays on `firebase_auth` 6.x uses 0.1.1.

Then use `firebase_auth`'s API exactly as on iOS — the watchOS implementation
registers automatically via Flutter's federated plugin runner.

Expand Down
6 changes: 3 additions & 3 deletions packages/firebase_auth_watchos/example/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@ environment:
dependencies:
flutter:
sdk: flutter
firebase_auth: ^6.7.0
firebase_auth: ^7.0.1
firebase_auth_watchos:
path: ../
barcode_widget: "^2.0.4"
collection: ^1.16.0
firebase_core: "^4.12.1"
firebase_core: "^5.1.0"
firebase_core_watchos:
path: ../../firebase_core_watchos
firebase_messaging: "^16.4.3"
firebase_messaging: "^17.0.1"
firebase_messaging_watchos:
path: ../../firebase_messaging_watchos
flutter_facebook_auth: "^7.1.5"
Expand Down
58 changes: 54 additions & 4 deletions packages/firebase_auth_watchos/lib/firebase_auth_watchos.dart
Original file line number Diff line number Diff line change
Expand Up @@ -416,20 +416,65 @@ class FirebaseAuthWatchos extends FirebaseAuthPlatform {
// A fresh stream per call, matching FlutterFire semantics: emits the
// current user immediately, then again whenever the watched generation
// moves. The generation is bumped natively by the SDK's listeners.
//
// The native side installs the listeners on the first native call for an
// app, and the SDK then calls each one once, asynchronously, with the user
// it has when the call runs. A counter that is still 0 at this stream's
// first snapshot is waiting for that call. When a move of the generation
// is that call alone, it is left out if it reports the user the stream
// last emitted, and emitted if the user changed before the call ran: the
// auth-state listener reports a uid only once, so that call may be the
// only report of a sign-out. firebase_auth drops a first callback that
// carries a user and forwards one that carries none.
//
// As in firebase_auth 7.x, a signed-out state is reported once: a null is
// skipped when the last value emitted, the first one included, was null
// too.
Stream<UserPlatform?> _watch(int Function(_AuthSnapshot) generationOf) {
late StreamController<UserPlatform?> controller;
int? lastGeneration;
int? lastSettledGeneration;
bool emitted = false;
String? lastUid;
bool? authInstallCallPending;
bool? idTokenInstallCallPending;
void onPoll(
_AuthSnapshot? snapshot, Object? error, StackTrace? stackTrace) {
if (error != null) {
controller.addError(error, stackTrace!);
return;
}
final int generation = generationOf(snapshot!);
if (generation != lastGeneration) {
lastGeneration = generation;
controller.add(snapshot.user);
final _AuthSnapshot current = snapshot!;
final bool authPending =
authInstallCallPending ??= current.authGeneration == 0;
final bool idTokenPending =
idTokenInstallCallPending ??= current.idTokenGeneration == 0;
final int generation = generationOf(current);
if (generation == lastGeneration) {
return;
}
// The generation without the install calls: when it did not move, the
// install calls are all that happened.
final int settledGeneration = generationOf(_AuthSnapshot(
authGeneration:
_withoutInstallCall(current.authGeneration, authPending),
idTokenGeneration:
_withoutInstallCall(current.idTokenGeneration, idTokenPending),
userMutations: current.userMutations,
user: current.user,
));
final bool installCallsOnly = settledGeneration == lastSettledGeneration;
lastGeneration = generation;
lastSettledGeneration = settledGeneration;
final UserPlatform? user = current.user;
if (emitted &&
user?.uid == lastUid &&
(user == null || installCallsOnly)) {
return;
}
emitted = true;
lastUid = user?.uid;
controller.add(user);
}

controller = StreamController<UserPlatform?>(
Expand All @@ -450,6 +495,11 @@ class FirebaseAuthWatchos extends FirebaseAuthPlatform {
return controller.stream;
}

// A native listener counter without the bump of the listener's install
// call, when that call was still to come at a stream's first snapshot.
static int _withoutInstallCall(int generation, bool installCallPending) =>
installCallPending && generation > 0 ? generation - 1 : generation;

/// Starts a native operation and polls until its result arrives.
/// Throws [FirebaseAuthException] on an in-band error.
Future<Map<String, Object?>> _awaitOp(Map<String, Object?> request) async {
Expand Down
6 changes: 3 additions & 3 deletions packages/firebase_auth_watchos/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: firebase_auth_watchos
description: "watchOS implementation of firebase_auth, bridging the Firebase Apple SDK (FirebaseAuth) over dart:ffi for email, anonymous, and token sign-in."
version: 0.1.1
version: 0.2.0
homepage: https://flutterwatch.dev
repository: https://github.com/flutterwatch/plugins
issue_tracker: https://github.com/flutterwatch/plugins/issues
Expand All @@ -24,8 +24,8 @@ environment:

dependencies:
ffi: ^2.1.0
firebase_auth_platform_interface: ^9.0.5
firebase_core: ^4.0.0
firebase_auth_platform_interface: ^9.1.1
firebase_core: ^5.0.0
flutter:
sdk: flutter

Expand Down
Loading
Loading