# Channels and Messages

> \[!CAUTION]
>
> Programmable Chat has been deprecated and is no longer supported. Instead, we'll be focusing on the next generation of chat: Twilio Conversations. Find out more about the [EOL process here](https://www.twilio.com/en-us/changelog/programmable-chat-end-of-life-notice).
>
> If you're starting a new project, please visit the [Conversations Docs](/docs/conversations) to begin. If you've already built on Programmable Chat, please visit our [Migration Guide](/docs/conversations/migrating-chat-conversations) to learn about how to switch.

In a Programmable Chat application, a Channel is where all the action happens. Whether a chat is between two users or two hundred, a Channel is where messages are sent, received, and archived for later viewing by offline clients.

Programmable Chat has two objects for working with channels - `Channel Descriptors` and `Channels`. A Channel Descriptor is a lightweight representation of the key attributes of a public or private channel. When requesting a list of channels, you will receive Channel Descriptors that serve as a snapshot in time for each channel. These channel attributes will not update if modified remotely, so you should use the descriptors only to assist your user in discovering and joining a channel.

Channel Descriptors can be used to obtain a full channel object or view the following information:

* Channel SID
* Friendly Name
* Unique Name
* Date Created
* Created By
* Date Updated
* Channel Attributes
* Messages and Members Count
* Last Consumed Message Index (if available)
* Status (if available)
* Type (private or public)

A full Channel object allows you to join and interact with the channel. Let's dive into a few of the key techniques you'll need to employ while working with channels and messages in your application.

