feat(firebase): tvOS support for firebase_performance - #9
Conversation
There was a problem hiding this comment.
Review
Clean port — the discipline here is exactly right, and the verification is the most convincing part. Three small changes requested, none of them in the ported native code.
1. plugins/README.md Ports table is not updated
AUTHORING.md §4 makes this a step of its own — "Edit plugins/README.md, add the new row to the Ports table" — and the porting checklist repeats it. All sixteen existing packages have a row; this one doesn't, so the package is undiscoverable from the repo index.
I see the PR checklist says only firebase_performance_tvos files are touched, so this looks deliberate. It's the one place where touching a file outside the package is the documented expectation.
| [`firebase_performance_tvos`](packages/firebase_performance_tvos) [](https://pub.dev/packages/firebase_performance_tvos) | [`firebase_performance`](https://pub.dev/packages/firebase_performance) |2. Both pubspec_overrides.yaml files are obsolete — by their own comment
Each says "Remove once firebase_core_tvos 0.0.1 is on pub.dev." It is:
$ curl -s https://pub.dev/api/packages/firebase_core_tvos | jq -r .latest.version
0.0.1They aren't merely redundant — the example one is load-bearing today, because example/pubspec.yaml declares firebase_core_tvos as a path: dependency while this package requires it as hosted ^0.0.1, and pub refuses to mix sources for one package. The override is what collapses them. So removing the overrides alone breaks the example:
$ flutter pub get # overrides removed, example still on path:
Because firebase_performance_example depends on firebase_performance_tvos from path
which depends on firebase_core_tvos ^0.0.1, firebase_core_tvos from hosted is required.
So, because firebase_performance_example depends on firebase_core_tvos from path,
version solving failed.Removing both overrides and switching the example to the hosted constraint resolves cleanly — verified:
# example/pubspec.yaml
- firebase_core_tvos:
- path: ../../firebase_core_tvos
+ firebase_core_tvos: ^0.0.1$ flutter pub get
Got dependencies!
Resolving dependencies in `./example`...
Got dependencies in `./example`.Two notes on scope: the same stale pair sits in cloud_firestore_tvos, firebase_auth_tvos, firebase_messaging_tvos and firebase_storage_tvos, so cleaning those up is a separate change, not yours. And the example override's comment says "Gitignored" — it is committed, and the package .gitignore doesn't list it. (.pubignore does exclude it, so publishing was never at risk.)
3. The example's Info.plist still identifies itself as the storage example
example/tvos/Runner/Info.plist is byte-identical to firebase_storage_tvos's, display strings included:
<key>CFBundleDisplayName</key>
<string>Firebase_storage_example</string>
...
<key>CFBundleName</key>
<string>firebase_storage_example</string>Installed on an Apple TV, this example shows up as "Firebase_storage_example" on the home screen and in Settings — so anyone running both example apps gets two tiles they can't tell apart. PRODUCT_BUNDLE_IDENTIFIER is correctly com.example.firebasePerformanceExample, so Firebase app matching is unaffected; only the human-facing strings are wrong.
These are the only two stale references in the package — I grepped all 59 files, and the sole other storage hit is the legitimate storageBucket field in firebase_options.dart.
What I verified
The repository has no CI — there is no .github/workflows, and no PR in it has ever reported a check, despite AUTHORING.md §6 saying "CI will run flutter pub get + flutter analyze on the new package." So I ran the checks by hand and looked at the port mechanically.
The port is minimal and faithful. Diffed all three Swift files against firebase_performance 0.11.4+3 as published on pub.dev. Exactly three deltas, every one necessary:
# FirebasePerformanceMessages.g.swift
-#if os(iOS)
+#if (os(iOS) || os(tvOS))
# FirebasePerformancePlugin.swift
-#if canImport(firebase_core)
- import firebase_core
-#else
- import firebase_core_shared
-#endif
+import firebase_core_tvos
- #elseif os(iOS)
+ #elseif (os(iOS) || os(tvOS))Constants.swift is byte-identical to upstream. No stubs, no disabled regions, nothing rewritten that didn't have to be — which is what makes the next rebase onto a new firebase_performance cheap.
Podspec. Structurally matches the merged firebase_storage_tvos, and Firebase/Performance '~> 12.15.0' is aligned with every other Firebase _tvos package in the repo, firebase_core_tvos included — so CocoaPods resolves one Firebase version across the stack.
The tvOS 15.0 floor is right, not a guess. Firebase Performance 12.15.0's own podspec declares tvos_deployment_target = '15.0'.
"No feature disables" holds more strongly than the README claims. The README reads as a statement about the plugin's API surface, but the automatic instrumentation is fine too: FPRScreenTraceTracker.m in the Firebase Apple SDK carries explicit #if TARGET_OS_TV branches — it even recomputes the slow-frame budget on UIScreenModeDidChangeNotification, because a tvOS refresh rate can change with the display mode. Screen traces are supported on tvOS by design, not tolerated.
Hygiene. firebase_options.dart is all REPLACE_ME — no project credentials committed. No absolute paths or personal identifiers anywhere in the 59 files.
Static checks (Flutter 3.44.0 stable):
$ flutter pub get # package + example
Got dependencies!
$ dart analyze # package
No issues found!
$ (cd example && dart analyze)
No issues found!
$ flutter test
00:00 +1: All tests passed!A repo-level note your README raised, and I went and checked
Not a request on this PR — it applies equally to all five existing Firebase packages, and you inherited it. But your "Version alignment matters" warning is more load-bearing than it reads, so it's worth writing down where the sharp edge actually is.
firebase_core_tvos encodes FirebaseOptions as a positional list of exactly fifteen elements, decoded with an unchecked index:
pigeonResult.recaptchaSiteKey = GetNullableObjectAtIndex(list, 14);
…
static id GetNullableObjectAtIndex(NSArray<id> *array, NSInteger key) {
id result = array[key]; // no bounds checkinitializeApp sends those options back to Dart, and the Dart side is equally positional — firebase_core_platform_interface 7.1.0, lib/src/pigeon/messages.pigeon.dart:186-205:
static CoreFirebaseOptions decode(Object result) {
result as List<Object?>;
return CoreFirebaseOptions(
apiKey: result[0]! as String,
…
recaptchaSiteKey: result[14] as String?,
);
}Fifteen on each side today, so it works. The two directions are not symmetric: a Dart side with fewer fields ignores the trailing element harmlessly, while a Dart side with more reads result[15] off a fifteen-element list and throws — a RangeError at Firebase.initializeApp(), which is the launch crash your README describes in prose.
The guard against that is one line in firebase_core_tvos:
firebase_core: ^4.11.0It cannot do the job, and tightening it doesn't help either. firebase_core 4.11.0 declares firebase_core_platform_interface: ^7.1.0, so the arity floats across all of 7.x no matter how narrowly firebase_core itself is pinned — even an exact firebase_core: 4.11.0 leaves the field count free to change under it. The only constraint that actually pins the arity is a direct one on firebase_core_platform_interface in firebase_core_tvos, since that is the package the generated code's shape comes from.
Worth a separate issue rather than anything here — it applies to all five Firebase _tvos packages, and only firebase_core_tvos needs the change.
One more note
test/firebase_performance_tvos_test.dart is the porter's placeholder (expect(1 + 1, 2)). That matches every sibling and AUTHORING.md §5 says plugin testing in this repo is app-driven, so it isn't something to fix here. Worth saying out loud, though: with no CI and no unit tests, your manual verification is the only gate this package has — and it is a good one. Two distinct named traces with durations, on the simulator and a physical Apple TV 4K in release/AOT, confirmed in the dashboard rather than inferred from a log line, is more evidence than most ports arrive with.
Happy to approve once the README row, the override cleanup and the Info.plist strings land.
| <string>Firebase_storage_example</string> | ||
| <key>CFBundleExecutable</key> | ||
| <string>$(EXECUTABLE_NAME)</string> | ||
| <key>CFBundleIdentifier</key> | ||
| <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string> | ||
| <key>CFBundleInfoDictionaryVersion</key> | ||
| <string>6.0</string> | ||
| <key>CFBundleName</key> | ||
| <string>firebase_storage_example</string> |
There was a problem hiding this comment.
Copy-paste leftover: this file is byte-identical to firebase_storage_tvos's example Info.plist, display strings included. Installed on an Apple TV the example shows up as Firebase_storage_example on the home screen and in Settings, so running both examples side by side gives two tiles you can't tell apart.
PRODUCT_BUNDLE_IDENTIFIER is correct (com.example.firebasePerformanceExample), so Firebase app matching is unaffected — only the human-facing strings.
| <string>Firebase_storage_example</string> | |
| <key>CFBundleExecutable</key> | |
| <string>$(EXECUTABLE_NAME)</string> | |
| <key>CFBundleIdentifier</key> | |
| <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string> | |
| <key>CFBundleInfoDictionaryVersion</key> | |
| <string>6.0</string> | |
| <key>CFBundleName</key> | |
| <string>firebase_storage_example</string> | |
| <string>Firebase_performance_example</string> | |
| <key>CFBundleExecutable</key> | |
| <string>$(EXECUTABLE_NAME)</string> | |
| <key>CFBundleIdentifier</key> | |
| <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string> | |
| <key>CFBundleInfoDictionaryVersion</key> | |
| <string>6.0</string> | |
| <key>CFBundleName</key> | |
| <string>firebase_performance_example</string> |
(I grepped all 59 files — these two lines are the only stale references; the other storage hit is the legitimate storageBucket field.)
| # Local development only — resolves the unpublished firebase_core_tvos from the | ||
| # sibling package in this monorepo. Excluded from the published archive via | ||
| # .pubignore so consumers get the hosted `^0.0.1` instead. | ||
| dependency_overrides: | ||
| firebase_core_tvos: | ||
| path: ../firebase_core_tvos |
There was a problem hiding this comment.
This file and example/pubspec_overrides.yaml are both obsolete by their own comment — "Remove once firebase_core_tvos 0.0.1 is on pub.dev" — and it is:
$ curl -s https://pub.dev/api/packages/firebase_core_tvos | jq -r .latest.version
0.0.1Careful with the order, though: the example one is load-bearing right now. example/pubspec.yaml declares firebase_core_tvos as a path: dep while this package requires it hosted as ^0.0.1, and pub won't mix sources for one package — the override is what collapses them. Deleting the overrides alone breaks the example:
$ flutter pub get
Because firebase_performance_example depends on firebase_performance_tvos from path
which depends on firebase_core_tvos ^0.0.1, firebase_core_tvos from hosted is required.
So, because firebase_performance_example depends on firebase_core_tvos from path,
version solving failed.Deleting both and moving the example to the hosted constraint resolves cleanly — verified:
# example/pubspec.yaml
- firebase_core_tvos:
- path: ../../firebase_core_tvos
+ firebase_core_tvos: ^0.0.1The same stale pair sits in cloud_firestore_tvos, firebase_auth_tvos, firebase_messaging_tvos and firebase_storage_tvos, so cleaning those is a separate change, not yours. Also: the example override's comment says "Gitignored" — it is committed, and the package .gitignore doesn't list it. (.pubignore does exclude it, so publishing was never at risk.)
What does this PR do?
Adds federated
firebase_performance_tvos— Firebase Performance Monitoring for Apple TV, built on the Firebase Apple SDK. Re-exports thefirebase_performanceDart API and ships the native tvOSpluginClass; depends onfirebase_core_tvos. Full Performance API (custom traces + HTTP metrics); no features disabled.Package(s) touched:
firebase_performance_tvos(new)How was it tested?
Verified against a live Firebase project on both the tvOS simulator and a physical Apple TV 4K (release/AOT).
Firebase Performance Monitoring is successfully initialized!, custom traces + an HTTP metric were captured and sent, and the traces were confirmed in the Firebase Performance dashboard (Custom traces tab) —tvos_smoke_trace(206 ms, simulator) andtvos_device_trace(207 ms, physical Apple TV). No features are disabled (Performance is UI-free).Platform.operatingSystem == "tvos"/Platform.isIOS == true.example/apptvos_smoke_trace, 206 ms)tvos_device_trace, 207 ms)dart analyzeis clean for the packageVersioning & changelog
version:set to0.0.1(new package)## 0.0.1entry at the top ofCHANGELOG.md0.x: initial0.0.1Checklist
firebase_performance_tvosfiles are touchedTODO/debug leftoversREADME.mddocuments tvOS behaviour (no feature limitations; version-alignment note)Notes for reviewers
_tvosleaf packages): the native code matchesfirebase_performance 0.11.4+3on thefirebase_core_platform_interface7.1.0 train (firebase_core 4.11.x). Mixingfirebase_core_tvoswith a differentfirebase_corewhoseFirebaseOptionslist differs can crash inCoreFirebaseOptions.fromListat launch (Dartoptions:init).Firebase/Performance+firebase_core_tvos, tvOS 15); Swiftfirebase_coreimport repointed tofirebase_core_tvos; generatedtvos/Package.swiftremoved (route via podspec); Dart re-export.