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
1 change: 1 addition & 0 deletions UPDATING.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ This guide lists the changes needed to upgrade from one version of Ably to a new
- `RestChannel`, `RestChannels`, `RestPresence`, `RestChannelOptions`, `RestHistoryParams` and `RestPresenceParams` have been removed. Use `RealtimeChannel`, `RealtimeChannels`, `RealtimePresence`, `RealtimeChannelOptions`, `RealtimeHistoryParams` and `RealtimePresenceParams`.
- `Message.fromEncoded`, `Message.fromEncodedArray`, `PresenceMessage.fromEncoded` and `PresenceMessage.fromEncodedArray` now take a `RealtimeChannelOptions` instead of a `RestChannelOptions`.
- `Push` and `PushChannel` no longer accept a `rest` client; they now require a `realtime` client.
- `RealtimeChannels.release()` now returns a `Future<void>`, which completes once the channel has been released. A realtime channel can only be released when it is in the `INITIALIZED`, `DETACHED`, or `FAILED` state. Releasing a channel in any other state no longer detaches it implicitly. Instead, the future completes with an `AblyException` with code 90011 and the channel is not released. To release an attached channel, `await channel.detach()` first and then `await realtime.channels.release(name)`.

## [Upgrading from v1.2.13]

Expand Down
2 changes: 1 addition & 1 deletion example/lib/ui/realtime_sliver.dart
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ class RealtimeSliver extends HookWidget {
TextButton(
onPressed: () async {
await channel.detach();
realtime.channels.release(Constants.channelName);
await realtime.channels.release(Constants.channelName);
channel = realtime.channels.get(Constants.channelName);
setupListeners(
connectionState, channelState, connectionId, recoveryKey);
Expand Down
6 changes: 3 additions & 3 deletions lib/src/common/src/channels.dart
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@ abstract class Channels<ChannelType> extends Iterable<ChannelType> {
/// Releases a [RealtimeChannel] object with a specified
/// [name], deleting it.
///
/// It also removes any listeners associated with the channel. To release a
/// channel, the [ChannelState] must be `INITIALIZED`, `DETACHED`, or
/// `FAILED`.
/// It also removes any listeners associated with the channel. A realtime
/// channel can only be released when it is in the `INITIALIZED`,
/// `DETACHED`, or `FAILED` state.
void release(String name) {
_channels.remove(name);
}
Expand Down
16 changes: 11 additions & 5 deletions lib/src/platform/src/realtime/realtime_channels.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,18 @@ class RealtimeChannels extends Channels<RealtimeChannel> {

/// Releases a [RealtimeChannel] object with a specified [name], deleting it.
///
/// It also removes any listeners associated with the channel. To release a
/// channel, the [ChannelState] must be `INITIALIZED`, `DETACHED`, or
/// `FAILED`.
/// It also removes any listeners associated with the channel. A realtime
/// channel can only be released when it is in the `INITIALIZED`,
/// `DETACHED`, or `FAILED` state. If the channel is in any other state, the
/// returned future completes with an [AblyException] with code 90011 and
/// the channel is not released. Await [RealtimeChannel.detach] before calling
/// `release()` on an attached channel. Releasing a channel that does not
/// exist completes without error.
@override
void release(String name) {
realtime.invoke<void>(PlatformMethod.releaseRealtimeChannel, {
Future<void> release(String name) async {
// The check against the channel state is left to the platform SDK, as
// [RealtimeChannel.state] is updated asynchronously and can lag behind it.
await realtime.invoke<void>(PlatformMethod.releaseRealtimeChannel, {
Comment thread
coderabbitai[bot] marked this conversation as resolved.
TxTransportKeys.channelName: name,
});
super.release(name);
Expand Down
5 changes: 5 additions & 0 deletions test/mock_method_call_manager.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ class MockMethodCallManager {
bool isAuthenticated = false;
final channels = <int, ClientOptions?>{};
final publishedMessages = <AblyMessage<dynamic>>[];
PlatformException? releaseRealtimeChannelError;

MockMethodCallManager() {
final channel =
Expand All @@ -22,6 +23,7 @@ class MockMethodCallManager {
void reset() {
channels.clear();
publishedMessages.clear();
releaseRealtimeChannelError = null;
handleCounter = 0;
final channel =
MethodChannel('io.ably.flutter.plugin', StandardMethodCodec(Codec()));
Expand Down Expand Up @@ -69,6 +71,9 @@ class MockMethodCallManager {
return null;

case PlatformMethod.releaseRealtimeChannel:
if (releaseRealtimeChannelError != null) {
throw releaseRealtimeChannelError!;
}
return null;

default:
Expand Down
40 changes: 34 additions & 6 deletions test/realtime/channels_test.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:ably_pubsub_device_flutter/ably_pubsub_device_flutter.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';

import '../mock_method_call_manager.dart';
Expand Down Expand Up @@ -87,30 +88,57 @@ void main() {

group('#release', () {
test(
'(RSN4a) Takes one argument, the channel name,'
' and releases the corresponding channel entity', () {
'(RTS4d) Releasing a channel the platform SDK releases'
' removes it from the collection', () async {
channels
..get('channel-1')
..get('channel-2');
expect(channels.length, 2);
channels.release('channel-1');
await channels.release('channel-1');
expect(channels.length, 1);
expect(channels.exists('channel-1'), false);
});

test(
'(RSN4b) Calling release() with a channel name'
'(RTS4c) Calling release() with a channel name'
' that does not correspond to an extant channel'
' entity must return without error', () {
' must return without error', () async {
channels
..get('channel-1')
..get('channel-2');
expect(channels.length, 2);
channels.release('channel-3');
await channels.release('channel-3');
expect(channels.length, 2);
expect(channels.exists('channel-1'), true);
expect(channels.exists('channel-2'), true);
});

test(
'(RTS4e) If the platform SDK fails to release the channel,'
' release() completes with its error and the channel is not'
' removed', () async {
final channel = channels.get('channel-1');
manager.releaseRealtimeChannelError = PlatformException(
code: '90011',
message: 'Can only release a channel in a state where there is no'
' possibility of further updates from the server being'
' received (initialized, detached, or failed).'
' The current state is attached',
details: ErrorInfo(code: 90011, statusCode: 400),
);

await expectLater(
channels.release('channel-1'),
throwsA(
isA<AblyException>()
.having((e) => e.errorInfo?.code, 'errorInfo.code', 90011)
.having((e) => e.errorInfo?.statusCode, 'errorInfo.statusCode',
400),
),
);
expect(channels.exists('channel-1'), true);
expect(channels.get('channel-1'), same(channel));
});
});
});
}
Loading