As the Programmable Chat API is set to sunset in 2022, we will no longer maintain these chat tutorials.
Please see our Conversations API QuickStart to start building robust virtual spaces for conversation.
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.
If you're starting a new project, please visit the Conversations Docs to begin. If you've already built on Programmable Chat, please visit our Migration Guide to learn about how to switch.
Ready to implement a chat application using Twilio Programmable Chat Client? Here is how it works at a high level:
For your convenience, we consolidated the source code for this tutorial in a single GitHub repository. Feel free to clone it and tweak it as required.
The first thing you need to create a client is an access token. This token holds information about your Twilio account and Programmable Chat API keys. We have created a web version of Twilio chat in different languages. You can use any of these to generate the token:
We use Volley to make a request to our server and get the access token.
app/src/main/java/com/twilio/twiliochat/chat/accesstoken/AccessTokenFetcher.java
_73package com.twilio.twiliochat.chat.accesstoken;_73_73import android.content.Context;_73import android.content.res.Resources;_73import android.util.Log;_73_73import com.android.volley.Request;_73import com.android.volley.Response;_73import com.android.volley.VolleyError;_73import com.android.volley.toolbox.JsonObjectRequest;_73import com.twilio.twiliochat.R;_73import com.twilio.twiliochat.application.SessionManager;_73import com.twilio.twiliochat.application.TwilioChatApplication;_73import com.twilio.twiliochat.chat.listeners.TaskCompletionListener;_73_73import org.json.JSONException;_73import org.json.JSONObject;_73_73import java.util.HashMap;_73import java.util.Map;_73_73public class AccessTokenFetcher {_73_73 private Context context;_73_73 public AccessTokenFetcher(Context context) {_73 this.context = context;_73 }_73_73 public void fetch(final TaskCompletionListener<String, String> listener) {_73 JSONObject obj = new JSONObject(getTokenRequestParams(context));_73 String identity = SessionManager.getInstance().getUsername();_73 String requestUrl = getStringResource(R.string.token_url) + "?identity=" + identity;_73 Log.d(TwilioChatApplication.TAG, "Requesting access token from: " + requestUrl);_73_73 JsonObjectRequest jsonObjReq =_73 new JsonObjectRequest(Request.Method.GET, requestUrl, obj, new Response.Listener<JSONObject>() {_73_73 @Override_73 public void onResponse(JSONObject response) {_73 String token = null;_73 try {_73 token = response.getString("token");_73 } catch (JSONException e) {_73 Log.e(TwilioChatApplication.TAG, e.getLocalizedMessage(), e);_73 listener.onError("Failed to parse token JSON response");_73 }_73 listener.onSuccess(token);_73 }_73 }, new Response.ErrorListener() {_73_73 @Override_73 public void onErrorResponse(VolleyError error) {_73 Log.e(TwilioChatApplication.TAG, error.getLocalizedMessage(), error);_73 listener.onError("Failed to fetch token");_73 }_73 });_73 jsonObjReq.setShouldCache(false);_73 TokenRequest.getInstance().addToRequestQueue(jsonObjReq);_73 }_73_73 private Map<String, String> getTokenRequestParams(Context context) {_73 Map<String, String> params = new HashMap<>();_73 params.put("identity", SessionManager.getInstance().getUsername());_73 return params;_73 }_73_73 private String getStringResource(int id) {_73 Resources resources = TwilioChatApplication.get().getResources();_73 return resources.getString(id);_73 }_73_73}
Now it's time to use that token to initialize your Twilio client.
Before creating a Programmable Chat Client instance we need to decide the region it will connect to and the synchronization strategy used to initialize it.
We can then pass this information along with the access token generated in the previous step and wait for the client to be ready.
app/src/main/java/com/twilio/twiliochat/chat/ChatClientBuilder.java
_42package com.twilio.twiliochat.chat;_42_42import android.content.Context;_42_42import com.twilio.chat.CallbackListener;_42import com.twilio.chat.ChatClient;_42import com.twilio.chat.ErrorInfo;_42import com.twilio.twiliochat.chat.listeners.TaskCompletionListener;_42_42public class ChatClientBuilder extends CallbackListener<ChatClient> {_42_42 private Context context;_42 private TaskCompletionListener<ChatClient, String> buildListener;_42_42 public ChatClientBuilder(Context context) {_42 this.context = context;_42 }_42_42 public void build(String token, final TaskCompletionListener<ChatClient, String> listener) {_42 ChatClient.Properties props =_42 new ChatClient.Properties.Builder()_42 .setRegion("us1")_42 .createProperties();_42_42 this.buildListener = listener;_42 ChatClient.create(context.getApplicationContext(),_42 token,_42 props,_42 this);_42 }_42_42_42 @Override_42 public void onSuccess(ChatClient chatClient) {_42 this.buildListener.onSuccess(chatClient);_42 }_42_42 @Override_42 public void onError(ErrorInfo errorInfo) {_42 this.buildListener.onError(errorInfo.getMessage());_42 }_42}
The next step will be getting a channel list.
Our ChannelManager
class takes care of everything related to channels. The first thing we need to do when the class is initialized, is to store a list of channels of type Channel
. To do this we call the method getChannels
from the Programmable Chat Client and extract a Channel
object from each ChannelDescriptor
returned.
app/src/main/java/com/twilio/twiliochat/chat/channels/ChannelManager.java
_339package com.twilio.twiliochat.chat.channels;_339_339import android.content.res.Resources;_339import android.os.Handler;_339import android.os.Looper;_339import android.util.Log;_339_339import com.twilio.chat.CallbackListener;_339import com.twilio.chat.Channel;_339import com.twilio.chat.Channel.ChannelType;_339import com.twilio.chat.ChannelDescriptor;_339import com.twilio.chat.Channels;_339import com.twilio.chat.ChatClient;_339import com.twilio.chat.ChatClientListener;_339import com.twilio.chat.ErrorInfo;_339import com.twilio.chat.Paginator;_339import com.twilio.chat.StatusListener;_339import com.twilio.chat.User;_339import com.twilio.twiliochat.R;_339import com.twilio.twiliochat.application.TwilioChatApplication;_339import com.twilio.twiliochat.chat.ChatClientManager;_339import com.twilio.twiliochat.chat.accesstoken.AccessTokenFetcher;_339import com.twilio.twiliochat.chat.listeners.TaskCompletionListener;_339_339import java.util.ArrayList;_339import java.util.Collections;_339import java.util.List;_339_339public class ChannelManager implements ChatClientListener {_339 private static ChannelManager sharedManager = new ChannelManager();_339 public Channel generalChannel;_339 private ChatClientManager chatClientManager;_339 private ChannelExtractor channelExtractor;_339 private List<Channel> channels;_339 private Channels channelsObject;_339 private ChatClientListener listener;_339 private String defaultChannelName;_339 private String defaultChannelUniqueName;_339 private Handler handler;_339 private Boolean isRefreshingChannels = false;_339_339 private ChannelManager() {_339 this.chatClientManager = TwilioChatApplication.get().getChatClientManager();_339 this.channelExtractor = new ChannelExtractor();_339 this.listener = this;_339 defaultChannelName = getStringResource(R.string.default_channel_name);_339 defaultChannelUniqueName = getStringResource(R.string.default_channel_unique_name);_339 handler = setupListenerHandler();_339 }_339_339 public static ChannelManager getInstance() {_339 return sharedManager;_339 }_339_339 public List<Channel> getChannels() {_339 return channels;_339 }_339_339 public String getDefaultChannelName() {_339 return this.defaultChannelName;_339 }_339_339 public void leaveChannelWithHandler(Channel channel, StatusListener handler) {_339 channel.leave(handler);_339 }_339_339 public void deleteChannelWithHandler(Channel channel, StatusListener handler) {_339 channel.destroy(handler);_339 }_339_339 public void populateChannels(final LoadChannelListener listener) {_339 if (this.chatClientManager == null || this.isRefreshingChannels) {_339 return;_339 }_339 this.isRefreshingChannels = true;_339_339 handler.post(new Runnable() {_339 @Override_339 public void run() {_339 channelsObject = chatClientManager.getChatClient().getChannels();_339_339 channelsObject.getPublicChannelsList(new CallbackListener<Paginator<ChannelDescriptor>>() {_339 @Override_339 public void onSuccess(Paginator<ChannelDescriptor> channelDescriptorPaginator) {_339 extractChannelsFromPaginatorAndPopulate(channelDescriptorPaginator, listener);_339 }_339 });_339_339 }_339 });_339 }_339_339 private void extractChannelsFromPaginatorAndPopulate(final Paginator<ChannelDescriptor> channelsPaginator,_339 final LoadChannelListener listener) {_339 channels = new ArrayList<>();_339 ChannelManager.this.channels.clear();_339 channelExtractor.extractAndSortFromChannelDescriptor(channelsPaginator,_339 new TaskCompletionListener<List<Channel>, String>() {_339 @Override_339 public void onSuccess(List<Channel> channels) {_339 ChannelManager.this.channels.addAll(channels);_339 Collections.sort(ChannelManager.this.channels, new CustomChannelComparator());_339 ChannelManager.this.isRefreshingChannels = false;_339 chatClientManager.addClientListener(ChannelManager.this);_339 listener.onChannelsFinishedLoading(ChannelManager.this.channels);_339 }_339_339 @Override_339 public void onError(String errorText) {_339 Log.e(TwilioChatApplication.TAG,"Error populating channels: " + errorText);_339 }_339 });_339 }_339_339 public void createChannelWithName(String name, final StatusListener handler) {_339 this.channelsObject_339 .channelBuilder()_339 .withFriendlyName(name)_339 .withType(ChannelType.PUBLIC)_339 .build(new CallbackListener<Channel>() {_339 @Override_339 public void onSuccess(final Channel newChannel) {_339 handler.onSuccess();_339 }_339_339 @Override_339 public void onError(ErrorInfo errorInfo) {_339 handler.onError(errorInfo);_339 }_339 });_339 }_339_339 public void joinOrCreateGeneralChannelWithCompletion(final StatusListener listener) {_339 channelsObject.getChannel(defaultChannelUniqueName, new CallbackListener<Channel>() {_339 @Override_339 public void onSuccess(Channel channel) {_339 ChannelManager.this.generalChannel = channel;_339 if (channel != null) {_339 joinGeneralChannelWithCompletion(listener);_339 } else {_339 createGeneralChannelWithCompletion(listener);_339 }_339 }_339 });_339 }_339_339 private void joinGeneralChannelWithCompletion(final StatusListener listener) {_339 if (generalChannel.getStatus() == Channel.ChannelStatus.JOINED) {_339 listener.onSuccess();_339 return;_339 }_339 this.generalChannel.join(new StatusListener() {_339 @Override_339 public void onSuccess() {_339 listener.onSuccess();_339 }_339_339 @Override_339 public void onError(ErrorInfo errorInfo) {_339 listener.onError(errorInfo);_339 }_339 });_339 }_339_339 private void createGeneralChannelWithCompletion(final StatusListener listener) {_339 this.channelsObject_339 .channelBuilder()_339 .withFriendlyName(defaultChannelName)_339 .withUniqueName(defaultChannelUniqueName)_339 .withType(ChannelType.PUBLIC)_339 .build(new CallbackListener<Channel>() {_339 @Override_339 public void onSuccess(final Channel channel) {_339 ChannelManager.this.generalChannel = channel;_339 ChannelManager.this.channels.add(channel);_339 joinGeneralChannelWithCompletion(listener);_339 }_339_339 @Override_339 public void onError(ErrorInfo errorInfo) {_339 listener.onError(errorInfo);_339 }_339 });_339 }_339_339 public void setChannelListener(ChatClientListener listener) {_339 this.listener = listener;_339 }_339_339 private String getStringResource(int id) {_339 Resources resources = TwilioChatApplication.get().getResources();_339 return resources.getString(id);_339 }_339_339 @Override_339 public void onChannelAdded(Channel channel) {_339 if (listener != null) {_339 listener.onChannelAdded(channel);_339 }_339 }_339_339 @Override_339 public void onChannelUpdated(Channel channel, Channel.UpdateReason updateReason) {_339 if (listener != null) {_339 listener.onChannelUpdated(channel, updateReason);_339 }_339 }_339_339 @Override_339 public void onChannelDeleted(Channel channel) {_339 if (listener != null) {_339 listener.onChannelDeleted(channel);_339 }_339 }_339_339 @Override_339 public void onChannelSynchronizationChange(Channel channel) {_339 if (listener != null) {_339 listener.onChannelSynchronizationChange(channel);_339 }_339 }_339_339 @Override_339 public void onError(ErrorInfo errorInfo) {_339 if (listener != null) {_339 listener.onError(errorInfo);_339 }_339 }_339_339 @Override_339 public void onClientSynchronization(ChatClient.SynchronizationStatus synchronizationStatus) {_339_339 }_339_339 @Override_339 public void onChannelJoined(Channel channel) {_339_339 }_339_339 @Override_339 public void onChannelInvited(Channel channel) {_339_339 }_339_339 @Override_339 public void onUserUpdated(User user, User.UpdateReason updateReason) {_339 if (listener != null) {_339 listener.onUserUpdated(user, updateReason);_339 }_339 }_339_339 @Override_339 public void onUserSubscribed(User user) {_339_339 }_339_339 @Override_339 public void onUserUnsubscribed(User user) {_339_339 }_339_339 @Override_339 public void onNewMessageNotification(String s, String s1, long l) {_339_339 }_339_339 @Override_339 public void onAddedToChannelNotification(String s) {_339_339 }_339_339 @Override_339 public void onInvitedToChannelNotification(String s) {_339_339 }_339_339 @Override_339 public void onRemovedFromChannelNotification(String s) {_339_339 }_339_339 @Override_339 public void onNotificationSubscribed() {_339_339 }_339_339 @Override_339 public void onNotificationFailed(ErrorInfo errorInfo) {_339_339 }_339_339 @Override_339 public void onConnectionStateChange(ChatClient.ConnectionState connectionState) {_339_339 }_339_339 @Override_339 public void onTokenExpired() {_339 refreshAccessToken();_339 }_339_339 @Override_339 public void onTokenAboutToExpire() {_339 refreshAccessToken();_339 }_339_339 private void refreshAccessToken() {_339 AccessTokenFetcher accessTokenFetcher = chatClientManager.getAccessTokenFetcher();_339 accessTokenFetcher.fetch(new TaskCompletionListener<String, String>() {_339 @Override_339 public void onSuccess(String token) {_339 ChannelManager.this.chatClientManager.getChatClient().updateToken(token, new StatusListener() {_339 @Override_339 public void onSuccess() {_339 Log.d(TwilioChatApplication.TAG, "Successfully updated access token.");_339 }_339 });_339 }_339_339 @Override_339 public void onError(String message) {_339 Log.e(TwilioChatApplication.TAG,"Error trying to fetch token: " + message);_339 }_339 });_339 }_339_339 private Handler setupListenerHandler() {_339 Looper looper;_339 Handler handler;_339 if ((looper = Looper.myLooper()) != null) {_339 handler = new Handler(looper);_339 } else if ((looper = Looper.getMainLooper()) != null) {_339 handler = new Handler(looper);_339 } else {_339 throw new IllegalArgumentException("Channel Listener must have a Looper.");_339 }_339 return handler;_339 }_339}
Let's see how we can listen to events from the chat client so we can update our app's state.
The Programmable Chat Client will trigger events such as onChannelAdded
or onChannelDeleted
on our application. Given the creation or deletion of a channel, we'll reload the channel list in the sliding panel. If a channel is deleted, but we were currently on that same channel, the application will automatically join the general channel.
You must set your ChatClient
to listen to events using a ChatClientListener. In this particular case, MainChatActivity
implements ChatClientListener, but it's methods are called from the ChannelManager
class that also implements ChatClientListener (who is the client's listener). ChannelManager
is used as an event handler proxy. Twilio chat sets the listener when loading the channels.
app/src/main/java/com/twilio/twiliochat/chat/MainChatActivity.java
_565package com.twilio.twiliochat.chat;_565_565import android.app.Activity;_565import android.app.ProgressDialog;_565import android.content.Context;_565import android.content.DialogInterface;_565import android.content.Intent;_565import android.content.res.Resources;_565import android.os.Bundle;_565import android.os.Handler;_565import android.util.Log;_565import android.view.Menu;_565import android.view.MenuItem;_565import android.view.View;_565import android.widget.AdapterView;_565import android.widget.Button;_565import android.widget.ListView;_565import android.widget.TextView;_565_565import androidx.appcompat.app.ActionBarDrawerToggle;_565import androidx.appcompat.app.AppCompatActivity;_565import androidx.appcompat.widget.Toolbar;_565import androidx.core.view.GravityCompat;_565import androidx.drawerlayout.widget.DrawerLayout;_565import androidx.fragment.app.FragmentTransaction;_565import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;_565_565import com.twilio.chat.Channel;_565import com.twilio.chat.ChatClient;_565import com.twilio.chat.ChatClientListener;_565import com.twilio.chat.ErrorInfo;_565import com.twilio.chat.StatusListener;_565import com.twilio.chat.User;_565import com.twilio.twiliochat.R;_565import com.twilio.twiliochat.application.AlertDialogHandler;_565import com.twilio.twiliochat.application.SessionManager;_565import com.twilio.twiliochat.application.TwilioChatApplication;_565import com.twilio.twiliochat.chat.channels.ChannelAdapter;_565import com.twilio.twiliochat.chat.channels.ChannelManager;_565import com.twilio.twiliochat.chat.channels.LoadChannelListener;_565import com.twilio.twiliochat.chat.listeners.InputOnClickListener;_565import com.twilio.twiliochat.chat.listeners.TaskCompletionListener;_565import com.twilio.twiliochat.landing.LoginActivity;_565_565import java.util.List;_565_565public class MainChatActivity extends AppCompatActivity implements ChatClientListener {_565 private Context context;_565 private Activity mainActivity;_565 private Button logoutButton;_565 private Button addChannelButton;_565 private TextView usernameTextView;_565 private ChatClientManager chatClientManager;_565 private ListView channelsListView;_565 private ChannelAdapter channelAdapter;_565 private ChannelManager channelManager;_565 private MainChatFragment chatFragment;_565 private DrawerLayout drawer;_565 private ProgressDialog progressDialog;_565 private MenuItem leaveChannelMenuItem;_565 private MenuItem deleteChannelMenuItem;_565 private SwipeRefreshLayout refreshLayout;_565_565 @Override_565 protected void onDestroy() {_565 super.onDestroy();_565 new Handler().post(new Runnable() {_565 @Override_565 public void run() {_565 chatClientManager.shutdown();_565 TwilioChatApplication.get().getChatClientManager().setChatClient(null);_565 }_565 });_565 }_565_565 @Override_565 protected void onCreate(Bundle savedInstanceState) {_565 super.onCreate(savedInstanceState);_565 setContentView(R.layout.activity_main_chat);_565 Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);_565 setSupportActionBar(toolbar);_565_565 drawer = (DrawerLayout) findViewById(R.id.drawer_layout);_565 ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(this, drawer, toolbar,_565 R.string.navigation_drawer_open, R.string.navigation_drawer_close);_565 drawer.setDrawerListener(toggle);_565 toggle.syncState();_565_565 refreshLayout = (SwipeRefreshLayout) findViewById(R.id.refreshLayout);_565_565 FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();_565_565 chatFragment = new MainChatFragment();_565 fragmentTransaction.add(R.id.fragment_container, chatFragment);_565 fragmentTransaction.commit();_565_565 context = this;_565 mainActivity = this;_565 logoutButton = (Button) findViewById(R.id.buttonLogout);_565 addChannelButton = (Button) findViewById(R.id.buttonAddChannel);_565 usernameTextView = (TextView) findViewById(R.id.textViewUsername);_565 channelsListView = (ListView) findViewById(R.id.listViewChannels);_565_565 channelManager = ChannelManager.getInstance();_565 setUsernameTextView();_565_565 setUpListeners();_565 checkTwilioClient();_565 }_565_565 private void setUpListeners() {_565 logoutButton.setOnClickListener(new View.OnClickListener() {_565 @Override_565 public void onClick(View v) {_565 promptLogout();_565 }_565 });_565 addChannelButton.setOnClickListener(new View.OnClickListener() {_565 @Override_565 public void onClick(View v) {_565 showAddChannelDialog();_565 }_565 });_565 refreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {_565 @Override_565 public void onRefresh() {_565 refreshLayout.setRefreshing(true);_565 refreshChannels();_565 }_565 });_565 channelsListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {_565 @Override_565 public void onItemClick(AdapterView<?> parent, View view, int position, long id) {_565 setChannel(position);_565 }_565 });_565 }_565_565 @Override_565 public void onBackPressed() {_565 if (drawer.isDrawerOpen(GravityCompat.START)) {_565 drawer.closeDrawer(GravityCompat.START);_565 } else {_565 super.onBackPressed();_565 }_565 }_565_565 @Override_565 public boolean onCreateOptionsMenu(Menu menu) {_565 getMenuInflater().inflate(R.menu.main_chat, menu);_565 this.leaveChannelMenuItem = menu.findItem(R.id.action_leave_channel);_565 this.leaveChannelMenuItem.setVisible(false);_565 this.deleteChannelMenuItem = menu.findItem(R.id.action_delete_channel);_565 this.deleteChannelMenuItem.setVisible(false);_565 return true;_565 }_565_565 @Override_565 public boolean onOptionsItemSelected(MenuItem item) {_565 int id = item.getItemId();_565_565 if (id == R.id.action_leave_channel) {_565 leaveCurrentChannel();_565 return true;_565 }_565 if (id == R.id.action_delete_channel) {_565 promptChannelDeletion();_565 }_565_565 return super.onOptionsItemSelected(item);_565 }_565_565 private String getStringResource(int id) {_565 Resources resources = getResources();_565 return resources.getString(id);_565 }_565_565 private void refreshChannels() {_565 channelManager.populateChannels(new LoadChannelListener() {_565 @Override_565 public void onChannelsFinishedLoading(final List<Channel> channels) {_565 runOnUiThread(new Runnable() {_565 @Override_565 public void run() {_565 channelAdapter.setChannels(channels);_565 refreshLayout.setRefreshing(false);_565 }_565 });_565 }_565 });_565 }_565_565 private void populateChannels() {_565 channelManager.setChannelListener(this);_565 channelManager.populateChannels(new LoadChannelListener() {_565 @Override_565 public void onChannelsFinishedLoading(List<Channel> channels) {_565 channelAdapter = new ChannelAdapter(mainActivity, channels);_565 channelsListView.setAdapter(channelAdapter);_565 MainChatActivity.this.channelManager_565 .joinOrCreateGeneralChannelWithCompletion(new StatusListener() {_565 @Override_565 public void onSuccess() {_565 runOnUiThread(new Runnable() {_565 @Override_565 public void run() {_565 channelAdapter.notifyDataSetChanged();_565 stopActivityIndicator();_565 setChannel(0);_565 }_565 });_565 }_565_565 @Override_565 public void onError(ErrorInfo errorInfo) {_565 showAlertWithMessage(getStringResource(R.string.generic_error) + " - " + errorInfo.getMessage());_565 }_565 });_565 }_565 });_565 }_565_565 private void setChannel(final int position) {_565 List<Channel> channels = channelManager.getChannels();_565 if (channels == null) {_565 return;_565 }_565 final Channel currentChannel = chatFragment.getCurrentChannel();_565 final Channel selectedChannel = channels.get(position);_565 if (currentChannel != null && currentChannel.getSid().contentEquals(selectedChannel.getSid())) {_565 drawer.closeDrawer(GravityCompat.START);_565 return;_565 }_565 hideMenuItems(position);_565 if (selectedChannel != null) {_565 showActivityIndicator("Joining " + selectedChannel.getFriendlyName() + " channel");_565 if (currentChannel != null && currentChannel.getStatus() == Channel.ChannelStatus.JOINED) {_565 this.channelManager.leaveChannelWithHandler(currentChannel, new StatusListener() {_565 @Override_565 public void onSuccess() {_565 joinChannel(selectedChannel);_565 }_565_565 @Override_565 public void onError(ErrorInfo errorInfo) {_565 stopActivityIndicator();_565 }_565 });_565 return;_565 }_565 joinChannel(selectedChannel);_565 stopActivityIndicator();_565 } else {_565 stopActivityIndicator();_565 showAlertWithMessage(getStringResource(R.string.generic_error));_565 Log.e(TwilioChatApplication.TAG,"Selected channel out of range");_565 }_565 }_565_565 private void joinChannel(final Channel selectedChannel) {_565 runOnUiThread(new Runnable() {_565 @Override_565 public void run() {_565 chatFragment.setCurrentChannel(selectedChannel, new StatusListener() {_565 @Override_565 public void onSuccess() {_565 MainChatActivity.this.stopActivityIndicator();_565 }_565_565 @Override_565 public void onError(ErrorInfo errorInfo) {_565 }_565 });_565 setTitle(selectedChannel.getFriendlyName());_565 drawer.closeDrawer(GravityCompat.START);_565 }_565 });_565 }_565_565 private void hideMenuItems(final int position) {_565 runOnUiThread(new Runnable() {_565 @Override_565 public void run() {_565 MainChatActivity.this.leaveChannelMenuItem.setVisible(position != 0);_565 MainChatActivity.this.deleteChannelMenuItem.setVisible(position != 0);_565 }_565 });_565 }_565_565 private void showAddChannelDialog() {_565 String message = getStringResource(R.string.new_channel_prompt);_565 AlertDialogHandler.displayInputDialog(message, context, new InputOnClickListener() {_565 @Override_565 public void onClick(String input) {_565 if (input.length() == 0) {_565 showAlertWithMessage(getStringResource(R.string.channel_name_required_message));_565 return;_565 }_565 createChannelWithName(input);_565 }_565 });_565 }_565_565 private void createChannelWithName(String name) {_565 name = name.trim();_565 if (name.toLowerCase()_565 .contentEquals(this.channelManager.getDefaultChannelName().toLowerCase())) {_565 showAlertWithMessage(getStringResource(R.string.channel_name_equals_default_name));_565 return;_565 }_565 this.channelManager.createChannelWithName(name, new StatusListener() {_565 @Override_565 public void onSuccess() {_565 refreshChannels();_565 }_565_565 @Override_565 public void onError(ErrorInfo errorInfo) {_565 showAlertWithMessage(getStringResource(R.string.generic_error) + " - " + errorInfo.getMessage());_565 }_565 });_565 }_565_565 private void promptChannelDeletion() {_565 String message = getStringResource(R.string.channel_delete_prompt_message);_565 AlertDialogHandler.displayCancellableAlertWithHandler(message, context,_565 new DialogInterface.OnClickListener() {_565 @Override_565 public void onClick(DialogInterface dialog, int which) {_565 deleteCurrentChannel();_565 }_565 });_565 }_565_565 private void deleteCurrentChannel() {_565 Channel currentChannel = chatFragment.getCurrentChannel();_565 channelManager.deleteChannelWithHandler(currentChannel, new StatusListener() {_565 @Override_565 public void onSuccess() {_565 }_565_565 @Override_565 public void onError(ErrorInfo errorInfo) {_565 showAlertWithMessage(getStringResource(R.string.message_deletion_forbidden));_565 }_565 });_565 }_565_565 private void leaveCurrentChannel() {_565 final Channel currentChannel = chatFragment.getCurrentChannel();_565 if (currentChannel.getStatus() == Channel.ChannelStatus.NOT_PARTICIPATING) {_565 setChannel(0);_565 return;_565 }_565 channelManager.leaveChannelWithHandler(currentChannel, new StatusListener() {_565 @Override_565 public void onSuccess() {_565 setChannel(0);_565 }_565_565 @Override_565 public void onError(ErrorInfo errorInfo) {_565 stopActivityIndicator();_565 }_565 });_565 }_565_565 private void checkTwilioClient() {_565 showActivityIndicator(getStringResource(R.string.loading_channels_message));_565 chatClientManager = TwilioChatApplication.get().getChatClientManager();_565 if (chatClientManager.getChatClient() == null) {_565 initializeClient();_565 } else {_565 populateChannels();_565 }_565 }_565_565 private void initializeClient() {_565 chatClientManager.connectClient(new TaskCompletionListener<Void, String>() {_565 @Override_565 public void onSuccess(Void aVoid) {_565 populateChannels();_565 }_565_565 @Override_565 public void onError(String errorMessage) {_565 stopActivityIndicator();_565 showAlertWithMessage("Client connection error: " + errorMessage);_565 }_565 });_565 }_565_565 private void promptLogout() {_565 final String message = getStringResource(R.string.logout_prompt_message);_565 runOnUiThread(new Runnable() {_565 @Override_565 public void run() {_565 AlertDialogHandler.displayCancellableAlertWithHandler(message, context,_565 new DialogInterface.OnClickListener() {_565 @Override_565 public void onClick(DialogInterface dialog, int which) {_565 SessionManager.getInstance().logoutUser();_565 showLoginActivity();_565 }_565 });_565 }_565 });_565_565 }_565_565 private void showLoginActivity() {_565 Intent launchIntent = new Intent();_565 launchIntent.setClass(getApplicationContext(), LoginActivity.class);_565 startActivity(launchIntent);_565 finish();_565 }_565_565 private void setUsernameTextView() {_565 String username =_565 SessionManager.getInstance().getUserDetails().get(SessionManager.KEY_USERNAME);_565 usernameTextView.setText(username);_565 }_565_565 private void stopActivityIndicator() {_565 runOnUiThread(new Runnable() {_565 @Override_565 public void run() {_565 if (progressDialog.isShowing()) {_565 progressDialog.dismiss();_565 }_565 }_565 });_565 }_565_565 private void showActivityIndicator(final String message) {_565 runOnUiThread(new Runnable() {_565 @Override_565 public void run() {_565 progressDialog = new ProgressDialog(MainChatActivity.this.mainActivity);_565 progressDialog.setMessage(message);_565 progressDialog.show();_565 progressDialog.setCanceledOnTouchOutside(false);_565 progressDialog.setCancelable(false);_565 }_565 });_565 }_565_565 private void showAlertWithMessage(final String message) {_565 runOnUiThread(new Runnable() {_565 @Override_565 public void run() {_565 AlertDialogHandler.displayAlertWithMessage(message, context);_565 }_565 });_565 }_565_565_565_565 @Override_565 public void onChannelAdded(Channel channel) {_565 Log.d(TwilioChatApplication.TAG,"Channel Added");_565 refreshChannels();_565 }_565_565 @Override_565 public void onChannelDeleted(final Channel channel) {_565 Log.d(TwilioChatApplication.TAG,"Channel Deleted");_565 Channel currentChannel = chatFragment.getCurrentChannel();_565 if (channel.getSid().contentEquals(currentChannel.getSid())) {_565 chatFragment.setCurrentChannel(null, null);_565 setChannel(0);_565 }_565 refreshChannels();_565 }_565_565 @Override_565 public void onChannelInvited(Channel channel) {_565_565 }_565_565 @Override_565 public void onChannelSynchronizationChange(Channel channel) {_565_565 }_565_565 @Override_565 public void onError(ErrorInfo errorInfo) {_565_565 }_565_565 @Override_565 public void onClientSynchronization(ChatClient.SynchronizationStatus synchronizationStatus) {_565_565 }_565_565 @Override_565 public void onConnectionStateChange(ChatClient.ConnectionState connectionState) {_565_565 }_565_565 @Override_565 public void onTokenExpired() {_565_565 }_565_565 @Override_565 public void onTokenAboutToExpire() {_565_565 }_565_565 @Override_565 public void onChannelJoined(Channel channel) {_565_565 }_565_565 @Override_565 public void onChannelUpdated(Channel channel, Channel.UpdateReason updateReason) {_565_565 }_565_565 @Override_565 public void onUserUpdated(User user, User.UpdateReason updateReason) {_565_565 }_565_565 @Override_565 public void onUserSubscribed(User user) {_565_565 }_565_565 @Override_565 public void onUserUnsubscribed(User user) {_565_565 }_565_565 @Override_565 public void onNewMessageNotification(String s, String s1, long l) {_565_565 }_565_565 @Override_565 public void onAddedToChannelNotification(String s) {_565_565 }_565_565 @Override_565 public void onInvitedToChannelNotification(String s) {_565_565 }_565_565 @Override_565 public void onRemovedFromChannelNotification(String s) {_565_565 }_565_565 @Override_565 public void onNotificationSubscribed() {_565_565 }_565_565 @Override_565 public void onNotificationFailed(ErrorInfo errorInfo) {_565_565 }_565}
Next, we need a default channel.
This application will try to join a channel called "General Channel" when it starts. If the channel doesn't exist, it'll create one with that name. The scope of this example application will show you how to work only with public channels, but the Programmable Chat Client allows you to create private channels and handle invitations.
Notice we set a unique name for the general channel as we don't want to create a new general channel every time we start the application.
app/src/main/java/com/twilio/twiliochat/chat/channels/ChannelManager.java
_339package com.twilio.twiliochat.chat.channels;_339_339import android.content.res.Resources;_339import android.os.Handler;_339import android.os.Looper;_339import android.util.Log;_339_339import com.twilio.chat.CallbackListener;_339import com.twilio.chat.Channel;_339import com.twilio.chat.Channel.ChannelType;_339import com.twilio.chat.ChannelDescriptor;_339import com.twilio.chat.Channels;_339import com.twilio.chat.ChatClient;_339import com.twilio.chat.ChatClientListener;_339import com.twilio.chat.ErrorInfo;_339import com.twilio.chat.Paginator;_339import com.twilio.chat.StatusListener;_339import com.twilio.chat.User;_339import com.twilio.twiliochat.R;_339import com.twilio.twiliochat.application.TwilioChatApplication;_339import com.twilio.twiliochat.chat.ChatClientManager;_339import com.twilio.twiliochat.chat.accesstoken.AccessTokenFetcher;_339import com.twilio.twiliochat.chat.listeners.TaskCompletionListener;_339_339import java.util.ArrayList;_339import java.util.Collections;_339import java.util.List;_339_339public class ChannelManager implements ChatClientListener {_339 private static ChannelManager sharedManager = new ChannelManager();_339 public Channel generalChannel;_339 private ChatClientManager chatClientManager;_339 private ChannelExtractor channelExtractor;_339 private List<Channel> channels;_339 private Channels channelsObject;_339 private ChatClientListener listener;_339 private String defaultChannelName;_339 private String defaultChannelUniqueName;_339 private Handler handler;_339 private Boolean isRefreshingChannels = false;_339_339 private ChannelManager() {_339 this.chatClientManager = TwilioChatApplication.get().getChatClientManager();_339 this.channelExtractor = new ChannelExtractor();_339 this.listener = this;_339 defaultChannelName = getStringResource(R.string.default_channel_name);_339 defaultChannelUniqueName = getStringResource(R.string.default_channel_unique_name);_339 handler = setupListenerHandler();_339 }_339_339 public static ChannelManager getInstance() {_339 return sharedManager;_339 }_339_339 public List<Channel> getChannels() {_339 return channels;_339 }_339_339 public String getDefaultChannelName() {_339 return this.defaultChannelName;_339 }_339_339 public void leaveChannelWithHandler(Channel channel, StatusListener handler) {_339 channel.leave(handler);_339 }_339_339 public void deleteChannelWithHandler(Channel channel, StatusListener handler) {_339 channel.destroy(handler);_339 }_339_339 public void populateChannels(final LoadChannelListener listener) {_339 if (this.chatClientManager == null || this.isRefreshingChannels) {_339 return;_339 }_339 this.isRefreshingChannels = true;_339_339 handler.post(new Runnable() {_339 @Override_339 public void run() {_339 channelsObject = chatClientManager.getChatClient().getChannels();_339_339 channelsObject.getPublicChannelsList(new CallbackListener<Paginator<ChannelDescriptor>>() {_339 @Override_339 public void onSuccess(Paginator<ChannelDescriptor> channelDescriptorPaginator) {_339 extractChannelsFromPaginatorAndPopulate(channelDescriptorPaginator, listener);_339 }_339 });_339_339 }_339 });_339 }_339_339 private void extractChannelsFromPaginatorAndPopulate(final Paginator<ChannelDescriptor> channelsPaginator,_339 final LoadChannelListener listener) {_339 channels = new ArrayList<>();_339 ChannelManager.this.channels.clear();_339 channelExtractor.extractAndSortFromChannelDescriptor(channelsPaginator,_339 new TaskCompletionListener<List<Channel>, String>() {_339 @Override_339 public void onSuccess(List<Channel> channels) {_339 ChannelManager.this.channels.addAll(channels);_339 Collections.sort(ChannelManager.this.channels, new CustomChannelComparator());_339 ChannelManager.this.isRefreshingChannels = false;_339 chatClientManager.addClientListener(ChannelManager.this);_339 listener.onChannelsFinishedLoading(ChannelManager.this.channels);_339 }_339_339 @Override_339 public void onError(String errorText) {_339 Log.e(TwilioChatApplication.TAG,"Error populating channels: " + errorText);_339 }_339 });_339 }_339_339 public void createChannelWithName(String name, final StatusListener handler) {_339 this.channelsObject_339 .channelBuilder()_339 .withFriendlyName(name)_339 .withType(ChannelType.PUBLIC)_339 .build(new CallbackListener<Channel>() {_339 @Override_339 public void onSuccess(final Channel newChannel) {_339 handler.onSuccess();_339 }_339_339 @Override_339 public void onError(ErrorInfo errorInfo) {_339 handler.onError(errorInfo);_339 }_339 });_339 }_339_339 public void joinOrCreateGeneralChannelWithCompletion(final StatusListener listener) {_339 channelsObject.getChannel(defaultChannelUniqueName, new CallbackListener<Channel>() {_339 @Override_339 public void onSuccess(Channel channel) {_339 ChannelManager.this.generalChannel = channel;_339 if (channel != null) {_339 joinGeneralChannelWithCompletion(listener);_339 } else {_339 createGeneralChannelWithCompletion(listener);_339 }_339 }_339 });_339 }_339_339 private void joinGeneralChannelWithCompletion(final StatusListener listener) {_339 if (generalChannel.getStatus() == Channel.ChannelStatus.JOINED) {_339 listener.onSuccess();_339 return;_339 }_339 this.generalChannel.join(new StatusListener() {_339 @Override_339 public void onSuccess() {_339 listener.onSuccess();_339 }_339_339 @Override_339 public void onError(ErrorInfo errorInfo) {_339 listener.onError(errorInfo);_339 }_339 });_339 }_339_339 private void createGeneralChannelWithCompletion(final StatusListener listener) {_339 this.channelsObject_339 .channelBuilder()_339 .withFriendlyName(defaultChannelName)_339 .withUniqueName(defaultChannelUniqueName)_339 .withType(ChannelType.PUBLIC)_339 .build(new CallbackListener<Channel>() {_339 @Override_339 public void onSuccess(final Channel channel) {_339 ChannelManager.this.generalChannel = channel;_339 ChannelManager.this.channels.add(channel);_339 joinGeneralChannelWithCompletion(listener);_339 }_339_339 @Override_339 public void onError(ErrorInfo errorInfo) {_339 listener.onError(errorInfo);_339 }_339 });_339 }_339_339 public void setChannelListener(ChatClientListener listener) {_339 this.listener = listener;_339 }_339_339 private String getStringResource(int id) {_339 Resources resources = TwilioChatApplication.get().getResources();_339 return resources.getString(id);_339 }_339_339 @Override_339 public void onChannelAdded(Channel channel) {_339 if (listener != null) {_339 listener.onChannelAdded(channel);_339 }_339 }_339_339 @Override_339 public void onChannelUpdated(Channel channel, Channel.UpdateReason updateReason) {_339 if (listener != null) {_339 listener.onChannelUpdated(channel, updateReason);_339 }_339 }_339_339 @Override_339 public void onChannelDeleted(Channel channel) {_339 if (listener != null) {_339 listener.onChannelDeleted(channel);_339 }_339 }_339_339 @Override_339 public void onChannelSynchronizationChange(Channel channel) {_339 if (listener != null) {_339 listener.onChannelSynchronizationChange(channel);_339 }_339 }_339_339 @Override_339 public void onError(ErrorInfo errorInfo) {_339 if (listener != null) {_339 listener.onError(errorInfo);_339 }_339 }_339_339 @Override_339 public void onClientSynchronization(ChatClient.SynchronizationStatus synchronizationStatus) {_339_339 }_339_339 @Override_339 public void onChannelJoined(Channel channel) {_339_339 }_339_339 @Override_339 public void onChannelInvited(Channel channel) {_339_339 }_339_339 @Override_339 public void onUserUpdated(User user, User.UpdateReason updateReason) {_339 if (listener != null) {_339 listener.onUserUpdated(user, updateReason);_339 }_339 }_339_339 @Override_339 public void onUserSubscribed(User user) {_339_339 }_339_339 @Override_339 public void onUserUnsubscribed(User user) {_339_339 }_339_339 @Override_339 public void onNewMessageNotification(String s, String s1, long l) {_339_339 }_339_339 @Override_339 public void onAddedToChannelNotification(String s) {_339_339 }_339_339 @Override_339 public void onInvitedToChannelNotification(String s) {_339_339 }_339_339 @Override_339 public void onRemovedFromChannelNotification(String s) {_339_339 }_339_339 @Override_339 public void onNotificationSubscribed() {_339_339 }_339_339 @Override_339 public void onNotificationFailed(ErrorInfo errorInfo) {_339_339 }_339_339 @Override_339 public void onConnectionStateChange(ChatClient.ConnectionState connectionState) {_339_339 }_339_339 @Override_339 public void onTokenExpired() {_339 refreshAccessToken();_339 }_339_339 @Override_339 public void onTokenAboutToExpire() {_339 refreshAccessToken();_339 }_339_339 private void refreshAccessToken() {_339 AccessTokenFetcher accessTokenFetcher = chatClientManager.getAccessTokenFetcher();_339 accessTokenFetcher.fetch(new TaskCompletionListener<String, String>() {_339 @Override_339 public void onSuccess(String token) {_339 ChannelManager.this.chatClientManager.getChatClient().updateToken(token, new StatusListener() {_339 @Override_339 public void onSuccess() {_339 Log.d(TwilioChatApplication.TAG, "Successfully updated access token.");_339 }_339 });_339 }_339_339 @Override_339 public void onError(String message) {_339 Log.e(TwilioChatApplication.TAG,"Error trying to fetch token: " + message);_339 }_339 });_339 }_339_339 private Handler setupListenerHandler() {_339 Looper looper;_339 Handler handler;_339 if ((looper = Looper.myLooper()) != null) {_339 handler = new Handler(looper);_339 } else if ((looper = Looper.getMainLooper()) != null) {_339 handler = new Handler(looper);_339 } else {_339 throw new IllegalArgumentException("Channel Listener must have a Looper.");_339 }_339 return handler;_339 }_339}
Now let's listen for some channel events.
We set a channel's listener to MainChatFragment
that implements ChannelListener
, and here we implemented the following methods that listen to channel events:
onMessageAdded
: When someone sends a message to the channel you are connected to.
onMemberAdded
: When someone joins the channel.
onMemberDeleted
: When someone leaves the channel.
As you may have noticed, each one of these methods includes useful objects as parameters. One example is the actual message that was added to the channel.
app/src/main/java/com/twilio/twiliochat/chat/MainChatFragment.java
_215package com.twilio.twiliochat.chat;_215_215import android.app.Activity;_215import android.content.Context;_215import android.os.Bundle;_215import androidx.fragment.app.Fragment;_215import android.view.KeyEvent;_215import android.view.LayoutInflater;_215import android.view.View;_215import android.view.ViewGroup;_215import android.widget.Button;_215import android.widget.EditText;_215import android.widget.ListView;_215import android.widget.TextView;_215_215import com.twilio.chat.CallbackListener;_215import com.twilio.chat.Channel;_215import com.twilio.chat.ChannelListener;_215import com.twilio.chat.ErrorInfo;_215import com.twilio.chat.Member;_215import com.twilio.chat.Message;_215import com.twilio.chat.Messages;_215import com.twilio.chat.StatusListener;_215import com.twilio.twiliochat.R;_215import com.twilio.twiliochat.chat.messages.JoinedStatusMessage;_215import com.twilio.twiliochat.chat.messages.LeftStatusMessage;_215import com.twilio.twiliochat.chat.messages.MessageAdapter;_215import com.twilio.twiliochat.chat.messages.StatusMessage;_215_215import java.util.List;_215_215public class MainChatFragment extends Fragment implements ChannelListener {_215 Context context;_215 Activity mainActivity;_215 Button sendButton;_215 ListView messagesListView;_215 EditText messageTextEdit;_215_215 MessageAdapter messageAdapter;_215 Channel currentChannel;_215 Messages messagesObject;_215_215 public MainChatFragment() {_215 }_215_215 public static MainChatFragment newInstance() {_215 MainChatFragment fragment = new MainChatFragment();_215 return fragment;_215 }_215_215 @Override_215 public void onCreate(Bundle savedInstanceState) {_215 super.onCreate(savedInstanceState);_215 context = this.getActivity();_215 mainActivity = this.getActivity();_215 }_215_215 @Override_215 public View onCreateView(LayoutInflater inflater, ViewGroup container,_215 Bundle savedInstanceState) {_215 View view = inflater.inflate(R.layout.fragment_main_chat, container, false);_215 sendButton = (Button) view.findViewById(R.id.buttonSend);_215 messagesListView = (ListView) view.findViewById(R.id.listViewMessages);_215 messageTextEdit = (EditText) view.findViewById(R.id.editTextMessage);_215_215 messageAdapter = new MessageAdapter(mainActivity);_215 messagesListView.setAdapter(messageAdapter);_215 setUpListeners();_215 setMessageInputEnabled(false);_215_215 return view;_215 }_215_215 @Override_215 public void onAttach(Context context) {_215 super.onAttach(context);_215 }_215_215 @Override_215 public void onDetach() {_215 super.onDetach();_215 }_215_215 public Channel getCurrentChannel() {_215 return currentChannel;_215 }_215_215 public void setCurrentChannel(Channel currentChannel, final StatusListener handler) {_215 if (currentChannel == null) {_215 this.currentChannel = null;_215 return;_215 }_215 if (!currentChannel.equals(this.currentChannel)) {_215 setMessageInputEnabled(false);_215 this.currentChannel = currentChannel;_215 this.currentChannel.addListener(this);_215 if (this.currentChannel.getStatus() == Channel.ChannelStatus.JOINED) {_215 loadMessages(handler);_215 } else {_215 this.currentChannel.join(new StatusListener() {_215 @Override_215 public void onSuccess() {_215 loadMessages(handler);_215 }_215_215 @Override_215 public void onError(ErrorInfo errorInfo) {_215 }_215 });_215 }_215 }_215 }_215_215 private void loadMessages(final StatusListener handler) {_215 this.messagesObject = this.currentChannel.getMessages();_215_215 if (messagesObject != null) {_215 messagesObject.getLastMessages(100, new CallbackListener<List<Message>>() {_215 @Override_215 public void onSuccess(List<Message> messageList) {_215 messageAdapter.setMessages(messageList);_215 setMessageInputEnabled(true);_215 messageTextEdit.requestFocus();_215 handler.onSuccess();_215 }_215 });_215 }_215 }_215_215 private void setUpListeners() {_215 sendButton.setOnClickListener(new View.OnClickListener() {_215 @Override_215 public void onClick(View v) {_215 sendMessage();_215 }_215 });_215 messageTextEdit.setOnEditorActionListener(new TextView.OnEditorActionListener() {_215 @Override_215 public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {_215 return false;_215 }_215 });_215 }_215_215 private void sendMessage() {_215 String messageText = getTextInput();_215 if (messageText.length() == 0) {_215 return;_215 }_215 Message.Options messageOptions = Message.options().withBody(messageText);_215 this.messagesObject.sendMessage(messageOptions, null);_215 clearTextInput();_215 }_215_215 private void setMessageInputEnabled(final boolean enabled) {_215 mainActivity.runOnUiThread(new Runnable() {_215 @Override_215 public void run() {_215 MainChatFragment.this.sendButton.setEnabled(enabled);_215 MainChatFragment.this.messageTextEdit.setEnabled(enabled);_215 }_215 });_215 }_215_215 private String getTextInput() {_215 return messageTextEdit.getText().toString();_215 }_215_215 private void clearTextInput() {_215 messageTextEdit.setText("");_215 }_215_215 @Override_215 public void onMessageAdded(Message message) {_215 messageAdapter.addMessage(message);_215 }_215_215 @Override_215 public void onMemberAdded(Member member) {_215 StatusMessage statusMessage = new JoinedStatusMessage(member.getIdentity());_215 this.messageAdapter.addStatusMessage(statusMessage);_215 }_215_215 @Override_215 public void onMemberDeleted(Member member) {_215 StatusMessage statusMessage = new LeftStatusMessage(member.getIdentity());_215 this.messageAdapter.addStatusMessage(statusMessage);_215 }_215_215 @Override_215 public void onMessageUpdated(Message message, Message.UpdateReason updateReason) {_215 }_215_215 @Override_215 public void onMessageDeleted(Message message) {_215 }_215_215 @Override_215 public void onMemberUpdated(Member member, Member.UpdateReason updateReason) {_215 }_215_215 @Override_215 public void onTypingStarted(Channel channel, Member member) {_215 }_215_215 @Override_215 public void onTypingEnded(Channel channel, Member member) {_215 }_215_215 @Override_215 public void onSynchronizationChanged(Channel channel) {_215 }_215_215_215}
We've actually got a real chat app going here, but let's make it more interesting with multiple channels.
The application uses a Drawer Layout to show a list of the channels created for that Twilio account.
When you tap on the name of a channel, from the sidebar, that channel is set on the MainChatFragment
. The setCurrentChannel
method takes care of joining to the selected channel and loading the messages.
app/src/main/java/com/twilio/twiliochat/chat/MainChatFragment.java
_215package com.twilio.twiliochat.chat;_215_215import android.app.Activity;_215import android.content.Context;_215import android.os.Bundle;_215import androidx.fragment.app.Fragment;_215import android.view.KeyEvent;_215import android.view.LayoutInflater;_215import android.view.View;_215import android.view.ViewGroup;_215import android.widget.Button;_215import android.widget.EditText;_215import android.widget.ListView;_215import android.widget.TextView;_215_215import com.twilio.chat.CallbackListener;_215import com.twilio.chat.Channel;_215import com.twilio.chat.ChannelListener;_215import com.twilio.chat.ErrorInfo;_215import com.twilio.chat.Member;_215import com.twilio.chat.Message;_215import com.twilio.chat.Messages;_215import com.twilio.chat.StatusListener;_215import com.twilio.twiliochat.R;_215import com.twilio.twiliochat.chat.messages.JoinedStatusMessage;_215import com.twilio.twiliochat.chat.messages.LeftStatusMessage;_215import com.twilio.twiliochat.chat.messages.MessageAdapter;_215import com.twilio.twiliochat.chat.messages.StatusMessage;_215_215import java.util.List;_215_215public class MainChatFragment extends Fragment implements ChannelListener {_215 Context context;_215 Activity mainActivity;_215 Button sendButton;_215 ListView messagesListView;_215 EditText messageTextEdit;_215_215 MessageAdapter messageAdapter;_215 Channel currentChannel;_215 Messages messagesObject;_215_215 public MainChatFragment() {_215 }_215_215 public static MainChatFragment newInstance() {_215 MainChatFragment fragment = new MainChatFragment();_215 return fragment;_215 }_215_215 @Override_215 public void onCreate(Bundle savedInstanceState) {_215 super.onCreate(savedInstanceState);_215 context = this.getActivity();_215 mainActivity = this.getActivity();_215 }_215_215 @Override_215 public View onCreateView(LayoutInflater inflater, ViewGroup container,_215 Bundle savedInstanceState) {_215 View view = inflater.inflate(R.layout.fragment_main_chat, container, false);_215 sendButton = (Button) view.findViewById(R.id.buttonSend);_215 messagesListView = (ListView) view.findViewById(R.id.listViewMessages);_215 messageTextEdit = (EditText) view.findViewById(R.id.editTextMessage);_215_215 messageAdapter = new MessageAdapter(mainActivity);_215 messagesListView.setAdapter(messageAdapter);_215 setUpListeners();_215 setMessageInputEnabled(false);_215_215 return view;_215 }_215_215 @Override_215 public void onAttach(Context context) {_215 super.onAttach(context);_215 }_215_215 @Override_215 public void onDetach() {_215 super.onDetach();_215 }_215_215 public Channel getCurrentChannel() {_215 return currentChannel;_215 }_215_215 public void setCurrentChannel(Channel currentChannel, final StatusListener handler) {_215 if (currentChannel == null) {_215 this.currentChannel = null;_215 return;_215 }_215 if (!currentChannel.equals(this.currentChannel)) {_215 setMessageInputEnabled(false);_215 this.currentChannel = currentChannel;_215 this.currentChannel.addListener(this);_215 if (this.currentChannel.getStatus() == Channel.ChannelStatus.JOINED) {_215 loadMessages(handler);_215 } else {_215 this.currentChannel.join(new StatusListener() {_215 @Override_215 public void onSuccess() {_215 loadMessages(handler);_215 }_215_215 @Override_215 public void onError(ErrorInfo errorInfo) {_215 }_215 });_215 }_215 }_215 }_215_215 private void loadMessages(final StatusListener handler) {_215 this.messagesObject = this.currentChannel.getMessages();_215_215 if (messagesObject != null) {_215 messagesObject.getLastMessages(100, new CallbackListener<List<Message>>() {_215 @Override_215 public void onSuccess(List<Message> messageList) {_215 messageAdapter.setMessages(messageList);_215 setMessageInputEnabled(true);_215 messageTextEdit.requestFocus();_215 handler.onSuccess();_215 }_215 });_215 }_215 }_215_215 private void setUpListeners() {_215 sendButton.setOnClickListener(new View.OnClickListener() {_215 @Override_215 public void onClick(View v) {_215 sendMessage();_215 }_215 });_215 messageTextEdit.setOnEditorActionListener(new TextView.OnEditorActionListener() {_215 @Override_215 public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {_215 return false;_215 }_215 });_215 }_215_215 private void sendMessage() {_215 String messageText = getTextInput();_215 if (messageText.length() == 0) {_215 return;_215 }_215 Message.Options messageOptions = Message.options().withBody(messageText);_215 this.messagesObject.sendMessage(messageOptions, null);_215 clearTextInput();_215 }_215_215 private void setMessageInputEnabled(final boolean enabled) {_215 mainActivity.runOnUiThread(new Runnable() {_215 @Override_215 public void run() {_215 MainChatFragment.this.sendButton.setEnabled(enabled);_215 MainChatFragment.this.messageTextEdit.setEnabled(enabled);_215 }_215 });_215 }_215_215 private String getTextInput() {_215 return messageTextEdit.getText().toString();_215 }_215_215 private void clearTextInput() {_215 messageTextEdit.setText("");_215 }_215_215 @Override_215 public void onMessageAdded(Message message) {_215 messageAdapter.addMessage(message);_215 }_215_215 @Override_215 public void onMemberAdded(Member member) {_215 StatusMessage statusMessage = new JoinedStatusMessage(member.getIdentity());_215 this.messageAdapter.addStatusMessage(statusMessage);_215 }_215_215 @Override_215 public void onMemberDeleted(Member member) {_215 StatusMessage statusMessage = new LeftStatusMessage(member.getIdentity());_215 this.messageAdapter.addStatusMessage(statusMessage);_215 }_215_215 @Override_215 public void onMessageUpdated(Message message, Message.UpdateReason updateReason) {_215 }_215_215 @Override_215 public void onMessageDeleted(Message message) {_215 }_215_215 @Override_215 public void onMemberUpdated(Member member, Member.UpdateReason updateReason) {_215 }_215_215 @Override_215 public void onTypingStarted(Channel channel, Member member) {_215 }_215_215 @Override_215 public void onTypingEnded(Channel channel, Member member) {_215 }_215_215 @Override_215 public void onSynchronizationChanged(Channel channel) {_215 }_215_215_215}
If we can join other channels, we'll need some way for a super user to create new channels (and delete old ones).
We use an input dialog so the user can type the name of the new channel. The only restriction here is that the user can't create a channel called "General Channel". Other than that, creating a channel is as simple as using the channelBuilder
as shown and providing at the very least a channel type.
You can provide additional parameters to the builder as we did with a general channel to set a unique name. There's a list of methods you can use in the client library API docs.
app/src/main/java/com/twilio/twiliochat/chat/channels/ChannelManager.java
_339package com.twilio.twiliochat.chat.channels;_339_339import android.content.res.Resources;_339import android.os.Handler;_339import android.os.Looper;_339import android.util.Log;_339_339import com.twilio.chat.CallbackListener;_339import com.twilio.chat.Channel;_339import com.twilio.chat.Channel.ChannelType;_339import com.twilio.chat.ChannelDescriptor;_339import com.twilio.chat.Channels;_339import com.twilio.chat.ChatClient;_339import com.twilio.chat.ChatClientListener;_339import com.twilio.chat.ErrorInfo;_339import com.twilio.chat.Paginator;_339import com.twilio.chat.StatusListener;_339import com.twilio.chat.User;_339import com.twilio.twiliochat.R;_339import com.twilio.twiliochat.application.TwilioChatApplication;_339import com.twilio.twiliochat.chat.ChatClientManager;_339import com.twilio.twiliochat.chat.accesstoken.AccessTokenFetcher;_339import com.twilio.twiliochat.chat.listeners.TaskCompletionListener;_339_339import java.util.ArrayList;_339import java.util.Collections;_339import java.util.List;_339_339public class ChannelManager implements ChatClientListener {_339 private static ChannelManager sharedManager = new ChannelManager();_339 public Channel generalChannel;_339 private ChatClientManager chatClientManager;_339 private ChannelExtractor channelExtractor;_339 private List<Channel> channels;_339 private Channels channelsObject;_339 private ChatClientListener listener;_339 private String defaultChannelName;_339 private String defaultChannelUniqueName;_339 private Handler handler;_339 private Boolean isRefreshingChannels = false;_339_339 private ChannelManager() {_339 this.chatClientManager = TwilioChatApplication.get().getChatClientManager();_339 this.channelExtractor = new ChannelExtractor();_339 this.listener = this;_339 defaultChannelName = getStringResource(R.string.default_channel_name);_339 defaultChannelUniqueName = getStringResource(R.string.default_channel_unique_name);_339 handler = setupListenerHandler();_339 }_339_339 public static ChannelManager getInstance() {_339 return sharedManager;_339 }_339_339 public List<Channel> getChannels() {_339 return channels;_339 }_339_339 public String getDefaultChannelName() {_339 return this.defaultChannelName;_339 }_339_339 public void leaveChannelWithHandler(Channel channel, StatusListener handler) {_339 channel.leave(handler);_339 }_339_339 public void deleteChannelWithHandler(Channel channel, StatusListener handler) {_339 channel.destroy(handler);_339 }_339_339 public void populateChannels(final LoadChannelListener listener) {_339 if (this.chatClientManager == null || this.isRefreshingChannels) {_339 return;_339 }_339 this.isRefreshingChannels = true;_339_339 handler.post(new Runnable() {_339 @Override_339 public void run() {_339 channelsObject = chatClientManager.getChatClient().getChannels();_339_339 channelsObject.getPublicChannelsList(new CallbackListener<Paginator<ChannelDescriptor>>() {_339 @Override_339 public void onSuccess(Paginator<ChannelDescriptor> channelDescriptorPaginator) {_339 extractChannelsFromPaginatorAndPopulate(channelDescriptorPaginator, listener);_339 }_339 });_339_339 }_339 });_339 }_339_339 private void extractChannelsFromPaginatorAndPopulate(final Paginator<ChannelDescriptor> channelsPaginator,_339 final LoadChannelListener listener) {_339 channels = new ArrayList<>();_339 ChannelManager.this.channels.clear();_339 channelExtractor.extractAndSortFromChannelDescriptor(channelsPaginator,_339 new TaskCompletionListener<List<Channel>, String>() {_339 @Override_339 public void onSuccess(List<Channel> channels) {_339 ChannelManager.this.channels.addAll(channels);_339 Collections.sort(ChannelManager.this.channels, new CustomChannelComparator());_339 ChannelManager.this.isRefreshingChannels = false;_339 chatClientManager.addClientListener(ChannelManager.this);_339 listener.onChannelsFinishedLoading(ChannelManager.this.channels);_339 }_339_339 @Override_339 public void onError(String errorText) {_339 Log.e(TwilioChatApplication.TAG,"Error populating channels: " + errorText);_339 }_339 });_339 }_339_339 public void createChannelWithName(String name, final StatusListener handler) {_339 this.channelsObject_339 .channelBuilder()_339 .withFriendlyName(name)_339 .withType(ChannelType.PUBLIC)_339 .build(new CallbackListener<Channel>() {_339 @Override_339 public void onSuccess(final Channel newChannel) {_339 handler.onSuccess();_339 }_339_339 @Override_339 public void onError(ErrorInfo errorInfo) {_339 handler.onError(errorInfo);_339 }_339 });_339 }_339_339 public void joinOrCreateGeneralChannelWithCompletion(final StatusListener listener) {_339 channelsObject.getChannel(defaultChannelUniqueName, new CallbackListener<Channel>() {_339 @Override_339 public void onSuccess(Channel channel) {_339 ChannelManager.this.generalChannel = channel;_339 if (channel != null) {_339 joinGeneralChannelWithCompletion(listener);_339 } else {_339 createGeneralChannelWithCompletion(listener);_339 }_339 }_339 });_339 }_339_339 private void joinGeneralChannelWithCompletion(final StatusListener listener) {_339 if (generalChannel.getStatus() == Channel.ChannelStatus.JOINED) {_339 listener.onSuccess();_339 return;_339 }_339 this.generalChannel.join(new StatusListener() {_339 @Override_339 public void onSuccess() {_339 listener.onSuccess();_339 }_339_339 @Override_339 public void onError(ErrorInfo errorInfo) {_339 listener.onError(errorInfo);_339 }_339 });_339 }_339_339 private void createGeneralChannelWithCompletion(final StatusListener listener) {_339 this.channelsObject_339 .channelBuilder()_339 .withFriendlyName(defaultChannelName)_339 .withUniqueName(defaultChannelUniqueName)_339 .withType(ChannelType.PUBLIC)_339 .build(new CallbackListener<Channel>() {_339 @Override_339 public void onSuccess(final Channel channel) {_339 ChannelManager.this.generalChannel = channel;_339 ChannelManager.this.channels.add(channel);_339 joinGeneralChannelWithCompletion(listener);_339 }_339_339 @Override_339 public void onError(ErrorInfo errorInfo) {_339 listener.onError(errorInfo);_339 }_339 });_339 }_339_339 public void setChannelListener(ChatClientListener listener) {_339 this.listener = listener;_339 }_339_339 private String getStringResource(int id) {_339 Resources resources = TwilioChatApplication.get().getResources();_339 return resources.getString(id);_339 }_339_339 @Override_339 public void onChannelAdded(Channel channel) {_339 if (listener != null) {_339 listener.onChannelAdded(channel);_339 }_339 }_339_339 @Override_339 public void onChannelUpdated(Channel channel, Channel.UpdateReason updateReason) {_339 if (listener != null) {_339 listener.onChannelUpdated(channel, updateReason);_339 }_339 }_339_339 @Override_339 public void onChannelDeleted(Channel channel) {_339 if (listener != null) {_339 listener.onChannelDeleted(channel);_339 }_339 }_339_339 @Override_339 public void onChannelSynchronizationChange(Channel channel) {_339 if (listener != null) {_339 listener.onChannelSynchronizationChange(channel);_339 }_339 }_339_339 @Override_339 public void onError(ErrorInfo errorInfo) {_339 if (listener != null) {_339 listener.onError(errorInfo);_339 }_339 }_339_339 @Override_339 public void onClientSynchronization(ChatClient.SynchronizationStatus synchronizationStatus) {_339_339 }_339_339 @Override_339 public void onChannelJoined(Channel channel) {_339_339 }_339_339 @Override_339 public void onChannelInvited(Channel channel) {_339_339 }_339_339 @Override_339 public void onUserUpdated(User user, User.UpdateReason updateReason) {_339 if (listener != null) {_339 listener.onUserUpdated(user, updateReason);_339 }_339 }_339_339 @Override_339 public void onUserSubscribed(User user) {_339_339 }_339_339 @Override_339 public void onUserUnsubscribed(User user) {_339_339 }_339_339 @Override_339 public void onNewMessageNotification(String s, String s1, long l) {_339_339 }_339_339 @Override_339 public void onAddedToChannelNotification(String s) {_339_339 }_339_339 @Override_339 public void onInvitedToChannelNotification(String s) {_339_339 }_339_339 @Override_339 public void onRemovedFromChannelNotification(String s) {_339_339 }_339_339 @Override_339 public void onNotificationSubscribed() {_339_339 }_339_339 @Override_339 public void onNotificationFailed(ErrorInfo errorInfo) {_339_339 }_339_339 @Override_339 public void onConnectionStateChange(ChatClient.ConnectionState connectionState) {_339_339 }_339_339 @Override_339 public void onTokenExpired() {_339 refreshAccessToken();_339 }_339_339 @Override_339 public void onTokenAboutToExpire() {_339 refreshAccessToken();_339 }_339_339 private void refreshAccessToken() {_339 AccessTokenFetcher accessTokenFetcher = chatClientManager.getAccessTokenFetcher();_339 accessTokenFetcher.fetch(new TaskCompletionListener<String, String>() {_339 @Override_339 public void onSuccess(String token) {_339 ChannelManager.this.chatClientManager.getChatClient().updateToken(token, new StatusListener() {_339 @Override_339 public void onSuccess() {_339 Log.d(TwilioChatApplication.TAG, "Successfully updated access token.");_339 }_339 });_339 }_339_339 @Override_339 public void onError(String message) {_339 Log.e(TwilioChatApplication.TAG,"Error trying to fetch token: " + message);_339 }_339 });_339 }_339_339 private Handler setupListenerHandler() {_339 Looper looper;_339 Handler handler;_339 if ((looper = Looper.myLooper()) != null) {_339 handler = new Handler(looper);_339 } else if ((looper = Looper.getMainLooper()) != null) {_339 handler = new Handler(looper);_339 } else {_339 throw new IllegalArgumentException("Channel Listener must have a Looper.");_339 }_339 return handler;_339 }_339}
Cool, we now know how to create a channel, let's say that we created a lot of channels by mistake. In that case, it would be useful to be able to delete those unnecessary channels. That's our next step!
Deleting a channel is easier than creating one. The application lets the user delete the channel they are currently joined to through a menu option. In order to delete the channel from Twilio you have to call the destroy
method on the channel you are trying to delete. But you still need to provide a StatusListener
to handle the success or failure of the operation.
app/src/main/java/com/twilio/twiliochat/chat/channels/ChannelManager.java
_339package com.twilio.twiliochat.chat.channels;_339_339import android.content.res.Resources;_339import android.os.Handler;_339import android.os.Looper;_339import android.util.Log;_339_339import com.twilio.chat.CallbackListener;_339import com.twilio.chat.Channel;_339import com.twilio.chat.Channel.ChannelType;_339import com.twilio.chat.ChannelDescriptor;_339import com.twilio.chat.Channels;_339import com.twilio.chat.ChatClient;_339import com.twilio.chat.ChatClientListener;_339import com.twilio.chat.ErrorInfo;_339import com.twilio.chat.Paginator;_339import com.twilio.chat.StatusListener;_339import com.twilio.chat.User;_339import com.twilio.twiliochat.R;_339import com.twilio.twiliochat.application.TwilioChatApplication;_339import com.twilio.twiliochat.chat.ChatClientManager;_339import com.twilio.twiliochat.chat.accesstoken.AccessTokenFetcher;_339import com.twilio.twiliochat.chat.listeners.TaskCompletionListener;_339_339import java.util.ArrayList;_339import java.util.Collections;_339import java.util.List;_339_339public class ChannelManager implements ChatClientListener {_339 private static ChannelManager sharedManager = new ChannelManager();_339 public Channel generalChannel;_339 private ChatClientManager chatClientManager;_339 private ChannelExtractor channelExtractor;_339 private List<Channel> channels;_339 private Channels channelsObject;_339 private ChatClientListener listener;_339 private String defaultChannelName;_339 private String defaultChannelUniqueName;_339 private Handler handler;_339 private Boolean isRefreshingChannels = false;_339_339 private ChannelManager() {_339 this.chatClientManager = TwilioChatApplication.get().getChatClientManager();_339 this.channelExtractor = new ChannelExtractor();_339 this.listener = this;_339 defaultChannelName = getStringResource(R.string.default_channel_name);_339 defaultChannelUniqueName = getStringResource(R.string.default_channel_unique_name);_339 handler = setupListenerHandler();_339 }_339_339 public static ChannelManager getInstance() {_339 return sharedManager;_339 }_339_339 public List<Channel> getChannels() {_339 return channels;_339 }_339_339 public String getDefaultChannelName() {_339 return this.defaultChannelName;_339 }_339_339 public void leaveChannelWithHandler(Channel channel, StatusListener handler) {_339 channel.leave(handler);_339 }_339_339 public void deleteChannelWithHandler(Channel channel, StatusListener handler) {_339 channel.destroy(handler);_339 }_339_339 public void populateChannels(final LoadChannelListener listener) {_339 if (this.chatClientManager == null || this.isRefreshingChannels) {_339 return;_339 }_339 this.isRefreshingChannels = true;_339_339 handler.post(new Runnable() {_339 @Override_339 public void run() {_339 channelsObject = chatClientManager.getChatClient().getChannels();_339_339 channelsObject.getPublicChannelsList(new CallbackListener<Paginator<ChannelDescriptor>>() {_339 @Override_339 public void onSuccess(Paginator<ChannelDescriptor> channelDescriptorPaginator) {_339 extractChannelsFromPaginatorAndPopulate(channelDescriptorPaginator, listener);_339 }_339 });_339_339 }_339 });_339 }_339_339 private void extractChannelsFromPaginatorAndPopulate(final Paginator<ChannelDescriptor> channelsPaginator,_339 final LoadChannelListener listener) {_339 channels = new ArrayList<>();_339 ChannelManager.this.channels.clear();_339 channelExtractor.extractAndSortFromChannelDescriptor(channelsPaginator,_339 new TaskCompletionListener<List<Channel>, String>() {_339 @Override_339 public void onSuccess(List<Channel> channels) {_339 ChannelManager.this.channels.addAll(channels);_339 Collections.sort(ChannelManager.this.channels, new CustomChannelComparator());_339 ChannelManager.this.isRefreshingChannels = false;_339 chatClientManager.addClientListener(ChannelManager.this);_339 listener.onChannelsFinishedLoading(ChannelManager.this.channels);_339 }_339_339 @Override_339 public void onError(String errorText) {_339 Log.e(TwilioChatApplication.TAG,"Error populating channels: " + errorText);_339 }_339 });_339 }_339_339 public void createChannelWithName(String name, final StatusListener handler) {_339 this.channelsObject_339 .channelBuilder()_339 .withFriendlyName(name)_339 .withType(ChannelType.PUBLIC)_339 .build(new CallbackListener<Channel>() {_339 @Override_339 public void onSuccess(final Channel newChannel) {_339 handler.onSuccess();_339 }_339_339 @Override_339 public void onError(ErrorInfo errorInfo) {_339 handler.onError(errorInfo);_339 }_339 });_339 }_339_339 public void joinOrCreateGeneralChannelWithCompletion(final StatusListener listener) {_339 channelsObject.getChannel(defaultChannelUniqueName, new CallbackListener<Channel>() {_339 @Override_339 public void onSuccess(Channel channel) {_339 ChannelManager.this.generalChannel = channel;_339 if (channel != null) {_339 joinGeneralChannelWithCompletion(listener);_339 } else {_339 createGeneralChannelWithCompletion(listener);_339 }_339 }_339 });_339 }_339_339 private void joinGeneralChannelWithCompletion(final StatusListener listener) {_339 if (generalChannel.getStatus() == Channel.ChannelStatus.JOINED) {_339 listener.onSuccess();_339 return;_339 }_339 this.generalChannel.join(new StatusListener() {_339 @Override_339 public void onSuccess() {_339 listener.onSuccess();_339 }_339_339 @Override_339 public void onError(ErrorInfo errorInfo) {_339 listener.onError(errorInfo);_339 }_339 });_339 }_339_339 private void createGeneralChannelWithCompletion(final StatusListener listener) {_339 this.channelsObject_339 .channelBuilder()_339 .withFriendlyName(defaultChannelName)_339 .withUniqueName(defaultChannelUniqueName)_339 .withType(ChannelType.PUBLIC)_339 .build(new CallbackListener<Channel>() {_339 @Override_339 public void onSuccess(final Channel channel) {_339 ChannelManager.this.generalChannel = channel;_339 ChannelManager.this.channels.add(channel);_339 joinGeneralChannelWithCompletion(listener);_339 }_339_339 @Override_339 public void onError(ErrorInfo errorInfo) {_339 listener.onError(errorInfo);_339 }_339 });_339 }_339_339 public void setChannelListener(ChatClientListener listener) {_339 this.listener = listener;_339 }_339_339 private String getStringResource(int id) {_339 Resources resources = TwilioChatApplication.get().getResources();_339 return resources.getString(id);_339 }_339_339 @Override_339 public void onChannelAdded(Channel channel) {_339 if (listener != null) {_339 listener.onChannelAdded(channel);_339 }_339 }_339_339 @Override_339 public void onChannelUpdated(Channel channel, Channel.UpdateReason updateReason) {_339 if (listener != null) {_339 listener.onChannelUpdated(channel, updateReason);_339 }_339 }_339_339 @Override_339 public void onChannelDeleted(Channel channel) {_339 if (listener != null) {_339 listener.onChannelDeleted(channel);_339 }_339 }_339_339 @Override_339 public void onChannelSynchronizationChange(Channel channel) {_339 if (listener != null) {_339 listener.onChannelSynchronizationChange(channel);_339 }_339 }_339_339 @Override_339 public void onError(ErrorInfo errorInfo) {_339 if (listener != null) {_339 listener.onError(errorInfo);_339 }_339 }_339_339 @Override_339 public void onClientSynchronization(ChatClient.SynchronizationStatus synchronizationStatus) {_339_339 }_339_339 @Override_339 public void onChannelJoined(Channel channel) {_339_339 }_339_339 @Override_339 public void onChannelInvited(Channel channel) {_339_339 }_339_339 @Override_339 public void onUserUpdated(User user, User.UpdateReason updateReason) {_339 if (listener != null) {_339 listener.onUserUpdated(user, updateReason);_339 }_339 }_339_339 @Override_339 public void onUserSubscribed(User user) {_339_339 }_339_339 @Override_339 public void onUserUnsubscribed(User user) {_339_339 }_339_339 @Override_339 public void onNewMessageNotification(String s, String s1, long l) {_339_339 }_339_339 @Override_339 public void onAddedToChannelNotification(String s) {_339_339 }_339_339 @Override_339 public void onInvitedToChannelNotification(String s) {_339_339 }_339_339 @Override_339 public void onRemovedFromChannelNotification(String s) {_339_339 }_339_339 @Override_339 public void onNotificationSubscribed() {_339_339 }_339_339 @Override_339 public void onNotificationFailed(ErrorInfo errorInfo) {_339_339 }_339_339 @Override_339 public void onConnectionStateChange(ChatClient.ConnectionState connectionState) {_339_339 }_339_339 @Override_339 public void onTokenExpired() {_339 refreshAccessToken();_339 }_339_339 @Override_339 public void onTokenAboutToExpire() {_339 refreshAccessToken();_339 }_339_339 private void refreshAccessToken() {_339 AccessTokenFetcher accessTokenFetcher = chatClientManager.getAccessTokenFetcher();_339 accessTokenFetcher.fetch(new TaskCompletionListener<String, String>() {_339 @Override_339 public void onSuccess(String token) {_339 ChannelManager.this.chatClientManager.getChatClient().updateToken(token, new StatusListener() {_339 @Override_339 public void onSuccess() {_339 Log.d(TwilioChatApplication.TAG, "Successfully updated access token.");_339 }_339 });_339 }_339_339 @Override_339 public void onError(String message) {_339 Log.e(TwilioChatApplication.TAG,"Error trying to fetch token: " + message);_339 }_339 });_339 }_339_339 private Handler setupListenerHandler() {_339 Looper looper;_339 Handler handler;_339 if ((looper = Looper.myLooper()) != null) {_339 handler = new Handler(looper);_339 } else if ((looper = Looper.getMainLooper()) != null) {_339 handler = new Handler(looper);_339 } else {_339 throw new IllegalArgumentException("Channel Listener must have a Looper.");_339 }_339 return handler;_339 }_339}
That's it! We've built an Android application with the Twilio Chat SDK. Now you are more than prepared to set up your own chat application.
If you're a Java developer working with Twilio, you might want to check out these other tutorials:
Two-Factor Authentication with Authy
Learn to implement two-factor authentication (2FA) in your web app with Twilio-powered Authy. 2FA helps further secure your users' data by validating more than just a password.
Thanks for checking out this tutorial! If you have any feedback to share with us, we'd love to hear it. Tweet @twilio to let us know what you think.