# Channel states
Channels transition through multiple states throughout their lifecycle. Understanding under which conditions the state of a channel changes, and managing those changes, is important to ensure that your applications behave as expected.
## States
A channel can exist in any of the following states:
| State | Description |
|-------|-------------|
| Initialized | The channel has been initialized, but no attach has been attempted yet. |
| Attaching | An attach has been initiated by sending a request to Ably. This is a transient state and will be followed either by a transition to attached, suspended, or failed. |
| Attached | An attach has succeeded. In the attached state a client can publish and subscribe to messages, and enter the presence set. |
| Detaching | A detach has been initiated on the attached channel by sending a request to Ably. This is a transient state and will be followed either by a transition to detached or failed. |
| Detached | The channel, having previously been attached, has been detached by the client. |
| Suspended | The channel having previously been attached, has lost continuity. This is normally due to the client being disconnected from Ably for more than two minutes. The client will automatically attempt to reattach as soon as connectivity is restored. |
| Failed | An indefinite failure condition. This state is entered if a channel error has been received from the Ably service, such as an attempt to attach without the necessary access rights. |
## Attach to a channel
Attaching to a channel ensures that it is created in the Ably system and that all messages published on the channel are received by any channel listeners registered when [subscribing](https://ably.com/docs/pub-sub#subscribe). Channels are not pre-configured or provisioned by Ably in advance. They are created on demand when clients attach, and remain active until such time that there are no remaining attached clients.
A channel will automatically close when all of the following criteria are met:
* There are no more realtime clients attached to it
* Approximately one minute has passed since the last client detached
* Approximately one minute has passed since the last message was published to the channel
Although [`attach()`](https://ably.com/docs/api/realtime-sdk/channels#attach) can be called explicitly, it is more common for a client to [subscribe](https://ably.com/docs/pub-sub#subscribe) directly to a channel, which will automatically initiate the attach. This is also known as an implicit attachment.
The following example explicitly attaches to a channel, which results in the channel being provisioned in Ably's global realtime cluster. This channel will remain available globally until there are no more clients attached to the channel:
```realtime_javascript
const channel = realtime.channels.get('chatroom');
await channel.attach();
```
```realtime_nodejs
const channel = realtime.channels.get('chatroom');
await channel.attach();
```
```realtime_ruby
realtime.channels.get('chatroom').attach do |channel|
puts "'chatroom' exists and is now available globally in every datacenter"
end
```
```realtime_python
channel = realtime.channels.get('chatroom')
await channel.attach()
print("'chatroom' exists and is now available globally in every datacenter")
```
```realtime_java
Channel channel = realtime.channels.get("chatroom");
channel.on(new ChannelStateListener() {
@Override
public void onChannelStateChanged(ChannelStateChange state) {
switch (state.current) {
case attached: {
System.out.println("'chatroom' exists and is now available globally");
}
}
}
});
```
```realtime_csharp
IRealtimeChannel channel = realtime.Channels.Get("chatroom");
channel.Attach((success, error) => {
Console.WriteLine("'chatroom' exists and is now available globally");
});
```
```realtime_objc
[[realtime.channels get:@"chatroom" options:options] attach:^(ARTErrorInfo *error) {
NSLog(@"'chatroom' exists and is now available globally in every datacenter");
}];
```
```realtime_swift
realtime.channels.get("chatroom").attach { error in
print("'chatroom' exists and is now available globally in every datacenter")
}
```
```realtime_flutter
final channel = realtime.channels.get('your-channel-name');
final channelMessageSubscription = channel
.on()
.listen((ably.ChannelStateChange state) {
switch (state.current) {
case ably.ChannelState.attached: {
print("'chatroom' exists and is now available globally");
break;
}
default:
break;
}
});
```
```realtime_go
channel := realtime.Channels.Get("channelName")
channel.Attach(context.Background())
```
### Attach errors
Normally, errors in attaching to a channel are communicated through the [`attach()`](https://ably.com/docs/api/realtime-sdk/channels#attach) callback. For implicit attaches, where a client only calls `subscribe()`, there is no callback.In these instances, listen for [channel state changes](#listen) to monitor errors.
This is also true in other cases where a channel is attached or re-attached automatically, for example, following the library reconnecting to Ably after a period of time in the `suspended` state.
### Detach from a channel
A client can detach from a channel so that it no longer receives any messages published to it. Detaching is different to unsubscribing from a channel because [`unsubscribe()`](https://ably.com/docs/api/realtime-sdk/channels#unsubscribe) is a client-side operation. The Ably platform does not know that a client has unsubscribed and will continue to stream messages to that client until [`detach()`](https://ably.com/docs/api/realtime-sdk/channels#detach) is called.
The following is an example of detaching from a channel:
```realtime_javascript
const channel = realtime.channels.get('chatroom');
await channel.detach();
```
```realtime_nodejs
const channel = realtime.channels.get('chatroom');
await channel.detach();
```
```realtime_ruby
channel.detach
channel.on(:detached) do |channel_state_change|
puts "detached from the channel #{channel.name}"
end
```
```realtime_python
await channel.detach()
```
```realtime_java
channel.on(ChannelEvent.detached, new ChannelStateListener() {
@Override
public void onChannelStateChanged(ChannelStateChange stateChange) {
System.out.println("Detached from the channel " + channel.name);
if (stateChange.reason != null) {
System.out.println(stateChange.reason.toString());
}
}
});
channel.detach();
```
```realtime_csharp
channel.Detach();
channel.On(ChannelEvent.Detached, stateChange => {
Console.WriteLine("detached from the channel " + channel.Name);
});
```
```realtime_objc
[channel detach]
[channel on:ARTChannelEventDetached callback:^(ARTChannelStateChange *stateChange) {
NSLog(@"detached from the channel ", channel.name);
}];
```
```realtime_swift
channel.detach()
channel.on(.detached) { stateChange in
print("detached from the channel \(channel.name)")
}
```
```realtime_flutter
channel.detach();
final stateChangeListener = channel
.on(ably.ChannelEvent.detached)
.listen((ably.ChannelStateChange state) {
print('detached from the channel ${channel.name}');
});
```
```realtime_go
channel := realtime.Channels.Get("channelName")
channel.Detach(context.Background())
```
## Listen for state changes
The `Channel` object is an `EventEmitter`. Events are emitted with a `name` that corresponds to the new channel state, whenever there is a change in the state of a channel.
Register a listener to monitor the current channel state. This can be a listener for the first occurrence, using [`once()`](https://ably.com/docs/api/realtime-sdk/channels#once), or for every change using [`on()`](https://ably.com/docs/api/realtime-sdk/channels#on).
Use the [`on()`](https://ably.com/docs/api/realtime-sdk/channels#on) method to register a listener for a specific channel state:
```realtime_javascript
channel.on('attached', (stateChange) => {
console.log('channel ' + channel.name + ' is now attached');
console.log('Message continuity on this channel ' + \
(stateChange.resumed ? 'was' : 'was not') + ' preserved');
});
```
```realtime_nodejs
channel.on('attached', (stateChange) => {
console.log('channel ' + channel.name + ' is now attached');
console.log('Message continuity on this channel ' + \
(stateChange.resumed ? 'was' : 'was not') + ' preserved');
});
```
```realtime_ruby
channel.on(:attached) do |channel_state_change|
puts "channel #{channel.name} is now attached"
puts "Message continuity #{channel_state_change.resumed ? 'was' : 'was not'} preserved"
end
```
```realtime_python
def listener(state_change):
print(f'{channel.name} is now {state_change.current}')
channel.on('attached', listener)
```
```realtime_java
channel.on(ChannelEvent.attached, new ChannelStateListener() {
@Override
public void onChannelStateChanged(ChannelStateChange stateChange) {
System.out.println("channel " + channel.name + " is now attached");
if (stateChange.resumed) {
System.out.println("Message continuity was preserved");
} else {
System.out.println("Message continuity was not preserved");
}
}
});
```
```realtime_csharp
IRealtimeChannel channel = realtime.Channels.Get("chatroom");
channel.On(ChannelEvent.Attached, stateChange => {
Console.WriteLine("channel " + channel.Name + " is now attached");
if (stateChange.Resumed) {
Console.WriteLine("Message continuity was preserved");
} else {
Console.WriteLine("Message continuity was not preserved");
}
});
```
```realtime_objc
[channel on:ARTChannelEventAttached callback:^(ARTChannelStateChange *stateChange) {
NSLog(@"channel %@ is now attached", channel.name);
if (stateChange.resumed) {
NSLog(@"Message continuity was preserved");
} else {
NSLog(@"Message continuity was not preserved");
}
}];
```
```realtime_swift
channel.on(.attached) { stateChange in
print("channel \(channel.name) is now attached")
if (stateChange.resumed) {
print("Message continuity was preserved")
} else {
print("Message continuity was not preserved")
}
}
```
```realtime_flutter
final stateChangeListener = channel
.on(ably.ChannelEvent.attached)
.listen((ably.ChannelStateChange state) {
print('channel ${channel.name} is now attached');
if (state.resumed) {
print('Message continuity was preserved');
} else {
print('Message continuity was not preserved');
}
});
```
```realtime_go
channel.On(ably.ChannelEventAttached, func(stateChange ably.ChannelStateChange) {
fmt.Printf("channel '%v' is now attached\n", channel.Name)
if stateChange.Resumed {
fmt.Printf("Message continuity on this channel was preserved\n")
} else {
fmt.Printf("Message continuity on this channel was not preserved\n")
}
})
```
You can also use the [`on()`](https://ably.com/docs/api/realtime-sdk/channels#on) method to register a listener for all channel state changes:
```realtime_javascript
const myListener = (stateChange) => {
console.log('channel state is ' + stateChange.current);
console.log('previous state was ' + stateChange.previous);
if (stateChange.reason) {
console.log('the reason for the state change was: ' + stateChange.reason.toString());
}
});
channel.on(myListener);
```
```realtime_nodejs
const myListener = (stateChange) => {
console.log('channel state is ' + stateChange.current);
console.log('previous state was ' + stateChange.previous);
if (stateChange.reason) {
console.log('the reason for the state change was: ' + stateChange.reason.toString());
}
});
channel.on(myListener);
```
```realtime_ruby
channel.on do |channel_state_change|
puts "channel state is #{channel_state_change.current}"
end
```
```realtime_python
def listener(state_change):
print(f'{channel.name} is now {state_change.current}')
channel.on(listener)
```
```realtime_java
channel.on(new ChannelStateListener() {
@Override
public void onChannelStateChanged(ChannelStateChange stateChange) {
ChannelState currentState = stateChange.current;
ErrorInfo reason = stateChange.reason;
System.out.println("Channel state is " + currentState);
if (reason != null) {
System.out.println("Reason: " + reason.message);
}
}
});
```
```realtime_csharp
channel.On(stateChange => Console.WriteLine("channel state is " + stateChange.Current));
```
```realtime_objc
ARTEventListener *listener = [channel on:^(ARTChannelStateChange *stateChange) {
NSLog(@"channel state is %@", stateChange.current);
}];
```
```realtime_swift
let listener = channel.on { stateChange in
print("channel state is \(stateChange.current)")
}
```
```realtime_flutter
final stateChangeListener = channel
.on()
.listen((ably.ChannelStateChange stateChange) {
print('channel state is ${stateChange.current.name}');
});
```
```realtime_go
channel.OnAll(func(stateChange ably.ChannelStateChange) {
fmt.Printf("channel state is '%v'", stateChange.Current)
fmt.Printf("previous state was '%v'", stateChange.Previous)
if stateChange.Reason != nil {
fmt.Printf("the reason for the state change was: '%v'", stateChange.Reason)
}
})
```
Listeners are passed a [`ChannelStateChange`](https://ably.com/docs/api/realtime-sdk/channels#channel-state-change) object in the first argument. This object has the following properties:
* `current` / `previous`: the present and last state of the channel.
* `resumed`: a flag indicating whether message continuity on the channel is preserved since the last time the channel was attached.
* `reason`: the reason for the state change, if available.
`this` within the listener function is a reference to an event object whose `event` property is the name of the event that fired. This allows a listener to listen for all events with a single registration and still know which type of event is fired.
Use the [`off()`](https://ably.com/docs/api/realtime-sdk/channels#off) method to remove listeners:
```realtime_javascript
/* remove the listener registered for a single event */
channel.off('attached', myListener);
/* remove the listener registered for all events */
channel.off(myListener);
```
```realtime_nodejs
/* remove the listener registered for a single event */
channel.off('attached', myListener);
/* remove the listener registered for all events */
channel.off(myListener);
```
```realtime_ruby
# remove the listener proc registered for a single event
channel.off(:attached, &my_proc)
# remove the listener proc registered for all events
channel.off(&my_proc)
```
```realtime_python
# remove a single listener
channel.off(listener)
# remove all listeners
channel.off()
```
```realtime_java
/* remove the listener registered for a single event */
channel.off(ChannelEvent.attached, channelStateListener);
/* remove the listener registered for all events */
channel.off(channelStateListener);
```
```realtime_csharp
// remove the listener registered for a single event
channel.Off(ChannelEvent.Attached, channelStateListener);
// remove the listener registered for all events
channel.Off(channelStateListener);
```
```realtime_objc
// remove the listener registered for a single event
[channel off:ARTChannelEventAttached listener:listener];
// remove the listener registered for all events
[channel off:listener];
```
```realtime_swift
// remove the listener registered for a single event
channel.off(.attached, listener: listener)
// remove the listener registered for all events
channel.off(listener)
```
```realtime_flutter
// cancel stream subscription on the listener to stop receiving the events
stateChangeListener.cancel();
```
```realtime_go
channel.Off(ably.ChannelEventAttached)
channel.OffAll()
```
### Update events
The `Channel` object can also emit one event that is not a state change; an `update` event.
This happens when there's a change to channel conditions for which the channel state doesn't change. For example, a partial loss of message continuity on a channel, typically after a [resume](https://ably.com/docs/connect/states#resume), for which the channel state remains `attached`. This leads to an `update` event being emitted, with both `current` and `previous` set to "`attached`", and the `resumed` flag set to `false`.
If you receive such an event, you'll know there may be messages you've missed on the channel, and if necessary you can use [history](https://ably.com/docs/api/realtime-sdk/channels#history) to retrieve them.
## Channel state and connection state
[Connection state](https://ably.com/docs/connect/states) also impacts the state of a channel in the following ways:
* If the connection state becomes `CLOSED`, all channels will become `DETACHED`
* If the connection state becomes `FAILED`, all channels will become `FAILED`
* If the connection state becomes `SUSPENDED`, all previously-`ATTACHED` or `ATTACHING` channels will become `SUSPENDED`
* If the connection state becomes `CONNECTED`, any channels that were `SUSPENDED` will be automatically reattached
## Handle channel failures
Channel attach and detach operations are asynchronous. After initiating an attach request, the client will wait for a response from Ably that confirms that the channel is established on the service and then trigger a [state change](#states) event.
Ably SDKs will attempt to automatically recover from non-fatal error conditions. However, you can handle them yourself if you prefer by subscribing to channel state changes, or using the callbacks availablewaiting for a result when explicitly calling [`attach()`](https://ably.com/docs/api/realtime-sdk/channels#attach).
```realtime_javascript
const channel = realtime.channels.get('private:chatroom');
channel.on('failed', (stateChange) => {
console.log('Channel failed, reason: ', stateChange.reason);
});
await channel.attach();
```
```realtime_nodejs
const channel = realtime.channels.get('private:chatroom');
channel.on('failed', (stateChange) => {
console.log('Channel failed, reason: ', stateChange.reason);
});
await channel.attach();
```
```realtime_ruby
deferrable = realtime.channels.get('private:chatroom').attach
deferrable.errback do |error|
puts "Attach failed: #{error}"
end
```
```realtime_python
channel = realtime.channels.get('private:chatroom')
# Attach to the channel
try:
await channel.attach()
print("Attached to channel successfully")
except Exception as err:
print("Attach failed:", err)
```
```realtime_java
Channel channel = realtime.channels.get("private:chatroom");
channel.on(new ChannelStateListener() {
@Override
public void onChannelStateChanged(ChannelStateChange stateChange) {
switch (stateChange.current) {
case failed:
ErrorInfo reason = stateChange.reason;
System.out.println("Attach failed: " + (reason != null ? reason.message : "Unknown reason"));
break;
}
}
});
channel.attach();
```
```realtime_csharp
IRealtimeChannel privateChannel = realtime.Channels.Get("private:chatroom");
privateChannel.Attach((_, error) => {
if (error != null)
{
Console.WriteLine("Attach failed: " + error.Message);
}
});
```
```realtime_objc
[[realtime.channels get:@"private:chatroom"] attach:^(ARTErrorInfo *error) {
if (error) {
NSLog(@"Attach failed: %@", error);
}
}];
```
```realtime_swift
realtime.channels.get("private:chatroom").attach { error in
if let error = error {
print("Attach failed: \(error)")
}
}
```
```realtime_flutter
final channel = realtime.channels.get('private:chatroom');
channel
.on()
.listen((ably.ChannelStateChange stateChange) {
switch (stateChange.current) {
case ably.ChannelState.failed:
print('Attach failed: ${stateChange.reason?.message}');
break;
// Add other cases if needed
default:
break;
}
});
channel.attach();
```
```realtime_go
channel := realtime.Channels.Get("private:chatroom")
channel.On(ably.ChannelEventFailed, func(stateChange ably.ChannelStateChange) {
fmt.Printf("Channel failed, reason: '%v'", stateChange.Reason)
})
channel.Attach(context.Background())
```
### Fatal channel errors
Some classes of errors are fatal. These cause the channel to move to the `FAILED` state. Ably SDKs will not attempt any automatic recovery actions. For example, when attempting to attach to a channel, with a token that doesn't have the `subscribe` [capability](https://ably.com/docs/auth/capabilities) for that channel, will cause that channel to enter the `FAILED` state.
Whilst fatal errors won't get better on their own, they are fixable. For example, if a channel goes into the `FAILED` state due to the client not having the right capabilities to attach to it, that client could call [`authorize()`](https://ably.com/docs/api/realtime-sdk/authentication#authorize) to obtain a new token which does have the right capabilities, then call [`attach()`](https://ably.com/docs/api/realtime-sdk/channels#attach) on the channel. The library will not automatically reattach in the `FAILED` state, however explicit calls to [`attach()`](https://ably.com/docs/api/realtime-sdk/channels#attach) will make the client try again.
### Non-fatal errors
Some types of errors are non-fatal. For example, a client may have network connectivity issues, or a channel may experience a loss of strict message continuity. Ably SDKs will automatically attempt to recover from these events. If channel continuity is lost in the process, the library will notify you through a `resumed` flag in the `ATTACHED` or `UPDATE` event, so that you can decide how to handle the failure.
For every channel `ATTACHED` and `UPDATE` event, the [`ChannelStateChange`](https://ably.com/docs/api/realtime-sdk/types#channel-state-change) object contains a `resumed` attribute. When `true`, there has been no loss of continuity from the last time the channel was attached. When `false`, there has been a loss of continuity.
For example:
* The first time a client attaches to a channel on a fresh connection, `resumed` will be `false`, as there was nothing to continue from.
* If a client successfully [recovers](https://ably.com/docs/connect/states) a connection and reattaches to its channels, the `resumed` flag on the `ATTACHED` events will tell it whether message continuity was preserved, or not. Any channel for which it's `true`, is guaranteed to receive every message it missed while the client was disconnected.
* If a client "resumes or [recovers](https://ably.com/docs/connect/states) a connection unsuccessfully continuity is lost and the client receives a fresh connection. This generally happens because the client was disconnected for more than two minutes, which is how long Ably holds connection state for. If the client were resuming, all the channels (which will have gone into the `SUSPENDED` state after two minutes) will still reattach automatically, and the client will receive `ATTACHED` events with `resumed` set to `false`.
* If Ably needs to signal a loss of message continuity on an attached channel, the client will receive an `UPDATE` event with `resumed` set to `false`. This occurs in situations such as a partially successful resume, where the client was disconnected for less than two minutes.