Chat with Android and Java

Ready to implement a chat application using Twilio Programmable Chat Client? Here is how it works at a high level:

  1. Twilio Programmable Chat is the core product we'll be using to handle all the chat functionality
  2. We use a server side app to generate a user access token which contains all your Twilio account information. The Programmable chat Client uses this token to connect with the API
  3. Twilio Access Manager is the part of the SDK that handles access tokens and refreshes them upon token expiration

Properati built a web and mobile messaging app to help real estate buyers and sellers connect in real time. Learn more here.

Initialize the Client - Part 1: Fetch access token

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.

Loading Code Samples...
Language
package com.twilio.twiliochat.chat.accesstoken;

import android.content.Context;
import android.content.res.Resources;
import android.provider.Settings;

import com.android.volley.Request;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.JsonObjectRequest;
import com.twilio.twiliochat.R;
import com.twilio.twiliochat.application.SessionManager;
import com.twilio.twiliochat.application.TwilioChatApplication;
import com.twilio.twiliochat.chat.listeners.TaskCompletionListener;

import org.json.JSONException;
import org.json.JSONObject;

import java.util.HashMap;
import java.util.Map;

public class AccessTokenFetcher {

  private Context context;

  public AccessTokenFetcher(Context context) {
    this.context = context;
  }

  public void fetch(final TaskCompletionListener<String, String> listener) {
    JSONObject obj = new JSONObject(getTokenRequestParams(context));
    String requestUrl = getStringResource(R.string.token_url);

    JsonObjectRequest jsonObjReq =
        new JsonObjectRequest(Request.Method.POST, requestUrl, obj, new Response.Listener<JSONObject>() {

          @Override
          public void onResponse(JSONObject response) {
            String token = null;
            try {
              token = response.getString("token");
            } catch (JSONException e) {
              e.printStackTrace();
              listener.onError("Failed to parse token JSON response");
            }
            listener.onSuccess(token);
          }
        }, new Response.ErrorListener() {

          @Override
          public void onErrorResponse(VolleyError error) {

            listener.onError("Failed to fetch token");
          }
        });
    jsonObjReq.setShouldCache(false);
    TokenRequest.getInstance().addToRequestQueue(jsonObjReq);
  }

  private Map<String, String> getTokenRequestParams(Context context) {
    String androidId =
        Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID);
    Map<String, String> params = new HashMap<>();
    params.put("deviceId", androidId);
    params.put("identity", SessionManager.getInstance().getUsername());
    return params;
  }

  private String getStringResource(int id) {
    Resources resources = TwilioChatApplication.get().getResources();
    return resources.getString(id);
  }

}
app/src/main/java/com/twilio/twiliochat/chat/accesstoken/AccessTokenFetcher.java
Fetch Access Token

app/src/main/java/com/twilio/twiliochat/chat/accesstoken/AccessTokenFetcher.java

Now it's time to use that token to initialize your Twilio Client.

Initialize the Client - Part 2: Build the 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.

Loading Code Samples...
Language
package com.twilio.twiliochat.chat;

import android.content.Context;

import com.twilio.chat.CallbackListener;
import com.twilio.chat.ChatClient;
import com.twilio.chat.ErrorInfo;
import com.twilio.twiliochat.chat.listeners.TaskCompletionListener;

public class ChatClientBuilder extends CallbackListener<ChatClient> {

  private Context context;
  private TaskCompletionListener<ChatClient, String> buildListener;

  public ChatClientBuilder(Context context) {
    this.context = context;
  }

  public void build(String token, final TaskCompletionListener<ChatClient, String> listener) {
    ChatClient.Properties props =
        new ChatClient.Properties.Builder()
            .setSynchronizationStrategy(ChatClient.SynchronizationStrategy.CHANNELS_LIST)
            .setRegion("us1")
            .createProperties();

    this.buildListener = listener;
    ChatClient.create(context.getApplicationContext(),
        token,
        props,
        this);
  }


  @Override
  public void onSuccess(ChatClient chatClient) {
    this.buildListener.onSuccess(chatClient);
  }

  @Override
  public void onError(ErrorInfo errorInfo) {
    this.buildListener.onError(errorInfo.getErrorText());
  }
}
app/src/main/java/com/twilio/twiliochat/chat/ChatClientBuilder.java
Build the Chat Client

app/src/main/java/com/twilio/twiliochat/chat/ChatClientBuilder.java

The next step will be getting a channel list.

Get the 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.

Loading Code Samples...
Language
package com.twilio.twiliochat.chat.channels;

import android.content.res.Resources;
import android.os.Handler;
import android.os.Looper;

import com.twilio.chat.CallbackListener;
import com.twilio.chat.Channel;
import com.twilio.chat.Channel.ChannelType;
import com.twilio.chat.ChannelDescriptor;
import com.twilio.chat.Channels;
import com.twilio.chat.ChatClient;
import com.twilio.chat.ChatClientListener;
import com.twilio.chat.ErrorInfo;
import com.twilio.chat.Paginator;
import com.twilio.chat.StatusListener;
import com.twilio.chat.UserInfo;
import com.twilio.twiliochat.R;
import com.twilio.twiliochat.application.TwilioChatApplication;
import com.twilio.twiliochat.chat.ChatClientManager;
import com.twilio.twiliochat.chat.listeners.TaskCompletionListener;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class ChannelManager implements ChatClientListener {
  private static ChannelManager sharedManager = new ChannelManager();
  public Channel generalChannel;
  private ChatClientManager chatClientManager;
  private ChannelExtractor channelExtractor;
  private List<Channel> channels;
  private Channels channelsObject;
  private ChatClientListener listener;
  private String defaultChannelName;
  private String defaultChannelUniqueName;
  private Handler handler;
  private Boolean isRefreshingChannels = false;

  private ChannelManager() {
    this.chatClientManager = TwilioChatApplication.get().getChatClientManager();
    this.channelExtractor = new ChannelExtractor();
    this.listener = this;
    defaultChannelName = getStringResource(R.string.default_channel_name);
    defaultChannelUniqueName = getStringResource(R.string.default_channel_unique_name);
    handler = setupListenerHandler();
  }

  public static ChannelManager getInstance() {
    return sharedManager;
  }

  public List<Channel> getChannels() {
    return channels;
  }

  public String getDefaultChannelName() {
    return this.defaultChannelName;
  }

  public void leaveChannelWithHandler(Channel channel, StatusListener handler) {
    channel.leave(handler);
  }

  public void deleteChannelWithHandler(Channel channel, StatusListener handler) {
    channel.destroy(handler);
  }

  public void populateChannels(final LoadChannelListener listener) {
    if (this.chatClientManager == null || this.isRefreshingChannels) {
      return;
    }
    this.isRefreshingChannels = true;

    handler.post(new Runnable() {
      @Override
      public void run() {
        channelsObject = chatClientManager.getChatClient().getChannels();

        channelsObject.getPublicChannels(new CallbackListener<Paginator<ChannelDescriptor>>() {
          @Override
          public void onSuccess(Paginator<ChannelDescriptor> channelDescriptorPaginator) {
            extractChannelsFromPaginatorAndPopulate(channelDescriptorPaginator, listener);
          }
        });

      }
    });
  }

  private void extractChannelsFromPaginatorAndPopulate(final Paginator<ChannelDescriptor> channelsPaginator,
                                                       final LoadChannelListener listener) {
    channels = new ArrayList<>();
    ChannelManager.this.channels.clear();
    channelExtractor.extractAndSortFromChannelDescriptor(channelsPaginator,
        new TaskCompletionListener<List<Channel>, String>() {
      @Override
      public void onSuccess(List<Channel> channels) {
        ChannelManager.this.channels.addAll(channels);
        Collections.sort(ChannelManager.this.channels, new CustomChannelComparator());
        ChannelManager.this.isRefreshingChannels = false;
        chatClientManager.setClientListener(ChannelManager.this);
        listener.onChannelsFinishedLoading(ChannelManager.this.channels);
      }

      @Override
      public void onError(String errorText) {
        System.out.println("Error populating channels: " + errorText);
      }
    });
  }

  public void createChannelWithName(String name, final StatusListener handler) {
    this.channelsObject
        .channelBuilder()
        .withFriendlyName(name)
        .withType(ChannelType.PUBLIC)
        .build(new CallbackListener<Channel>() {
          @Override
          public void onSuccess(final Channel newChannel) {
            handler.onSuccess();
          }

          @Override
          public void onError(ErrorInfo errorInfo) {
            handler.onError(errorInfo);
          }
        });
  }

  public void joinOrCreateGeneralChannelWithCompletion(final StatusListener listener) {
    channelsObject.getChannel(defaultChannelUniqueName, new CallbackListener<Channel>() {
      @Override
      public void onSuccess(Channel channel) {
        ChannelManager.this.generalChannel = channel;
        if (channel != null) {
          joinGeneralChannelWithCompletion(listener);
        } else {
          createGeneralChannelWithCompletion(listener);
        }
      }
    });
  }

  private void joinGeneralChannelWithCompletion(final StatusListener listener) {
    this.generalChannel.join(new StatusListener() {
      @Override
      public void onSuccess() {
        listener.onSuccess();
      }

      @Override
      public void onError(ErrorInfo errorInfo) {
        listener.onError(errorInfo);
      }
    });
  }

  private void createGeneralChannelWithCompletion(final StatusListener listener) {
    this.channelsObject
        .channelBuilder()
        .withFriendlyName(defaultChannelName)
        .withUniqueName(defaultChannelUniqueName)
        .withType(ChannelType.PUBLIC)
        .build(new CallbackListener<Channel>() {
          @Override
          public void onSuccess(final Channel channel) {
            ChannelManager.this.generalChannel = channel;
            ChannelManager.this.channels.add(channel);
            joinGeneralChannelWithCompletion(listener);
          }

          @Override
          public void onError(ErrorInfo errorInfo) {
            listener.onError(errorInfo);
          }
        });
  }

  public void setChannelListener(ChatClientListener listener) {
    this.listener = listener;
  }

  private String getStringResource(int id) {
    Resources resources = TwilioChatApplication.get().getResources();
    return resources.getString(id);
  }

  @Override
  public void onChannelAdd(Channel channel) {
    if (listener != null) {
      listener.onChannelAdd(channel);
    }
  }

  @Override
  public void onChannelInvite(Channel channel) {

  }

  @Override
  public void onChannelChange(Channel channel) {
    if (listener != null) {
      listener.onChannelChange(channel);
    }
  }

  @Override
  public void onChannelDelete(Channel channel) {
    if (listener != null) {
      listener.onChannelDelete(channel);
    }
  }

  @Override
  public void onChannelSynchronizationChange(Channel channel) {
    if (listener != null) {
      listener.onChannelSynchronizationChange(channel);
    }
  }

  @Override
  public void onError(ErrorInfo errorInfo) {
    if (listener != null) {
      listener.onError(errorInfo);
    }
  }

  @Override
  public void onUserInfoChange(UserInfo userInfo, UserInfo.UpdateReason updateReason) {
    if (listener != null) {
      listener.onUserInfoChange(userInfo, updateReason);
    }
  }

  @Override
  public void onClientSynchronization(ChatClient.SynchronizationStatus synchronizationStatus) {

  }

  @Override
  public void onToastNotification(String s, String s1) {

  }

  @Override
  public void onToastSubscribed() {

  }

  @Override
  public void onToastFailed(ErrorInfo errorInfo) {

  }

  @Override
  public void onConnectionStateChange(ChatClient.ConnectionState connectionState) {

  }

  private Handler setupListenerHandler() {
    Looper looper;
    Handler handler;
    if ((looper = Looper.myLooper()) != null) {
      handler = new Handler(looper);
    } else if ((looper = Looper.getMainLooper()) != null) {
      handler = new Handler(looper);
    } else {
      throw new IllegalArgumentException("Channel Listener must have a Looper.");
    }
    return handler;
  }
}
app/src/main/java/com/twilio/twiliochat/chat/channels/ChannelManager.java
Get Channel List

app/src/main/java/com/twilio/twiliochat/chat/channels/ChannelManager.java

Let's see how we can listen to events from the chat client so we can update our app's state.

Listen to Client Events

The Programmable Chat Client will trigger events such as onChannelAdd or onChannelDelete 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.

Loading Code Samples...
Language
package com.twilio.twiliochat.chat;

import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.Resources;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.view.GravityCompat;
import android.support.v4.widget.DrawerLayout;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.app.ActionBarDrawerToggle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.Button;
import android.widget.ListView;
import android.widget.TextView;

import com.twilio.chat.Channel;
import com.twilio.chat.ChatClient;
import com.twilio.chat.ChatClientListener;
import com.twilio.chat.ErrorInfo;
import com.twilio.chat.StatusListener;
import com.twilio.chat.UserInfo;
import com.twilio.twiliochat.R;
import com.twilio.twiliochat.application.AlertDialogHandler;
import com.twilio.twiliochat.application.SessionManager;
import com.twilio.twiliochat.application.TwilioChatApplication;
import com.twilio.twiliochat.chat.channels.ChannelAdapter;
import com.twilio.twiliochat.chat.channels.ChannelManager;
import com.twilio.twiliochat.chat.channels.LoadChannelListener;
import com.twilio.twiliochat.chat.listeners.InputOnClickListener;
import com.twilio.twiliochat.chat.listeners.TaskCompletionListener;
import com.twilio.twiliochat.landing.LoginActivity;

import java.util.List;

public class MainChatActivity extends AppCompatActivity implements ChatClientListener {
  private Context context;
  private Activity mainActivity;
  private Button logoutButton;
  private Button addChannelButton;
  private TextView usernameTextView;
  private ChatClientManager chatClientManager;
  private ListView channelsListView;
  private ChannelAdapter channelAdapter;
  private ChannelManager channelManager;
  private MainChatFragment chatFragment;
  private DrawerLayout drawer;
  private ProgressDialog progressDialog;
  private MenuItem leaveChannelMenuItem;
  private MenuItem deleteChannelMenuItem;
  private SwipeRefreshLayout refreshLayout;