* [Listing public channel descriptors](#list-public-channel-descriptors)
* [Listing user channel descriptors](#list-user-channel-descriptors)
* [Get a channel from a channel descriptor](#channel-from-channel-descriptor)
* [Create a channel](#create-channel)
* [Join a channel](#join-channel)
* [Send messages to a channel](#send-messages-to-a-channel)
* [Get most recent messages from a channel](#get-the-most-recent-messages-from-a-channel)
* [Invite other users to a channel](#invite-other-users-to-a-channel)
* [Accept an invitation to a channel](#accept-an-invitation-to-a-channel)
* [Get a list of subscribed channels](#get-a-list-of-subscribed-channels)
* [Subscribe for channel events](#subscribe-for-channel-events)
* [Delete a channel](#delete-a-channel)

## List Public Channel Descriptors

If your programmable chat instance has public channels that are discoverable by a user, you can fetch a list of Channel Descriptors for all public channels in the system:

List Public Channels

```js
chatClient.getPublicChannelDescriptors().then(function(paginator) {
  for (i = 0; i < paginator.items.length; i++) {
    const channel = paginator.items[i];
    console.log('Channel: ' + channel.friendlyName);
  }
});
```

```java
chatClient.getChannels().getPublicChannelsList(new CallbackListener<Paginator<ChannelDescriptor>>() {
  @Override
  public void onSuccess(Paginator<ChannelDescriptor> channelPaginator) {
    for (ChannelDescriptor channel : channelPaginator.getItems()) {
      Log.d(TAG, "Channel named: " + channel.getFriendlyName());
    }
  }
});
```

```objective-c
[[self.client channelsList] publicChannelDescriptorsWithCompletion:^(TCHResult *result, 
  TCHChannelDescriptorPaginator *paginator) {
  if ([result isSuccessful]) {
    for (TCHChannelDescriptor *channel in paginator.items) {
      NSLog(@"Channel: %@", channel.friendlyName);
    }
  }
}];
```

```swift
client.channelsList().publicChannelDescriptorsWithCompletion({ (result, paginator) in
  if (result.isSuccessful()) {
    for channel in paginator.items() {
        print("Channel: \(channel.friendlyName)")
    }
  }
})
```

## List User Channel Descriptors

If your programmable chat instance has user channels that the current user belongs to, you can fetch a list of User Channel Descriptors for this user. To list public channels that have not yet been joined, see list public channels above.

List User Channels

```js
chatClient.getUserChannelDescriptors().then(function(paginator) {
  for (i=0; i<paginator.items.length; i++) {
    var channel = paginator.items[i];
    console.log('Channel: ' + channel.friendlyName);
  }
});
```

```java
chatClient.getChannels().getUserChannelsList(new CallbackListener<Paginator<ChannelDescriptor>>() {
  @Override
  public void onSuccess(Paginator<ChannelDescriptor> channelPaginator) {
    for (ChannelDescriptor channel : channelPaginator.getItems()) {
      Log.d(TAG, "Channel named: " + channel.getFriendlyName());
    }
  }
});
```

```objective-c
[[self.client channelsList] userChannelDescriptorsWithCompletion:^(TCHResult *result, 
  TCHChannelDescriptorPaginator *paginator) {
  if ([result isSuccessful]) {
    for (TCHChannelDescriptor *channel in paginator.items) {
      NSLog(@"Channel: %@", channel.friendlyName);
    }
  }
}];
```

```swift
client.channelsList().userChannelDescriptorsWithCompletion({ (result, paginator) in
  if (result.isSuccessful()) {
    for channel in paginator.items() {
        print("Channel: \(channel.friendlyName)")
    }
  }
})
```

## Get A Channel from a Channel Descriptor \[#channel-from-channel-descriptor]

After you have retrieved a list of channel descriptors, you will typically want to get a specific Channel and interact with it directly. In the following example, you can see how. Note that this is not necessary for the JavaScript Chat SDK as the `client.getChannel()`
gets the channel descriptor and instantiates the channel internally for you.

Get A Channel from a Channel Descriptor

```java
channelDescriptor.getChannel(new CallbackListener<Channel>() {
    @Override
    public void onSuccess(Channel channel) {
      Log.d(TAG, "Channel Status: " + channel.getStatus());
    }
});
```

```objective-c
[channelDescriptor channelWithCompletion:^(TCHResult *result, TCHChannel *channel) {
  if([result isSuccessful]) {
    NSLog(@"ChannelStatus: %@", channel.status);
  }
}]
```

```swift
channelDescriptor?.channel(completion:{ (result, channel) in
  if result!.isSuccessful() {
    print("Channel Status: \(String(describing: channel?.status))")
  }
})
```

## Create a Channel \[#create-channel]

Before you can start sending messages, you first need a Channel that can receive messages. Here is how you create a channel.

Create a Channel

```js
// Create a Channel
chatClient
  .createChannel({
    uniqueName: 'general',
    friendlyName: 'General Chat Channel',
  })
  .then(function(channel) {
    console.log('Created general channel:');
    console.log(channel);
  });
```

```java
mChatClient.getChannels().channelBuilder().
  .withFriendlyName("general")
  .withType(Channel.ChannelType.PUBLIC)
  .build(new CallbackListener<Channel>() {
      @Override
      public void onSuccess(Channel channel) {
          if (channel != null) {
              Log.d(TAG,"Success creating channel");
          }
      }

      @Override
      public void onError(ErrorInfo errorInfo) {
          Log.e(TAG,"Error creating channel: " + errorInfo.getErrorText());
      }
  });
```

```objective-c
// Create the general channel (for public use) if it hasn't been created yet
NSDictionary *options = @{
                         TCHChannelOptionFriendlyName: @"General Chat Channel",
                         TCHChannelOptionType: @(TCHChannelTypePublic)
                         };
[channels createChannelWithOptions:options completion:^(TCHResult *result, TCHChannel *channel) {
    if([result isSuccessful]) {
        NSLog(@"Channel created.");
    } else {
        NSLog(@"Channel NOT created.");
    }
}];
```

```swift
let options = [
    TCHChannelOptionFriendlyName: "General Channel",
    TCHChannelOptionType: TCHChannelType.public.rawValue
] as [String : Any]
client?.channelsList().createChannel(options: options, completion: { channelResult, channel in
    if (channelResult?.isSuccessful())! {
        print("Channel created.")
    } else {
        print("Channel NOT created.")
    }
})
```

## Join a Channel \[#join-channel]

Once you've created a channel, a user must join it to begin receiving or sending messages on that channel.

Join a Channel

```js
// Join a previously created channel
client.on('channelJoined', function(channel) {
  console.log('Joined channel ' + channel.friendlyName);
});

myChannel.join().catch(function(err) {
  console.error(
    "Couldn't join channel " + channel.friendlyName + ' because ' + err
  );
});
```

```java
private void joinChannel(final Channel channel) {
    Log.d(TAG, "Joining Channel: " + channel.getUniqueName());
    channel.join(new StatusListener() {
        @Override
        public void onSuccess() {
            Log.d(TAG, "Joined channel");
        }

        @Override
        public void onError(ErrorInfo errorInfo) {
            Log.e(TAG,"Error joining channel: " + errorInfo.toString());
        }
    });
}
```

```objective-c
// Where "client" is an authenticated client obtained with
// [TwilioChatClient chatClientWithToken:properties:delegate:]
// and the callback for chatClient:synchronizationStatusChanged: has been called with TCHClientSynchronizationStatusChannelsListCompleted
TCHChannel *channel = [[self.client channelsList] channelWithUniqueName:@"general"];
if (channel) {
    [channel joinWithCompletion:^(TCHResult *result) {
        if ([result isSuccessful]) {
            NSLog(@"Channel joined.");
        } else {
            NSLog(@"Channel NOT joined.");
        }
    }];
}
```

```swift
// Where "client" is an authenticated client obtained with
// TwilioChatClient(token:, properties:, delegate:)
// and the callback for chatClient(client:, synchronizationStatusChanged:) has been called with .completed
let channel = client.channelsList().channel(withSidOrUniqueName:"general")
if let channel = channel {
    channel.join(completion: { channelResult in
        if channelResult.isSuccessful() {
            print("Channel joined.")
        } else {
            print("Channel NOT joined.")
        }
    }
}
```

## Send Messages to a Channel

Once you're a member of a channel, you can send a message to it.

A `message` is a bit of data that is sent first to the Twilio backend where it is stored for later access by members of the channel. The message is then pushed out in real time to all
channel members currently online. Only users subscribed to your channel will receive your messages.

Sending Messages

```js
// Send a message to a previously created Channel
const msg = $('#chat-input').val();
myChannel.sendMessage(msg);
```

```java
channel.getMessages().sendMessage("test", new StatusListener() {
    @Override
    public void onSuccess() {
      Log.d(TAG,"Message sent successfully");
    }

    @Override
    public void onError(ErrorInfo errorInfo) {
        Log.e(TAG,"Error sending message: " + errorInfo.toString());
    }
});
```

```objective-c
TCHMessageOptions *options = [[TCHMessageOptions new] withBody:@"test"];
[self.channel.messages sendMessageWithOptions:options completion:^(TCHResult *result, TCHMessage *message) {
    if ([result isSuccessful]) {
        NSLog(@"Message sent.");
    } else {
        NSLog(@"Message NOT sent.");
    }
}];
```

```swift
// Where "channel" is a TCHChannel
if let messages = channel.messages {
  let options = TCHMessageOptions().withBody("test")
  messages.sendMessage(with: options) { result, message in
    if result.isSuccessful() {
      print("Message sent.")
    } else {
      print("Message NOT sent.")
    }
  }
}
```

Today, a message is just a string of text. Available in *beta* you can also [send other media types, like images and binary data](/docs/chat/media-support).

## Get the Most Recent Messages from a Channel

With a channel object in hand, you can fetch the most recent messages from the channel. Use this to provide history within the channel. You can choose how many messages you want to retrieve.

> \[!NOTE]
>
> **Note**: The Message index property may increment by more than 1 between messages. These indices will be ordered by when the message was received and you should use the index property to order them in your UI.

Get Most Recent Messages

```js
// Get Messages for a previously created channel
channel.getMessages().then(function(messages) {
  const totalMessages = messages.items.length;
  for (i = 0; i < totalMessages; i++) {
    const message = messages.items[i];
    console.log('Author:' + message.author);
  }
  console.log('Total Messages:' + totalMessages);
});
```

```java
channel.getMessages().getLastMessages(50, new CallbackListener<List<Message>>() {
    @Override
    public void onSuccess(List<Message> messages) {
      for (Message message : messages) {
        Log.d(TAG, "Message Body: " + message.getMessageBody());
      }
    }
  });
```

```objective-c
[self.channel.messages getLastMessagesWithCount:100 
  completion:^(TCHResult *result, NSArray<TCHMessage *> *messages) {
  for (TCHMessage *message in messages) {
    NSLog(@"Message body: %@", message.body);
  }
}];
```

```swift
channel.messages.getLastMessagesWithCount(100, completion: { (result, messages) in
  for message in messages {
    print("Message body: \(message.body)")
  }
})
```

You can also be notified of any new incoming messages with an event handler. Use this handler to update your user interface to display new messages.

Listening for New Messages

```js
// Listen for new messages sent to a channel
myChannel.on('messageAdded', function(message) {
  console.log(message.author, message.body);
});
```

```java
private ChannelListener mDefaultChannelListener = new ChannelListener() {
  @Override
  public void onMessageAdded(final Message message) {
    Log.d(TAG, "Message added: " + message.getMessageBody());
  }
}
```

```objective-c
- (void) chatClient:(TwilioChatClient *)client channel:(TCHChannel *)channel 
    messageAdded:(TCHMessage *)message {
    
  NSLog(@"%@ said: %@", message.author, message.body);
}
```

```swift
extension ViewController: TwilioChatClientDelegate {
    // Called whenever a channel we've joined receives a new message
    func chatClient(client: TwilioChatClient!, channel: TCHChannel!,
                    messageAdded message: TCHMessage!) {
        print("\(message.author) said: \(message.body)")
    }
}
```

## Invite other Users to a Channel

Sometimes you might feel lonely in a channel. Rather than sending messages to yourself, invite a friend to come and chat! It doesn't matter if the channel is public or private - you are always able to invite another user to any channel you own.

Sending an Invite

```js
// Invite another member to your channel
myChannel.invite('elmo').then(function() {
  console.log('Your friend has been invited!');
});
```

```java
channel.getMembers().inviteByIdentity("Juan", new StatusListener() {
  @Override
  public void onSuccess() {
    Log.d(TAG,"User Invited!");
  }
});
```

```objective-c
[self.channel.members inviteByIdentity:@"Juan" completion:^(TCHResult *result) {
    if([result isSuccessful]) {
        NSLog(@"User invited.");
    } else {
        NSLog(@"User NOT invited.");
    }
}];
```

```swift
channel.members.inviteByIdentity("Juan") { result in
    if result.isSuccessful() {
        print("User invited.")
    } else {
        print("User NOT invited.")
    }
}
```

## Accept an Invitation to a Channel

Social acceptance is a great feeling. Accepting an invite to a channel means you too can partake in banter with other channel members.

Accepting an Invite

```js
// Listen for new invitations to your Client
chatClient.on('channelInvited', function(channel) {
  console.log('Invited to channel ' + channel.friendlyName);
  // Join the channel that you were invited to
  channel.join();
});
```

```java
@Override
public void onChannelInvited(final Channel channel) {
    channel.join(new StatusListener() {
        @Override
        public void onSuccess() {
            Log.d(TAG, "Joined Channel: " + channel.getFriendlyName());
        }
    });
}
```

```objective-c
- (void)chatClient:(TwilioChatClient *)client channelAdded:(TCHChannel *)channel {
    if(channel.status == TCHChannelStatusInvited) {
        [channel joinWithCompletion:^(TCHResult *result) {
            if ([result isSuccessful]) {
                NSLog(@"Successfully accepted invite.");
            } else {
                NSLog(@"Failed to accept invite.");
            }
        }];
    }
}
```

```swift
extension ViewController: TwilioChatClientDelegate {
  // Called whenever a channel is added
  func chatClient(client: TwilioChatClient!, channelAdded channel: TCHChannel!) {
    if(channel.status == TCHChannelStatus.Invited) {
      channel.joinWithCompletion() { channelResult in
        if channelResult.isSuccessful() {
            print("Successfully accepted invite.");
        } else {
            print("Failed to accept invite.");
        }
      }
    }
  }
}
```

## Get a List of Subscribed Channels

Listing subscribed channels lets you perform actions on them as a channel member (e.g., `invite` or `display`). This method only shows the channels where the current programmable chat user is a member. To list public channels that have not yet been joined, see list public channels above.

Retrieve Channels

```js
chatClient.getSubscribedChannels().then(function(paginator) {
  for (i = 0; i < paginator.items.length; i++) {
    const channel = paginator.items[i];
    console.log('Channel: ' + channel.friendlyName);
  }
});
```

```java
List<Channel> channels = chatClient.getChannels().getSubscribedChannels();
for (Channel channel : channels) {
    Log.d(TAG, "Channel named: " + channel.getFriendlyName());
}
```

```objective-c
NSArray<TCHChannel *> *channels = [[self.client channelsList] subscribedChannels];
for (TCHChannel *channel in channels) {
  NSLog(@"Channel: %@", channel.friendlyName);
}
```

```swift
let channels = client.channelsList().subscribedChannels()
for channel in channels {
    print("Channel: \(channel.friendlyName)")
}
```

## Subscribe for Channel Events

Channels are a flurry of activity. Members join and leave, messages are sent and received, and channel states change. As a member of a channel, you'll want to know the status of the channel. You may want to receive a notification when the channel is deleted or changed. Channel event listeners help you do just that.

These event listeners will notify your app when a channel's state changes. Once you receive the notification, you can perform the necessary actions in your app to react to it.

Handle Channel Events

```js
// A channel has become visible to the Client
chatClient.on('channelAdded', function(channel) {
  console.log('Channel added: ' + channel.friendlyName);
});
// A channel is no longer visible to the Client
chatClient.on('channelRemoved', function(channel) {
  console.log('Channel removed: ' + channel.friendlyName);
});
// A channel's attributes or metadata have changed.
chatClient.on('channelUpdated', function(channel) {
  console.log('Channel updates: ' + channel.sid);
});
```

```java
@Override
public void onMessageAdded(Message message) {
  Log.d(TAG, "Message added: " + message.getMessageBody());
}

@Override
public void onMessageUpdated(Message message) {
  Log.d(TAG, "Message changed: " + message.getMessageBody());
}

@Override
public void onMessageDeleted(Message message) {
  Log.d(TAG, "Message deleted");
}
```

```objective-c
- (void)chatClient:(TwilioChatClient *)client 
      channelAdded:(TCHChannel *)channel {
    NSLog(@"Channel added: %@", channel.friendlyName);
}

- (void)chatClient:(TwilioChatClient *)client 
           channel:(TCHChannel *)channel 
           updated:(TCHChannelUpdate)update {
    NSLog(@"Channel changed: %@", channel.friendlyName);
}

- (void)chatClient:(TwilioChatClient *)client 
      channelDeleted:(TCHChannel *)channel {
    NSLog(@"Channel deleted: %@", channel.friendlyName);
}
```

```swift
extension ViewController: TwilioChatClientDelegate {
    func chatClient(client: TwilioChatClient!, channelAdded channel: TCHChannel!) {
        print("Channel added: \(channel.friendlyName)")
    }
    
    func chatClient(client: TwilioChatClient!, channel channel: TCHChannel!, updated update: TCHChannelUpdate) {
        print("Channel changed: \(channel.friendlyName)")
    }
    
    func chatClient(client: TwilioChatClient!, channelDeleted channel: TCHChannel!) {
        print("Channel deleted: \(channel.friendlyName)")
    }
}
```

Your event listeners will also notify your app when channel members perform some action (including when they leave, join, change, or start/stop typing). Using these listeners, you can provide real-time updates to your application users.

Handle Member Events

```js
// Listen for members joining a channel
myChannel.on('memberJoined', function(member) {
  console.log(member.identity + 'has joined the channel.');
});
// Listen for members user info changing
myChannel.on('memberInfoUpdated', function(member) {
  console.log(member.identity + 'updated their info.');
});
// Listen for members leaving a channel
myChannel.on('memberLeft', function(member) {
  console.log(member.identity + 'has left the channel.');
});
// Listen for members typing
myChannel.on('typingStarted', function(member) {
  console.log(member.identity + 'is currently typing.');
});
// Listen for members typing
myChannel.on('typingEnded', function(member) {
  console.log(member.identity + 'has stopped typing.');
});
```

```java
@Override
public void onMemberJoined(Member member) {
  Log.d(TAG, "Member joined: " + member.getUserInfo().getIdentity());
}

@Override
public void onMemberUpdated(Member member) {
  Log.d(TAG, "Member changed: " + member.getUserInfo().getIdentity());
}

@Override
public void onMemberDeleted(Member member) {
  Log.d(TAG, "Member deleted: " + member.getUserInfo().getIdentity());
}

@Override
public void onTypingStarted(Member member) {
  Log.d(TAG, "Started Typing: " + member.getUserInfo().getIdentity());
}

@Override
public void onTypingEnded(Member member) {
  Log.d(TAG, "Ended Typing: " + member.getUserInfo().getIdentity());
}

@Override
public void onSynchronizationChanged(Channel channel) {

}
```

```objective-c
- (void)chatClient:(TwilioChatClient *)client
           channel:(TCHChannel *)channel
      messageAdded:(TCHMessage *)message {
    [self addMessages:@[message]];
}


- (void)chatClient:(TwilioChatClient *)client
           channel:(TCHChannel *)channel
      memberJoined:(TCHMember *)member {
    NSLog(@"Member joined: %@", member.identity);
}

- (void)chatClient:(TwilioChatClient *)client
           channel:(TCHChannel *)channel
            member:(TCHMember *)member 
           updated:(TCHMemberUpdate)update {
    NSLog(@"Member changed: %@", member.identity);
}

- (void)chatClient:(TwilioChatClient *)client
           channel:(TCHChannel *)channel
        memberLeft:(TCHMember *)member {
    NSLog(@"Member left: %@", member.identity);
}

- (void)chatClient:(TwilioChatClient *)client
typingStartedOnChannel:(TCHChannel *)channel
            member:(TCHMember *)member {
    NSLog(@"Member started typing: %@", member.identity);
}

- (void)chatClient:(TwilioChatClient *)client
typingEndedOnChannel:(TCHChannel *)channel
            member:(TCHMember *)member {
    NSLog(@"Member ended typing: %@", member.identity);
}
```

```swift
extension ViewController: TwilioChatClientDelegate {
    func chatClient(client: TwilioChatClient!, channel: TCHChannel!,
                    memberJoined member: TCHMember!) {
        print("Member joined: \(member.identity)")
    }
    
    func chatClient(client: TwilioChatClient!, channel: TCHChannel!,
                    member member: TCHMember!, updated update: TCHMemberUpdate) {
        print("Member changed: \(member.identity)")
    }
    
    func chatClient(client: TwilioChatClient!, channel: TCHChannel!,
                    memberLeft member: TCHMember!) {
        print("Member left: \(member.identity)")
    }
    
    func chatClient(client: TwilioChatClient!,
                    typingStartedOnChannel channel: TCHChannel!,
                                           member: TCHMember!) {
        print("Member started typing: \(member.identity)")
    }
    
    func chatClient(client: TwilioChatClient!,
                    typingEndedOnChannel channel: TCHChannel!,
                                         member: TCHMember!) {
        print("Member ended typing: \(member.identity)")
    }
    

}
```

## Delete a Channel

Deleting a channel both deletes the message history and removes all members from it.

Delete a Channel

```js
// Delete a previously created Channel
myChannel.delete().then(function(channel) {
  console.log('Deleted channel: ' + channel.sid);
});
```

```java
channel.destroy(new StatusListener() {
  @Override
  public void onSuccess() {
    Log.d(TAG, "Successfully deleted channel");
  }
  
  @Override
  public void onError(ErrorInfo errorInfo) {
    Log.d(TAG, "Error deleting channel: " + errorInfo.getErrorText());
  }
});
```

```objective-c
[self.channel destroyWithCompletion:^(TCHResult *result) {
    if([result isSuccessful]) {
        NSLog(@"Channel destroyed.");
    } else {
        NSLog(@"Channel NOT destroyed.");
    }
}];
```

```swift
channel.destroyWithCompletion { result in
    if result.isSuccessful() {
        print("Channel destroyed.")
    } else {
        print("Channel NOT destroyed.")
    }
}
```

You can only delete channels that you have permissions to delete. Deleting a channel means it cannot be retrieved at a later date for any reason. Delete with caution!

Now that you know all there is to know about channels, might we suggest learning more about [the REST API](/docs/chat/rest)? With the REST API, you can execute many of these same actions from your server-side code.

Next: [Media Support in Chat](/docs/chat/media-support)
