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
1package com.twilio.twiliochat.chat.accesstoken;23import android.content.Context;4import android.content.res.Resources;5import android.util.Log;67import com.android.volley.Request;8import com.android.volley.Response;9import com.android.volley.VolleyError;10import com.android.volley.toolbox.JsonObjectRequest;11import com.twilio.twiliochat.R;12import com.twilio.twiliochat.application.SessionManager;13import com.twilio.twiliochat.application.TwilioChatApplication;14import com.twilio.twiliochat.chat.listeners.TaskCompletionListener;1516import org.json.JSONException;17import org.json.JSONObject;1819import java.util.HashMap;20import java.util.Map;2122public class AccessTokenFetcher {2324private Context context;2526public AccessTokenFetcher(Context context) {27this.context = context;28}2930public void fetch(final TaskCompletionListener<String, String> listener) {31JSONObject obj = new JSONObject(getTokenRequestParams(context));32String identity = SessionManager.getInstance().getUsername();33String requestUrl = getStringResource(R.string.token_url) + "?identity=" + identity;34Log.d(TwilioChatApplication.TAG, "Requesting access token from: " + requestUrl);3536JsonObjectRequest jsonObjReq =37new JsonObjectRequest(Request.Method.GET, requestUrl, obj, new Response.Listener<JSONObject>() {3839@Override40public void onResponse(JSONObject response) {41String token = null;42try {43token = response.getString("token");44} catch (JSONException e) {45Log.e(TwilioChatApplication.TAG, e.getLocalizedMessage(), e);46listener.onError("Failed to parse token JSON response");47}48listener.onSuccess(token);49}50}, new Response.ErrorListener() {5152@Override53public void onErrorResponse(VolleyError error) {54Log.e(TwilioChatApplication.TAG, error.getLocalizedMessage(), error);55listener.onError("Failed to fetch token");56}57});58jsonObjReq.setShouldCache(false);59TokenRequest.getInstance().addToRequestQueue(jsonObjReq);60}6162private Map<String, String> getTokenRequestParams(Context context) {63Map<String, String> params = new HashMap<>();64params.put("identity", SessionManager.getInstance().getUsername());65return params;66}6768private String getStringResource(int id) {69Resources resources = TwilioChatApplication.get().getResources();70return resources.getString(id);71}7273}74
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
1package com.twilio.twiliochat.chat;23import android.content.Context;45import com.twilio.chat.CallbackListener;6import com.twilio.chat.ChatClient;7import com.twilio.chat.ErrorInfo;8import com.twilio.twiliochat.chat.listeners.TaskCompletionListener;910public class ChatClientBuilder extends CallbackListener<ChatClient> {1112private Context context;13private TaskCompletionListener<ChatClient, String> buildListener;1415public ChatClientBuilder(Context context) {16this.context = context;17}1819public void build(String token, final TaskCompletionListener<ChatClient, String> listener) {20ChatClient.Properties props =21new ChatClient.Properties.Builder()22.setRegion("us1")23.createProperties();2425this.buildListener = listener;26ChatClient.create(context.getApplicationContext(),27token,28props,29this);30}313233@Override34public void onSuccess(ChatClient chatClient) {35this.buildListener.onSuccess(chatClient);36}3738@Override39public void onError(ErrorInfo errorInfo) {40this.buildListener.onError(errorInfo.getMessage());41}42}43
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
1package com.twilio.twiliochat.chat.channels;23import android.content.res.Resources;4import android.os.Handler;5import android.os.Looper;6import android.util.Log;78import com.twilio.chat.CallbackListener;9import com.twilio.chat.Channel;10import com.twilio.chat.Channel.ChannelType;11import com.twilio.chat.ChannelDescriptor;12import com.twilio.chat.Channels;13import com.twilio.chat.ChatClient;14import com.twilio.chat.ChatClientListener;15import com.twilio.chat.ErrorInfo;16import com.twilio.chat.Paginator;17import com.twilio.chat.StatusListener;18import com.twilio.chat.User;19import com.twilio.twiliochat.R;20import com.twilio.twiliochat.application.TwilioChatApplication;21import com.twilio.twiliochat.chat.ChatClientManager;22import com.twilio.twiliochat.chat.accesstoken.AccessTokenFetcher;23import com.twilio.twiliochat.chat.listeners.TaskCompletionListener;2425import java.util.ArrayList;26import java.util.Collections;27import java.util.List;2829public class ChannelManager implements ChatClientListener {30private static ChannelManager sharedManager = new ChannelManager();31public Channel generalChannel;32private ChatClientManager chatClientManager;33private ChannelExtractor channelExtractor;34private List<Channel> channels;35private Channels channelsObject;36private ChatClientListener listener;37private String defaultChannelName;38private String defaultChannelUniqueName;39private Handler handler;40private Boolean isRefreshingChannels = false;4142private ChannelManager() {43this.chatClientManager = TwilioChatApplication.get().getChatClientManager();44this.channelExtractor = new ChannelExtractor();45this.listener = this;46defaultChannelName = getStringResource(R.string.default_channel_name);47defaultChannelUniqueName = getStringResource(R.string.default_channel_unique_name);48handler = setupListenerHandler();49}5051public static ChannelManager getInstance() {52return sharedManager;53}5455public List<Channel> getChannels() {56return channels;57}5859public String getDefaultChannelName() {60return this.defaultChannelName;61}6263public void leaveChannelWithHandler(Channel channel, StatusListener handler) {64channel.leave(handler);65}6667public void deleteChannelWithHandler(Channel channel, StatusListener handler) {68channel.destroy(handler);69}7071public void populateChannels(final LoadChannelListener listener) {72if (this.chatClientManager == null || this.isRefreshingChannels) {73return;74}75this.isRefreshingChannels = true;7677handler.post(new Runnable() {78@Override79public void run() {80channelsObject = chatClientManager.getChatClient().getChannels();8182channelsObject.getPublicChannelsList(new CallbackListener<Paginator<ChannelDescriptor>>() {83@Override84public void onSuccess(Paginator<ChannelDescriptor> channelDescriptorPaginator) {85extractChannelsFromPaginatorAndPopulate(channelDescriptorPaginator, listener);86}87});8889}90});91}9293private void extractChannelsFromPaginatorAndPopulate(final Paginator<ChannelDescriptor> channelsPaginator,94final LoadChannelListener listener) {95channels = new ArrayList<>();96ChannelManager.this.channels.clear();97channelExtractor.extractAndSortFromChannelDescriptor(channelsPaginator,98new TaskCompletionListener<List<Channel>, String>() {99@Override100public void onSuccess(List<Channel> channels) {101ChannelManager.this.channels.addAll(channels);102Collections.sort(ChannelManager.this.channels, new CustomChannelComparator());103ChannelManager.this.isRefreshingChannels = false;104chatClientManager.addClientListener(ChannelManager.this);105listener.onChannelsFinishedLoading(ChannelManager.this.channels);106}107108@Override109public void onError(String errorText) {110Log.e(TwilioChatApplication.TAG,"Error populating channels: " + errorText);111}112});113}114115public void createChannelWithName(String name, final StatusListener handler) {116this.channelsObject117.channelBuilder()118.withFriendlyName(name)119.withType(ChannelType.PUBLIC)120.build(new CallbackListener<Channel>() {121@Override122public void onSuccess(final Channel newChannel) {123handler.onSuccess();124}125126@Override127public void onError(ErrorInfo errorInfo) {128handler.onError(errorInfo);129}130});131}132133public void joinOrCreateGeneralChannelWithCompletion(final StatusListener listener) {134channelsObject.getChannel(defaultChannelUniqueName, new CallbackListener<Channel>() {135@Override136public void onSuccess(Channel channel) {137ChannelManager.this.generalChannel = channel;138if (channel != null) {139joinGeneralChannelWithCompletion(listener);140} else {141createGeneralChannelWithCompletion(listener);142}143}144});145}146147private void joinGeneralChannelWithCompletion(final StatusListener listener) {148if (generalChannel.getStatus() == Channel.ChannelStatus.JOINED) {149listener.onSuccess();150return;151}152this.generalChannel.join(new StatusListener() {153@Override154public void onSuccess() {155listener.onSuccess();156}157158@Override159public void onError(ErrorInfo errorInfo) {160listener.onError(errorInfo);161}162});163}164165private void createGeneralChannelWithCompletion(final StatusListener listener) {166this.channelsObject167.channelBuilder()168.withFriendlyName(defaultChannelName)169.withUniqueName(defaultChannelUniqueName)170.withType(ChannelType.PUBLIC)171.build(new CallbackListener<Channel>() {172@Override173public void onSuccess(final Channel channel) {174ChannelManager.this.generalChannel = channel;175ChannelManager.this.channels.add(channel);176joinGeneralChannelWithCompletion(listener);177}178179@Override180public void onError(ErrorInfo errorInfo) {181listener.onError(errorInfo);182}183});184}185186public void setChannelListener(ChatClientListener listener) {187this.listener = listener;188}189190private String getStringResource(int id) {191Resources resources = TwilioChatApplication.get().getResources();192return resources.getString(id);193}194195@Override196public void onChannelAdded(Channel channel) {197if (listener != null) {198listener.onChannelAdded(channel);199}200}201202@Override203public void onChannelUpdated(Channel channel, Channel.UpdateReason updateReason) {204if (listener != null) {205listener.onChannelUpdated(channel, updateReason);206}207}208209@Override210public void onChannelDeleted(Channel channel) {211if (listener != null) {212listener.onChannelDeleted(channel);213}214}215216@Override217public void onChannelSynchronizationChange(Channel channel) {218if (listener != null) {219listener.onChannelSynchronizationChange(channel);220}221}222223@Override224public void onError(ErrorInfo errorInfo) {225if (listener != null) {226listener.onError(errorInfo);227}228}229230@Override231public void onClientSynchronization(ChatClient.SynchronizationStatus synchronizationStatus) {232233}234235@Override236public void onChannelJoined(Channel channel) {237238}239240@Override241public void onChannelInvited(Channel channel) {242243}244245@Override246public void onUserUpdated(User user, User.UpdateReason updateReason) {247if (listener != null) {248listener.onUserUpdated(user, updateReason);249}250}251252@Override253public void onUserSubscribed(User user) {254255}256257@Override258public void onUserUnsubscribed(User user) {259260}261262@Override263public void onNewMessageNotification(String s, String s1, long l) {264265}266267@Override268public void onAddedToChannelNotification(String s) {269270}271272@Override273public void onInvitedToChannelNotification(String s) {274275}276277@Override278public void onRemovedFromChannelNotification(String s) {279280}281282@Override283public void onNotificationSubscribed() {284285}286287@Override288public void onNotificationFailed(ErrorInfo errorInfo) {289290}291292@Override293public void onConnectionStateChange(ChatClient.ConnectionState connectionState) {294295}296297@Override298public void onTokenExpired() {299refreshAccessToken();300}301302@Override303public void onTokenAboutToExpire() {304refreshAccessToken();305}306307private void refreshAccessToken() {308AccessTokenFetcher accessTokenFetcher = chatClientManager.getAccessTokenFetcher();309accessTokenFetcher.fetch(new TaskCompletionListener<String, String>() {310@Override311public void onSuccess(String token) {312ChannelManager.this.chatClientManager.getChatClient().updateToken(token, new StatusListener() {313@Override314public void onSuccess() {315Log.d(TwilioChatApplication.TAG, "Successfully updated access token.");316}317});318}319320@Override321public void onError(String message) {322Log.e(TwilioChatApplication.TAG,"Error trying to fetch token: " + message);323}324});325}326327private Handler setupListenerHandler() {328Looper looper;329Handler handler;330if ((looper = Looper.myLooper()) != null) {331handler = new Handler(looper);332} else if ((looper = Looper.getMainLooper()) != null) {333handler = new Handler(looper);334} else {335throw new IllegalArgumentException("Channel Listener must have a Looper.");336}337return handler;338}339}340
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
1package com.twilio.twiliochat.chat;23import android.app.Activity;4import android.app.ProgressDialog;5import android.content.Context;6import android.content.DialogInterface;7import android.content.Intent;8import android.content.res.Resources;9import android.os.Bundle;10import android.os.Handler;11import android.util.Log;12import android.view.Menu;13import android.view.MenuItem;14import android.view.View;15import android.widget.AdapterView;16import android.widget.Button;17import android.widget.ListView;18import android.widget.TextView;1920import androidx.appcompat.app.ActionBarDrawerToggle;21import androidx.appcompat.app.AppCompatActivity;22import androidx.appcompat.widget.Toolbar;23import androidx.core.view.GravityCompat;24import androidx.drawerlayout.widget.DrawerLayout;25import androidx.fragment.app.FragmentTransaction;26import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;2728import com.twilio.chat.Channel;29import com.twilio.chat.ChatClient;30import com.twilio.chat.ChatClientListener;31import com.twilio.chat.ErrorInfo;32import com.twilio.chat.StatusListener;33import com.twilio.chat.User;34import com.twilio.twiliochat.R;35import com.twilio.twiliochat.application.AlertDialogHandler;36import com.twilio.twiliochat.application.SessionManager;37import com.twilio.twiliochat.application.TwilioChatApplication;38import com.twilio.twiliochat.chat.channels.ChannelAdapter;39import com.twilio.twiliochat.chat.channels.ChannelManager;40import com.twilio.twiliochat.chat.channels.LoadChannelListener;41import com.twilio.twiliochat.chat.listeners.InputOnClickListener;42import com.twilio.twiliochat.chat.listeners.TaskCompletionListener;43import com.twilio.twiliochat.landing.LoginActivity;4445import java.util.List;4647public class MainChatActivity extends AppCompatActivity implements ChatClientListener {48private Context context;49private Activity mainActivity;50private Button logoutButton;51private Button addChannelButton;52private TextView usernameTextView;53private ChatClientManager chatClientManager;54private ListView channelsListView;55private ChannelAdapter channelAdapter;56private ChannelManager channelManager;57private MainChatFragment chatFragment;58private DrawerLayout drawer;59private ProgressDialog progressDialog;60private MenuItem leaveChannelMenuItem;61private MenuItem deleteChannelMenuItem;62private SwipeRefreshLayout refreshLayout;6364@Override65protected void onDestroy() {66super.onDestroy();67new Handler().post(new Runnable() {68@Override69public void run() {70chatClientManager.shutdown();71TwilioChatApplication.get().getChatClientManager().setChatClient(null);72}73});74}7576@Override77protected void onCreate(Bundle savedInstanceState) {78super.onCreate(savedInstanceState);79setContentView(R.layout.activity_main_chat);80Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);81setSupportActionBar(toolbar);8283drawer = (DrawerLayout) findViewById(R.id.drawer_layout);84ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(this, drawer, toolbar,85R.string.navigation_drawer_open, R.string.navigation_drawer_close);86drawer.setDrawerListener(toggle);87toggle.syncState();8889refreshLayout = (SwipeRefreshLayout) findViewById(R.id.refreshLayout);9091FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();9293chatFragment = new MainChatFragment();94fragmentTransaction.add(R.id.fragment_container, chatFragment);95fragmentTransaction.commit();9697context = this;98mainActivity = this;99logoutButton = (Button) findViewById(R.id.buttonLogout);100addChannelButton = (Button) findViewById(R.id.buttonAddChannel);101usernameTextView = (TextView) findViewById(R.id.textViewUsername);102channelsListView = (ListView) findViewById(R.id.listViewChannels);103104channelManager = ChannelManager.getInstance();105setUsernameTextView();106107setUpListeners();108checkTwilioClient();109}110111private void setUpListeners() {112logoutButton.setOnClickListener(new View.OnClickListener() {113@Override114public void onClick(View v) {115promptLogout();116}117});118addChannelButton.setOnClickListener(new View.OnClickListener() {119@Override120public void onClick(View v) {121showAddChannelDialog();122}123});124refreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {125@Override126public void onRefresh() {127refreshLayout.setRefreshing(true);128refreshChannels();129}130});131channelsListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {132@Override133public void onItemClick(AdapterView<?> parent, View view, int position, long id) {134setChannel(position);135}136});137}138139@Override140public void onBackPressed() {141if (drawer.isDrawerOpen(GravityCompat.START)) {142drawer.closeDrawer(GravityCompat.START);143} else {144super.onBackPressed();145}146}147148@Override149public boolean onCreateOptionsMenu(Menu menu) {150getMenuInflater().inflate(R.menu.main_chat, menu);151this.leaveChannelMenuItem = menu.findItem(R.id.action_leave_channel);152this.leaveChannelMenuItem.setVisible(false);153this.deleteChannelMenuItem = menu.findItem(R.id.action_delete_channel);154this.deleteChannelMenuItem.setVisible(false);155return true;156}157158@Override159public boolean onOptionsItemSelected(MenuItem item) {160int id = item.getItemId();161162if (id == R.id.action_leave_channel) {163leaveCurrentChannel();164return true;165}166if (id == R.id.action_delete_channel) {167promptChannelDeletion();168}169170return super.onOptionsItemSelected(item);171}172173private String getStringResource(int id) {174Resources resources = getResources();175return resources.getString(id);176}177178private void refreshChannels() {179channelManager.populateChannels(new LoadChannelListener() {180@Override181public void onChannelsFinishedLoading(final List<Channel> channels) {182runOnUiThread(new Runnable() {183@Override184public void run() {185channelAdapter.setChannels(channels);186refreshLayout.setRefreshing(false);187}188});189}190});191}192193private void populateChannels() {194channelManager.setChannelListener(this);195channelManager.populateChannels(new LoadChannelListener() {196@Override197public void onChannelsFinishedLoading(List<Channel> channels) {198channelAdapter = new ChannelAdapter(mainActivity, channels);199channelsListView.setAdapter(channelAdapter);200MainChatActivity.this.channelManager201.joinOrCreateGeneralChannelWithCompletion(new StatusListener() {202@Override203public void onSuccess() {204runOnUiThread(new Runnable() {205@Override206public void run() {207channelAdapter.notifyDataSetChanged();208stopActivityIndicator();209setChannel(0);210}211});212}213214@Override215public void onError(ErrorInfo errorInfo) {216showAlertWithMessage(getStringResource(R.string.generic_error) + " - " + errorInfo.getMessage());217}218});219}220});221}222223private void setChannel(final int position) {224List<Channel> channels = channelManager.getChannels();225if (channels == null) {226return;227}228final Channel currentChannel = chatFragment.getCurrentChannel();229final Channel selectedChannel = channels.get(position);230if (currentChannel != null && currentChannel.getSid().contentEquals(selectedChannel.getSid())) {231drawer.closeDrawer(GravityCompat.START);232return;233}234hideMenuItems(position);235if (selectedChannel != null) {236showActivityIndicator("Joining " + selectedChannel.getFriendlyName() + " channel");237if (currentChannel != null && currentChannel.getStatus() == Channel.ChannelStatus.JOINED) {238this.channelManager.leaveChannelWithHandler(currentChannel, new StatusListener() {239@Override240public void onSuccess() {241joinChannel(selectedChannel);242}243244@Override245public void onError(ErrorInfo errorInfo) {246stopActivityIndicator();247}248});249return;250}251joinChannel(selectedChannel);252stopActivityIndicator();253} else {254stopActivityIndicator();255showAlertWithMessage(getStringResource(R.string.generic_error));256Log.e(TwilioChatApplication.TAG,"Selected channel out of range");257}258}259260private void joinChannel(final Channel selectedChannel) {261runOnUiThread(new Runnable() {262@Override263public void run() {264chatFragment.setCurrentChannel(selectedChannel, new StatusListener() {265@Override266public void onSuccess() {267MainChatActivity.this.stopActivityIndicator();268}269270@Override271public void onError(ErrorInfo errorInfo) {272}273});274setTitle(selectedChannel.getFriendlyName());275drawer.closeDrawer(GravityCompat.START);276}277});278}279280private void hideMenuItems(final int position) {281runOnUiThread(new Runnable() {282@Override283public void run() {284MainChatActivity.this.leaveChannelMenuItem.setVisible(position != 0);285MainChatActivity.this.deleteChannelMenuItem.setVisible(position != 0);286}287});288}289290private void showAddChannelDialog() {291String message = getStringResource(R.string.new_channel_prompt);292AlertDialogHandler.displayInputDialog(message, context, new InputOnClickListener() {293@Override294public void onClick(String input) {295if (input.length() == 0) {296showAlertWithMessage(getStringResource(R.string.channel_name_required_message));297return;298}299createChannelWithName(input);300}301});302}303304private void createChannelWithName(String name) {305name = name.trim();306if (name.toLowerCase()307.contentEquals(this.channelManager.getDefaultChannelName().toLowerCase())) {308showAlertWithMessage(getStringResource(R.string.channel_name_equals_default_name));309return;310}311this.channelManager.createChannelWithName(name, new StatusListener() {312@Override313public void onSuccess() {314refreshChannels();315}316317@Override318public void onError(ErrorInfo errorInfo) {319showAlertWithMessage(getStringResource(R.string.generic_error) + " - " + errorInfo.getMessage());320}321});322}323324private void promptChannelDeletion() {325String message = getStringResource(R.string.channel_delete_prompt_message);326AlertDialogHandler.displayCancellableAlertWithHandler(message, context,327new DialogInterface.OnClickListener() {328@Override329public void onClick(DialogInterface dialog, int which) {330deleteCurrentChannel();331}332});333}334335private void deleteCurrentChannel() {336Channel currentChannel = chatFragment.getCurrentChannel();337channelManager.deleteChannelWithHandler(currentChannel, new StatusListener() {338@Override339public void onSuccess() {340}341342@Override343public void onError(ErrorInfo errorInfo) {344showAlertWithMessage(getStringResource(R.string.message_deletion_forbidden));345}346});347}348349private void leaveCurrentChannel() {350final Channel currentChannel = chatFragment.getCurrentChannel();351if (currentChannel.getStatus() == Channel.ChannelStatus.NOT_PARTICIPATING) {352setChannel(0);353return;354}355channelManager.leaveChannelWithHandler(currentChannel, new StatusListener() {356@Override357public void onSuccess() {358setChannel(0);359}360361@Override362public void onError(ErrorInfo errorInfo) {363stopActivityIndicator();364}365});366}367368private void checkTwilioClient() {369showActivityIndicator(getStringResource(R.string.loading_channels_message));370chatClientManager = TwilioChatApplication.get().getChatClientManager();371if (chatClientManager.getChatClient() == null) {372initializeClient();373} else {374populateChannels();375}376}377378private void initializeClient() {379chatClientManager.connectClient(new TaskCompletionListener<Void, String>() {380@Override381public void onSuccess(Void aVoid) {382populateChannels();383}384385@Override386public void onError(String errorMessage) {387stopActivityIndicator();388showAlertWithMessage("Client connection error: " + errorMessage);389}390});391}392393private void promptLogout() {394final String message = getStringResource(R.string.logout_prompt_message);395runOnUiThread(new Runnable() {396@Override397public void run() {398AlertDialogHandler.displayCancellableAlertWithHandler(message, context,399new DialogInterface.OnClickListener() {400@Override401public void onClick(DialogInterface dialog, int which) {402SessionManager.getInstance().logoutUser();403showLoginActivity();404}405});406}407});408409}410411private void showLoginActivity() {412Intent launchIntent = new Intent();413launchIntent.setClass(getApplicationContext(), LoginActivity.class);414startActivity(launchIntent);415finish();416}417418private void setUsernameTextView() {419String username =420SessionManager.getInstance().getUserDetails().get(SessionManager.KEY_USERNAME);421usernameTextView.setText(username);422}423424private void stopActivityIndicator() {425runOnUiThread(new Runnable() {426@Override427public void run() {428if (progressDialog.isShowing()) {429progressDialog.dismiss();430}431}432});433}434435private void showActivityIndicator(final String message) {436runOnUiThread(new Runnable() {437@Override438public void run() {439progressDialog = new ProgressDialog(MainChatActivity.this.mainActivity);440progressDialog.setMessage(message);441progressDialog.show();442progressDialog.setCanceledOnTouchOutside(false);443progressDialog.setCancelable(false);444}445});446}447448private void showAlertWithMessage(final String message) {449runOnUiThread(new Runnable() {450@Override451public void run() {452AlertDialogHandler.displayAlertWithMessage(message, context);453}454});455}456457458459@Override460public void onChannelAdded(Channel channel) {461Log.d(TwilioChatApplication.TAG,"Channel Added");462refreshChannels();463}464465@Override466public void onChannelDeleted(final Channel channel) {467Log.d(TwilioChatApplication.TAG,"Channel Deleted");468Channel currentChannel = chatFragment.getCurrentChannel();469if (channel.getSid().contentEquals(currentChannel.getSid())) {470chatFragment.setCurrentChannel(null, null);471setChannel(0);472}473refreshChannels();474}475476@Override477public void onChannelInvited(Channel channel) {478479}480481@Override482public void onChannelSynchronizationChange(Channel channel) {483484}485486@Override487public void onError(ErrorInfo errorInfo) {488489}490491@Override492public void onClientSynchronization(ChatClient.SynchronizationStatus synchronizationStatus) {493494}495496@Override497public void onConnectionStateChange(ChatClient.ConnectionState connectionState) {498499}500501@Override502public void onTokenExpired() {503504}505506@Override507public void onTokenAboutToExpire() {508509}510511@Override512public void onChannelJoined(Channel channel) {513514}515516@Override517public void onChannelUpdated(Channel channel, Channel.UpdateReason updateReason) {518519}520521@Override522public void onUserUpdated(User user, User.UpdateReason updateReason) {523524}525526@Override527public void onUserSubscribed(User user) {528529}530531@Override532public void onUserUnsubscribed(User user) {533534}535536@Override537public void onNewMessageNotification(String s, String s1, long l) {538539}540541@Override542public void onAddedToChannelNotification(String s) {543544}545546@Override547public void onInvitedToChannelNotification(String s) {548549}550551@Override552public void onRemovedFromChannelNotification(String s) {553554}555556@Override557public void onNotificationSubscribed() {558559}560561@Override562public void onNotificationFailed(ErrorInfo errorInfo) {563564}565}566
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
1package com.twilio.twiliochat.chat.channels;23import android.content.res.Resources;4import android.os.Handler;5import android.os.Looper;6import android.util.Log;78import com.twilio.chat.CallbackListener;9import com.twilio.chat.Channel;10import com.twilio.chat.Channel.ChannelType;11import com.twilio.chat.ChannelDescriptor;12import com.twilio.chat.Channels;13import com.twilio.chat.ChatClient;14import com.twilio.chat.ChatClientListener;15import com.twilio.chat.ErrorInfo;16import com.twilio.chat.Paginator;17import com.twilio.chat.StatusListener;18import com.twilio.chat.User;19import com.twilio.twiliochat.R;20import com.twilio.twiliochat.application.TwilioChatApplication;21import com.twilio.twiliochat.chat.ChatClientManager;22import com.twilio.twiliochat.chat.accesstoken.AccessTokenFetcher;23import com.twilio.twiliochat.chat.listeners.TaskCompletionListener;2425import java.util.ArrayList;26import java.util.Collections;27import java.util.List;2829public class ChannelManager implements ChatClientListener {30private static ChannelManager sharedManager = new ChannelManager();31public Channel generalChannel;32private ChatClientManager chatClientManager;33private ChannelExtractor channelExtractor;34private List<Channel> channels;35private Channels channelsObject;36private ChatClientListener listener;37private String defaultChannelName;38private String defaultChannelUniqueName;39private Handler handler;40private Boolean isRefreshingChannels = false;4142private ChannelManager() {43this.chatClientManager = TwilioChatApplication.get().getChatClientManager();44this.channelExtractor = new ChannelExtractor();45this.listener = this;46defaultChannelName = getStringResource(R.string.default_channel_name);47defaultChannelUniqueName = getStringResource(R.string.default_channel_unique_name);48handler = setupListenerHandler();49}5051public static ChannelManager getInstance() {52return sharedManager;53}5455public List<Channel> getChannels() {56return channels;57}5859public String getDefaultChannelName() {60return this.defaultChannelName;61}6263public void leaveChannelWithHandler(Channel channel, StatusListener handler) {64channel.leave(handler);65}6667public void deleteChannelWithHandler(Channel channel, StatusListener handler) {68channel.destroy(handler);69}7071public void populateChannels(final LoadChannelListener listener) {72if (this.chatClientManager == null || this.isRefreshingChannels) {73return;74}75this.isRefreshingChannels = true;7677handler.post(new Runnable() {78@Override79public void run() {80channelsObject = chatClientManager.getChatClient().getChannels();8182channelsObject.getPublicChannelsList(new CallbackListener<Paginator<ChannelDescriptor>>() {83@Override84public void onSuccess(Paginator<ChannelDescriptor> channelDescriptorPaginator) {85extractChannelsFromPaginatorAndPopulate(channelDescriptorPaginator, listener);86}87});8889}90});91}9293private void extractChannelsFromPaginatorAndPopulate(final Paginator<ChannelDescriptor> channelsPaginator,94final LoadChannelListener listener) {95channels = new ArrayList<>();96ChannelManager.this.channels.clear();97channelExtractor.extractAndSortFromChannelDescriptor(channelsPaginator,98new TaskCompletionListener<List<Channel>, String>() {99@Override100public void onSuccess(List<Channel> channels) {101ChannelManager.this.channels.addAll(channels);102Collections.sort(ChannelManager.this.channels, new CustomChannelComparator());103ChannelManager.this.isRefreshingChannels = false;104chatClientManager.addClientListener(ChannelManager.this);105listener.onChannelsFinishedLoading(ChannelManager.this.channels);106}107108@Override109public void onError(String errorText) {110Log.e(TwilioChatApplication.TAG,"Error populating channels: " + errorText);111}112});113}114115public void createChannelWithName(String name, final StatusListener handler) {116this.channelsObject117.channelBuilder()118.withFriendlyName(name)119.withType(ChannelType.PUBLIC)120.build(new CallbackListener<Channel>() {121@Override122public void onSuccess(final Channel newChannel) {123handler.onSuccess();124}125126@Override127public void onError(ErrorInfo errorInfo) {128handler.onError(errorInfo);129}130});131}132133public void joinOrCreateGeneralChannelWithCompletion(final StatusListener listener) {134channelsObject.getChannel(defaultChannelUniqueName, new CallbackListener<Channel>() {135@Override136public void onSuccess(Channel channel) {137ChannelManager.this.generalChannel = channel;138if (channel != null) {139joinGeneralChannelWithCompletion(listener);140} else {141createGeneralChannelWithCompletion(listener);142}143}144});145}146147private void joinGeneralChannelWithCompletion(final StatusListener listener) {148if (generalChannel.getStatus() == Channel.ChannelStatus.JOINED) {149listener.onSuccess();150return;151}152this.generalChannel.join(new StatusListener() {153@Override154public void onSuccess() {155listener.onSuccess();156}157158@Override159public void onError(ErrorInfo errorInfo) {160listener.onError(errorInfo);161}162});163}164165private void createGeneralChannelWithCompletion(final StatusListener listener) {166this.channelsObject167.channelBuilder()168.withFriendlyName(defaultChannelName)169.withUniqueName(defaultChannelUniqueName)170.withType(ChannelType.PUBLIC)171.build(new CallbackListener<Channel>() {172@Override173public void onSuccess(final Channel channel) {174ChannelManager.this.generalChannel = channel;175ChannelManager.this.channels.add(channel);176joinGeneralChannelWithCompletion(listener);177}178179@Override180public void onError(ErrorInfo errorInfo) {181listener.onError(errorInfo);182}183});184}185186public void setChannelListener(ChatClientListener listener) {187this.listener = listener;188}189190private String getStringResource(int id) {191Resources resources = TwilioChatApplication.get().getResources();192return resources.getString(id);193}194195@Override196public void onChannelAdded(Channel channel) {197if (listener != null) {198listener.onChannelAdded(channel);199}200}201202@Override203public void onChannelUpdated(Channel channel, Channel.UpdateReason updateReason) {204if (listener != null) {205listener.onChannelUpdated(channel, updateReason);206}207}208209@Override210public void onChannelDeleted(Channel channel) {211if (listener != null) {212listener.onChannelDeleted(channel);213}214}215216@Override217public void onChannelSynchronizationChange(Channel channel) {218if (listener != null) {219listener.onChannelSynchronizationChange(channel);220}221}222223@Override224public void onError(ErrorInfo errorInfo) {225if (listener != null) {226listener.onError(errorInfo);227}228}229230@Override231public void onClientSynchronization(ChatClient.SynchronizationStatus synchronizationStatus) {232233}234235@Override236public void onChannelJoined(Channel channel) {237238}239240@Override241public void onChannelInvited(Channel channel) {242243}244245@Override246public void onUserUpdated(User user, User.UpdateReason updateReason) {247if (listener != null) {248listener.onUserUpdated(user, updateReason);249}250}251252@Override253public void onUserSubscribed(User user) {254255}256257@Override258public void onUserUnsubscribed(User user) {259260}261262@Override263public void onNewMessageNotification(String s, String s1, long l) {264265}266267@Override268public void onAddedToChannelNotification(String s) {269270}271272@Override273public void onInvitedToChannelNotification(String s) {274275}276277@Override278public void onRemovedFromChannelNotification(String s) {279280}281282@Override283public void onNotificationSubscribed() {284285}286287@Override288public void onNotificationFailed(ErrorInfo errorInfo) {289290}291292@Override293public void onConnectionStateChange(ChatClient.ConnectionState connectionState) {294295}296297@Override298public void onTokenExpired() {299refreshAccessToken();300}301302@Override303public void onTokenAboutToExpire() {304refreshAccessToken();305}306307private void refreshAccessToken() {308AccessTokenFetcher accessTokenFetcher = chatClientManager.getAccessTokenFetcher();309accessTokenFetcher.fetch(new TaskCompletionListener<String, String>() {310@Override311public void onSuccess(String token) {312ChannelManager.this.chatClientManager.getChatClient().updateToken(token, new StatusListener() {313@Override314public void onSuccess() {315Log.d(TwilioChatApplication.TAG, "Successfully updated access token.");316}317});318}319320@Override321public void onError(String message) {322Log.e(TwilioChatApplication.TAG,"Error trying to fetch token: " + message);323}324});325}326327private Handler setupListenerHandler() {328Looper looper;329Handler handler;330if ((looper = Looper.myLooper()) != null) {331handler = new Handler(looper);332} else if ((looper = Looper.getMainLooper()) != null) {333handler = new Handler(looper);334} else {335throw new IllegalArgumentException("Channel Listener must have a Looper.");336}337return handler;338}339}340
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
1package com.twilio.twiliochat.chat;23import android.app.Activity;4import android.content.Context;5import android.os.Bundle;6import androidx.fragment.app.Fragment;7import android.view.KeyEvent;8import android.view.LayoutInflater;9import android.view.View;10import android.view.ViewGroup;11import android.widget.Button;12import android.widget.EditText;13import android.widget.ListView;14import android.widget.TextView;1516import com.twilio.chat.CallbackListener;17import com.twilio.chat.Channel;18import com.twilio.chat.ChannelListener;19import com.twilio.chat.ErrorInfo;20import com.twilio.chat.Member;21import com.twilio.chat.Message;22import com.twilio.chat.Messages;23import com.twilio.chat.StatusListener;24import com.twilio.twiliochat.R;25import com.twilio.twiliochat.chat.messages.JoinedStatusMessage;26import com.twilio.twiliochat.chat.messages.LeftStatusMessage;27import com.twilio.twiliochat.chat.messages.MessageAdapter;28import com.twilio.twiliochat.chat.messages.StatusMessage;2930import java.util.List;3132public class MainChatFragment extends Fragment implements ChannelListener {33Context context;34Activity mainActivity;35Button sendButton;36ListView messagesListView;37EditText messageTextEdit;3839MessageAdapter messageAdapter;40Channel currentChannel;41Messages messagesObject;4243public MainChatFragment() {44}4546public static MainChatFragment newInstance() {47MainChatFragment fragment = new MainChatFragment();48return fragment;49}5051@Override52public void onCreate(Bundle savedInstanceState) {53super.onCreate(savedInstanceState);54context = this.getActivity();55mainActivity = this.getActivity();56}5758@Override59public View onCreateView(LayoutInflater inflater, ViewGroup container,60Bundle savedInstanceState) {61View view = inflater.inflate(R.layout.fragment_main_chat, container, false);62sendButton = (Button) view.findViewById(R.id.buttonSend);63messagesListView = (ListView) view.findViewById(R.id.listViewMessages);64messageTextEdit = (EditText) view.findViewById(R.id.editTextMessage);6566messageAdapter = new MessageAdapter(mainActivity);67messagesListView.setAdapter(messageAdapter);68setUpListeners();69setMessageInputEnabled(false);7071return view;72}7374@Override75public void onAttach(Context context) {76super.onAttach(context);77}7879@Override80public void onDetach() {81super.onDetach();82}8384public Channel getCurrentChannel() {85return currentChannel;86}8788public void setCurrentChannel(Channel currentChannel, final StatusListener handler) {89if (currentChannel == null) {90this.currentChannel = null;91return;92}93if (!currentChannel.equals(this.currentChannel)) {94setMessageInputEnabled(false);95this.currentChannel = currentChannel;96this.currentChannel.addListener(this);97if (this.currentChannel.getStatus() == Channel.ChannelStatus.JOINED) {98loadMessages(handler);99} else {100this.currentChannel.join(new StatusListener() {101@Override102public void onSuccess() {103loadMessages(handler);104}105106@Override107public void onError(ErrorInfo errorInfo) {108}109});110}111}112}113114private void loadMessages(final StatusListener handler) {115this.messagesObject = this.currentChannel.getMessages();116117if (messagesObject != null) {118messagesObject.getLastMessages(100, new CallbackListener<List<Message>>() {119@Override120public void onSuccess(List<Message> messageList) {121messageAdapter.setMessages(messageList);122setMessageInputEnabled(true);123messageTextEdit.requestFocus();124handler.onSuccess();125}126});127}128}129130private void setUpListeners() {131sendButton.setOnClickListener(new View.OnClickListener() {132@Override133public void onClick(View v) {134sendMessage();135}136});137messageTextEdit.setOnEditorActionListener(new TextView.OnEditorActionListener() {138@Override139public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {140return false;141}142});143}144145private void sendMessage() {146String messageText = getTextInput();147if (messageText.length() == 0) {148return;149}150Message.Options messageOptions = Message.options().withBody(messageText);151this.messagesObject.sendMessage(messageOptions, null);152clearTextInput();153}154155private void setMessageInputEnabled(final boolean enabled) {156mainActivity.runOnUiThread(new Runnable() {157@Override158public void run() {159MainChatFragment.this.sendButton.setEnabled(enabled);160MainChatFragment.this.messageTextEdit.setEnabled(enabled);161}162});163}164165private String getTextInput() {166return messageTextEdit.getText().toString();167}168169private void clearTextInput() {170messageTextEdit.setText("");171}172173@Override174public void onMessageAdded(Message message) {175messageAdapter.addMessage(message);176}177178@Override179public void onMemberAdded(Member member) {180StatusMessage statusMessage = new JoinedStatusMessage(member.getIdentity());181this.messageAdapter.addStatusMessage(statusMessage);182}183184@Override185public void onMemberDeleted(Member member) {186StatusMessage statusMessage = new LeftStatusMessage(member.getIdentity());187this.messageAdapter.addStatusMessage(statusMessage);188}189190@Override191public void onMessageUpdated(Message message, Message.UpdateReason updateReason) {192}193194@Override195public void onMessageDeleted(Message message) {196}197198@Override199public void onMemberUpdated(Member member, Member.UpdateReason updateReason) {200}201202@Override203public void onTypingStarted(Channel channel, Member member) {204}205206@Override207public void onTypingEnded(Channel channel, Member member) {208}209210@Override211public void onSynchronizationChanged(Channel channel) {212}213214215}216
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
1package com.twilio.twiliochat.chat;23import android.app.Activity;4import android.content.Context;5import android.os.Bundle;6import androidx.fragment.app.Fragment;7import android.view.KeyEvent;8import android.view.LayoutInflater;9import android.view.View;10import android.view.ViewGroup;11import android.widget.Button;12import android.widget.EditText;13import android.widget.ListView;14import android.widget.TextView;1516import com.twilio.chat.CallbackListener;17import com.twilio.chat.Channel;18import com.twilio.chat.ChannelListener;19import com.twilio.chat.ErrorInfo;20import com.twilio.chat.Member;21import com.twilio.chat.Message;22import com.twilio.chat.Messages;23import com.twilio.chat.StatusListener;24import com.twilio.twiliochat.R;25import com.twilio.twiliochat.chat.messages.JoinedStatusMessage;26import com.twilio.twiliochat.chat.messages.LeftStatusMessage;27import com.twilio.twiliochat.chat.messages.MessageAdapter;28import com.twilio.twiliochat.chat.messages.StatusMessage;2930import java.util.List;3132public class MainChatFragment extends Fragment implements ChannelListener {33Context context;34Activity mainActivity;35Button sendButton;36ListView messagesListView;37EditText messageTextEdit;3839MessageAdapter messageAdapter;40Channel currentChannel;41Messages messagesObject;4243public MainChatFragment() {44}4546public static MainChatFragment newInstance() {47MainChatFragment fragment = new MainChatFragment();48return fragment;49}5051@Override52public void onCreate(Bundle savedInstanceState) {53super.onCreate(savedInstanceState);54context = this.getActivity();55mainActivity = this.getActivity();56}5758@Override59public View onCreateView(LayoutInflater inflater, ViewGroup container,60Bundle savedInstanceState) {61View view = inflater.inflate(R.layout.fragment_main_chat, container, false);62sendButton = (Button) view.findViewById(R.id.buttonSend);63messagesListView = (ListView) view.findViewById(R.id.listViewMessages);64messageTextEdit = (EditText) view.findViewById(R.id.editTextMessage);6566messageAdapter = new MessageAdapter(mainActivity);67messagesListView.setAdapter(messageAdapter);68setUpListeners();69setMessageInputEnabled(false);7071return view;72}7374@Override75public void onAttach(Context context) {76super.onAttach(context);77}7879@Override80public void onDetach() {81super.onDetach();82}8384public Channel getCurrentChannel() {85return currentChannel;86}8788public void setCurrentChannel(Channel currentChannel, final StatusListener handler) {89if (currentChannel == null) {90this.currentChannel = null;91return;92}93if (!currentChannel.equals(this.currentChannel)) {94setMessageInputEnabled(false);95this.currentChannel = currentChannel;96this.currentChannel.addListener(this);97if (this.currentChannel.getStatus() == Channel.ChannelStatus.JOINED) {98loadMessages(handler);99} else {100this.currentChannel.join(new StatusListener() {101@Override102public void onSuccess() {103loadMessages(handler);104}105106@Override107public void onError(ErrorInfo errorInfo) {108}109});110}111}112}113114private void loadMessages(final StatusListener handler) {115this.messagesObject = this.currentChannel.getMessages();116117if (messagesObject != null) {118messagesObject.getLastMessages(100, new CallbackListener<List<Message>>() {119@Override120public void onSuccess(List<Message> messageList) {121messageAdapter.setMessages(messageList);122setMessageInputEnabled(true);123messageTextEdit.requestFocus();124handler.onSuccess();125}126});127}128}129130private void setUpListeners() {131sendButton.setOnClickListener(new View.OnClickListener() {132@Override133public void onClick(View v) {134sendMessage();135}136});137messageTextEdit.setOnEditorActionListener(new TextView.OnEditorActionListener() {138@Override139public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {140return false;141}142});143}144145private void sendMessage() {146String messageText = getTextInput();147if (messageText.length() == 0) {148return;149}150Message.Options messageOptions = Message.options().withBody(messageText);151this.messagesObject.sendMessage(messageOptions, null);152clearTextInput();153}154155private void setMessageInputEnabled(final boolean enabled) {156mainActivity.runOnUiThread(new Runnable() {157@Override158public void run() {159MainChatFragment.this.sendButton.setEnabled(enabled);160MainChatFragment.this.messageTextEdit.setEnabled(enabled);161}162});163}164165private String getTextInput() {166return messageTextEdit.getText().toString();167}168169private void clearTextInput() {170messageTextEdit.setText("");171}172173@Override174public void onMessageAdded(Message message) {175messageAdapter.addMessage(message);176}177178@Override179public void onMemberAdded(Member member) {180StatusMessage statusMessage = new JoinedStatusMessage(member.getIdentity());181this.messageAdapter.addStatusMessage(statusMessage);182}183184@Override185public void onMemberDeleted(Member member) {186StatusMessage statusMessage = new LeftStatusMessage(member.getIdentity());187this.messageAdapter.addStatusMessage(statusMessage);188}189190@Override191public void onMessageUpdated(Message message, Message.UpdateReason updateReason) {192}193194@Override195public void onMessageDeleted(Message message) {196}197198@Override199public void onMemberUpdated(Member member, Member.UpdateReason updateReason) {200}201202@Override203public void onTypingStarted(Channel channel, Member member) {204}205206@Override207public void onTypingEnded(Channel channel, Member member) {208}209210@Override211public void onSynchronizationChanged(Channel channel) {212}213214215}216
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 involves 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
1package com.twilio.twiliochat.chat.channels;23import android.content.res.Resources;4import android.os.Handler;5import android.os.Looper;6import android.util.Log;78import com.twilio.chat.CallbackListener;9import com.twilio.chat.Channel;10import com.twilio.chat.Channel.ChannelType;11import com.twilio.chat.ChannelDescriptor;12import com.twilio.chat.Channels;13import com.twilio.chat.ChatClient;14import com.twilio.chat.ChatClientListener;15import com.twilio.chat.ErrorInfo;16import com.twilio.chat.Paginator;17import com.twilio.chat.StatusListener;18import com.twilio.chat.User;19import com.twilio.twiliochat.R;20import com.twilio.twiliochat.application.TwilioChatApplication;21import com.twilio.twiliochat.chat.ChatClientManager;22import com.twilio.twiliochat.chat.accesstoken.AccessTokenFetcher;23import com.twilio.twiliochat.chat.listeners.TaskCompletionListener;2425import java.util.ArrayList;26import java.util.Collections;27import java.util.List;2829public class ChannelManager implements ChatClientListener {30private static ChannelManager sharedManager = new ChannelManager();31public Channel generalChannel;32private ChatClientManager chatClientManager;33private ChannelExtractor channelExtractor;34private List<Channel> channels;35private Channels channelsObject;36private ChatClientListener listener;37private String defaultChannelName;38private String defaultChannelUniqueName;39private Handler handler;40private Boolean isRefreshingChannels = false;4142private ChannelManager() {43this.chatClientManager = TwilioChatApplication.get().getChatClientManager();44this.channelExtractor = new ChannelExtractor();45this.listener = this;46defaultChannelName = getStringResource(R.string.default_channel_name);47defaultChannelUniqueName = getStringResource(R.string.default_channel_unique_name);48handler = setupListenerHandler();49}5051public static ChannelManager getInstance() {52return sharedManager;53}5455public List<Channel> getChannels() {56return channels;57}5859public String getDefaultChannelName() {60return this.defaultChannelName;61}6263public void leaveChannelWithHandler(Channel channel, StatusListener handler) {64channel.leave(handler);65}6667public void deleteChannelWithHandler(Channel channel, StatusListener handler) {68channel.destroy(handler);69}7071public void populateChannels(final LoadChannelListener listener) {72if (this.chatClientManager == null || this.isRefreshingChannels) {73return;74}75this.isRefreshingChannels = true;7677handler.post(new Runnable() {78@Override79public void run() {80channelsObject = chatClientManager.getChatClient().getChannels();8182channelsObject.getPublicChannelsList(new CallbackListener<Paginator<ChannelDescriptor>>() {83@Override84public void onSuccess(Paginator<ChannelDescriptor> channelDescriptorPaginator) {85extractChannelsFromPaginatorAndPopulate(channelDescriptorPaginator, listener);86}87});8889}90});91}9293private void extractChannelsFromPaginatorAndPopulate(final Paginator<ChannelDescriptor> channelsPaginator,94final LoadChannelListener listener) {95channels = new ArrayList<>();96ChannelManager.this.channels.clear();97channelExtractor.extractAndSortFromChannelDescriptor(channelsPaginator,98new TaskCompletionListener<List<Channel>, String>() {99@Override100public void onSuccess(List<Channel> channels) {101ChannelManager.this.channels.addAll(channels);102Collections.sort(ChannelManager.this.channels, new CustomChannelComparator());103ChannelManager.this.isRefreshingChannels = false;104chatClientManager.addClientListener(ChannelManager.this);105listener.onChannelsFinishedLoading(ChannelManager.this.channels);106}107108@Override109public void onError(String errorText) {110Log.e(TwilioChatApplication.TAG,"Error populating channels: " + errorText);111}112});113}114115public void createChannelWithName(String name, final StatusListener handler) {116this.channelsObject117.channelBuilder()118.withFriendlyName(name)119.withType(ChannelType.PUBLIC)120.build(new CallbackListener<Channel>() {121@Override122public void onSuccess(final Channel newChannel) {123handler.onSuccess();124}125126@Override127public void onError(ErrorInfo errorInfo) {128handler.onError(errorInfo);129}130});131}132133public void joinOrCreateGeneralChannelWithCompletion(final StatusListener listener) {134channelsObject.getChannel(defaultChannelUniqueName, new CallbackListener<Channel>() {135@Override136public void onSuccess(Channel channel) {137ChannelManager.this.generalChannel = channel;138if (channel != null) {139joinGeneralChannelWithCompletion(listener);140} else {141createGeneralChannelWithCompletion(listener);142}143}144});145}146147private void joinGeneralChannelWithCompletion(final StatusListener listener) {148if (generalChannel.getStatus() == Channel.ChannelStatus.JOINED) {149listener.onSuccess();150return;151}152this.generalChannel.join(new StatusListener() {153@Override154public void onSuccess() {155listener.onSuccess();156}157158@Override159public void onError(ErrorInfo errorInfo) {160listener.onError(errorInfo);161}162});163}164165private void createGeneralChannelWithCompletion(final StatusListener listener) {166this.channelsObject167.channelBuilder()168.withFriendlyName(defaultChannelName)169.withUniqueName(defaultChannelUniqueName)170.withType(ChannelType.PUBLIC)171.build(new CallbackListener<Channel>() {172@Override173public void onSuccess(final Channel channel) {174ChannelManager.this.generalChannel = channel;175ChannelManager.this.channels.add(channel);176joinGeneralChannelWithCompletion(listener);177}178179@Override180public void onError(ErrorInfo errorInfo) {181listener.onError(errorInfo);182}183});184}185186public void setChannelListener(ChatClientListener listener) {187this.listener = listener;188}189190private String getStringResource(int id) {191Resources resources = TwilioChatApplication.get().getResources();192return resources.getString(id);193}194195@Override196public void onChannelAdded(Channel channel) {197if (listener != null) {198listener.onChannelAdded(channel);199}200}201202@Override203public void onChannelUpdated(Channel channel, Channel.UpdateReason updateReason) {204if (listener != null) {205listener.onChannelUpdated(channel, updateReason);206}207}208209@Override210public void onChannelDeleted(Channel channel) {211if (listener != null) {212listener.onChannelDeleted(channel);213}214}215216@Override217public void onChannelSynchronizationChange(Channel channel) {218if (listener != null) {219listener.onChannelSynchronizationChange(channel);220}221}222223@Override224public void onError(ErrorInfo errorInfo) {225if (listener != null) {226listener.onError(errorInfo);227}228}229230@Override231public void onClientSynchronization(ChatClient.SynchronizationStatus synchronizationStatus) {232233}234235@Override236public void onChannelJoined(Channel channel) {237238}239240@Override241public void onChannelInvited(Channel channel) {242243}244245@Override246public void onUserUpdated(User user, User.UpdateReason updateReason) {247if (listener != null) {248listener.onUserUpdated(user, updateReason);249}250}251252@Override253public void onUserSubscribed(User user) {254255}256257@Override258public void onUserUnsubscribed(User user) {259260}261262@Override263public void onNewMessageNotification(String s, String s1, long l) {264265}266267@Override268public void onAddedToChannelNotification(String s) {269270}271272@Override273public void onInvitedToChannelNotification(String s) {274275}276277@Override278public void onRemovedFromChannelNotification(String s) {279280}281282@Override283public void onNotificationSubscribed() {284285}286287@Override288public void onNotificationFailed(ErrorInfo errorInfo) {289290}291292@Override293public void onConnectionStateChange(ChatClient.ConnectionState connectionState) {294295}296297@Override298public void onTokenExpired() {299refreshAccessToken();300}301302@Override303public void onTokenAboutToExpire() {304refreshAccessToken();305}306307private void refreshAccessToken() {308AccessTokenFetcher accessTokenFetcher = chatClientManager.getAccessTokenFetcher();309accessTokenFetcher.fetch(new TaskCompletionListener<String, String>() {310@Override311public void onSuccess(String token) {312ChannelManager.this.chatClientManager.getChatClient().updateToken(token, new StatusListener() {313@Override314public void onSuccess() {315Log.d(TwilioChatApplication.TAG, "Successfully updated access token.");316}317});318}319320@Override321public void onError(String message) {322Log.e(TwilioChatApplication.TAG,"Error trying to fetch token: " + message);323}324});325}326327private Handler setupListenerHandler() {328Looper looper;329Handler handler;330if ((looper = Looper.myLooper()) != null) {331handler = new Handler(looper);332} else if ((looper = Looper.getMainLooper()) != null) {333handler = new Handler(looper);334} else {335throw new IllegalArgumentException("Channel Listener must have a Looper.");336}337return handler;338}339}340
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
1package com.twilio.twiliochat.chat.channels;23import android.content.res.Resources;4import android.os.Handler;5import android.os.Looper;6import android.util.Log;78import com.twilio.chat.CallbackListener;9import com.twilio.chat.Channel;10import com.twilio.chat.Channel.ChannelType;11import com.twilio.chat.ChannelDescriptor;12import com.twilio.chat.Channels;13import com.twilio.chat.ChatClient;14import com.twilio.chat.ChatClientListener;15import com.twilio.chat.ErrorInfo;16import com.twilio.chat.Paginator;17import com.twilio.chat.StatusListener;18import com.twilio.chat.User;19import com.twilio.twiliochat.R;20import com.twilio.twiliochat.application.TwilioChatApplication;21import com.twilio.twiliochat.chat.ChatClientManager;22import com.twilio.twiliochat.chat.accesstoken.AccessTokenFetcher;23import com.twilio.twiliochat.chat.listeners.TaskCompletionListener;2425import java.util.ArrayList;26import java.util.Collections;27import java.util.List;2829public class ChannelManager implements ChatClientListener {30private static ChannelManager sharedManager = new ChannelManager();31public Channel generalChannel;32private ChatClientManager chatClientManager;33private ChannelExtractor channelExtractor;34private List<Channel> channels;35private Channels channelsObject;36private ChatClientListener listener;37private String defaultChannelName;38private String defaultChannelUniqueName;39private Handler handler;40private Boolean isRefreshingChannels = false;4142private ChannelManager() {43this.chatClientManager = TwilioChatApplication.get().getChatClientManager();44this.channelExtractor = new ChannelExtractor();45this.listener = this;46defaultChannelName = getStringResource(R.string.default_channel_name);47defaultChannelUniqueName = getStringResource(R.string.default_channel_unique_name);48handler = setupListenerHandler();49}5051public static ChannelManager getInstance() {52return sharedManager;53}5455public List<Channel> getChannels() {56return channels;57}5859public String getDefaultChannelName() {60return this.defaultChannelName;61}6263public void leaveChannelWithHandler(Channel channel, StatusListener handler) {64channel.leave(handler);65}6667public void deleteChannelWithHandler(Channel channel, StatusListener handler) {68channel.destroy(handler);69}7071public void populateChannels(final LoadChannelListener listener) {72if (this.chatClientManager == null || this.isRefreshingChannels) {73return;74}75this.isRefreshingChannels = true;7677handler.post(new Runnable() {78@Override79public void run() {80channelsObject = chatClientManager.getChatClient().getChannels();8182channelsObject.getPublicChannelsList(new CallbackListener<Paginator<ChannelDescriptor>>() {83@Override84public void onSuccess(Paginator<ChannelDescriptor> channelDescriptorPaginator) {85extractChannelsFromPaginatorAndPopulate(channelDescriptorPaginator, listener);86}87});8889}90});91}9293private void extractChannelsFromPaginatorAndPopulate(final Paginator<ChannelDescriptor> channelsPaginator,94final LoadChannelListener listener) {95channels = new ArrayList<>();96ChannelManager.this.channels.clear();97channelExtractor.extractAndSortFromChannelDescriptor(channelsPaginator,98new TaskCompletionListener<List<Channel>, String>() {99@Override100public void onSuccess(List<Channel> channels) {101ChannelManager.this.channels.addAll(channels);102Collections.sort(ChannelManager.this.channels, new CustomChannelComparator());103ChannelManager.this.isRefreshingChannels = false;104chatClientManager.addClientListener(ChannelManager.this);105listener.onChannelsFinishedLoading(ChannelManager.this.channels);106}107108@Override109public void onError(String errorText) {110Log.e(TwilioChatApplication.TAG,"Error populating channels: " + errorText);111}112});113}114115public void createChannelWithName(String name, final StatusListener handler) {116this.channelsObject117.channelBuilder()118.withFriendlyName(name)119.withType(ChannelType.PUBLIC)120.build(new CallbackListener<Channel>() {121@Override122public void onSuccess(final Channel newChannel) {123handler.onSuccess();124}125126@Override127public void onError(ErrorInfo errorInfo) {128handler.onError(errorInfo);129}130});131}132133public void joinOrCreateGeneralChannelWithCompletion(final StatusListener listener) {134channelsObject.getChannel(defaultChannelUniqueName, new CallbackListener<Channel>() {135@Override136public void onSuccess(Channel channel) {137ChannelManager.this.generalChannel = channel;138if (channel != null) {139joinGeneralChannelWithCompletion(listener);140} else {141createGeneralChannelWithCompletion(listener);142}143}144});145}146147private void joinGeneralChannelWithCompletion(final StatusListener listener) {148if (generalChannel.getStatus() == Channel.ChannelStatus.JOINED) {149listener.onSuccess();150return;151}152this.generalChannel.join(new StatusListener() {153@Override154public void onSuccess() {155listener.onSuccess();156}157158@Override159public void onError(ErrorInfo errorInfo) {160listener.onError(errorInfo);161}162});163}164165private void createGeneralChannelWithCompletion(final StatusListener listener) {166this.channelsObject167.channelBuilder()168.withFriendlyName(defaultChannelName)169.withUniqueName(defaultChannelUniqueName)170.withType(ChannelType.PUBLIC)171.build(new CallbackListener<Channel>() {172@Override173public void onSuccess(final Channel channel) {174ChannelManager.this.generalChannel = channel;175ChannelManager.this.channels.add(channel);176joinGeneralChannelWithCompletion(listener);177}178179@Override180public void onError(ErrorInfo errorInfo) {181listener.onError(errorInfo);182}183});184}185186public void setChannelListener(ChatClientListener listener) {187this.listener = listener;188}189190private String getStringResource(int id) {191Resources resources = TwilioChatApplication.get().getResources();192return resources.getString(id);193}194195@Override196public void onChannelAdded(Channel channel) {197if (listener != null) {198listener.onChannelAdded(channel);199}200}201202@Override203public void onChannelUpdated(Channel channel, Channel.UpdateReason updateReason) {204if (listener != null) {205listener.onChannelUpdated(channel, updateReason);206}207}208209@Override210public void onChannelDeleted(Channel channel) {211if (listener != null) {212listener.onChannelDeleted(channel);213}214}215216@Override217public void onChannelSynchronizationChange(Channel channel) {218if (listener != null) {219listener.onChannelSynchronizationChange(channel);220}221}222223@Override224public void onError(ErrorInfo errorInfo) {225if (listener != null) {226listener.onError(errorInfo);227}228}229230@Override231public void onClientSynchronization(ChatClient.SynchronizationStatus synchronizationStatus) {232233}234235@Override236public void onChannelJoined(Channel channel) {237238}239240@Override241public void onChannelInvited(Channel channel) {242243}244245@Override246public void onUserUpdated(User user, User.UpdateReason updateReason) {247if (listener != null) {248listener.onUserUpdated(user, updateReason);249}250}251252@Override253public void onUserSubscribed(User user) {254255}256257@Override258public void onUserUnsubscribed(User user) {259260}261262@Override263public void onNewMessageNotification(String s, String s1, long l) {264265}266267@Override268public void onAddedToChannelNotification(String s) {269270}271272@Override273public void onInvitedToChannelNotification(String s) {274275}276277@Override278public void onRemovedFromChannelNotification(String s) {279280}281282@Override283public void onNotificationSubscribed() {284285}286287@Override288public void onNotificationFailed(ErrorInfo errorInfo) {289290}291292@Override293public void onConnectionStateChange(ChatClient.ConnectionState connectionState) {294295}296297@Override298public void onTokenExpired() {299refreshAccessToken();300}301302@Override303public void onTokenAboutToExpire() {304refreshAccessToken();305}306307private void refreshAccessToken() {308AccessTokenFetcher accessTokenFetcher = chatClientManager.getAccessTokenFetcher();309accessTokenFetcher.fetch(new TaskCompletionListener<String, String>() {310@Override311public void onSuccess(String token) {312ChannelManager.this.chatClientManager.getChatClient().updateToken(token, new StatusListener() {313@Override314public void onSuccess() {315Log.d(TwilioChatApplication.TAG, "Successfully updated access token.");316}317});318}319320@Override321public void onError(String message) {322Log.e(TwilioChatApplication.TAG,"Error trying to fetch token: " + message);323}324});325}326327private Handler setupListenerHandler() {328Looper looper;329Handler handler;330if ((looper = Looper.myLooper()) != null) {331handler = new Handler(looper);332} else if ((looper = Looper.getMainLooper()) != null) {333handler = new Handler(looper);334} else {335throw new IllegalArgumentException("Channel Listener must have a Looper.");336}337return handler;338}339}340
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.