  @Override
  protected void onDestroy() {
    super.onDestroy();
    new Handler().post(new Runnable() {
      @Override
      public void run() {
        chatClientManager.shutdown();
        TwilioChatApplication.get().getChatClientManager().setChatClient(null);
      }
    });
  }

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main_chat);
    Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
    setSupportActionBar(toolbar);

    drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
    ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(this, drawer, toolbar,
        R.string.navigation_drawer_open, R.string.navigation_drawer_close);
    drawer.setDrawerListener(toggle);
    toggle.syncState();

    refreshLayout = (SwipeRefreshLayout) findViewById(R.id.refreshLayout);

    FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();

    chatFragment = new MainChatFragment();
    fragmentTransaction.add(R.id.fragment_container, chatFragment);
    fragmentTransaction.commit();

    context = this;
    mainActivity = this;
    logoutButton = (Button) findViewById(R.id.buttonLogout);
    addChannelButton = (Button) findViewById(R.id.buttonAddChannel);
    usernameTextView = (TextView) findViewById(R.id.textViewUsername);
    channelsListView = (ListView) findViewById(R.id.listViewChannels);

    channelManager = ChannelManager.getInstance();
    setUsernameTextView();

    setUpListeners();
    checkTwilioClient();
  }

  private void setUpListeners() {
    logoutButton.setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View v) {
        promptLogout();
      }
    });
    addChannelButton.setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View v) {
        showAddChannelDialog();
      }
    });
    refreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
      @Override
      public void onRefresh() {
        refreshLayout.setRefreshing(true);
        refreshChannels();
      }
    });
    channelsListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
      @Override
      public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        setChannel(position);
      }
    });
  }

  @Override
  public void onBackPressed() {
    if (drawer.isDrawerOpen(GravityCompat.START)) {
      drawer.closeDrawer(GravityCompat.START);
    } else {
      super.onBackPressed();
    }
  }

  @Override
  public boolean onCreateOptionsMenu(Menu menu) {
    getMenuInflater().inflate(R.menu.main_chat, menu);
    this.leaveChannelMenuItem = menu.findItem(R.id.action_leave_channel);
    this.leaveChannelMenuItem.setVisible(false);
    this.deleteChannelMenuItem = menu.findItem(R.id.action_delete_channel);
    this.deleteChannelMenuItem.setVisible(false);
    return true;
  }

  @Override
  public boolean onOptionsItemSelected(MenuItem item) {
    int id = item.getItemId();

    if (id == R.id.action_leave_channel) {
      leaveCurrentChannel();
      return true;
    }
    if (id == R.id.action_delete_channel) {
      promptChannelDeletion();
    }

    return super.onOptionsItemSelected(item);
  }

  private String getStringResource(int id) {
    Resources resources = getResources();
    return resources.getString(id);
  }

  private void refreshChannels() {
    channelManager.populateChannels(new LoadChannelListener() {
      @Override
      public void onChannelsFinishedLoading(final List<Channel> channels) {
        runOnUiThread(new Runnable() {
          @Override
          public void run() {
            channelAdapter.setChannels(channels);
            refreshLayout.setRefreshing(false);
          }
        });
      }
    });
  }

  private void populateChannels() {
    channelManager.setChannelListener(this);
    channelManager.populateChannels(new LoadChannelListener() {
      @Override
      public void onChannelsFinishedLoading(List<Channel> channels) {
        channelAdapter = new ChannelAdapter(mainActivity, channels);
        channelsListView.setAdapter(channelAdapter);
        MainChatActivity.this.channelManager
            .joinOrCreateGeneralChannelWithCompletion(new StatusListener() {
              @Override
              public void onSuccess() {
                runOnUiThread(new Runnable() {
                  @Override
                  public void run() {
                    channelAdapter.notifyDataSetChanged();
                    stopActivityIndicator();
                    setChannel(0);
                  }
                });
              }

              @Override
              public void onError(ErrorInfo errorInfo) {
                showAlertWithMessage(getStringResource(R.string.generic_error));
              }
            });
      }
    });
  }

  private void setChannel(final int position) {
    List<Channel> channels = channelManager.getChannels();
    if (channels == null) {
      return;
    }
    final Channel currentChannel = chatFragment.getCurrentChannel();
    final Channel selectedChannel = channels.get(position);
    if (currentChannel != null && currentChannel.getSid().contentEquals(selectedChannel.getSid())) {
      drawer.closeDrawer(GravityCompat.START);
      return;
    }
    hideMenuItems(position);
    if (selectedChannel != null) {
      showActivityIndicator("Joining " + selectedChannel.getFriendlyName() + " channel");
      if (currentChannel != null && currentChannel.getStatus() == Channel.ChannelStatus.JOINED) {
        this.channelManager.leaveChannelWithHandler(currentChannel, new StatusListener() {
          @Override
          public void onSuccess() {
            joinChannel(selectedChannel);
          }

          @Override
          public void onError(ErrorInfo errorInfo) {
            stopActivityIndicator();
          }
        });
        return;
      }
      joinChannel(selectedChannel);
      stopActivityIndicator();
    } else {
      stopActivityIndicator();
      showAlertWithMessage(getStringResource(R.string.generic_error));
      System.out.println("Selected channel out of range");
    }
  }

  private void joinChannel(final Channel selectedChannel) {
    runOnUiThread(new Runnable() {
      @Override
      public void run() {
        chatFragment.setCurrentChannel(selectedChannel, new StatusListener() {
          @Override
          public void onSuccess() {
            MainChatActivity.this.stopActivityIndicator();
          }

          @Override
          public void onError(ErrorInfo errorInfo) {
          }
        });
        setTitle(selectedChannel.getFriendlyName());
        drawer.closeDrawer(GravityCompat.START);
      }
    });
  }

  private void hideMenuItems(final int position) {
    runOnUiThread(new Runnable() {
      @Override
      public void run() {
        MainChatActivity.this.leaveChannelMenuItem.setVisible(position != 0);
        MainChatActivity.this.deleteChannelMenuItem.setVisible(position != 0);
      }
    });
  }

  private void showAddChannelDialog() {
    String message = getStringResource(R.string.new_channel_prompt);
    AlertDialogHandler.displayInputDialog(message, context, new InputOnClickListener() {
      @Override
      public void onClick(String input) {
        if (input.length() == 0) {
          showAlertWithMessage(getStringResource(R.string.channel_name_required_message));
          return;
        }
        createChannelWithName(input);
      }
    });
  }

  private void createChannelWithName(String name) {
    name = name.trim();
    if (name.toLowerCase()
        .contentEquals(this.channelManager.getDefaultChannelName().toLowerCase())) {
      showAlertWithMessage(getStringResource(R.string.channel_name_equals_default_name));
      return;
    }
    this.channelManager.createChannelWithName(name, new StatusListener() {
      @Override
      public void onSuccess() {
        refreshChannels();
      }

      @Override
      public void onError(ErrorInfo errorInfo) {
        showAlertWithMessage(getStringResource(R.string.generic_error));
      }
    });
  }

  private void promptChannelDeletion() {
    String message = getStringResource(R.string.channel_delete_prompt_message);
    AlertDialogHandler.displayCancellableAlertWithHandler(message, context,
        new DialogInterface.OnClickListener() {
          @Override
          public void onClick(DialogInterface dialog, int which) {
            deleteCurrentChannel();
          }
        });
  }

  private void deleteCurrentChannel() {
    Channel currentChannel = chatFragment.getCurrentChannel();
    channelManager.deleteChannelWithHandler(currentChannel, new StatusListener() {
      @Override
      public void onSuccess() {
      }

      @Override
      public void onError(ErrorInfo errorInfo) {
        showAlertWithMessage(getStringResource(R.string.message_deletion_forbidden));
      }
    });
  }

  private void leaveCurrentChannel() {
    final Channel currentChannel = chatFragment.getCurrentChannel();
    if (currentChannel.getStatus() == Channel.ChannelStatus.NOT_PARTICIPATING) {
      setChannel(0);
      return;
    }
    channelManager.leaveChannelWithHandler(currentChannel, new StatusListener() {
      @Override
      public void onSuccess() {
        setChannel(0);
      }

      @Override
      public void onError(ErrorInfo errorInfo) {
        stopActivityIndicator();
      }
    });
  }

  private void checkTwilioClient() {
    showActivityIndicator(getStringResource(R.string.loading_channels_message));
    chatClientManager = TwilioChatApplication.get().getChatClientManager();
    if (chatClientManager.getChatClient() == null) {
      initializeClient();
    } else {
      populateChannels();
    }
  }

  private void initializeClient() {
    chatClientManager.connectClient(new TaskCompletionListener<Void, String>() {
      @Override
      public void onSuccess(Void aVoid) {
        populateChannels();
      }

      @Override
      public void onError(String errorMessage) {
        stopActivityIndicator();
        showAlertWithMessage("Client connection error");
      }
    });
  }

  private void promptLogout() {
    final String message = getStringResource(R.string.logout_prompt_message);
    runOnUiThread(new Runnable() {
      @Override
      public void run() {
        AlertDialogHandler.displayCancellableAlertWithHandler(message, context,
            new DialogInterface.OnClickListener() {
              @Override
              public void onClick(DialogInterface dialog, int which) {
                SessionManager.getInstance().logoutUser();
                showLoginActivity();
              }
            });
      }
    });

  }

  private void showLoginActivity() {
    Intent launchIntent = new Intent();
    launchIntent.setClass(getApplicationContext(), LoginActivity.class);
    startActivity(launchIntent);
    finish();
  }

  private void setUsernameTextView() {
    String username =
        SessionManager.getInstance().getUserDetails().get(SessionManager.KEY_USERNAME);
    usernameTextView.setText(username);
  }

  private void stopActivityIndicator() {
    runOnUiThread(new Runnable() {
      @Override
      public void run() {
        if (progressDialog.isShowing()) {
          progressDialog.dismiss();
        }
      }
    });
  }

  private void showActivityIndicator(final String message) {
    runOnUiThread(new Runnable() {
      @Override
      public void run() {
        progressDialog = new ProgressDialog(MainChatActivity.this.mainActivity);
        progressDialog.setMessage(message);
        progressDialog.show();
        progressDialog.setCanceledOnTouchOutside(false);
        progressDialog.setCancelable(false);
      }
    });
  }

  private void showAlertWithMessage(final String message) {
    runOnUiThread(new Runnable() {
      @Override
      public void run() {
        AlertDialogHandler.displayAlertWithMessage(message, context);
      }
    });
  }

  @Override
  public void onChannelAdd(Channel channel) {
    System.out.println("Channel Added");
    refreshChannels();
  }

  @Override
  public void onChannelDelete(final Channel channel) {
    System.out.println("Channel Deleted");
    Channel currentChannel = chatFragment.getCurrentChannel();
    if (channel.getSid().contentEquals(currentChannel.getSid())) {
      chatFragment.setCurrentChannel(null, null);
      setChannel(0);
    }
    refreshChannels();
  }

  @Override
  public void onChannelInvite(Channel channel) {

  }

  @Override
  public void onChannelSynchronizationChange(Channel channel) {

  }

  @Override
  public void onError(ErrorInfo errorInfo) {

  }

  @Override
  public void onUserInfoChange(UserInfo userInfo, UserInfo.UpdateReason updateReason) {

  }

  @Override
  public void onClientSynchronization(ChatClient.SynchronizationStatus synchronizationStatus) {

  }

  @Override
  public void onToastNotification(String s, String s1) {

  }

  @Override
  public void onToastSubscribed() {

  }

  @Override
  public void onToastFailed(ErrorInfo errorInfo) {

  }

  @Override
  public void onConnectionStateChange(ChatClient.ConnectionState connectionState) {

  }

  @Override
  public void onChannelChange(Channel channel) {
  }
}
app/src/main/java/com/twilio/twiliochat/chat/MainChatActivity.java
Listen for Client Events

app/src/main/java/com/twilio/twiliochat/chat/MainChatActivity.java

Next, we need a default channel.

Join the General 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.

Loading Code Samples...
Language
package com.twilio.twiliochat.chat.channels;

import android.content.res.Resources;
import android.os.Handler;
import android.os.Looper;

