Repository navigation
Description
These came up while validating the Pub/Sub 2.0 website docs against integration/v2 at 1202d99bc7f8e561ba4e0a6c1ddafbc562740426. They're grouped here so they can be triaged together. If you'd rather track any of them on its own, split it into a separate issue.
Summary
| # | Finding | Priority | Website docs depend on it? | Recommendation |
|---|---|---|---|---|
| 1 | RealtimeChannels.release() is documented to fail with 90011, but the pinned 1.x native SDKs never produce it. The native bump also forces an endpoint decision |
P1 | Yes: until it lands, the Flutter docs keep the 1.x "detach before release" wording | Bump to the native next majors, forward the release error on both platforms, and replace environment/restHost/realtimeHost with endpoint |
| 2 | Ably-Agent still identifies as ably-flutter and has no ably-pubsub-device flag |
P1 | No | Once ably/ably-common#361 merges, rename the agent to ably-pubsub-flutter and add the device flag |
| 3 | UPDATING.md doesn't say to remove ably_flutter, and the two packages can't be used in one app |
P2 | Yes: the website push guide relies on this note | Add one line to UPDATING.md |
| 4 | Connection.ping() always throws UnimplementedError (since 1.x) |
P3 | No | Implement it, or remove it while 2.0 allows breaking changes |
| 5 | createClient() failures surface as several unhandled async errors (since 1.x) |
P3 | No | Validate obvious option errors in Dart, or report creation failures once |
P1 = should be fixed before the 2.0 release · P2 = should be fixed soon · P3 = nice to have.
1. RealtimeChannels.release() documents error 90011, but the pinned 1.x native SDKs never produce it (P1)
What's wrong
On integration/v2, release() is documented to fail with 90011 for a channel that isn't INITIALIZED, DETACHED or FAILED (RTS4e). On a device, though, releasing an attached channel still succeeds and detaches it implicitly, which is the 1.x behaviour.
This is known (see History). This item tracks the work to the 2.0 release, together with the ClientOptions change that the native bump forces.
Evidence
What the docs say
realtime_channels.dart#L20-L28: "If the channel is in any other state, the returned future completes with an [AblyException] with code 90011 and the channel is not released."UPDATING.md#L24: "Releasing a channel in any other state no longer detaches it implicitly. Instead, the future completes with anAblyExceptionwith code 90011…"
What happens
release()leaves the state check to the platform SDK (realtime_channels.dart#L30-L37).- Both platform handlers call the native
releaseand report success unconditionally:- Android:
AblyMethodCallHandler.java#L395-L401(channels.release(channelName); result.success(null);) - iOS:
AblyFlutter.m#L373-L383([realtime.channels release:channelName]; result(nil);)
- Android:
- The plugin still pins the 1.x natives:
android/build.gradle#L28(io.ably:ably-android:1.6.0) andably_pubsub_device_flutter.podspec#L21(Ably 1.2.53). Neither checks the channel state:- ably-java 1.6.0 removes the channel, then calls
detach()and only logs errors (AblyRealtime.java#L226-L239). - ably-cocoa 1.2.53 detaches, then removes (
ARTRealtimeChannels.m#L99-L135).
- ably-java 1.6.0 removes the channel, then calls
- On both platforms the detach runs without a listener, so the Dart future completes before the channel reaches
DETACHED. - The 90011 path is covered only through a mocked method channel:
test/realtime/channels_test.dart#L116-L141,test/mock_method_call_manager.dart#L73-L77.
I didn't reproduce this on a device. It follows from the native sources quoted above: neither native release has an error path that could reach the handler, and the handler has no error path either.
The native bump has a knock-on change
- The native next majors that implement RTS4c-e are merged on their integration branches, but not released yet:
- ably-cocoa
integration/v2: https://github.com/ably/ably-cocoa/pull/2315,release:error: - ably-pubsub-java
integration/v2:release(String) throws AblyException
- ably-cocoa
- Their
ClientOptionshave onlyendpoint;environment,restHostandrealtimeHostare gone (ably-pubsub-javaClientOptions.java#L88, ably-cocoaARTClientOptions.h#L34). - The plugin still exposes and forwards those three fields:
- Dart:
client_options.dart#L22,#L26,#L77 - Android:
AblyMessageCodec.java#L333-L344 - iOS:
AblyFlutterReader.m#L129-L136
- Dart:
- So the bump also means deciding on
ClientOptions.endpointfor 2.0, with anUPDATING.mdentry.
History
- The docs were added on purpose, and the native gap is known. 60fa9cf (Simon Woolf, feat!: make RealtimeChannels.release() async and surface platform errors #610) made
release()async and added the dartdoc and the UPDATING line. Its commit message says the check "is done by the platform SDKs (ably-java, ably-cocoa) in their next major versions". - feat!: make RealtimeChannels.release() async and surface platform errors #610 recorded the follow-up but nothing tracks it. feat!: make RealtimeChannels.release() async and surface platform errors #610's "Still to do" says the handlers must pass the native release error back, which depends on native releases that "don't exist yet". A review comment on feat!: make RealtimeChannels.release() async and surface platform errors #610 flagged the handlers. SimonWoolf's reply (feat!: make RealtimeChannels.release() async and surface platform errors #610 (comment)) says both handlers need updating as part of bumping to native SDK versions that implement RTS4c-e on this integration branch. No follow-up issue was created, so nothing tracks it to the release. This item is that tracker.
- The handlers and pins are unchanged since 1.x. The handlers date from 2021 (iOS) and 2023 (Android). The native pins come from 2bd3e5d (ably-java 1.6.0) and 0835886 (ably-cocoa 1.2.53).
- feat!: make RealtimeChannels.release() async and surface platform errors #610 doesn't mention the
endpointknock-on.
Impact
- 2.0 documents a breaking change (RTS4e) that doesn't happen on devices. Apps written against the docs never see 90011, for example ones that catch it to decide whether to detach first.
- Until this lands, the ably.com docs have to keep the 1.x "detached before release" wording for Flutter, along with the
await channel.detach(); await realtime.channels.release(name);pattern.
Recommendation
- Bump to the native next-major releases that implement RTS4c-e.
- Forward the release error on both platforms:
- Android: in
releaseRealtimeChannel, catch theAblyExceptionthatreleasenow throws and pass it tohandleAblyException(result, e). - iOS: call
release:error:, and when it returnsNO, return aFlutterErrorbuilt from theARTErrorInfo.
- Android: in
- Add a device or integration test: releasing an attached channel completes with 90011 (statusCode 400), and the channel is still there.
- Replace
environment/restHost/realtimeHostwithendpoint(or map them), and document it inUPDATING.md. - Optionally, add a
90011constant toErrorCodes(error_codes.dart#L2-L9). - If 2.0 has to ship before the native majors, change the dartdoc and
UPDATING.md#L24to describe what the plugin actually does.
2. Ably-Agent still identifies as ably-flutter and has no ably-pubsub-device flag (P1)
What's wrong
#604 deliberately left the ably-flutter/<version> agent identifier unchanged, noting that "the family rename belongs to the split, gated on ably-common#361". The split is now on integration/v2, but the plugin still builds its native clients with the 1.x agents. So 2.0 traffic can't be told apart from ably_flutter 1.x traffic by identifier.
Evidence
What it sends now (derived from the source, not captured on a device; pubspec version 1.2.44):
Android: ably-java/1.6.0 dart/<dart-version> ably-flutter/1.2.44 android/<api-level>
iOS: ably-cocoa/1.2.53 ably-flutter/1.2.44 dart/<dart-version> iOS/<os-version>
The same value goes in the HTTP Ably-Agent header and the realtime agent query param.
- The plugin adds
ably-flutter/<version>anddart/<version>, and nothing else:- Android:
AblyMessageCodec.java#L359-L362 - iOS:
AblyFlutterReader.m#L151-L157
- Android:
- It constructs the native clients directly, not through a device entry point:
AblyInstanceStore.java#L83,AblyFlutter.m#L77. - No test asserts the agent value.
- The other 2.0 device SDKs already send the flag:
- ably-pubsub-java's device builder:
PubSubDevice.kt#L419 - ably-cocoa
integration/v2:ARTPubSubDevice.m#L13-L28
- ably-pubsub-java's device builder:
History
- The agent dates from 2021. It came in with 4a05da6 (Add ably agent header #188), and the
dartentry with Dart runtime version as agent header #367. Neither has changed since. - The rename was explicitly deferred, not rejected. Update repository references for rename to ably-pubsub-flutter #604 (61532cb) kept the identifier as a deferral to the split, gated on Register the Pub/Sub split agent identifiers: per-side flags and per-language families ably-common#361. The split has since landed (the package rename in
ea51456f), but the agent wasn't flipped. So this item finishes planned work; it doesn't reverse a decision. - Register the Pub/Sub split agent identifiers: per-side flags and per-language families ably-common#361 is still open. It is approved but not merged, and
protocol/agents.jsonon ably-commonmainhasably-flutterand noably-pubsub-*entry. RSC7d5 requires products to be in that file, so the flip has to wait for Register the Pub/Sub split agent identifiers: per-side flags and per-language families ably-common#361 to merge.
Impact
- The 2.0 agent convention in Register the Pub/Sub split agent identifiers: per-side flags and per-language families ably-common#361 (open, approved) would register
ably-pubsub-flutteras Flutter's 2.0 identifier, awrapperover the Java and Cocoa cores.- Each SDK flips to its new identifier at the new major: "old identifier ⇒ legacy-package traffic, new ⇒ new-package traffic".
- Every 2.0 client also sends the versionless side flag,
ably-pubsub-deviceorably-pubsub-server.
- If 2.0.0 ships with
ably-flutterand no side flag, its traffic counts as legacy-package traffic. Migration tracking and 1.x end-of-life work can then separate it only by version number. That can't be fixed after release for the versions already out.
Recommendation
- Once Register the Pub/Sub split agent identifiers: per-side flags and per-language families ably-common#361 is merged: rename the plugin's agent entry from
ably-fluttertoably-pubsub-flutter, and add the versionlessably-pubsub-deviceflag on both platforms:- Android:
put("ably-pubsub-device", null). ably-java writes anullversion as the bare name. - iOS: set the value to the exported
ARTClientInformationAgentNotVersionedconstant. ably-cocoa compares it by pointer, so a string literal won't work.
- Android:
- If the natives are bumped for 2.0 (item 1 above): create the clients through their device entry points instead (
PubSubDevicebuilder,ARTPubSubDevice). These add the flag themselves. Keep the plugin's ownably-pubsub-flutterentry. - Tests: add a unit test on both platforms that asserts the agents map.
3. UPDATING.md doesn't say to remove ably_flutter, and the two packages can't be used together (P2)
What's wrong
UPDATING.md#L7-L10 says the package was renamed, but not that the old one has to go. The new package keeps the old native identity, so the two clash in one app.
Evidence
- The new package keeps the old native identity:
- the same Android package and plugin class (
pubspec.yaml#L22-L29,io.ably.flutter.plugin/AblyFlutter); - the same Java and ObjC class names;
- the same method-channel names (
io.ably.flutter.plugin,io.ably.flutter.stream).
- the same Android package and plugin class (
flutter pub getaccepts both packages without complaint (checked). The clash only shows up later:- duplicate-class errors in the Android build;
- duplicate or ambiguous ObjC classes on iOS;
- two plugins on one method channel.
- Caveat: I couldn't run the two-plugin Android or iOS build for this check. The build failures are inferred from the identical class and channel names above, plus the same duplicate-class failure mode in Version conflict with Intercom Android SDK #584.
History
This is an omission in the 2.0 package rename (ea51456f). Nothing records a decision to leave it out.
Impact
An app that migrates incrementally, or picks up ably_flutter through another dependency, hits the clash with no warning from the upgrade guide.
Recommendation
Add to UPDATING.md: "Remove ably_flutter from your dependencies; the two packages cannot be used in the same app."
4. Connection.ping() always throws (P3)
What's wrong
connection.dart#L107-L115 documents a round-trip ping, but the body is throw UnimplementedError();, thrown synchronously.
Evidence
The same Dart-level repro as item 5 shows ping(): UnimplementedError. The body is identical on main and in every tag since 1.2.0.
History
This is long-standing 1.x behaviour, not a 2.0 regression. It has been unimplemented since b8104ca8 (2020).
Impact
Code that follows the dartdoc fails at runtime. Both native SDKs support ping.
Recommendation
Either implement it, or remove it while 2.0 still allows breaking changes.
5. createClient() failures surface as unhandled async errors (P3)
What's wrong
createClient()returns immediately (pub_sub_client.dart#L102-L103), and the native client is created in the background.- The resulting future has no error handler (
platform_object.dart#L18-L22). Connection,Push, and each channel's presence and push objects all chain off that future. So one creation failure produces several unhandled async errors, then a rethrow from every later call.
Evidence
Example: a relative authUrl such as '/auth'. On Android, ably-java rejects it in the Auth constructor, and the plugin returns that error from createRealtime (AblyMethodCallHandler.java#L308-L311). I didn't check iOS behaviour for a relative authUrl.
Dart-level reproduction (mocked platform channel)
Run with flutter test (Flutter 3.19.2) in a worktree at 1202d99b. The mock makes createRealtime throw the kind of PlatformException the Android handler returns for authUrl: '/auth'. The error code in the mock is illustrative; I didn't check which code ably-java assigns to a malformed URL.
createClient returned synchronously: PubSubClient
uncaught async errors: 2
- AblyException: … message=(no protocol: /auth) …
- AblyException: … message=(no protocol: /auth) …
connect(): rethrows AblyException: … no protocol: /auth …
attach(): rethrows AblyException: … no protocol: /auth …
ping(): UnimplementedError
00:01 +0 -1: … [E] (further uncaught AblyExceptions from RealtimePresence / PushChannel created by channels.get())
History
This is long-standing 1.x behaviour, not a 2.0 regression. The same PlatformObject pattern drove Realtime(...) in 1.x (2020-2021); createClient() only wraps the constructor. #551 shows a user hitting a creation failure in 1.x as an unhandled exception.
Impact
Apps get unhandled async errors (one per dependent object) instead of a single, catchable failure. 2.0 is the cheap window to change this.
Recommendation
Either validate obvious option errors in Dart, or report creation failures once, through connection state or a documented error.
Checklist
- Native bump to the RTS4c-e majors, release error forwarded on Android and iOS,
endpointreplacesenvironment/restHost/realtimeHost(or the dartdoc and UPDATING are changed to match 1.x behaviour) - Agent renamed to
ably-pubsub-flutterwith theably-pubsub-deviceflag, after Register the Pub/Sub split agent identifiers: per-side flags and per-language families ably-common#361 merges - UPDATING.md says to remove
ably_flutter -
Connection.ping()implemented or removed -
createClient()failures reported once, or validated in Dart
Related
- 2.0 release PR: Integration branch for ably_pubsub_device_flutter v2 package #606
- feat!: make RealtimeChannels.release() async and surface platform errors #610: async
release()and its "Still to do" - Update repository references for rename to ably-pubsub-flutter #604: the deferred agent rename
- Error on Android Build #551 (1.x creation failure) and Version conflict with Intercom Android SDK #584 (duplicate-class build failure)
- Register the Pub/Sub split agent identifiers: per-side flags and per-language families ably-common#361: the agent identifiers
- https://github.com/ably/ably-cocoa/pull/2315: the ably-cocoa
release:error:change
Activity