import com.twilio.chat.CallbackListener;
import com.twilio.chat.Channel;
import com.twilio.chat.Channel.ChannelType;
import com.twilio.chat.ChannelDescriptor;
import com.twilio.chat.Channels;
import com.twilio.chat.ChatClient;
import com.twilio.chat.ChatClientListener;
import com.twilio.chat.ErrorInfo;
import com.twilio.chat.Paginator;
import com.twilio.chat.StatusListener;
import com.twilio.chat.UserInfo;
import com.twilio.twiliochat.R;
import com.twilio.twiliochat.application.TwilioChatApplication;
import com.twilio.twiliochat.chat.ChatClientManager;
import com.twilio.twiliochat.chat.listeners.TaskCompletionListener;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class ChannelManager implements ChatClientListener {
  private static ChannelManager sharedManager = new ChannelManager();
  public Channel generalChannel;
  private ChatClientManager chatClientManager;
  private ChannelExtractor channelExtractor;
  private List<Channel> channels;
  private Channels channelsObject;
  private ChatClientListener listener;
  private String defaultChannelName;
  private String defaultChannelUniqueName;
  private Handler handler;
  private Boolean isRefreshingChannels = false;

  private ChannelManager() {
    this.chatClientManager = TwilioChatApplication.get().getChatClientManager();
    this.channelExtractor = new ChannelExtractor();
    this.listener = this;
    defaultChannelName = getStringResource(R.string.default_channel_name);
    defaultChannelUniqueName = getStringResource(R.string.default_channel_unique_name);
    handler = setupListenerHandler();
  }

  public static ChannelManager getInstance() {
    return sharedManager;
  }

  public List<Channel> getChannels() {
    return channels;
  }

  public String getDefaultChannelName() {
    return this.defaultChannelName;
  }

  public void leaveChannelWithHandler(Channel channel, StatusListener handler) {
    channel.leave(handler);
  }

  public void deleteChannelWithHandler(Channel channel, StatusListener handler) {
    channel.destroy(handler);
  }

  public void populateChannels(final LoadChannelListener listener) {
    if (this.chatClientManager == null || this.isRefreshingChannels) {
      return;
    }
    this.isRefreshingChannels = true;

    handler.post(new Runnable() {
      @Override
      public void run() {
        channelsObject = chatClientManager.getChatClient().getChannels();

        channelsObject.getPublicChannels(new CallbackListener<Paginator<ChannelDescriptor>>() {
          @Override
          public void onSuccess(Paginator<ChannelDescriptor> channelDescriptorPaginator) {
            extractChannelsFromPaginatorAndPopulate(channelDescriptorPaginator, listener);
          }
        });

      }
    });
  }

  private void extractChannelsFromPaginatorAndPopulate(final Paginator<ChannelDescriptor> channelsPaginator,
                                                       final LoadChannelListener listener) {
    channels = new ArrayList<>();
    ChannelManager.this.channels.clear();
    channelExtractor.extractAndSortFromChannelDescriptor(channelsPaginator,
        new TaskCompletionListener<List<Channel>, String>() {
      @Override
      public void onSuccess(List<Channel> channels) {
        ChannelManager.this.channels.addAll(channels);
        Collections.sort(ChannelManager.this.channels, new CustomChannelComparator());
        ChannelManager.this.isRefreshingChannels = false;
        chatClientManager.setClientListener(ChannelManager.this);
        listener.onChannelsFinishedLoading(ChannelManager.this.channels);
      }

      @Override
      public void onError(String errorText) {
        System.out.println("Error populating channels: " + errorText);
      }
    });
  }

  public void createChannelWithName(String name, final StatusListener handler) {
    this.channelsObject
        .channelBuilder()
        .withFriendlyName(name)
        .withType(ChannelType.PUBLIC)
        .build(new CallbackListener<Channel>() {
          @Override
          public void onSuccess(final Channel newChannel) {
            handler.onSuccess();
          }

          @Override
          public void onError(ErrorInfo errorInfo) {
            handler.onError(errorInfo);
          }
        });
  }

  public void joinOrCreateGeneralChannelWithCompletion(final StatusListener listener) {
    channelsObject.getChannel(defaultChannelUniqueName, new CallbackListener<Channel>() {
      @Override
      public void onSuccess(Channel channel) {
        ChannelManager.this.generalChannel = channel;
        if (channel != null) {
          joinGeneralChannelWithCompletion(listener);
        } else {
          createGeneralChannelWithCompletion(listener);
        }
      }
    });
  }

  private void joinGeneralChannelWithCompletion(final StatusListener listener) {
    this.generalChannel.join(new StatusListener() {
      @Override
      public void onSuccess() {
        listener.onSuccess();
      }

      @Override
      public void onError(ErrorInfo errorInfo) {
        listener.onError(errorInfo);
      }
    });
  }

  private void createGeneralChannelWithCompletion(final StatusListener listener) {
    this.channelsObject
        .channelBuilder()
        .withFriendlyName(defaultChannelName)
        .withUniqueName(defaultChannelUniqueName)
        .withType(ChannelType.PUBLIC)
        .build(new CallbackListener<Channel>() {
          @Override
          public void onSuccess(final Channel channel) {
            ChannelManager.this.generalChannel = channel;
            ChannelManager.this.channels.add(channel);
            joinGeneralChannelWithCompletion(listener);
          }

          @Override
          public void onError(ErrorInfo errorInfo) {
            listener.onError(errorInfo);
          }
        });
  }

  public void setChannelListener(ChatClientListener listener) {
    this.listener = listener;
  }

  private String getStringResource(int id) {
    Resources resources = TwilioChatApplication.get().getResources();
    return resources.getString(id);
  }

  @Override
  public void onChannelAdd(Channel channel) {
    if (listener != null) {
      listener.onChannelAdd(channel);
    }
  }

  @Override
  public void onChannelInvite(Channel channel) {

  }

  @Override
  public void onChannelChange(Channel channel) {
    if (listener != null) {
      listener.onChannelChange(channel);
    }
  }

  @Override
  public void onChannelDelete(Channel channel) {
    if (listener != null) {
      listener.onChannelDelete(channel);
    }
  }

  @Override
  public void onChannelSynchronizationChange(Channel channel) {
    if (listener != null) {
      listener.onChannelSynchronizationChange(channel);
    }
  }

  @Override
  public void onError(ErrorInfo errorInfo) {
    if (listener != null) {
      listener.onError(errorInfo);
    }
  }

  @Override
  public void onUserInfoChange(UserInfo userInfo, UserInfo.UpdateReason updateReason) {
    if (listener != null) {
      listener.onUserInfoChange(userInfo, updateReason);
    }
  }

  @Override
  public void onClientSynchronization(ChatClient.SynchronizationStatus synchronizationStatus) {

  }

  @Override
  public void onToastNotification(String s, String s1) {

  }

  @Override
  public void onToastSubscribed() {

  }

  @Override
  public void onToastFailed(ErrorInfo errorInfo) {

  }

  @Override
  public void onConnectionStateChange(ChatClient.ConnectionState connectionState) {

  }

  private Handler setupListenerHandler() {
    Looper looper;
    Handler handler;
    if ((looper = Looper.myLooper()) != null) {
      handler = new Handler(looper);
    } else if ((looper = Looper.getMainLooper()) != null) {
      handler = new Handler(looper);
    } else {
      throw new IllegalArgumentException("Channel Listener must have a Looper.");
    }
    return handler;
  }
}
app/src/main/java/com/twilio/twiliochat/chat/channels/ChannelManager.java
Join or Create a General Channel

app/src/main/java/com/twilio/twiliochat/chat/channels/ChannelManager.java

Now let's listen for some channel events.

Listen to 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:

  • onMessageAdd: When someone sends a message to the channel you are connected to.
  • onMemberJoin: When someone joins the channel.
  • onMemberDelete: When someone leaves the channel.

As you may have noticed, each one of these methods include useful objects as parameters. One example is the actual message that was added to the channel.

Loading Code Samples...
Language
package com.twilio.twiliochat.chat;

import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.TextView;

import com.twilio.chat.CallbackListener;
import com.twilio.chat.Channel;
import com.twilio.chat.ChannelListener;
import com.twilio.chat.ErrorInfo;
import com.twilio.chat.Member;
import com.twilio.chat.Message;
import com.twilio.chat.Messages;
import com.twilio.chat.StatusListener;
import com.twilio.twiliochat.R;
import com.twilio.twiliochat.chat.messages.JoinedStatusMessage;
import com.twilio.twiliochat.chat.messages.LeftStatusMessage;
import com.twilio.twiliochat.chat.messages.MessageAdapter;
import com.twilio.twiliochat.chat.messages.StatusMessage;

import java.util.List;

public class MainChatFragment extends Fragment implements ChannelListener {
  Context context;
  Activity mainActivity;
  Button sendButton;
  ListView messagesListView;
  EditText messageTextEdit;

  MessageAdapter messageAdapter;
  Channel currentChannel;
  Messages messagesObject;

  public MainChatFragment() {
  }

  public static MainChatFragment newInstance() {
    MainChatFragment fragment = new MainChatFragment();
    return fragment;
  }

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    context = this.getActivity();
    mainActivity = this.getActivity();
  }

  @Override
  public View onCreateView(LayoutInflater inflater, ViewGroup container,
                           Bundle savedInstanceState) {
    View view = inflater.inflate(R.layout.fragment_main_chat, container, false);
    sendButton = (Button) view.findViewById(R.id.buttonSend);
    messagesListView = (ListView) view.findViewById(R.id.listViewMessages);
    messageTextEdit = (EditText) view.findViewById(R.id.editTextMessage);

    messageAdapter = new MessageAdapter(mainActivity);
    messagesListView.setAdapter(messageAdapter);
    setUpListeners();
    setMessageInputEnabled(false);

    return view;
  }

  @Override
  public void onAttach(Context context) {
    super.onAttach(context);
  }

  @Override
  public void onDetach() {
    super.onDetach();
  }

  public Channel getCurrentChannel() {
    return currentChannel;
  }

  public void setCurrentChannel(Channel currentChannel, final StatusListener handler) {
    if (currentChannel == null) {
      this.currentChannel = null;
      return;
    }
    if (!currentChannel.equals(this.currentChannel)) {
      setMessageInputEnabled(false);
      this.currentChannel = currentChannel;
      this.currentChannel.addListener(this);
      if (this.currentChannel.getStatus() == Channel.ChannelStatus.JOINED) {
        loadMessages(handler);
      } else {
        this.currentChannel.join(new StatusListener() {
          @Override
          public void onSuccess() {
            loadMessages(handler);
          }

          @Override
          public void onError(ErrorInfo errorInfo) {
          }
        });
      }
    }
  }

  private void loadMessages(final StatusListener handler) {
    this.messagesObject = this.currentChannel.getMessages();

    if (messagesObject != null) {
      messagesObject.getLastMessages(100, new CallbackListener<List<Message>>() {
        @Override
        public void onSuccess(List<Message> messageList) {
          messageAdapter.setMessages(messageList);
          setMessageInputEnabled(true);
          messageTextEdit.requestFocus();
          handler.onSuccess();
        }
      });
    }
  }

  private void setUpListeners() {
    sendButton.setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View v) {
        sendMessage();
      }
    });
    messageTextEdit.setOnEditorActionListener(new TextView.OnEditorActionListener() {
      @Override
      public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
        return false;
      }
    });
  }

  private void sendMessage() {
    String messageText = getTextInput();
    if (messageText.length() == 0) {
      return;
    }
    Message newMessage = this.messagesObject.createMessage(messageText);
    this.messagesObject.sendMessage(newMessage, null);
    clearTextInput();
  }

  private void setMessageInputEnabled(final boolean enabled) {
    mainActivity.runOnUiThread(new Runnable() {
      @Override
      public void run() {
        MainChatFragment.this.sendButton.setEnabled(enabled);
        MainChatFragment.this.messageTextEdit.setEnabled(enabled);
      }
    });
  }

  private String getTextInput() {
    return messageTextEdit.getText().toString();
  }

  private void clearTextInput() {
    messageTextEdit.setText("");
  }

  @Override
  public void onMessageAdd(Message message) {
    messageAdapter.addMessage(message);
  }

  @Override
  public void onMemberJoin(Member member) {
    StatusMessage statusMessage = new JoinedStatusMessage(member.getUserInfo().getIdentity());
    this.messageAdapter.addStatusMessage(statusMessage);
  }

  @Override
  public void onMemberDelete(Member member) {
    StatusMessage statusMessage = new LeftStatusMessage(member.getUserInfo().getIdentity());
    this.messageAdapter.addStatusMessage(statusMessage);
  }

  @Override
  public void onMessageChange(Message message) {
  }

  @Override
  public void onMessageDelete(Message message) {
  }

  @Override
  public void onMemberChange(Member member) {
  }

  @Override
  public void onTypingStarted(Member member) {
  }

  @Override
  public void onTypingEnded(Member member) {
  }

  @Override
  public void onSynchronizationChange(Channel channel) {
  }
}
app/src/main/java/com/twilio/twiliochat/chat/MainChatFragment.java
Listen to Channel Events

app/src/main/java/com/twilio/twiliochat/chat/MainChatFragment.java

We've actually got a real chat app going here, but let's make it more interesting with multiple channels.

Join Other 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.

Loading Code Samples...
Language
package com.twilio.twiliochat.chat;

import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.TextView;

import com.twilio.chat.CallbackListener;
import com.twilio.chat.Channel;
import com.twilio.chat.ChannelListener;
import com.twilio.chat.ErrorInfo;
import com.twilio.chat.Member;
import com.twilio.chat.Message;
import com.twilio.chat.Messages;
import com.twilio.chat.StatusListener;
import com.twilio.twiliochat.R;
import com.twilio.twiliochat.chat.messages.JoinedStatusMessage;
import com.twilio.twiliochat.chat.messages.LeftStatusMessage;
import com.twilio.twiliochat.chat.messages.MessageAdapter;
import com.twilio.twiliochat.chat.messages.StatusMessage;

import java.util.List;

public class MainChatFragment extends Fragment implements ChannelListener {
  Context context;
  Activity mainActivity;
  Button sendButton;
  ListView messagesListView;
  EditText messageTextEdit;

  MessageAdapter messageAdapter;
  Channel currentChannel;
  Messages messagesObject;

  public MainChatFragment() {
  }

  public static MainChatFragment newInstance() {
    MainChatFragment fragment = new MainChatFragment();
    return fragment;
  }

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    context = this.getActivity();
    mainActivity = this.getActivity();
  }

  @Override
  public View onCreateView(LayoutInflater inflater, ViewGroup container,
                           Bundle savedInstanceState) {
    View view = inflater.inflate(R.layout.fragment_main_chat, container, false);
    sendButton = (Button) view.findViewById(R.id.buttonSend);
    messagesListView = (ListView) view.findViewById(R.id.listViewMessages);
    messageTextEdit = (EditText) view.findViewById(R.id.editTextMessage);

    messageAdapter = new MessageAdapter(mainActivity);
    messagesListView.setAdapter(messageAdapter);
    setUpListeners();
    setMessageInputEnabled(false);

    return view;
  }

  @Override
  public void onAttach(Context context) {
    super.onAttach(context);
  }

  @Override
  public void onDetach() {
    super.onDetach();
  }

  public Channel getCurrentChannel() {
    return currentChannel;
  }

  public void setCurrentChannel(Channel currentChannel, final StatusListener handler) {
    if (currentChannel == null) {
      this.currentChannel = null;
      return;
    }
    if (!currentChannel.equals(this.currentChannel)) {
      setMessageInputEnabled(false);
      this.currentChannel = currentChannel;
      this.currentChannel.addListener(this);
      if (this.currentChannel.getStatus() == Channel.ChannelStatus.JOINED) {
        loadMessages(handler);
      } else {
        this.currentChannel.join(new StatusListener() {
          @Override
          public void onSuccess() {
            loadMessages(handler);
          }

          @Override
          public void onError(ErrorInfo errorInfo) {
          }
        });
      }
    }
  }

  private void loadMessages(final StatusListener handler) {
    this.messagesObject = this.currentChannel.getMessages();

    if (messagesObject != null) {
      messagesObject.getLastMessages(100, new CallbackListener<List<Message>>() {
        @Override
        public void onSuccess(List<Message> messageList) {
          messageAdapter.setMessages(messageList);
          setMessageInputEnabled(true);
          messageTextEdit.requestFocus();
          handler.onSuccess();
        }
      });
    }
  }

  private void setUpListeners() {
    sendButton.setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View v) {
        sendMessage();
      }
    });
    messageTextEdit.setOnEditorActionListener(new TextView.OnEditorActionListener() {
      @Override
      public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
        return false;
      }
    });
  }

  private void sendMessage() {
    String messageText = getTextInput();
    if (messageText.length() == 0) {
      return;
    }
    Message newMessage = this.messagesObject.createMessage(messageText);
    this.messagesObject.sendMessage(newMessage, null);
    clearTextInput();
  }

  private void setMessageInputEnabled(final boolean enabled) {
    mainActivity.runOnUiThread(new Runnable() {
      @Override
      public void run() {
        MainChatFragment.this.sendButton.setEnabled(enabled);
        MainChatFragment.this.messageTextEdit.setEnabled(enabled);
      }
    });
  }

  private String getTextInput() {
    return messageTextEdit.getText().toString();
  }

  private void clearTextInput() {
    messageTextEdit.setText("");
  }

  @Override
  public void onMessageAdd(Message message) {
    messageAdapter.addMessage(message);
  }

  @Override
  public void onMemberJoin(Member member) {
    StatusMessage statusMessage = new JoinedStatusMessage(member.getUserInfo().getIdentity());
    this.messageAdapter.addStatusMessage(statusMessage);
  }

  @Override
  public void onMemberDelete(Member member) {
    StatusMessage statusMessage = new LeftStatusMessage(member.getUserInfo().getIdentity());
    this.messageAdapter.addStatusMessage(statusMessage);
  }

  @Override
  public void onMessageChange(Message message) {
  }

  @Override
  public void onMessageDelete(Message message) {
  }

  @Override
  public void onMemberChange(Member member) {
  }

  @Override
  public void onTypingStarted(Member member) {
  }

  @Override
  public void onTypingEnded(Member member) {
  }

  @Override
  public void onSynchronizationChange(Channel channel) {
  }
}
app/src/main/java/com/twilio/twiliochat/chat/MainChatFragment.java
Join Other Channels

app/src/main/java/com/twilio/twiliochat/chat/MainChatFragment.java

If we can join other channels, we'll need some way for a super user to create new channels (and delete old ones).

Create a Channel

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 general channel to set a unique name. There's a list of methods you can use in the client library API docs.

Loading Code Samples...
Language
package com.twilio.twiliochat.chat.channels;

import android.content.res.Resources;
import android.os.Handler;
import android.os.Looper;

import com.twilio.chat.CallbackListener;
import com.twilio.chat.Channel;
import com.twilio.chat.Channel.ChannelType;
import com.twilio.chat.ChannelDescriptor;
import com.twilio.chat.Channels;
import com.twilio.chat.ChatClient;
import com.twilio.chat.ChatClientListener;
import com.twilio.chat.ErrorInfo;
import com.twilio.chat.Paginator;
import com.twilio.chat.StatusListener;
import com.twilio.chat.UserInfo;
import com.twilio.twiliochat.R;
import com.twilio.twiliochat.application.TwilioChatApplication;
import com.twilio.twiliochat.chat.ChatClientManager;
import com.twilio.twiliochat.chat.listeners.TaskCompletionListener;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class ChannelManager implements ChatClientListener {
  private static ChannelManager sharedManager = new ChannelManager();
  public Channel generalChannel;
  private ChatClientManager chatClientManager;
  private ChannelExtractor channelExtractor;
  private List<Channel> channels;
  private Channels channelsObject;
  private ChatClientListener listener;
  private String defaultChannelName;
  private String defaultChannelUniqueName;
  private Handler handler;
  private Boolean isRefreshingChannels = false;

  private ChannelManager() {
    this.chatClientManager = TwilioChatApplication.get().getChatClientManager();
    this.channelExtractor = new ChannelExtractor();
    this.listener = this;
    defaultChannelName = getStringResource(R.string.default_channel_name);
    defaultChannelUniqueName = getStringResource(R.string.default_channel_unique_name);
    handler = setupListenerHandler();
  }

  public static ChannelManager getInstance() {
    return sharedManager;
  }

  public List<Channel> getChannels() {
    return channels;
  }

  public String getDefaultChannelName() {
    return this.defaultChannelName;
  }

  public void leaveChannelWithHandler(Channel channel, StatusListener handler) {
    channel.leave(handler);
  }

  public void deleteChannelWithHandler(Channel channel, StatusListener handler) {
    channel.destroy(handler);
  }

  public void populateChannels(final LoadChannelListener listener) {
    if (this.chatClientManager == null || this.isRefreshingChannels) {
      return;
    }
    this.isRefreshingChannels = true;

    handler.post(new Runnable() {
      @Override
      public void run() {
        channelsObject = chatClientManager.getChatClient().getChannels();

        channelsObject.getPublicChannels(new CallbackListener<Paginator<ChannelDescriptor>>() {
          @Override
          public void onSuccess(Paginator<ChannelDescriptor> channelDescriptorPaginator) {
            extractChannelsFromPaginatorAndPopulate(channelDescriptorPaginator, listener);
          }
        });

      }
    });
  }

  private void extractChannelsFromPaginatorAndPopulate(final Paginator<ChannelDescriptor> channelsPaginator,
                                                       final LoadChannelListener listener) {
    channels = new ArrayList<>();
    ChannelManager.this.channels.clear();
    channelExtractor.extractAndSortFromChannelDescriptor(channelsPaginator,
        new TaskCompletionListener<List<Channel>, String>() {
      @Override
      public void onSuccess(List<Channel> channels) {
        ChannelManager.this.channels.addAll(channels);
        Collections.sort(ChannelManager.this.channels, new CustomChannelComparator());
        ChannelManager.this.isRefreshingChannels = false;
        chatClientManager.setClientListener(ChannelManager.this);
        listener.onChannelsFinishedLoading(ChannelManager.this.channels);
      }

      @Override
      public void onError(String errorText) {
        System.out.println("Error populating channels: " + errorText);
      }
    });
  }

  public void createChannelWithName(String name, final StatusListener handler) {
    this.channelsObject
        .channelBuilder()
        .withFriendlyName(name)
        .withType(ChannelType.PUBLIC)
        .build(new CallbackListener<Channel>() {
          @Override
          public void onSuccess(final Channel newChannel) {
            handler.onSuccess();
          }

          @Override
          public void onError(ErrorInfo errorInfo) {
            handler.onError(errorInfo);
          }
        });
  }

  public void joinOrCreateGeneralChannelWithCompletion(final StatusListener listener) {
    channelsObject.getChannel(defaultChannelUniqueName, new CallbackListener<Channel>() {
      @Override
      public void onSuccess(Channel channel) {
        ChannelManager.this.generalChannel = channel;
        if (channel != null) {
          joinGeneralChannelWithCompletion(listener);
        } else {
          createGeneralChannelWithCompletion(listener);
        }
      }
    });
  }

  private void joinGeneralChannelWithCompletion(final StatusListener listener) {
    this.generalChannel.join(new StatusListener() {
      @Override
      public void onSuccess() {
        listener.onSuccess();
      }

      @Override
      public void onError(ErrorInfo errorInfo) {
        listener.onError(errorInfo);
      }
    });
  }

  private void createGeneralChannelWithCompletion(final StatusListener listener) {
    this.channelsObject
        .channelBuilder()
        .withFriendlyName(defaultChannelName)
        .withUniqueName(defaultChannelUniqueName)
        .withType(ChannelType.PUBLIC)
        .build(new CallbackListener<Channel>() {
          @Override
          public void onSuccess(final Channel channel) {
            ChannelManager.this.generalChannel = channel;
            ChannelManager.this.channels.add(channel);
            joinGeneralChannelWithCompletion(listener);
          }

          @Override
          public void onError(ErrorInfo errorInfo) {
            listener.onError(errorInfo);
          }
        });
  }

  public void setChannelListener(ChatClientListener listener) {
    this.listener = listener;
  }

  private String getStringResource(int id) {
    Resources resources = TwilioChatApplication.get().getResources();
    return resources.getString(id);
  }

  @Override
  public void onChannelAdd(Channel channel) {
    if (listener != null) {
      listener.onChannelAdd(channel);
    }
  }

  @Override
  public void onChannelInvite(Channel channel) {

  }

  @Override
  public void onChannelChange(Channel channel) {
    if (listener != null) {
      listener.onChannelChange(channel);
    }
  }

  @Override
  public void onChannelDelete(Channel channel) {
    if (listener != null) {
      listener.onChannelDelete(channel);
    }
  }

  @Override
  public void onChannelSynchronizationChange(Channel channel) {
    if (listener != null) {
      listener.onChannelSynchronizationChange(channel);
    }
  }

  @Override
  public void onError(ErrorInfo errorInfo) {
    if (listener != null) {
      listener.onError(errorInfo);
    }
  }

  @Override
  public void onUserInfoChange(UserInfo userInfo, UserInfo.UpdateReason updateReason) {
    if (listener != null) {
      listener.onUserInfoChange(userInfo, updateReason);
    }
  }

  @Override
  public void onClientSynchronization(ChatClient.SynchronizationStatus synchronizationStatus) {

  }

  @Override
  public void onToastNotification(String s, String s1) {

  }

  @Override
  public void onToastSubscribed() {

  }

  @Override
  public void onToastFailed(ErrorInfo errorInfo) {

  }

  @Override
  public void onConnectionStateChange(ChatClient.ConnectionState connectionState) {

  }

  private Handler setupListenerHandler() {
    Looper looper;
    Handler handler;
    if ((looper = Looper.myLooper()) != null) {
      handler = new Handler(looper);
    } else if ((looper = Looper.getMainLooper()) != null) {
      handler = new Handler(looper);
    } else {
      throw new IllegalArgumentException("Channel Listener must have a Looper.");
    }
    return handler;
  }
}
app/src/main/java/com/twilio/twiliochat/chat/channels/ChannelManager.java
Create a Channel

app/src/main/java/com/twilio/twiliochat/chat/channels/ChannelManager.java

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!

Delete a Channel

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.

Loading Code Samples...
Language
package com.twilio.twiliochat.chat.channels;

import android.content.res.Resources;
import android.os.Handler;
import android.os.Looper;

import com.twilio.chat.CallbackListener;
import com.twilio.chat.Channel;
import com.twilio.chat.Channel.ChannelType;
import com.twilio.chat.ChannelDescriptor;
import com.twilio.chat.Channels;
import com.twilio.chat.ChatClient;
import com.twilio.chat.ChatClientListener;
import com.twilio.chat.ErrorInfo;
import com.twilio.chat.Paginator;
import com.twilio.chat.StatusListener;
import com.twilio.chat.UserInfo;
import com.twilio.twiliochat.R;
import com.twilio.twiliochat.application.TwilioChatApplication;
import com.twilio.twiliochat.chat.ChatClientManager;
import com.twilio.twiliochat.chat.listeners.TaskCompletionListener;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class ChannelManager implements ChatClientListener {
  private static ChannelManager sharedManager = new ChannelManager();
  public Channel generalChannel;
  private ChatClientManager chatClientManager;
  private ChannelExtractor channelExtractor;
  private List<Channel> channels;
  private Channels channelsObject;
  private ChatClientListener listener;
  private String defaultChannelName;
  private String defaultChannelUniqueName;
  private Handler handler;
  private Boolean isRefreshingChannels = false;

  private ChannelManager() {
    this.chatClientManager = TwilioChatApplication.get().getChatClientManager();
    this.channelExtractor = new ChannelExtractor();
    this.listener = this;
    defaultChannelName = getStringResource(R.string.default_channel_name);
    defaultChannelUniqueName = getStringResource(R.string.default_channel_unique_name);
    handler = setupListenerHandler();
  }

  public static ChannelManager getInstance() {
    return sharedManager;
  }

  public List<Channel> getChannels() {
    return channels;
  }

  public String getDefaultChannelName() {
    return this.defaultChannelName;
  }

  public void leaveChannelWithHandler(Channel channel, StatusListener handler) {
    channel.leave(handler);
  }

  public void deleteChannelWithHandler(Channel channel, StatusListener handler) {
    channel.destroy(handler);
  }

  public void populateChannels(final LoadChannelListener listener) {
    if (this.chatClientManager == null || this.isRefreshingChannels) {
      return;
    }
    this.isRefreshingChannels = true;

    handler.post(new Runnable() {
      @Override
      public void run() {
        channelsObject = chatClientManager.getChatClient().getChannels();

        channelsObject.getPublicChannels(new CallbackListener<Paginator<ChannelDescriptor>>() {
          @Override
          public void onSuccess(Paginator<ChannelDescriptor> channelDescriptorPaginator) {
            extractChannelsFromPaginatorAndPopulate(channelDescriptorPaginator, listener);
          }
        });

      }
    });
  }

  private void extractChannelsFromPaginatorAndPopulate(final Paginator<ChannelDescriptor> channelsPaginator,
                                                       final LoadChannelListener listener) {
    channels = new ArrayList<>();
    ChannelManager.this.channels.clear();
    channelExtractor.extractAndSortFromChannelDescriptor(channelsPaginator,
        new TaskCompletionListener<List<Channel>, String>() {
      @Override
      public void onSuccess(List<Channel> channels) {
        ChannelManager.this.channels.addAll(channels);
        Collections.sort(ChannelManager.this.channels, new CustomChannelComparator());
        ChannelManager.this.isRefreshingChannels = false;
        chatClientManager.setClientListener(ChannelManager.this);
        listener.onChannelsFinishedLoading(ChannelManager.this.channels);
      }

      @Override
      public void onError(String errorText) {
        System.out.println("Error populating channels: " + errorText);
      }
    });
  }

  public void createChannelWithName(String name, final StatusListener handler) {
    this.channelsObject
        .channelBuilder()
        .withFriendlyName(name)
        .withType(ChannelType.PUBLIC)
        .build(new CallbackListener<Channel>() {
          @Override
          public void onSuccess(final Channel newChannel) {
            handler.onSuccess();
          }

          @Override
          public void onError(ErrorInfo errorInfo) {
            handler.onError(errorInfo);
          }
        });
  }

  public void joinOrCreateGeneralChannelWithCompletion(final StatusListener listener) {
    channelsObject.getChannel(defaultChannelUniqueName, new CallbackListener<Channel>() {
      @Override
      public void onSuccess(Channel channel) {
        ChannelManager.this.generalChannel = channel;
        if (channel != null) {
          joinGeneralChannelWithCompletion(listener);
        } else {
          createGeneralChannelWithCompletion(listener);
        }
      }
    });
  }

  private void joinGeneralChannelWithCompletion(final StatusListener listener) {
    this.generalChannel.join(new StatusListener() {
      @Override
      public void onSuccess() {
        listener.onSuccess();
      }

      @Override
      public void onError(ErrorInfo errorInfo) {
        listener.onError(errorInfo);
      }
    });
  }

  private void createGeneralChannelWithCompletion(final StatusListener listener) {
    this.channelsObject
        .channelBuilder()
        .withFriendlyName(defaultChannelName)
        .withUniqueName(defaultChannelUniqueName)
        .withType(ChannelType.PUBLIC)
        .build(new CallbackListener<Channel>() {
          @Override
          public void onSuccess(final Channel channel) {
            ChannelManager.this.generalChannel = channel;
            ChannelManager.this.channels.add(channel);
            joinGeneralChannelWithCompletion(listener);
          }

          @Override
          public void onError(ErrorInfo errorInfo) {
            listener.onError(errorInfo);
          }
        });
  }

  public void setChannelListener(ChatClientListener listener) {
    this.listener = listener;
  }

  private String getStringResource(int id) {
    Resources resources = TwilioChatApplication.get().getResources();
    return resources.getString(id);
  }

  @Override
  public void onChannelAdd(Channel channel) {
    if (listener != null) {
      listener.onChannelAdd(channel);
    }
  }

  @Override
  public void onChannelInvite(Channel channel) {

  }

  @Override
  public void onChannelChange(Channel channel) {
    if (listener != null) {
      listener.onChannelChange(channel);
    }
  }

  @Override
  public void onChannelDelete(Channel channel) {
    if (listener != null) {
      listener.onChannelDelete(channel);
    }
  }

  @Override
  public void onChannelSynchronizationChange(Channel channel) {
    if (listener != null) {
      listener.onChannelSynchronizationChange(channel);
    }
  }

  @Override
  public void onError(ErrorInfo errorInfo) {
    if (listener != null) {
      listener.onError(errorInfo);
    }
  }

  @Override
  public void onUserInfoChange(UserInfo userInfo, UserInfo.UpdateReason updateReason) {
    if (listener != null) {
      listener.onUserInfoChange(userInfo, updateReason);
    }
  }

  @Override
  public void onClientSynchronization(ChatClient.SynchronizationStatus synchronizationStatus) {

  }

  @Override
  public void onToastNotification(String s, String s1) {

  }

  @Override
  public void onToastSubscribed() {

  }

  @Override
  public void onToastFailed(ErrorInfo errorInfo) {

  }

  @Override
  public void onConnectionStateChange(ChatClient.ConnectionState connectionState) {

  }

  private Handler setupListenerHandler() {
    Looper looper;
    Handler handler;
    if ((looper = Looper.myLooper()) != null) {
      handler = new Handler(looper);
    } else if ((looper = Looper.getMainLooper()) != null) {
      handler = new Handler(looper);
    } else {
      throw new IllegalArgumentException("Channel Listener must have a Looper.");
    }
    return handler;
  }
}
app/src/main/java/com/twilio/twiliochat/chat/channels/ChannelManager.java
Delete a Channel

app/src/main/java/com/twilio/twiliochat/chat/channels/ChannelManager.java

That's it! We've built an Android application with the Twilio Android SDK. Now you are more than prepared to set up your own chat application.

Where to Next?

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.

SMS and MMS Notifications

Learn how to build a server notification system that will alert all administrators via SMS when a server outage occurs.

Did this help?

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.

Mario Celi
David Prothero
Jose Oliveros
Andrew Baker
Agustin Camino

Need some help?

We all do sometimes; code is hard. Get help now from our support team, or lean on the wisdom of the crowd browsing the Twilio tag on Stack Overflow.

1 / 1
Loading Code Samples...
package com.twilio.twiliochat.chat.accesstoken;

import android.content.Context;
import android.content.res.Resources;
import android.provider.Settings;

import com.android.volley.Request;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.JsonObjectRequest;
import com.twilio.twiliochat.R;
import com.twilio.twiliochat.application.SessionManager;
import com.twilio.twiliochat.application.TwilioChatApplication;
import com.twilio.twiliochat.chat.listeners.TaskCompletionListener;

import org.json.JSONException;
import org.json.JSONObject;

import java.util.HashMap;
import java.util.Map;

public class AccessTokenFetcher {

  private Context context;

  public AccessTokenFetcher(Context context) {
    this.context = context;
  }

  public void fetch(final TaskCompletionListener<String, String> listener) {
    JSONObject obj = new JSONObject(getTokenRequestParams(context));
    String requestUrl = getStringResource(R.string.token_url);

    JsonObjectRequest jsonObjReq =
        new JsonObjectRequest(Request.Method.POST, requestUrl, obj, new Response.Listener<JSONObject>() {

          @Override
          public void onResponse(JSONObject response) {
            String token = null;
            try {
              token = response.getString("token");
            } catch (JSONException e) {
              e.printStackTrace();
              listener.onError("Failed to parse token JSON response");
            }
            listener.onSuccess(token);
          }
        }, new Response.ErrorListener() {

          @Override
          public void onErrorResponse(VolleyError error) {

            listener.onError("Failed to fetch token");
          }
        });
    jsonObjReq.setShouldCache(false);
    TokenRequest.getInstance().addToRequestQueue(jsonObjReq);
  }

  private Map<String, String> getTokenRequestParams(Context context) {
    String androidId =
        Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID);
    Map<String, String> params = new HashMap<>();
    params.put("deviceId", androidId);
    params.put("identity", SessionManager.getInstance().getUsername());
    return params;
  }

  private String getStringResource(int id) {
    Resources resources = TwilioChatApplication.get().getResources();
    return resources.getString(id);
  }

}
package com.twilio.twiliochat.chat;

import android.content.Context;

import com.twilio.chat.CallbackListener;
import com.twilio.chat.ChatClient;
import com.twilio.chat.ErrorInfo;
import com.twilio.twiliochat.chat.listeners.TaskCompletionListener;

public class ChatClientBuilder extends CallbackListener<ChatClient> {

  private Context context;
  private TaskCompletionListener<ChatClient, String> buildListener;

  public ChatClientBuilder(Context context) {
    this.context = context;
  }

  public void build(String token, final TaskCompletionListener<ChatClient, String> listener) {
    ChatClient.Properties props =
        new ChatClient.Properties.Builder()
            .setSynchronizationStrategy(ChatClient.SynchronizationStrategy.CHANNELS_LIST)
            .setRegion("us1")
            .createProperties();

    this.buildListener = listener;
    ChatClient.create(context.getApplicationContext(),
        token,
        props,
        this);
  }


  @Override
  public void onSuccess(ChatClient chatClient) {
    this.buildListener.onSuccess(chatClient);
  }

  @Override
  public void onError(ErrorInfo errorInfo) {
    this.buildListener.onError(errorInfo.getErrorText());
  }
}
package com.twilio.twiliochat.chat.channels;

import android.content.res.Resources;
import android.os.Handler;
import android.os.Looper;

import com.twilio.chat.CallbackListener;
import com.twilio.chat.Channel;
import com.twilio.chat.Channel.ChannelType;
import com.twilio.chat.ChannelDescriptor;
import com.twilio.chat.Channels;
import com.twilio.chat.ChatClient;
import com.twilio.chat.ChatClientListener;
import com.twilio.chat.ErrorInfo;
import com.twilio.chat.Paginator;
import com.twilio.chat.StatusListener;
import com.twilio.chat.UserInfo;
import com.twilio.twiliochat.R;
import com.twilio.twiliochat.application.TwilioChatApplication;
import com.twilio.twiliochat.chat.ChatClientManager;
import com.twilio.twiliochat.chat.listeners.TaskCompletionListener;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class ChannelManager implements ChatClientListener {
  private static ChannelManager sharedManager = new ChannelManager();
  public Channel generalChannel;
  private ChatClientManager chatClientManager;
  private ChannelExtractor channelExtractor;
  private List<Channel> channels;
  private Channels channelsObject;
  private ChatClientListener listener;
  private String defaultChannelName;
  private String defaultChannelUniqueName;
  private Handler handler;
  private Boolean isRefreshingChannels = false;

  private ChannelManager() {
    this.chatClientManager = TwilioChatApplication.get().getChatClientManager();
    this.channelExtractor = new ChannelExtractor();
    this.listener = this;
    defaultChannelName = getStringResource(R.string.default_channel_name);
    defaultChannelUniqueName = getStringResource(R.string.default_channel_unique_name);
    handler = setupListenerHandler();
  }

  public static ChannelManager getInstance() {
    return sharedManager;
  }

  public List<Channel> getChannels() {
    return channels;
  }

  public String getDefaultChannelName() {
    return this.defaultChannelName;
  }

  public void leaveChannelWithHandler(Channel channel, StatusListener handler) {
    channel.leave(handler);
  }

  public void deleteChannelWithHandler(Channel channel, StatusListener handler) {
    channel.destroy(handler);
  }

  public void populateChannels(final LoadChannelListener listener) {
    if (this.chatClientManager == null || this.isRefreshingChannels) {
      return;
    }
    this.isRefreshingChannels = true;

    handler.post(new Runnable() {
      @Override
      public void run() {
        channelsObject = chatClientManager.getChatClient().getChannels();

        channelsObject.getPublicChannels(new CallbackListener<Paginator<ChannelDescriptor>>() {
          @Override
          public void onSuccess(Paginator<ChannelDescriptor> channelDescriptorPaginator) {
            extractChannelsFromPaginatorAndPopulate(channelDescriptorPaginator, listener);
          }
        });

      }
    });
  }

  private void extractChannelsFromPaginatorAndPopulate(final Paginator<ChannelDescriptor> channelsPaginator,
                                                       final LoadChannelListener listener) {
    channels = new ArrayList<>();
    ChannelManager.this.channels.clear();
    channelExtractor.extractAndSortFromChannelDescriptor(channelsPaginator,
        new TaskCompletionListener<List<Channel>, String>() {
      @Override
      public void onSuccess(List<Channel> channels) {
        ChannelManager.this.channels.addAll(channels);
        Collections.sort(ChannelManager.this.channels, new CustomChannelComparator());
        ChannelManager.this.isRefreshingChannels = false;
        chatClientManager.setClientListener(ChannelManager.this);
        listener.onChannelsFinishedLoading(ChannelManager.this.channels);
      }

      @Override
      public void onError(String errorText) {
        System.out.println("Error populating channels: " + errorText);
      }
    });
  }

  public void createChannelWithName(String name, final StatusListener handler) {
    this.channelsObject
        .channelBuilder()
        .withFriendlyName(name)
        .withType(ChannelType.PUBLIC)
        .build(new CallbackListener<Channel>() {
          @Override
          public void onSuccess(final Channel newChannel) {
            handler.onSuccess();
          }

          @Override
          public void onError(ErrorInfo errorInfo) {
            handler.onError(errorInfo);
          }
        });
  }

  public void joinOrCreateGeneralChannelWithCompletion(final StatusListener listener) {
    channelsObject.getChannel(defaultChannelUniqueName, new CallbackListener<Channel>() {
      @Override
      public void onSuccess(Channel channel) {
        ChannelManager.this.generalChannel = channel;
        if (channel != null) {
          joinGeneralChannelWithCompletion(listener);
        } else {
          createGeneralChannelWithCompletion(listener);
        }
      }
    });
  }

  private void joinGeneralChannelWithCompletion(final StatusListener listener) {
    this.generalChannel.join(new StatusListener() {
      @Override
      public void onSuccess() {
        listener.onSuccess();
      }

      @Override
      public void onError(ErrorInfo errorInfo) {
        listener.onError(errorInfo);
      }
    });
  }

  private void createGeneralChannelWithCompletion(final StatusListener listener) {
    this.channelsObject
        .channelBuilder()
        .withFriendlyName(defaultChannelName)
        .withUniqueName(defaultChannelUniqueName)
        .withType(ChannelType.PUBLIC)
        .build(new CallbackListener<Channel>() {
          @Override
          public void onSuccess(final Channel channel) {
            ChannelManager.this.generalChannel = channel;
            ChannelManager.this.channels.add(channel);
            joinGeneralChannelWithCompletion(listener);
          }

          @Override
          public void onError(ErrorInfo errorInfo) {
            listener.onError(errorInfo);
          }
        });
  }

  public void setChannelListener(ChatClientListener listener) {
    this.listener = listener;
  }

  private String getStringResource(int id) {
    Resources resources = TwilioChatApplication.get().getResources();
    return resources.getString(id);
  }

  @Override
  public void onChannelAdd(Channel channel) {
    if (listener != null) {
      listener.onChannelAdd(channel);
    }
  }

  @Override
  public void onChannelInvite(Channel channel) {

  }

  @Override
  public void onChannelChange(Channel channel) {
    if (listener != null) {
      listener.onChannelChange(channel);
    }
  }

  @Override
  public void onChannelDelete(Channel channel) {
    if (listener != null) {
      listener.onChannelDelete(channel);
    }
  }

  @Override
  public void onChannelSynchronizationChange(Channel channel) {
    if (listener != null) {
      listener.onChannelSynchronizationChange(channel);
    }
  }

  @Override
  public void onError(ErrorInfo errorInfo) {
    if (listener != null) {
      listener.onError(errorInfo);
    }
  }

  @Override
  public void onUserInfoChange(UserInfo userInfo, UserInfo.UpdateReason updateReason) {
    if (listener != null) {
      listener.onUserInfoChange(userInfo, updateReason);
    }
  }

  @Override
  public void onClientSynchronization(ChatClient.SynchronizationStatus synchronizationStatus) {

  }

  @Override
  public void onToastNotification(String s, String s1) {

  }

  @Override
  public void onToastSubscribed() {

  }

  @Override
  public void onToastFailed(ErrorInfo errorInfo) {

  }

  @Override
  public void onConnectionStateChange(ChatClient.ConnectionState connectionState) {

  }

  private Handler setupListenerHandler() {
    Looper looper;
    Handler handler;
    if ((looper = Looper.myLooper()) != null) {
      handler = new Handler(looper);
    } else if ((looper = Looper.getMainLooper()) != null) {
      handler = new Handler(looper);
    } else {
      throw new IllegalArgumentException("Channel Listener must have a Looper.");
    }
    return handler;
  }
}
package com.twilio.twiliochat.chat;

import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.Resources;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.view.GravityCompat;
import android.support.v4.widget.DrawerLayout;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.app.ActionBarDrawerToggle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.Button;
import android.widget.ListView;
import android.widget.TextView;

import com.twilio.chat.Channel;
import com.twilio.chat.ChatClient;
import com.twilio.chat.ChatClientListener;
import com.twilio.chat.ErrorInfo;
import com.twilio.chat.StatusListener;
import com.twilio.chat.UserInfo;
import com.twilio.twiliochat.R;
import com.twilio.twiliochat.application.AlertDialogHandler;
import com.twilio.twiliochat.application.SessionManager;
import com.twilio.twiliochat.application.TwilioChatApplication;
import com.twilio.twiliochat.chat.channels.ChannelAdapter;
import com.twilio.twiliochat.chat.channels.ChannelManager;
import com.twilio.twiliochat.chat.channels.LoadChannelListener;
import com.twilio.twiliochat.chat.listeners.InputOnClickListener;
import com.twilio.twiliochat.chat.listeners.TaskCompletionListener;
import com.twilio.twiliochat.landing.LoginActivity;

import java.util.List;

public class MainChatActivity extends AppCompatActivity implements ChatClientListener {
  private Context context;
  private Activity mainActivity;
  private Button logoutButton;
  private Button addChannelButton;
  private TextView usernameTextView;
  private ChatClientManager chatClientManager;
  private ListView channelsListView;
  private ChannelAdapter channelAdapter;
  private ChannelManager channelManager;
  private MainChatFragment chatFragment;
  private DrawerLayout drawer;
  private ProgressDialog progressDialog;
  private MenuItem leaveChannelMenuItem;
  private MenuItem deleteChannelMenuItem;
  private SwipeRefreshLayout refreshLayout;

  @Override
  protected void onDestroy() {
    super.onDestroy();
    new Handler().post(new Runnable() {
      @Override
      public void run() {
        chatClientManager.shutdown();
        TwilioChatApplication.get().getChatClientManager().setChatClient(null);
      }
    });
  }

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main_chat);
    Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
    setSupportActionBar(toolbar);

    drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
    ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(this, drawer, toolbar,
        R.string.navigation_drawer_open, R.string.navigation_drawer_close);
    drawer.setDrawerListener(toggle);
    toggle.syncState();

    refreshLayout = (SwipeRefreshLayout) findViewById(R.id.refreshLayout);

    FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();

    chatFragment = new MainChatFragment();
    fragmentTransaction.add(R.id.fragment_container, chatFragment);
    fragmentTransaction.commit();

    context = this;
    mainActivity = this;
    logoutButton = (Button) findViewById(R.id.buttonLogout);
    addChannelButton = (Button) findViewById(R.id.buttonAddChannel);
    usernameTextView = (TextView) findViewById(R.id.textViewUsername);
    channelsListView = (ListView) findViewById(R.id.listViewChannels);

    channelManager = ChannelManager.getInstance();
    setUsernameTextView();

    setUpListeners();
    checkTwilioClient();
  }

  private void setUpListeners() {
    logoutButton.setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View v) {
        promptLogout();
      }
    });
    addChannelButton.setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View v) {
        showAddChannelDialog();
      }
    });
    refreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
      @Override
      public void onRefresh() {
        refreshLayout.setRefreshing(true);
        refreshChannels();
      }
    });
    channelsListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
      @Override
      public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        setChannel(position);
      }
    });
  }

  @Override
  public void onBackPressed() {
    if (drawer.isDrawerOpen(GravityCompat.START)) {
      drawer.closeDrawer(GravityCompat.START);
    } else {
      super.onBackPressed();
    }
  }

  @Override
  public boolean onCreateOptionsMenu(Menu menu) {
    getMenuInflater().inflate(R.menu.main_chat, menu);
    this.leaveChannelMenuItem = menu.findItem(R.id.action_leave_channel);
    this.leaveChannelMenuItem.setVisible(false);
    this.deleteChannelMenuItem = menu.findItem(R.id.action_delete_channel);
    this.deleteChannelMenuItem.setVisible(false);
    return true;
  }

  @Override
  public boolean onOptionsItemSelected(MenuItem item) {
    int id = item.getItemId();

    if (id == R.id.action_leave_channel) {
      leaveCurrentChannel();
      return true;
    }
    if (id == R.id.action_delete_channel) {
      promptChannelDeletion();
    }

    return super.onOptionsItemSelected(item);
  }

  private String getStringResource(int id) {
    Resources resources = getResources();
    return resources.getString(id);
  }

  private void refreshChannels() {
    channelManager.populateChannels(new LoadChannelListener() {
      @Override
      public void onChannelsFinishedLoading(final List<Channel> channels) {
        runOnUiThread(new Runnable() {
          @Override
          public void run() {
            channelAdapter.setChannels(channels);
            refreshLayout.setRefreshing(false);
          }
        });
      }
    });
  }

  private void populateChannels() {
    channelManager.setChannelListener(this);
    channelManager.populateChannels(new LoadChannelListener() {
      @Override
      public void onChannelsFinishedLoading(List<Channel> channels) {
        channelAdapter = new ChannelAdapter(mainActivity, channels);
        channelsListView.setAdapter(channelAdapter);
        MainChatActivity.this.channelManager
            .joinOrCreateGeneralChannelWithCompletion(new StatusListener() {
              @Override
              public void onSuccess() {
                runOnUiThread(new Runnable() {
                  @Override
                  public void run() {
                    channelAdapter.notifyDataSetChanged();
                    stopActivityIndicator();
                    setChannel(0);
                  }
                });
              }

              @Override
              public void onError(ErrorInfo errorInfo) {
                showAlertWithMessage(getStringResource(R.string.generic_error));
              }
            });
      }
    });
  }

  private void setChannel(final int position) {
    List<Channel> channels = channelManager.getChannels();
    if (channels == null) {
      return;
    }
    final Channel currentChannel = chatFragment.getCurrentChannel();
    final Channel selectedChannel = channels.get(position);
    if (currentChannel != null && currentChannel.getSid().contentEquals(selectedChannel.getSid())) {
      drawer.closeDrawer(GravityCompat.START);
      return;
    }
    hideMenuItems(position);
    if (selectedChannel != null) {
      showActivityIndicator("Joining " + selectedChannel.getFriendlyName() + " channel");
      if (currentChannel != null && currentChannel.getStatus() == Channel.ChannelStatus.JOINED) {
        this.channelManager.leaveChannelWithHandler(currentChannel, new StatusListener() {
          @Override
          public void onSuccess() {
            joinChannel(selectedChannel);
          }

          @Override
          public void onError(ErrorInfo errorInfo) {
            stopActivityIndicator();
          }
        });
        return;
      }
      joinChannel(selectedChannel);
      stopActivityIndicator();
    } else {
      stopActivityIndicator();
      showAlertWithMessage(getStringResource(R.string.generic_error));
      System.out.println("Selected channel out of range");
    }
  }

  private void joinChannel(final Channel selectedChannel) {
    runOnUiThread(new Runnable() {
      @Override
      public void run() {
        chatFragment.setCurrentChannel(selectedChannel, new StatusListener() {
          @Override
          public void onSuccess() {
            MainChatActivity.this.stopActivityIndicator();
          }

          @Override
          public void onError(ErrorInfo errorInfo) {
          }
        });
        setTitle(selectedChannel.getFriendlyName());
        drawer.closeDrawer(GravityCompat.START);
      }
    });
  }

  private void hideMenuItems(final int position) {
    runOnUiThread(new Runnable() {
      @Override
      public void run() {
        MainChatActivity.this.leaveChannelMenuItem.setVisible(position != 0);
        MainChatActivity.this.deleteChannelMenuItem.setVisible(position != 0);
      }
    });
  }

  private void showAddChannelDialog() {
    String message = getStringResource(R.string.new_channel_prompt);
    AlertDialogHandler.displayInputDialog(message, context, new InputOnClickListener() {
      @Override
      public void onClick(String input) {
        if (input.length() == 0) {
          showAlertWithMessage(getStringResource(R.string.channel_name_required_message));
          return;
        }
        createChannelWithName(input);
      }
    });
  }

  private void createChannelWithName(String name) {
    name = name.trim();
    if (name.toLowerCase()
        .contentEquals(this.channelManager.getDefaultChannelName().toLowerCase())) {
      showAlertWithMessage(getStringResource(R.string.channel_name_equals_default_name));
      return;
    }
    this.channelManager.createChannelWithName(name, new StatusListener() {
      @Override
      public void onSuccess() {
        refreshChannels();
      }

      @Override
      public void onError(ErrorInfo errorInfo) {
        showAlertWithMessage(getStringResource(R.string.generic_error));
      }
    });
  }

  private void promptChannelDeletion() {
    String message = getStringResource(R.string.channel_delete_prompt_message);
    AlertDialogHandler.displayCancellableAlertWithHandler(message, context,
        new DialogInterface.OnClickListener() {
          @Override
          public void onClick(DialogInterface dialog, int which) {
            deleteCurrentChannel();
          }
        });
  }

  private void deleteCurrentChannel() {
    Channel currentChannel = chatFragment.getCurrentChannel();
    channelManager.deleteChannelWithHandler(currentChannel, new StatusListener() {
      @Override
      public void onSuccess() {
      }

      @Override
      public void onError(ErrorInfo errorInfo) {
        showAlertWithMessage(getStringResource(R.string.message_deletion_forbidden));
      }
    });
  }

  private void leaveCurrentChannel() {
    final Channel currentChannel = chatFragment.getCurrentChannel();
    if (currentChannel.getStatus() == Channel.ChannelStatus.NOT_PARTICIPATING) {
      setChannel(0);
      return;
    }
    channelManager.leaveChannelWithHandler(currentChannel, new StatusListener() {
      @Override
      public void onSuccess() {
        setChannel(0);
      }

      @Override
      public void onError(ErrorInfo errorInfo) {
        stopActivityIndicator();
      }
    });
  }

  private void checkTwilioClient() {
    showActivityIndicator(getStringResource(R.string.loading_channels_message));
    chatClientManager = TwilioChatApplication.get().getChatClientManager();
    if (chatClientManager.getChatClient() == null) {
      initializeClient();
    } else {
      populateChannels();
    }
  }

  private void initializeClient() {
    chatClientManager.connectClient(new TaskCompletionListener<Void, String>() {
      @Override
      public void onSuccess(Void aVoid) {
        populateChannels();
      }

      @Override
      public void onError(String errorMessage) {
        stopActivityIndicator();
        showAlertWithMessage("Client connection error");
      }
    });
  }

  private void promptLogout() {
    final String message = getStringResource(R.string.logout_prompt_message);
    runOnUiThread(new Runnable() {
      @Override
      public void run() {
        AlertDialogHandler.displayCancellableAlertWithHandler(message, context,
            new DialogInterface.OnClickListener() {
              @Override
              public void onClick(DialogInterface dialog, int which) {
                SessionManager.getInstance().logoutUser();
                showLoginActivity();
              }
            });
      }
    });

  }

  private void showLoginActivity() {
    Intent launchIntent = new Intent();
    launchIntent.setClass(getApplicationContext(), LoginActivity.class);
    startActivity(launchIntent);
    finish();
  }

  private void setUsernameTextView() {
    String username =
        SessionManager.getInstance().getUserDetails().get(SessionManager.KEY_USERNAME);
    usernameTextView.setText(username);
  }

  private void stopActivityIndicator() {
    runOnUiThread(new Runnable() {
      @Override
      public void run() {
        if (progressDialog.isShowing()) {
          progressDialog.dismiss();
        }
      }
    });
  }

  private void showActivityIndicator(final String message) {
    runOnUiThread(new Runnable() {
      @Override
      public void run() {
        progressDialog = new ProgressDialog(MainChatActivity.this.mainActivity);
        progressDialog.setMessage(message);
        progressDialog.show();
        progressDialog.setCanceledOnTouchOutside(false);
        progressDialog.setCancelable(false);
      }
    });
  }

  private void showAlertWithMessage(final String message) {
    runOnUiThread(new Runnable() {
      @Override
      public void run() {
        AlertDialogHandler.displayAlertWithMessage(message, context);
      }
    });
  }

  @Override
  public void onChannelAdd(Channel channel) {
    System.out.println("Channel Added");
    refreshChannels();
  }

  @Override
  public void onChannelDelete(final Channel channel) {
    System.out.println("Channel Deleted");
    Channel currentChannel = chatFragment.getCurrentChannel();
    if (channel.getSid().contentEquals(currentChannel.getSid())) {
      chatFragment.setCurrentChannel(null, null);
      setChannel(0);
    }
    refreshChannels();
  }

  @Override
  public void onChannelInvite(Channel channel) {

  }

  @Override
  public void onChannelSynchronizationChange(Channel channel) {

  }

  @Override
  public void onError(ErrorInfo errorInfo) {

  }

  @Override
  public void onUserInfoChange(UserInfo userInfo, UserInfo.UpdateReason updateReason) {

  }

  @Override
  public void onClientSynchronization(ChatClient.SynchronizationStatus synchronizationStatus) {

  }

  @Override
  public void onToastNotification(String s, String s1) {

  }

  @Override
  public void onToastSubscribed() {

  }

  @Override
  public void onToastFailed(ErrorInfo errorInfo) {

  }

  @Override
  public void onConnectionStateChange(ChatClient.ConnectionState connectionState) {

  }

  @Override
  public void onChannelChange(Channel channel) {
  }
}
package com.twilio.twiliochat.chat.channels;

import android.content.res.Resources;
import android.os.Handler;
import android.os.Looper;

import com.twilio.chat.CallbackListener;
import com.twilio.chat.Channel;
import com.twilio.chat.Channel.ChannelType;
import com.twilio.chat.ChannelDescriptor;
import com.twilio.chat.Channels;
import com.twilio.chat.ChatClient;
import com.twilio.chat.ChatClientListener;
import com.twilio.chat.ErrorInfo;
import com.twilio.chat.Paginator;
import com.twilio.chat.StatusListener;
import com.twilio.chat.UserInfo;
import com.twilio.twiliochat.R;
import com.twilio.twiliochat.application.TwilioChatApplication;
import com.twilio.twiliochat.chat.ChatClientManager;
import com.twilio.twiliochat.chat.listeners.TaskCompletionListener;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class ChannelManager implements ChatClientListener {
  private static ChannelManager sharedManager = new ChannelManager();
  public Channel generalChannel;
  private ChatClientManager chatClientManager;
  private ChannelExtractor channelExtractor;
  private List<Channel> channels;
  private Channels channelsObject;
  private ChatClientListener listener;
  private String defaultChannelName;
  private String defaultChannelUniqueName;
  private Handler handler;
  private Boolean isRefreshingChannels = false;

  private ChannelManager() {
    this.chatClientManager = TwilioChatApplication.get().getChatClientManager();
    this.channelExtractor = new ChannelExtractor();
    this.listener = this;
    defaultChannelName = getStringResource(R.string.default_channel_name);
    defaultChannelUniqueName = getStringResource(R.string.default_channel_unique_name);
    handler = setupListenerHandler();
  }

  public static ChannelManager getInstance() {
    return sharedManager;
  }

  public List<Channel> getChannels() {
    return channels;
  }

  public String getDefaultChannelName() {
    return this.defaultChannelName;
  }

  public void leaveChannelWithHandler(Channel channel, StatusListener handler) {
    channel.leave(handler);
  }

  public void deleteChannelWithHandler(Channel channel, StatusListener handler) {
    channel.destroy(handler);
  }

  public void populateChannels(final LoadChannelListener listener) {
    if (this.chatClientManager == null || this.isRefreshingChannels) {
      return;
    }
    this.isRefreshingChannels = true;

    handler.post(new Runnable() {
      @Override
      public void run() {
        channelsObject = chatClientManager.getChatClient().getChannels();

        channelsObject.getPublicChannels(new CallbackListener<Paginator<ChannelDescriptor>>() {
          @Override
          public void onSuccess(Paginator<ChannelDescriptor> channelDescriptorPaginator) {
            extractChannelsFromPaginatorAndPopulate(channelDescriptorPaginator, listener);
          }
        });

      }
    });
  }

  private void extractChannelsFromPaginatorAndPopulate(final Paginator<ChannelDescriptor> channelsPaginator,
                                                       final LoadChannelListener listener) {
    channels = new ArrayList<>();
    ChannelManager.this.channels.clear();
    channelExtractor.extractAndSortFromChannelDescriptor(channelsPaginator,
        new TaskCompletionListener<List<Channel>, String>() {
      @Override
      public void onSuccess(List<Channel> channels) {
        ChannelManager.this.channels.addAll(channels);
        Collections.sort(ChannelManager.this.channels, new CustomChannelComparator());
        ChannelManager.this.isRefreshingChannels = false;
        chatClientManager.setClientListener(ChannelManager.this);
        listener.onChannelsFinishedLoading(ChannelManager.this.channels);
      }

      @Override
      public void onError(String errorText) {
        System.out.println("Error populating channels: " + errorText);
      }
    });
  }

  public void createChannelWithName(String name, final StatusListener handler) {
    this.channelsObject
        .channelBuilder()
        .withFriendlyName(name)
        .withType(ChannelType.PUBLIC)
        .build(new CallbackListener<Channel>() {
          @Override
          public void onSuccess(final Channel newChannel) {
            handler.onSuccess();
          }

          @Override
          public void onError(ErrorInfo errorInfo) {
            handler.onError(errorInfo);
          }
        });
  }

  public void joinOrCreateGeneralChannelWithCompletion(final StatusListener listener) {
    channelsObject.getChannel(defaultChannelUniqueName, new CallbackListener<Channel>() {
      @Override
      public void onSuccess(Channel channel) {
        ChannelManager.this.generalChannel = channel;
        if (channel != null) {
          joinGeneralChannelWithCompletion(listener);
        } else {
          createGeneralChannelWithCompletion(listener);
        }
      }
    });
  }

  private void joinGeneralChannelWithCompletion(final StatusListener listener) {
    this.generalChannel.join(new StatusListener() {
      @Override
      public void onSuccess() {
        listener.onSuccess();
      }

      @Override
      public void onError(ErrorInfo errorInfo) {
        listener.onError(errorInfo);
      }
    });
  }

  private void createGeneralChannelWithCompletion(final StatusListener listener) {
    this.channelsObject
        .channelBuilder()
        .withFriendlyName(defaultChannelName)
        .withUniqueName(defaultChannelUniqueName)
        .withType(ChannelType.PUBLIC)
        .build(new CallbackListener<Channel>() {
          @Override
          public void onSuccess(final Channel channel) {
            ChannelManager.this.generalChannel = channel;
            ChannelManager.this.channels.add(channel);
            joinGeneralChannelWithCompletion(listener);
          }

          @Override
          public void onError(ErrorInfo errorInfo) {
            listener.onError(errorInfo);
          }
        });
  }

  public void setChannelListener(ChatClientListener listener) {
    this.listener = listener;
  }

  private String getStringResource(int id) {
    Resources resources = TwilioChatApplication.get().getResources();
    return resources.getString(id);
  }

  @Override
  public void onChannelAdd(Channel channel) {
    if (listener != null) {
      listener.onChannelAdd(channel);
    }
  }

  @Override
  public void onChannelInvite(Channel channel) {

  }

  @Override
  public void onChannelChange(Channel channel) {
    if (listener != null) {
      listener.onChannelChange(channel);
    }
  }

  @Override
  public void onChannelDelete(Channel channel) {
    if (listener != null) {
      listener.onChannelDelete(channel);
    }
  }

  @Override
  public void onChannelSynchronizationChange(Channel channel) {
    if (listener != null) {
      listener.onChannelSynchronizationChange(channel);
    }
  }

  @Override
  public void onError(ErrorInfo errorInfo) {
    if (listener != null) {
      listener.onError(errorInfo);
    }
  }

  @Override
  public void onUserInfoChange(UserInfo userInfo, UserInfo.UpdateReason updateReason) {
    if (listener != null) {
      listener.onUserInfoChange(userInfo, updateReason);
    }
  }

  @Override
  public void onClientSynchronization(ChatClient.SynchronizationStatus synchronizationStatus) {

  }

  @Override
  public void onToastNotification(String s, String s1) {

  }

  @Override
  public void onToastSubscribed() {

  }

  @Override
  public void onToastFailed(ErrorInfo errorInfo) {

  }

  @Override
  public void onConnectionStateChange(ChatClient.ConnectionState connectionState) {

  }

  private Handler setupListenerHandler() {
    Looper looper;
    Handler handler;
    if ((looper = Looper.myLooper()) != null) {
      handler = new Handler(looper);
    } else if ((looper = Looper.getMainLooper()) != null) {
      handler = new Handler(looper);
    } else {
      throw new IllegalArgumentException("Channel Listener must have a Looper.");
    }
    return handler;
  }
}
package com.twilio.twiliochat.chat;

import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.TextView;

import com.twilio.chat.CallbackListener;
import com.twilio.chat.Channel;
import com.twilio.chat.ChannelListener;
import com.twilio.chat.ErrorInfo;
import com.twilio.chat.Member;
import com.twilio.chat.Message;
import com.twilio.chat.Messages;
import com.twilio.chat.StatusListener;
import com.twilio.twiliochat.R;
import com.twilio.twiliochat.chat.messages.JoinedStatusMessage;
import com.twilio.twiliochat.chat.messages.LeftStatusMessage;
import com.twilio.twiliochat.chat.messages.MessageAdapter;
import com.twilio.twiliochat.chat.messages.StatusMessage;

import java.util.List;

public class MainChatFragment extends Fragment implements ChannelListener {
  Context context;
  Activity mainActivity;
  Button sendButton;
  ListView messagesListView;
  EditText messageTextEdit;

  MessageAdapter messageAdapter;
  Channel currentChannel;
  Messages messagesObject;

  public MainChatFragment() {
  }

  public static MainChatFragment newInstance() {
    MainChatFragment fragment = new MainChatFragment();
    return fragment;
  }

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    context = this.getActivity();
    mainActivity = this.getActivity();
  }

  @Override
  public View onCreateView(LayoutInflater inflater, ViewGroup container,
                           Bundle savedInstanceState) {
    View view = inflater.inflate(R.layout.fragment_main_chat, container, false);
    sendButton = (Button) view.findViewById(R.id.buttonSend);
    messagesListView = (ListView) view.findViewById(R.id.listViewMessages);
    messageTextEdit = (EditText) view.findViewById(R.id.editTextMessage);

    messageAdapter = new MessageAdapter(mainActivity);
    messagesListView.setAdapter(messageAdapter);
    setUpListeners();
    setMessageInputEnabled(false);

    return view;
  }

  @Override
  public void onAttach(Context context) {
    super.onAttach(context);
  }

  @Override
  public void onDetach() {
    super.onDetach();
  }

  public Channel getCurrentChannel() {
    return currentChannel;
  }

  public void setCurrentChannel(Channel currentChannel, final StatusListener handler) {
    if (currentChannel == null) {
      this.currentChannel = null;
      return;
    }
    if (!currentChannel.equals(this.currentChannel)) {
      setMessageInputEnabled(false);
      this.currentChannel = currentChannel;
      this.currentChannel.addListener(this);
      if (this.currentChannel.getStatus() == Channel.ChannelStatus.JOINED) {
        loadMessages(handler);
      } else {
        this.currentChannel.join(new StatusListener() {
          @Override
          public void onSuccess() {
            loadMessages(handler);
          }

          @Override
          public void onError(ErrorInfo errorInfo) {
          }
        });
      }
    }
  }

  private void loadMessages(final StatusListener handler) {
    this.messagesObject = this.currentChannel.getMessages();

    if (messagesObject != null) {
      messagesObject.getLastMessages(100, new CallbackListener<List<Message>>() {
        @Override
        public void onSuccess(List<Message> messageList) {
          messageAdapter.setMessages(messageList);
          setMessageInputEnabled(true);
          messageTextEdit.requestFocus();
          handler.onSuccess();
        }
      });
    }
  }

  private void setUpListeners() {
    sendButton.setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View v) {
        sendMessage();
      }
    });
    messageTextEdit.setOnEditorActionListener(new TextView.OnEditorActionListener() {
      @Override
      public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
        return false;
      }
    });
  }

  private void sendMessage() {
    String messageText = getTextInput();
    if (messageText.length() == 0) {
      return;
    }
    Message newMessage = this.messagesObject.createMessage(messageText);
    this.messagesObject.sendMessage(newMessage, null);
    clearTextInput();
  }

  private void setMessageInputEnabled(final boolean enabled) {
    mainActivity.runOnUiThread(new Runnable() {
      @Override
      public void run() {
        MainChatFragment.this.sendButton.setEnabled(enabled);
        MainChatFragment.this.messageTextEdit.setEnabled(enabled);
      }
    });
  }

  private String getTextInput() {
    return messageTextEdit.getText().toString();
  }

  private void clearTextInput() {
    messageTextEdit.setText("");
  }

  @Override
  public void onMessageAdd(Message message) {
    messageAdapter.addMessage(message);
  }

  @Override
  public void onMemberJoin(Member member) {
    StatusMessage statusMessage = new JoinedStatusMessage(member.getUserInfo().getIdentity());
    this.messageAdapter.addStatusMessage(statusMessage);
  }

  @Override
  public void onMemberDelete(Member member) {
    StatusMessage statusMessage = new LeftStatusMessage(member.getUserInfo().getIdentity());
    this.messageAdapter.addStatusMessage(statusMessage);
  }

  @Override
  public void onMessageChange(Message message) {
  }

  @Override
  public void onMessageDelete(Message message) {
  }

  @Override
  public void onMemberChange(Member member) {
  }

  @Override
  public void onTypingStarted(Member member) {
  }

  @Override
  public void onTypingEnded(Member member) {
  }

  @Override
  public void onSynchronizationChange(Channel channel) {
  }
}
package com.twilio.twiliochat.chat;

import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.TextView;

import com.twilio.chat.CallbackListener;
import com.twilio.chat.Channel;
import com.twilio.chat.ChannelListener;
import com.twilio.chat.ErrorInfo;
import com.twilio.chat.Member;
import com.twilio.chat.Message;
import com.twilio.chat.Messages;
import com.twilio.chat.StatusListener;
import com.twilio.twiliochat.R;
import com.twilio.twiliochat.chat.messages.JoinedStatusMessage;
import com.twilio.twiliochat.chat.messages.LeftStatusMessage;
import com.twilio.twiliochat.chat.messages.MessageAdapter;
import com.twilio.twiliochat.chat.messages.StatusMessage;

import java.util.List;

public class MainChatFragment extends Fragment implements ChannelListener {
  Context context;
  Activity mainActivity;
  Button sendButton;
  ListView messagesListView;
  EditText messageTextEdit;

  MessageAdapter messageAdapter;
  Channel currentChannel;
  Messages messagesObject;

  public MainChatFragment() {
  }

  public static MainChatFragment newInstance() {
    MainChatFragment fragment = new MainChatFragment();
    return fragment;
  }

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    context = this.getActivity();
    mainActivity = this.getActivity();
  }

  @Override
  public View onCreateView(LayoutInflater inflater, ViewGroup container,
                           Bundle savedInstanceState) {
    View view = inflater.inflate(R.layout.fragment_main_chat, container, false);
    sendButton = (Button) view.findViewById(R.id.buttonSend);
    messagesListView = (ListView) view.findViewById(R.id.listViewMessages);
    messageTextEdit = (EditText) view.findViewById(R.id.editTextMessage);

    messageAdapter = new MessageAdapter(mainActivity);
    messagesListView.setAdapter(messageAdapter);
    setUpListeners();
    setMessageInputEnabled(false);

    return view;
  }

  @Override
  public void onAttach(Context context) {
    super.onAttach(context);
  }

  @Override
  public void onDetach() {
    super.onDetach();
  }

  public Channel getCurrentChannel() {
    return currentChannel;
  }

  public void setCurrentChannel(Channel currentChannel, final StatusListener handler) {
    if (currentChannel == null) {
      this.currentChannel = null;
      return;
    }
    if (!currentChannel.equals(this.currentChannel)) {
      setMessageInputEnabled(false);
      this.currentChannel = currentChannel;
      this.currentChannel.addListener(this);
      if (this.currentChannel.getStatus() == Channel.ChannelStatus.JOINED) {
        loadMessages(handler);
      } else {
        this.currentChannel.join(new StatusListener() {
          @Override
          public void onSuccess() {
            loadMessages(handler);
          }

          @Override
          public void onError(ErrorInfo errorInfo) {
          }
        });
      }
    }
  }

  private void loadMessages(final StatusListener handler) {
    this.messagesObject = this.currentChannel.getMessages();

    if (messagesObject != null) {
      messagesObject.getLastMessages(100, new CallbackListener<List<Message>>() {
        @Override
        public void onSuccess(List<Message> messageList) {
          messageAdapter.setMessages(messageList);
          setMessageInputEnabled(true);
          messageTextEdit.requestFocus();
          handler.onSuccess();
        }
      });
    }
  }

  private void setUpListeners() {
    sendButton.setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View v) {
        sendMessage();
      }
    });
    messageTextEdit.setOnEditorActionListener(new TextView.OnEditorActionListener() {
      @Override
      public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
        return false;
      }
    });
  }

  private void sendMessage() {
    String messageText = getTextInput();
    if (messageText.length() == 0) {
      return;
    }
    Message newMessage = this.messagesObject.createMessage(messageText);
    this.messagesObject.sendMessage(newMessage, null);
    clearTextInput();
  }

  private void setMessageInputEnabled(final boolean enabled) {
    mainActivity.runOnUiThread(new Runnable() {
      @Override
      public void run() {
        MainChatFragment.this.sendButton.setEnabled(enabled);
        MainChatFragment.this.messageTextEdit.setEnabled(enabled);
      }
    });
  }

  private String getTextInput() {
    return messageTextEdit.getText().toString();
  }

  private void clearTextInput() {
    messageTextEdit.setText("");
  }

  @Override
  public void onMessageAdd(Message message) {
    messageAdapter.addMessage(message);
  }

  @Override
  public void onMemberJoin(Member member) {
    StatusMessage statusMessage = new JoinedStatusMessage(member.getUserInfo().getIdentity());
    this.messageAdapter.addStatusMessage(statusMessage);
  }

  @Override
  public void onMemberDelete(Member member) {
    StatusMessage statusMessage = new LeftStatusMessage(member.getUserInfo().getIdentity());
    this.messageAdapter.addStatusMessage(statusMessage);
  }

  @Override
  public void onMessageChange(Message message) {
  }

  @Override
  public void onMessageDelete(Message message) {
  }

  @Override
  public void onMemberChange(Member member) {
  }

  @Override
  public void onTypingStarted(Member member) {
  }

  @Override
  public void onTypingEnded(Member member) {
  }

  @Override
  public void onSynchronizationChange(Channel channel) {
  }
}
package com.twilio.twiliochat.chat.channels;

import android.content.res.Resources;
import android.os.Handler;
import android.os.Looper;

import com.twilio.chat.CallbackListener;
import com.twilio.chat.Channel;
import com.twilio.chat.Channel.ChannelType;
import com.twilio.chat.ChannelDescriptor;
import com.twilio.chat.Channels;
import com.twilio.chat.ChatClient;
import com.twilio.chat.ChatClientListener;
import com.twilio.chat.ErrorInfo;
import com.twilio.chat.Paginator;
import com.twilio.chat.StatusListener;
import com.twilio.chat.UserInfo;
import com.twilio.twiliochat.R;
import com.twilio.twiliochat.application.TwilioChatApplication;
import com.twilio.twiliochat.chat.ChatClientManager;
import com.twilio.twiliochat.chat.listeners.TaskCompletionListener;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class ChannelManager implements ChatClientListener {
  private static ChannelManager sharedManager = new ChannelManager();
  public Channel generalChannel;
  private ChatClientManager chatClientManager;
  private ChannelExtractor channelExtractor;
  private List<Channel> channels;
  private Channels channelsObject;
  private ChatClientListener listener;
  private String defaultChannelName;
  private String defaultChannelUniqueName;
  private Handler handler;
  private Boolean isRefreshingChannels = false;

  private ChannelManager() {
    this.chatClientManager = TwilioChatApplication.get().getChatClientManager();
    this.channelExtractor = new ChannelExtractor();
    this.listener = this;
    defaultChannelName = getStringResource(R.string.default_channel_name);
    defaultChannelUniqueName = getStringResource(R.string.default_channel_unique_name);
    handler = setupListenerHandler();
  }

  public static ChannelManager getInstance() {
    return sharedManager;
  }

  public List<Channel> getChannels() {
    return channels;
  }

  public String getDefaultChannelName() {
    return this.defaultChannelName;
  }

  public void leaveChannelWithHandler(Channel channel, StatusListener handler) {
    channel.leave(handler);
  }

  public void deleteChannelWithHandler(Channel channel, StatusListener handler) {
    channel.destroy(handler);
  }

  public void populateChannels(final LoadChannelListener listener) {
    if (this.chatClientManager == null || this.isRefreshingChannels) {
      return;
    }
    this.isRefreshingChannels = true;

    handler.post(new Runnable() {
      @Override
      public void run() {
        channelsObject = chatClientManager.getChatClient().getChannels();

        channelsObject.getPublicChannels(new CallbackListener<Paginator<ChannelDescriptor>>() {
          @Override
          public void onSuccess(Paginator<ChannelDescriptor> channelDescriptorPaginator) {
            extractChannelsFromPaginatorAndPopulate(channelDescriptorPaginator, listener);
          }
        });

      }
    });
  }

  private void extractChannelsFromPaginatorAndPopulate(final Paginator<ChannelDescriptor> channelsPaginator,
                                                       final LoadChannelListener listener) {
    channels = new ArrayList<>();
    ChannelManager.this.channels.clear();
    channelExtractor.extractAndSortFromChannelDescriptor(channelsPaginator,
        new TaskCompletionListener<List<Channel>, String>() {
      @Override
      public void onSuccess(List<Channel> channels) {
        ChannelManager.this.channels.addAll(channels);
        Collections.sort(ChannelManager.this.channels, new CustomChannelComparator());
        ChannelManager.this.isRefreshingChannels = false;
        chatClientManager.setClientListener(ChannelManager.this);
        listener.onChannelsFinishedLoading(ChannelManager.this.channels);
      }

      @Override
      public void onError(String errorText) {
        System.out.println("Error populating channels: " + errorText);
      }
    });
  }

  public void createChannelWithName(String name, final StatusListener handler) {
    this.channelsObject
        .channelBuilder()
        .withFriendlyName(name)
        .withType(ChannelType.PUBLIC)
        .build(new CallbackListener<Channel>() {
          @Override
          public void onSuccess(final Channel newChannel) {
            handler.onSuccess();
          }

          @Override
          public void onError(ErrorInfo errorInfo) {
            handler.onError(errorInfo);
          }
        });
  }

  public void joinOrCreateGeneralChannelWithCompletion(final StatusListener listener) {
    channelsObject.getChannel(defaultChannelUniqueName, new CallbackListener<Channel>() {
      @Override
      public void onSuccess(Channel channel) {
        ChannelManager.this.generalChannel = channel;
        if (channel != null) {
          joinGeneralChannelWithCompletion(listener);
        } else {
          createGeneralChannelWithCompletion(listener);
        }
      }
    });
  }

  private void joinGeneralChannelWithCompletion(final StatusListener listener) {
    this.generalChannel.join(new StatusListener() {
      @Override
      public void onSuccess() {
        listener.onSuccess();
      }

      @Override
      public void onError(ErrorInfo errorInfo) {
        listener.onError(errorInfo);
      }
    });
  }

  private void createGeneralChannelWithCompletion(final StatusListener listener) {
    this.channelsObject
        .channelBuilder()
        .withFriendlyName(defaultChannelName)
        .withUniqueName(defaultChannelUniqueName)
        .withType(ChannelType.PUBLIC)
        .build(new CallbackListener<Channel>() {
          @Override
          public void onSuccess(final Channel channel) {
            ChannelManager.this.generalChannel = channel;
            ChannelManager.this.channels.add(channel);
            joinGeneralChannelWithCompletion(listener);
          }

          @Override
          public void onError(ErrorInfo errorInfo) {
            listener.onError(errorInfo);
          }
        });
  }

  public void setChannelListener(ChatClientListener listener) {
    this.listener = listener;
  }

  private String getStringResource(int id) {
    Resources resources = TwilioChatApplication.get().getResources();
    return resources.getString(id);
  }

  @Override
  public void onChannelAdd(Channel channel) {
    if (listener != null) {
      listener.onChannelAdd(channel);
    }
  }

  @Override
  public void onChannelInvite(Channel channel) {

  }

  @Override
  public void onChannelChange(Channel channel) {
    if (listener != null) {
      listener.onChannelChange(channel);
    }
  }

  @Override
  public void onChannelDelete(Channel channel) {
    if (listener != null) {
      listener.onChannelDelete(channel);
    }
  }

  @Override
  public void onChannelSynchronizationChange(Channel channel) {
    if (listener != null) {
      listener.onChannelSynchronizationChange(channel);
    }
  }

  @Override
  public void onError(ErrorInfo errorInfo) {
    if (listener != null) {
      listener.onError(errorInfo);
    }
  }

  @Override
  public void onUserInfoChange(UserInfo userInfo, UserInfo.UpdateReason updateReason) {
    if (listener != null) {
      listener.onUserInfoChange(userInfo, updateReason);
    }
  }

  @Override
  public void onClientSynchronization(ChatClient.SynchronizationStatus synchronizationStatus) {

  }

  @Override
  public void onToastNotification(String s, String s1) {

  }

  @Override
  public void onToastSubscribed() {

  }

  @Override
  public void onToastFailed(ErrorInfo errorInfo) {

  }

  @Override
  public void onConnectionStateChange(ChatClient.ConnectionState connectionState) {

  }

  private Handler setupListenerHandler() {
    Looper looper;
    Handler handler;
    if ((looper = Looper.myLooper()) != null) {
      handler = new Handler(looper);
    } else if ((looper = Looper.getMainLooper()) != null) {
      handler = new Handler(looper);
    } else {
      throw new IllegalArgumentException("Channel Listener must have a Looper.");
    }
    return handler;
  }
}
package com.twilio.twiliochat.chat.channels;

import android.content.res.Resources;
import android.os.Handler;
import android.os.Looper;

import com.twilio.chat.CallbackListener;
import com.twilio.chat.Channel;
import com.twilio.chat.Channel.ChannelType;
import com.twilio.chat.ChannelDescriptor;
import com.twilio.chat.Channels;
import com.twilio.chat.ChatClient;
import com.twilio.chat.ChatClientListener;
import com.twilio.chat.ErrorInfo;
import com.twilio.chat.Paginator;
import com.twilio.chat.StatusListener;
import com.twilio.chat.UserInfo;
import com.twilio.twiliochat.R;
import com.twilio.twiliochat.application.TwilioChatApplication;
import com.twilio.twiliochat.chat.ChatClientManager;
import com.twilio.twiliochat.chat.listeners.TaskCompletionListener;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class ChannelManager implements ChatClientListener {
  private static ChannelManager sharedManager = new ChannelManager();
  public Channel generalChannel;
  private ChatClientManager chatClientManager;
  private ChannelExtractor channelExtractor;
  private List<Channel> channels;
  private Channels channelsObject;
  private ChatClientListener listener;
  private String defaultChannelName;
  private String defaultChannelUniqueName;
  private Handler handler;
  private Boolean isRefreshingChannels = false;

  private ChannelManager() {
    this.chatClientManager = TwilioChatApplication.get().getChatClientManager();
    this.channelExtractor = new ChannelExtractor();
    this.listener = this;
    defaultChannelName = getStringResource(R.string.default_channel_name);
    defaultChannelUniqueName = getStringResource(R.string.default_channel_unique_name);
    handler = setupListenerHandler();
  }

  public static ChannelManager getInstance() {
    return sharedManager;
  }

  public List<Channel> getChannels() {
    return channels;
  }

  public String getDefaultChannelName() {
    return this.defaultChannelName;
  }

  public void leaveChannelWithHandler(Channel channel, StatusListener handler) {
    channel.leave(handler);
  }

  public void deleteChannelWithHandler(Channel channel, StatusListener handler) {
    channel.destroy(handler);
  }

  public void populateChannels(final LoadChannelListener listener) {
    if (this.chatClientManager == null || this.isRefreshingChannels) {
      return;
    }
    this.isRefreshingChannels = true;

    handler.post(new Runnable() {
      @Override
      public void run() {
        channelsObject = chatClientManager.getChatClient().getChannels();

        channelsObject.getPublicChannels(new CallbackListener<Paginator<ChannelDescriptor>>() {
          @Override
          public void onSuccess(Paginator<ChannelDescriptor> channelDescriptorPaginator) {
            extractChannelsFromPaginatorAndPopulate(channelDescriptorPaginator, listener);
          }
        });

      }
    });
  }

  private void extractChannelsFromPaginatorAndPopulate(final Paginator<ChannelDescriptor> channelsPaginator,
                                                       final LoadChannelListener listener) {
    channels = new ArrayList<>();
    ChannelManager.this.channels.clear();
    channelExtractor.extractAndSortFromChannelDescriptor(channelsPaginator,
        new TaskCompletionListener<List<Channel>, String>() {
      @Override
      public void onSuccess(List<Channel> channels) {
        ChannelManager.this.channels.addAll(channels);
        Collections.sort(ChannelManager.this.channels, new CustomChannelComparator());
        ChannelManager.this.isRefreshingChannels = false;
        chatClientManager.setClientListener(ChannelManager.this);
        listener.onChannelsFinishedLoading(ChannelManager.this.channels);
      }

      @Override
      public void onError(String errorText) {
        System.out.println("Error populating channels: " + errorText);
      }
    });
  }

  public void createChannelWithName(String name, final StatusListener handler) {
    this.channelsObject
        .channelBuilder()
        .withFriendlyName(name)
        .withType(ChannelType.PUBLIC)
        .build(new CallbackListener<Channel>() {
          @Override
          public void onSuccess(final Channel newChannel) {
            handler.onSuccess();
          }

          @Override
          public void onError(ErrorInfo errorInfo) {
            handler.onError(errorInfo);
          }
        });
  }

  public void joinOrCreateGeneralChannelWithCompletion(final StatusListener listener) {
    channelsObject.getChannel(defaultChannelUniqueName, new CallbackListener<Channel>() {
      @Override
      public void onSuccess(Channel channel) {
        ChannelManager.this.generalChannel = channel;
        if (channel != null) {
          joinGeneralChannelWithCompletion(listener);
        } else {
          createGeneralChannelWithCompletion(listener);
        }
      }
    });
  }

  private void joinGeneralChannelWithCompletion(final StatusListener listener) {
    this.generalChannel.join(new StatusListener() {
      @Override
      public void onSuccess() {
        listener.onSuccess();
      }

      @Override
      public void onError(ErrorInfo errorInfo) {
        listener.onError(errorInfo);
      }
    });
  }

  private void createGeneralChannelWithCompletion(final StatusListener listener) {
    this.channelsObject
        .channelBuilder()
        .withFriendlyName(defaultChannelName)
        .withUniqueName(defaultChannelUniqueName)
        .withType(ChannelType.PUBLIC)
        .build(new CallbackListener<Channel>() {
          @Override
          public void onSuccess(final Channel channel) {
            ChannelManager.this.generalChannel = channel;
            ChannelManager.this.channels.add(channel);
            joinGeneralChannelWithCompletion(listener);
          }

          @Override
          public void onError(ErrorInfo errorInfo) {
            listener.onError(errorInfo);
          }
        });
  }

  public void setChannelListener(ChatClientListener listener) {
    this.listener = listener;
  }

  private String getStringResource(int id) {
    Resources resources = TwilioChatApplication.get().getResources();
    return resources.getString(id);
  }

  @Override
  public void onChannelAdd(Channel channel) {
    if (listener != null) {
      listener.onChannelAdd(channel);
    }
  }

  @Override
  public void onChannelInvite(Channel channel) {

  }

  @Override
  public void onChannelChange(Channel channel) {
    if (listener != null) {
      listener.onChannelChange(channel);
    }
  }

  @Override
  public void onChannelDelete(Channel channel) {
    if (listener != null) {
      listener.onChannelDelete(channel);
    }
  }

  @Override
  public void onChannelSynchronizationChange(Channel channel) {
    if (listener != null) {
      listener.onChannelSynchronizationChange(channel);
    }
  }

  @Override
  public void onError(ErrorInfo errorInfo) {
    if (listener != null) {
      listener.onError(errorInfo);
    }
  }

  @Override
  public void onUserInfoChange(UserInfo userInfo, UserInfo.UpdateReason updateReason) {
    if (listener != null) {
      listener.onUserInfoChange(userInfo, updateReason);
    }
  }

  @Override
  public void onClientSynchronization(ChatClient.SynchronizationStatus synchronizationStatus) {

  }

  @Override
  public void onToastNotification(String s, String s1) {

  }

  @Override
  public void onToastSubscribed() {

  }

  @Override
  public void onToastFailed(ErrorInfo errorInfo) {

  }

  @Override
  public void onConnectionStateChange(ChatClient.ConnectionState connectionState) {

  }

  private Handler setupListenerHandler() {
    Looper looper;
    Handler handler;
    if ((looper = Looper.myLooper()) != null) {
      handler = new Handler(looper);
    } else if ((looper = Looper.getMainLooper()) != null) {
      handler = new Handler(looper);
    } else {
      throw new IllegalArgumentException("Channel Listener must have a Looper.");
    }
    return handler;
  }
}