Skip to contentSkip to navigationSkip to topbar
On this page

Exploring the Conversations Android Quickstart


What does the Conversations Android Quickstart do? How does it work? How would you add something similar to your own project? We'll cover all of these questions and more in this behind the scenes look at the example application code.

If you haven't had a chance to try out the Conversations Android Quickstart app(link takes you to an external page) yet, follow the instructions in the README to get it up and running.

You'll also need to supply an access token with a Chat grant for a Conversations Service in the strings.xml resource file before running the application. You can generate an access token with the Twilio Command Line Interface (CLI):

twilio token:chat --identity <The test username> --chat-service-sid <ISXXX...>
macOSWindowsLinux

The suggested way to install twilio-cli on macOS is to use Homebrew(link takes you to an external page). If you don't already have it installed, visit the Homebrew site(link takes you to an external page) for installation instructions and then return here.

Once you have installed Homebrew, run the following command to install twilio-cli:

brew tap twilio/brew && brew install twilio
(information)

Info

For other installation methods, see the Twilio CLI Quickstart.


Quickstart Overview

quickstart-overview page anchor

The example application code uses Java as the programming language. We also only have one activity class, named MainActivity. We chose not to use fragments in this quickstart for simplicity, but you can certainly use them with Conversations.

We built a class named QuickstartConversationsManager to handle the interactions with the Conversations SDK.

Within the quickstart application, you will find examples of the following:

When you build an application that uses Conversations, you may be able to use the QuickstartConversationsManager and MainActivity classes as a start for your project. You may also just want to take a look at how the quickstart works, and then build your own solution with the classes in the SDK!


Adding Twilio Conversations to your Application

adding-twilio-conversations-to-your-application page anchor

When you build your solutions with Twilio Conversations, you need a Conversations Android SDK for your mobile app. You can install this library using Gradle.

Conversations SDK

conversations-sdk page anchor

You would typically start by adding the ConversationsClient from the com.twilio.conversations package to your project, and then work with Conversation objects to send and retrieve Message objects for a given conversation. Other important classes are User, Participant, and Media.

While we cover some of the basics of the Conversations SDK in this Quickstart, you can also find reference Javadocs for each class and interface(link takes you to an external page). We also consider some of these topics in more detail in other pages in our docs, which we will link to in each section that has a corresponding guide.

The Conversations SDK for Android is only one half of the solution. You'll also need to build a server to support your mobile application. Twilio supports six different languages and platforms for you to build with. Java might be the best choice if you are an Android developer looking to try out web application development, but you can use any of these to build your server.

For your chosen language and/or platform, pick the appropriate Twilio Helper Library:

On each of these pages, you will find instructions for setting up the Twilio helper library (also called a "server-side SDK"). We recommend using dependency management for the Twilio libraries, and you'll find directions for the most common build tools for your platform.

(information)

Info

If you don't already have a Twilio account, sign up for a Twilio trial account(link takes you to an external page), and then create a new project. You'll also need to create an API Key and API Secret pair to call Twilio's REST API, whether you use one of the Twilio helper libraries, or make the API calls yourself.


Understanding Identity, Access Tokens, and Chat Grants

understanding-identity-access-tokens-and-chat-grants page anchor

Each chat user in your Conversations project needs an identity - this could be their user id, their username, or some kind of other identifier. You could certainly have anonymous users in your Conversations - for instance, a web chat popup with a customer service agent on an e-commerce website - but in that case, you would still want to issue some kind of identifier from your application.

Once you build Twilio Conversations into your project, you should generate an access token with a ChatGrant for end users, along with the identity value.

With the Conversations Android Quickstart, the easiest way to get started is to create an access token from the Twilio Command Line Interface (CLI).


Difference between Access Tokens, Auth Tokens and API Keys

difference-between-access-tokens-auth-tokens-and-api-keys page anchor

As part of this project, you will see that there are three different ways of providing credentials for Twilio - access tokens, auth tokens, and API keys. What is the difference between all of these different styles?

Access tokens provide short-lived credentials for a single end user to work with your Twilio service from a JavaScript application running in a web browser, or from a native iOS or Android mobile application. Use the Twilio helper libraries in your back end web services to create access tokens for your front end applications to consume. Alternatively, use the Twilio CLI to create access tokens for testing. These access tokens have a built-in expiration, and need to be refreshed from your server if your users have long-running connections. The Conversations client will update your application when access tokens are about to expire, or if they have expired, so that you can refresh the token.

Although the names are similar, authentication (or auth) tokens are not the same as access tokens, and cannot be used in the same way. The auth token pairs with your Twilio account identifier (also called the account SID) to provide authentication for the Twilio REST API. Your auth token should be treated with the same care that you would use to secure your Twilio password, and should never be included directly in source code, made available to a client application, or checked into a file in source control.

Similar to auth tokens, API key/secret pairs secure access to the Twilio REST API for your account. When you create an API key and secret pair from the Twilio console, the secret will only be shown once, and then it won't be recoverable. In your back end application, you would authenticate to Twilio with a combination of your account identifier (also known as the "Account SID"), an API key, and an API secret.

The advantage of API keys over auth tokens is that you can rotate API keys on your server application, especially if you use one API key and secret pair for each application cluster or instance. This way, you can have multiple credentials under your Twilio account, and if you need to swap out a key pair and then deactivate it, you can do it on an application basis, not on an account basis.

Storing Credentials Securely

storing-credentials-securely page anchor

Whether you use auth tokens or API keys, we suggest that you store those credentials securely, and do not check them into source control. There are many different options for managing secure credentials that depend on how and where you run your development, staging, and production environments.

When you develop locally, look into using a .env file with your project, usually in conjunction with a library named dotenv. For .NET Core, read our article on Setting Twilio Environment Variables in Windows 10 with PowerShell and .NET Core 3.0(link takes you to an external page) to learn a lot more about this topic!


Retrieving a Conversations Access Token

retrieving-a-conversations-access-token page anchor

For the Conversations Quickstart, you can generate an access token using the Twilio Command Line Interface (CLI), and then paste that into the strings.xml file. While this works for getting the quickstart up and running, you will want to replace this with your own function that retrieves an access token.

You can use OkHttp(link takes you to an external page), Volley(link takes you to an external page) or another HTTP library to make an authenticated HTTP request to your server, where the server code would provide an access token with a ChatGrant that sets the identity for the user based on your own authentication mechanism (such as an API key, or your own token).

Ideally, this method would be usable for three different scenarios:

  1. Initializing the Conversations Client when your application loads
  2. Refreshing the access token when the Conversations Client notifies your application that the token is about to expire
  3. Refreshing the access token when the Conversations Client notifies your application that the token did expire

Initializing the Conversations Client

initializing-the-conversations-client page anchor

The first step is to get an access token. Once you have an access token (a string value), you can initialize a Twilio Conversations Client. This client is the central class in the Conversations SDK, and you need to keep it around after initialization. The client is designed to be long-lived, and it will fire events off that your project can subscribe to.

You'll need to create your own listener for the Conversations Client that implements the ConversationsClientListener interface. In the quick start, we created a class named QuickstartConversationsManager to encapsulate our usage of the Conversations SDK.

Initializing the Conversations Client

initializing-the-conversations-client-1 page anchor
1
package com.twilio.conversationsquickstart;
2
3
import android.content.Context;
4
import android.util.Log;
5
6
import com.google.gson.Gson;
7
import com.twilio.conversations.CallbackListener;
8
import com.twilio.conversations.Conversation;
9
import com.twilio.conversations.ConversationListener;
10
import com.twilio.conversations.ConversationsClient;
11
import com.twilio.conversations.ConversationsClientListener;
12
import com.twilio.conversations.ErrorInfo;
13
import com.twilio.conversations.Participant;
14
import com.twilio.conversations.Message;
15
import com.twilio.conversations.StatusListener;
16
import com.twilio.conversations.User;
17
18
import org.jetbrains.annotations.Nullable;
19
20
import java.io.IOException;
21
import java.util.ArrayList;
22
import java.util.List;
23
24
import okhttp3.OkHttpClient;
25
import okhttp3.Request;
26
import okhttp3.Response;
27
28
interface QuickstartConversationsManagerListener {
29
void receivedNewMessage();
30
void messageSentCallback();
31
void reloadMessages();
32
}
33
34
interface TokenResponseListener {
35
void receivedTokenResponse(boolean success, @Nullable Exception exception);
36
}
37
38
interface AccessTokenListener {
39
void receivedAccessToken(@Nullable String token, @Nullable Exception exception);
40
}
41
42
43
class QuickstartConversationsManager {
44
45
// This is the unique name of the conversation we are using
46
private final static String DEFAULT_CONVERSATION_NAME = "general";
47
48
final private ArrayList<Message> messages = new ArrayList<>();
49
50
private ConversationsClient conversationsClient;
51
52
private Conversation conversation;
53
54
private QuickstartConversationsManagerListener conversationsManagerListener;
55
56
private String tokenURL = "";
57
58
private class TokenResponse {
59
String token;
60
}
61
62
void retrieveAccessTokenFromServer(final Context context, String identity,
63
final TokenResponseListener listener) {
64
65
// Set the chat token URL in your strings.xml file
66
String chatTokenURL = context.getString(R.string.chat_token_url);
67
68
if ("https://YOUR_DOMAIN_HERE.twil.io/chat-token".equals(chatTokenURL)) {
69
listener.receivedTokenResponse(false, new Exception("You need to replace the chat token URL in strings.xml"));
70
return;
71
}
72
73
tokenURL = chatTokenURL + "?identity=" + identity;
74
75
new Thread(new Runnable() {
76
@Override
77
public void run() {
78
retrieveToken(new AccessTokenListener() {
79
@Override
80
public void receivedAccessToken(@Nullable String token,
81
@Nullable Exception exception) {
82
if (token != null) {
83
ConversationsClient.Properties props = ConversationsClient.Properties.newBuilder().createProperties();
84
ConversationsClient.create(context, token, props, mConversationsClientCallback);
85
listener.receivedTokenResponse(true,null);
86
} else {
87
listener.receivedTokenResponse(false, exception);
88
}
89
}
90
});
91
}
92
}).start();
93
}
94
95
void initializeWithAccessToken(final Context context, final String token) {
96
97
ConversationsClient.Properties props = ConversationsClient.Properties.newBuilder().createProperties();
98
ConversationsClient.create(context, token, props, mConversationsClientCallback);
99
}
100
101
private void retrieveToken(AccessTokenListener listener) {
102
OkHttpClient client = new OkHttpClient();
103
104
Request request = new Request.Builder()
105
.url(tokenURL)
106
.build();
107
try (Response response = client.newCall(request).execute()) {
108
String responseBody = "";
109
if (response != null && response.body() != null) {
110
responseBody = response.body().string();
111
}
112
Log.d(MainActivity.TAG, "Response from server: " + responseBody);
113
Gson gson = new Gson();
114
TokenResponse tokenResponse = gson.fromJson(responseBody,TokenResponse.class);
115
String accessToken = tokenResponse.token;
116
Log.d(MainActivity.TAG, "Retrieved access token from server: " + accessToken);
117
listener.receivedAccessToken(accessToken, null);
118
119
}
120
catch (IOException ex) {
121
Log.e(MainActivity.TAG, ex.getLocalizedMessage(),ex);
122
listener.receivedAccessToken(null, ex);
123
}
124
}
125
126
void sendMessage(String messageBody) {
127
if (conversation != null) {
128
Message.Options options = Message.options().withBody(messageBody);
129
Log.d(MainActivity.TAG,"Message created");
130
conversation.sendMessage(options, new CallbackListener<Message>() {
131
@Override
132
public void onSuccess(Message message) {
133
if (conversationsManagerListener != null) {
134
conversationsManagerListener.messageSentCallback();
135
}
136
}
137
});
138
}
139
}
140
141
142
private void loadChannels() {
143
if (conversationsClient == null || conversationsClient.getMyConversations() == null) {
144
return;
145
}
146
conversationsClient.getConversation(DEFAULT_CONVERSATION_NAME, new CallbackListener<Conversation>() {
147
@Override
148
public void onSuccess(Conversation conversation) {
149
if (conversation != null) {
150
if (conversation.getStatus() == Conversation.ConversationStatus.JOINED
151
|| conversation.getStatus() == Conversation.ConversationStatus.NOT_PARTICIPATING) {
152
Log.d(MainActivity.TAG, "Already Exists in Conversation: " + DEFAULT_CONVERSATION_NAME);
153
QuickstartConversationsManager.this.conversation = conversation;
154
QuickstartConversationsManager.this.conversation.addListener(mDefaultConversationListener);
155
QuickstartConversationsManager.this.loadPreviousMessages(conversation);
156
} else {
157
Log.d(MainActivity.TAG, "Joining Conversation: " + DEFAULT_CONVERSATION_NAME);
158
joinConversation(conversation);
159
}
160
}
161
}
162
163
@Override
164
public void onError(ErrorInfo errorInfo) {
165
Log.e(MainActivity.TAG, "Error retrieving conversation: " + errorInfo.getMessage());
166
createConversation();
167
}
168
169
});
170
}
171
172
private void createConversation() {
173
Log.d(MainActivity.TAG, "Creating Conversation: " + DEFAULT_CONVERSATION_NAME);
174
175
conversationsClient.createConversation(DEFAULT_CONVERSATION_NAME,
176
new CallbackListener<Conversation>() {
177
@Override
178
public void onSuccess(Conversation conversation) {
179
if (conversation != null) {
180
Log.d(MainActivity.TAG, "Joining Conversation: " + DEFAULT_CONVERSATION_NAME);
181
joinConversation(conversation);
182
}
183
}
184
185
@Override
186
public void onError(ErrorInfo errorInfo) {
187
Log.e(MainActivity.TAG, "Error creating conversation: " + errorInfo.getMessage());
188
}
189
});
190
}
191
192
193
private void joinConversation(final Conversation conversation) {
194
Log.d(MainActivity.TAG, "Joining Conversation: " + conversation.getUniqueName());
195
if (conversation.getStatus() == Conversation.ConversationStatus.JOINED) {
196
197
QuickstartConversationsManager.this.conversation = conversation;
198
Log.d(MainActivity.TAG, "Already joined default conversation");
199
QuickstartConversationsManager.this.conversation.addListener(mDefaultConversationListener);
200
return;
201
}
202
203
204
conversation.join(new StatusListener() {
205
@Override
206
public void onSuccess() {
207
QuickstartConversationsManager.this.conversation = conversation;
208
Log.d(MainActivity.TAG, "Joined default conversation");
209
QuickstartConversationsManager.this.conversation.addListener(mDefaultConversationListener);
210
QuickstartConversationsManager.this.loadPreviousMessages(conversation);
211
}
212
213
@Override
214
public void onError(ErrorInfo errorInfo) {
215
Log.e(MainActivity.TAG, "Error joining conversation: " + errorInfo.getMessage());
216
}
217
});
218
}
219
220
private void loadPreviousMessages(final Conversation conversation) {
221
conversation.getLastMessages(100,
222
new CallbackListener<List<Message>>() {
223
@Override
224
public void onSuccess(List<Message> result) {
225
messages.addAll(result);
226
if (conversationsManagerListener != null) {
227
conversationsManagerListener.reloadMessages();
228
}
229
}
230
});
231
}
232
233
private final ConversationsClientListener mConversationsClientListener =
234
new ConversationsClientListener() {
235
236
@Override
237
public void onConversationAdded(Conversation conversation) {
238
239
}
240
241
@Override
242
public void onConversationUpdated(Conversation conversation, Conversation.UpdateReason updateReason) {
243
244
}
245
246
@Override
247
public void onConversationDeleted(Conversation conversation) {
248
249
}
250
251
@Override
252
public void onConversationSynchronizationChange(Conversation conversation) {
253
254
}
255
256
@Override
257
public void onError(ErrorInfo errorInfo) {
258
259
}
260
261
@Override
262
public void onUserUpdated(User user, User.UpdateReason updateReason) {
263
264
}
265
266
@Override
267
public void onUserSubscribed(User user) {
268
269
}
270
271
@Override
272
public void onUserUnsubscribed(User user) {
273
274
}
275
276
@Override
277
public void onClientSynchronization(ConversationsClient.SynchronizationStatus synchronizationStatus) {
278
if (synchronizationStatus == ConversationsClient.SynchronizationStatus.COMPLETED) {
279
loadChannels();
280
}
281
}
282
283
@Override
284
public void onNewMessageNotification(String s, String s1, long l) {
285
286
}
287
288
@Override
289
public void onAddedToConversationNotification(String s) {
290
291
}
292
293
@Override
294
public void onRemovedFromConversationNotification(String s) {
295
296
}
297
298
@Override
299
public void onNotificationSubscribed() {
300
301
}
302
303
@Override
304
public void onNotificationFailed(ErrorInfo errorInfo) {
305
306
}
307
308
@Override
309
public void onConnectionStateChange(ConversationsClient.ConnectionState connectionState) {
310
311
}
312
313
@Override
314
public void onTokenExpired() {
315
316
}
317
318
@Override
319
public void onTokenAboutToExpire() {
320
retrieveToken(new AccessTokenListener() {
321
@Override
322
public void receivedAccessToken(@Nullable String token, @Nullable Exception exception) {
323
if (token != null) {
324
conversationsClient.updateToken(token, new StatusListener() {
325
@Override
326
public void onSuccess() {
327
Log.d(MainActivity.TAG, "Refreshed access token.");
328
}
329
});
330
}
331
}
332
});
333
}
334
};
335
336
private final CallbackListener<ConversationsClient> mConversationsClientCallback =
337
new CallbackListener<ConversationsClient>() {
338
@Override
339
public void onSuccess(ConversationsClient conversationsClient) {
340
QuickstartConversationsManager.this.conversationsClient = conversationsClient;
341
conversationsClient.addListener(QuickstartConversationsManager.this.mConversationsClientListener);
342
Log.d(MainActivity.TAG, "Success creating Twilio Conversations Client");
343
}
344
345
@Override
346
public void onError(ErrorInfo errorInfo) {
347
Log.e(MainActivity.TAG, "Error creating Twilio Conversations Client: " + errorInfo.getMessage());
348
}
349
};
350
351
352
private final ConversationListener mDefaultConversationListener = new ConversationListener() {
353
354
355
@Override
356
public void onMessageAdded(final Message message) {
357
Log.d(MainActivity.TAG, "Message added");
358
messages.add(message);
359
if (conversationsManagerListener != null) {
360
conversationsManagerListener.receivedNewMessage();
361
}
362
}
363
364
@Override
365
public void onMessageUpdated(Message message, Message.UpdateReason updateReason) {
366
Log.d(MainActivity.TAG, "Message updated: " + message.getMessageBody());
367
}
368
369
@Override
370
public void onMessageDeleted(Message message) {
371
Log.d(MainActivity.TAG, "Message deleted");
372
}
373
374
@Override
375
public void onParticipantAdded(Participant participant) {
376
Log.d(MainActivity.TAG, "Participant added: " + participant.getIdentity());
377
}
378
379
@Override
380
public void onParticipantUpdated(Participant participant, Participant.UpdateReason updateReason) {
381
Log.d(MainActivity.TAG, "Participant updated: " + participant.getIdentity() + " " + updateReason.toString());
382
}
383
384
@Override
385
public void onParticipantDeleted(Participant participant) {
386
Log.d(MainActivity.TAG, "Participant deleted: " + participant.getIdentity());
387
}
388
389
@Override
390
public void onTypingStarted(Conversation conversation, Participant participant) {
391
Log.d(MainActivity.TAG, "Started Typing: " + participant.getIdentity());
392
}
393
394
@Override
395
public void onTypingEnded(Conversation conversation, Participant participant) {
396
Log.d(MainActivity.TAG, "Ended Typing: " + participant.getIdentity());
397
}
398
399
@Override
400
public void onSynchronizationChanged(Conversation conversation) {
401
402
}
403
};
404
405
public ArrayList<Message> getMessages() {
406
return messages;
407
}
408
409
public void setListener(QuickstartConversationsManagerListener listener) {
410
this.conversationsManagerListener = listener;
411
}
412
}
413

Client Synchronization State

client-synchronization-state page anchor

After you initialize the Conversations client, the client needs to synchronize with the server. The onConversationSynchronizationChange method on each listener gets called when the synchronization status changes - the completed status is COMPLETED, which means that the Conversations, Participants and Messages collections are ready to use.

Waiting for Client to Synchonize

waiting-for-client-to-synchonize page anchor
1
package com.twilio.conversationsquickstart;
2
3
import android.content.Context;
4
import android.util.Log;
5
6
import com.google.gson.Gson;
7
import com.twilio.conversations.CallbackListener;
8
import com.twilio.conversations.Conversation;
9
import com.twilio.conversations.ConversationListener;
10
import com.twilio.conversations.ConversationsClient;
11
import com.twilio.conversations.ConversationsClientListener;
12
import com.twilio.conversations.ErrorInfo;
13
import com.twilio.conversations.Participant;
14
import com.twilio.conversations.Message;
15
import com.twilio.conversations.StatusListener;
16
import com.twilio.conversations.User;
17
18
import org.jetbrains.annotations.Nullable;
19
20
import java.io.IOException;
21
import java.util.ArrayList;
22
import java.util.List;
23
24
import okhttp3.OkHttpClient;
25
import okhttp3.Request;
26
import okhttp3.Response;
27
28
interface QuickstartConversationsManagerListener {
29
void receivedNewMessage();
30
void messageSentCallback();
31
void reloadMessages();
32
}
33
34
interface TokenResponseListener {
35
void receivedTokenResponse(boolean success, @Nullable Exception exception);
36
}
37
38
interface AccessTokenListener {
39
void receivedAccessToken(@Nullable String token, @Nullable Exception exception);
40
}
41
42
43
class QuickstartConversationsManager {
44
45
// This is the unique name of the conversation we are using
46
private final static String DEFAULT_CONVERSATION_NAME = "general";
47
48
final private ArrayList<Message> messages = new ArrayList<>();
49
50
private ConversationsClient conversationsClient;
51
52
private Conversation conversation;
53
54
private QuickstartConversationsManagerListener conversationsManagerListener;
55
56
private String tokenURL = "";
57
58
private class TokenResponse {
59
String token;
60
}
61
62
void retrieveAccessTokenFromServer(final Context context, String identity,
63
final TokenResponseListener listener) {
64
65
// Set the chat token URL in your strings.xml file
66
String chatTokenURL = context.getString(R.string.chat_token_url);
67
68
if ("https://YOUR_DOMAIN_HERE.twil.io/chat-token".equals(chatTokenURL)) {
69
listener.receivedTokenResponse(false, new Exception("You need to replace the chat token URL in strings.xml"));
70
return;
71
}
72
73
tokenURL = chatTokenURL + "?identity=" + identity;
74
75
new Thread(new Runnable() {
76
@Override
77
public void run() {
78
retrieveToken(new AccessTokenListener() {
79
@Override
80
public void receivedAccessToken(@Nullable String token,
81
@Nullable Exception exception) {
82
if (token != null) {
83
ConversationsClient.Properties props = ConversationsClient.Properties.newBuilder().createProperties();
84
ConversationsClient.create(context, token, props, mConversationsClientCallback);
85
listener.receivedTokenResponse(true,null);
86
} else {
87
listener.receivedTokenResponse(false, exception);
88
}
89
}
90
});
91
}
92
}).start();
93
}
94
95
void initializeWithAccessToken(final Context context, final String token) {
96
97
ConversationsClient.Properties props = ConversationsClient.Properties.newBuilder().createProperties();
98
ConversationsClient.create(context, token, props, mConversationsClientCallback);
99
}
100
101
private void retrieveToken(AccessTokenListener listener) {
102
OkHttpClient client = new OkHttpClient();
103
104
Request request = new Request.Builder()
105
.url(tokenURL)
106
.build();
107
try (Response response = client.newCall(request).execute()) {
108
String responseBody = "";
109
if (response != null && response.body() != null) {
110
responseBody = response.body().string();
111
}
112
Log.d(MainActivity.TAG, "Response from server: " + responseBody);
113
Gson gson = new Gson();
114
TokenResponse tokenResponse = gson.fromJson(responseBody,TokenResponse.class);
115
String accessToken = tokenResponse.token;
116
Log.d(MainActivity.TAG, "Retrieved access token from server: " + accessToken);
117
listener.receivedAccessToken(accessToken, null);
118
119
}
120
catch (IOException ex) {
121
Log.e(MainActivity.TAG, ex.getLocalizedMessage(),ex);
122
listener.receivedAccessToken(null, ex);
123
}
124
}
125
126
void sendMessage(String messageBody) {
127
if (conversation != null) {
128
Message.Options options = Message.options().withBody(messageBody);
129
Log.d(MainActivity.TAG,"Message created");
130
conversation.sendMessage(options, new CallbackListener<Message>() {
131
@Override
132
public void onSuccess(Message message) {
133
if (conversationsManagerListener != null) {
134
conversationsManagerListener.messageSentCallback();
135
}
136
}
137
});
138
}
139
}
140
141
142
private void loadChannels() {
143
if (conversationsClient == null || conversationsClient.getMyConversations() == null) {
144
return;
145
}
146
conversationsClient.getConversation(DEFAULT_CONVERSATION_NAME, new CallbackListener<Conversation>() {
147
@Override
148
public void onSuccess(Conversation conversation) {
149
if (conversation != null) {
150
if (conversation.getStatus() == Conversation.ConversationStatus.JOINED
151
|| conversation.getStatus() == Conversation.ConversationStatus.NOT_PARTICIPATING) {
152
Log.d(MainActivity.TAG, "Already Exists in Conversation: " + DEFAULT_CONVERSATION_NAME);
153
QuickstartConversationsManager.this.conversation = conversation;
154
QuickstartConversationsManager.this.conversation.addListener(mDefaultConversationListener);
155
QuickstartConversationsManager.this.loadPreviousMessages(conversation);
156
} else {
157
Log.d(MainActivity.TAG, "Joining Conversation: " + DEFAULT_CONVERSATION_NAME);
158
joinConversation(conversation);
159
}
160
}
161
}
162
163
@Override
164
public void onError(ErrorInfo errorInfo) {
165
Log.e(MainActivity.TAG, "Error retrieving conversation: " + errorInfo.getMessage());
166
createConversation();
167
}
168
169
});
170
}
171
172
private void createConversation() {
173
Log.d(MainActivity.TAG, "Creating Conversation: " + DEFAULT_CONVERSATION_NAME);
174
175
conversationsClient.createConversation(DEFAULT_CONVERSATION_NAME,
176
new CallbackListener<Conversation>() {
177
@Override
178
public void onSuccess(Conversation conversation) {
179
if (conversation != null) {
180
Log.d(MainActivity.TAG, "Joining Conversation: " + DEFAULT_CONVERSATION_NAME);
181
joinConversation(conversation);
182
}
183
}
184
185
@Override
186
public void onError(ErrorInfo errorInfo) {
187
Log.e(MainActivity.TAG, "Error creating conversation: " + errorInfo.getMessage());
188
}
189
});
190
}
191
192
193
private void joinConversation(final Conversation conversation) {
194
Log.d(MainActivity.TAG, "Joining Conversation: " + conversation.getUniqueName());
195
if (conversation.getStatus() == Conversation.ConversationStatus.JOINED) {
196
197
QuickstartConversationsManager.this.conversation = conversation;
198
Log.d(MainActivity.TAG, "Already joined default conversation");
199
QuickstartConversationsManager.this.conversation.addListener(mDefaultConversationListener);
200
return;
201
}
202
203
204
conversation.join(new StatusListener() {
205
@Override
206
public void onSuccess() {
207
QuickstartConversationsManager.this.conversation = conversation;
208
Log.d(MainActivity.TAG, "Joined default conversation");
209
QuickstartConversationsManager.this.conversation.addListener(mDefaultConversationListener);
210
QuickstartConversationsManager.this.loadPreviousMessages(conversation);
211
}
212
213
@Override
214
public void onError(ErrorInfo errorInfo) {
215
Log.e(MainActivity.TAG, "Error joining conversation: " + errorInfo.getMessage());
216
}
217
});
218
}
219
220
private void loadPreviousMessages(final Conversation conversation) {
221
conversation.getLastMessages(100,
222
new CallbackListener<List<Message>>() {
223
@Override
224
public void onSuccess(List<Message> result) {
225
messages.addAll(result);
226
if (conversationsManagerListener != null) {
227
conversationsManagerListener.reloadMessages();
228
}
229
}
230
});
231
}
232
233
private final ConversationsClientListener mConversationsClientListener =
234
new ConversationsClientListener() {
235
236
@Override
237
public void onConversationAdded(Conversation conversation) {
238
239
}
240
241
@Override
242
public void onConversationUpdated(Conversation conversation, Conversation.UpdateReason updateReason) {
243
244
}
245
246
@Override
247
public void onConversationDeleted(Conversation conversation) {
248
249
}
250
251
@Override
252
public void onConversationSynchronizationChange(Conversation conversation) {
253
254
}
255
256
@Override
257
public void onError(ErrorInfo errorInfo) {
258
259
}
260
261
@Override
262
public void onUserUpdated(User user, User.UpdateReason updateReason) {
263
264
}
265
266
@Override
267
public void onUserSubscribed(User user) {
268
269
}
270
271
@Override
272
public void onUserUnsubscribed(User user) {
273
274
}
275
276
@Override
277
public void onClientSynchronization(ConversationsClient.SynchronizationStatus synchronizationStatus) {
278
if (synchronizationStatus == ConversationsClient.SynchronizationStatus.COMPLETED) {
279
loadChannels();
280
}
281
}
282
283
@Override
284
public void onNewMessageNotification(String s, String s1, long l) {
285
286
}
287
288
@Override
289
public void onAddedToConversationNotification(String s) {
290
291
}
292
293
@Override
294
public void onRemovedFromConversationNotification(String s) {
295
296
}
297
298
@Override
299
public void onNotificationSubscribed() {
300
301
}
302
303
@Override
304
public void onNotificationFailed(ErrorInfo errorInfo) {
305
306
}
307
308
@Override
309
public void onConnectionStateChange(ConversationsClient.ConnectionState connectionState) {
310
311
}
312
313
@Override
314
public void onTokenExpired() {
315
316
}
317
318
@Override
319
public void onTokenAboutToExpire() {
320
retrieveToken(new AccessTokenListener() {
321
@Override
322
public void receivedAccessToken(@Nullable String token, @Nullable Exception exception) {
323
if (token != null) {
324
conversationsClient.updateToken(token, new StatusListener() {
325
@Override
326
public void onSuccess() {
327
Log.d(MainActivity.TAG, "Refreshed access token.");
328
}
329
});
330
}
331
}
332
});
333
}
334
};
335
336
private final CallbackListener<ConversationsClient> mConversationsClientCallback =
337
new CallbackListener<ConversationsClient>() {
338
@Override
339
public void onSuccess(ConversationsClient conversationsClient) {
340
QuickstartConversationsManager.this.conversationsClient = conversationsClient;
341
conversationsClient.addListener(QuickstartConversationsManager.this.mConversationsClientListener);
342
Log.d(MainActivity.TAG, "Success creating Twilio Conversations Client");
343
}
344
345
@Override
346
public void onError(ErrorInfo errorInfo) {
347
Log.e(MainActivity.TAG, "Error creating Twilio Conversations Client: " + errorInfo.getMessage());
348
}
349
};
350
351
352
private final ConversationListener mDefaultConversationListener = new ConversationListener() {
353
354
355
@Override
356
public void onMessageAdded(final Message message) {
357
Log.d(MainActivity.TAG, "Message added");
358
messages.add(message);
359
if (conversationsManagerListener != null) {
360
conversationsManagerListener.receivedNewMessage();
361
}
362
}
363
364
@Override
365
public void onMessageUpdated(Message message, Message.UpdateReason updateReason) {
366
Log.d(MainActivity.TAG, "Message updated: " + message.getMessageBody());
367
}
368
369
@Override
370
public void onMessageDeleted(Message message) {
371
Log.d(MainActivity.TAG, "Message deleted");
372
}
373
374
@Override
375
public void onParticipantAdded(Participant participant) {
376
Log.d(MainActivity.TAG, "Participant added: " + participant.getIdentity());
377
}
378
379
@Override
380
public void onParticipantUpdated(Participant participant, Participant.UpdateReason updateReason) {
381
Log.d(MainActivity.TAG, "Participant updated: " + participant.getIdentity() + " " + updateReason.toString());
382
}
383
384
@Override
385
public void onParticipantDeleted(Participant participant) {
386
Log.d(MainActivity.TAG, "Participant deleted: " + participant.getIdentity());
387
}
388
389
@Override
390
public void onTypingStarted(Conversation conversation, Participant participant) {
391
Log.d(MainActivity.TAG, "Started Typing: " + participant.getIdentity());
392
}
393
394
@Override
395
public void onTypingEnded(Conversation conversation, Participant participant) {
396
Log.d(MainActivity.TAG, "Ended Typing: " + participant.getIdentity());
397
}
398
399
@Override
400
public void onSynchronizationChanged(Conversation conversation) {
401
402
}
403
};
404
405
public ArrayList<Message> getMessages() {
406
return messages;
407
}
408
409
public void setListener(QuickstartConversationsManagerListener listener) {
410
this.conversationsManagerListener = listener;
411
}
412
}
413

The Conversation class is the building block of your Conversations application. In the Quickstart, we've set things up so that the user automatically joins one conversation. For instance, this conversation's unique id could be supplied by a back end service to represent a three way conversation between a restaurant, a customer, and a delivery driver.

Your user may have already joined the conversation, so you should check to see if they have before calling the join() method on the Conversation object.

1
package com.twilio.conversationsquickstart;
2
3
import android.content.Context;
4
import android.util.Log;
5
6
import com.google.gson.Gson;
7
import com.twilio.conversations.CallbackListener;
8
import com.twilio.conversations.Conversation;
9
import com.twilio.conversations.ConversationListener;
10
import com.twilio.conversations.ConversationsClient;
11
import com.twilio.conversations.ConversationsClientListener;
12
import com.twilio.conversations.ErrorInfo;
13
import com.twilio.conversations.Participant;
14
import com.twilio.conversations.Message;
15
import com.twilio.conversations.StatusListener;
16
import com.twilio.conversations.User;
17
18
import org.jetbrains.annotations.Nullable;
19
20
import java.io.IOException;
21
import java.util.ArrayList;
22
import java.util.List;
23
24
import okhttp3.OkHttpClient;
25
import okhttp3.Request;
26
import okhttp3.Response;
27
28
interface QuickstartConversationsManagerListener {
29
void receivedNewMessage();
30
void messageSentCallback();
31
void reloadMessages();
32
}
33
34
interface TokenResponseListener {
35
void receivedTokenResponse(boolean success, @Nullable Exception exception);
36
}
37
38
interface AccessTokenListener {
39
void receivedAccessToken(@Nullable String token, @Nullable Exception exception);
40
}
41
42
43
class QuickstartConversationsManager {
44
45
// This is the unique name of the conversation we are using
46
private final static String DEFAULT_CONVERSATION_NAME = "general";
47
48
final private ArrayList<Message> messages = new ArrayList<>();
49
50
private ConversationsClient conversationsClient;
51
52
private Conversation conversation;
53
54
private QuickstartConversationsManagerListener conversationsManagerListener;
55
56
private String tokenURL = "";
57
58
private class TokenResponse {
59
String token;
60
}
61
62
void retrieveAccessTokenFromServer(final Context context, String identity,
63
final TokenResponseListener listener) {
64
65
// Set the chat token URL in your strings.xml file
66
String chatTokenURL = context.getString(R.string.chat_token_url);
67
68
if ("https://YOUR_DOMAIN_HERE.twil.io/chat-token".equals(chatTokenURL)) {
69
listener.receivedTokenResponse(false, new Exception("You need to replace the chat token URL in strings.xml"));
70
return;
71
}
72
73
tokenURL = chatTokenURL + "?identity=" + identity;
74
75
new Thread(new Runnable() {
76
@Override
77
public void run() {
78
retrieveToken(new AccessTokenListener() {
79
@Override
80
public void receivedAccessToken(@Nullable String token,
81
@Nullable Exception exception) {
82
if (token != null) {
83
ConversationsClient.Properties props = ConversationsClient.Properties.newBuilder().createProperties();
84
ConversationsClient.create(context, token, props, mConversationsClientCallback);
85
listener.receivedTokenResponse(true,null);
86
} else {
87
listener.receivedTokenResponse(false, exception);
88
}
89
}
90
});
91
}
92
}).start();
93
}
94
95
void initializeWithAccessToken(final Context context, final String token) {
96
97
ConversationsClient.Properties props = ConversationsClient.Properties.newBuilder().createProperties();
98
ConversationsClient.create(context, token, props, mConversationsClientCallback);
99
}
100
101
private void retrieveToken(AccessTokenListener listener) {
102
OkHttpClient client = new OkHttpClient();
103
104
Request request = new Request.Builder()
105
.url(tokenURL)
106
.build();
107
try (Response response = client.newCall(request).execute()) {
108
String responseBody = "";
109
if (response != null && response.body() != null) {
110
responseBody = response.body().string();
111
}
112
Log.d(MainActivity.TAG, "Response from server: " + responseBody);
113
Gson gson = new Gson();
114
TokenResponse tokenResponse = gson.fromJson(responseBody,TokenResponse.class);
115
String accessToken = tokenResponse.token;
116
Log.d(MainActivity.TAG, "Retrieved access token from server: " + accessToken);
117
listener.receivedAccessToken(accessToken, null);
118
119
}
120
catch (IOException ex) {
121
Log.e(MainActivity.TAG, ex.getLocalizedMessage(),ex);
122
listener.receivedAccessToken(null, ex);
123
}
124
}
125
126
void sendMessage(String messageBody) {
127
if (conversation != null) {
128
Message.Options options = Message.options().withBody(messageBody);
129
Log.d(MainActivity.TAG,"Message created");
130
conversation.sendMessage(options, new CallbackListener<Message>() {
131
@Override
132
public void onSuccess(Message message) {
133
if (conversationsManagerListener != null) {
134
conversationsManagerListener.messageSentCallback();
135
}
136
}
137
});
138
}
139
}
140
141
142
private void loadChannels() {
143
if (conversationsClient == null || conversationsClient.getMyConversations() == null) {
144
return;
145
}
146
conversationsClient.getConversation(DEFAULT_CONVERSATION_NAME, new CallbackListener<Conversation>() {
147
@Override
148
public void onSuccess(Conversation conversation) {
149
if (conversation != null) {
150
if (conversation.getStatus() == Conversation.ConversationStatus.JOINED
151
|| conversation.getStatus() == Conversation.ConversationStatus.NOT_PARTICIPATING) {
152
Log.d(MainActivity.TAG, "Already Exists in Conversation: " + DEFAULT_CONVERSATION_NAME);
153
QuickstartConversationsManager.this.conversation = conversation;
154
QuickstartConversationsManager.this.conversation.addListener(mDefaultConversationListener);
155
QuickstartConversationsManager.this.loadPreviousMessages(conversation);
156
} else {
157
Log.d(MainActivity.TAG, "Joining Conversation: " + DEFAULT_CONVERSATION_NAME);
158
joinConversation(conversation);
159
}
160
}
161
}
162
163
@Override
164
public void onError(ErrorInfo errorInfo) {
165
Log.e(MainActivity.TAG, "Error retrieving conversation: " + errorInfo.getMessage());
166
createConversation();
167
}
168
169
});
170
}
171
172
private void createConversation() {
173
Log.d(MainActivity.TAG, "Creating Conversation: " + DEFAULT_CONVERSATION_NAME);
174
175
conversationsClient.createConversation(DEFAULT_CONVERSATION_NAME,
176
new CallbackListener<Conversation>() {
177
@Override
178
public void onSuccess(Conversation conversation) {
179
if (conversation != null) {
180
Log.d(MainActivity.TAG, "Joining Conversation: " + DEFAULT_CONVERSATION_NAME);
181
joinConversation(conversation);
182
}
183
}
184
185
@Override
186
public void onError(ErrorInfo errorInfo) {
187
Log.e(MainActivity.TAG, "Error creating conversation: " + errorInfo.getMessage());
188
}
189
});
190
}
191
192
193
private void joinConversation(final Conversation conversation) {
194
Log.d(MainActivity.TAG, "Joining Conversation: " + conversation.getUniqueName());
195
if (conversation.getStatus() == Conversation.ConversationStatus.JOINED) {
196
197
QuickstartConversationsManager.this.conversation = conversation;
198
Log.d(MainActivity.TAG, "Already joined default conversation");
199
QuickstartConversationsManager.this.conversation.addListener(mDefaultConversationListener);
200
return;
201
}
202
203
204
conversation.join(new StatusListener() {
205
@Override
206
public void onSuccess() {
207
QuickstartConversationsManager.this.conversation = conversation;
208
Log.d(MainActivity.TAG, "Joined default conversation");
209
QuickstartConversationsManager.this.conversation.addListener(mDefaultConversationListener);
210
QuickstartConversationsManager.this.loadPreviousMessages(conversation);
211
}
212
213
@Override
214
public void onError(ErrorInfo errorInfo) {
215
Log.e(MainActivity.TAG, "Error joining conversation: " + errorInfo.getMessage());
216
}
217
});
218
}
219
220
private void loadPreviousMessages(final Conversation conversation) {
221
conversation.getLastMessages(100,
222
new CallbackListener<List<Message>>() {
223
@Override
224
public void onSuccess(List<Message> result) {
225
messages.addAll(result);
226
if (conversationsManagerListener != null) {
227
conversationsManagerListener.reloadMessages();
228
}
229
}
230
});
231
}
232
233
private final ConversationsClientListener mConversationsClientListener =
234
new ConversationsClientListener() {
235
236
@Override
237
public void onConversationAdded(Conversation conversation) {
238
239
}
240
241
@Override
242
public void onConversationUpdated(Conversation conversation, Conversation.UpdateReason updateReason) {
243
244
}
245
246
@Override
247
public void onConversationDeleted(Conversation conversation) {
248
249
}
250
251
@Override
252
public void onConversationSynchronizationChange(Conversation conversation) {
253
254
}
255
256
@Override
257
public void onError(ErrorInfo errorInfo) {
258
259
}
260
261
@Override
262
public void onUserUpdated(User user, User.UpdateReason updateReason) {
263
264
}
265
266
@Override
267
public void onUserSubscribed(User user) {
268
269
}
270
271
@Override
272
public void onUserUnsubscribed(User user) {
273
274
}
275
276
@Override
277
public void onClientSynchronization(ConversationsClient.SynchronizationStatus synchronizationStatus) {
278
if (synchronizationStatus == ConversationsClient.SynchronizationStatus.COMPLETED) {
279
loadChannels();
280
}
281
}
282
283
@Override
284
public void onNewMessageNotification(String s, String s1, long l) {
285
286
}
287
288
@Override
289
public void onAddedToConversationNotification(String s) {
290
291
}
292
293
@Override
294
public void onRemovedFromConversationNotification(String s) {
295
296
}
297
298
@Override
299
public void onNotificationSubscribed() {
300
301
}
302
303
@Override
304
public void onNotificationFailed(ErrorInfo errorInfo) {
305
306
}
307
308
@Override
309
public void onConnectionStateChange(ConversationsClient.ConnectionState connectionState) {
310
311
}
312
313
@Override
314
public void onTokenExpired() {
315
316
}
317
318
@Override
319
public void onTokenAboutToExpire() {
320
retrieveToken(new AccessTokenListener() {
321
@Override
322
public void receivedAccessToken(@Nullable String token, @Nullable Exception exception) {
323
if (token != null) {
324
conversationsClient.updateToken(token, new StatusListener() {
325
@Override
326
public void onSuccess() {
327
Log.d(MainActivity.TAG, "Refreshed access token.");
328
}
329
});
330
}
331
}
332
});
333
}
334
};
335
336
private final CallbackListener<ConversationsClient> mConversationsClientCallback =
337
new CallbackListener<ConversationsClient>() {
338
@Override
339
public void onSuccess(ConversationsClient conversationsClient) {
340
QuickstartConversationsManager.this.conversationsClient = conversationsClient;
341
conversationsClient.addListener(QuickstartConversationsManager.this.mConversationsClientListener);
342
Log.d(MainActivity.TAG, "Success creating Twilio Conversations Client");
343
}
344
345
@Override
346
public void onError(ErrorInfo errorInfo) {
347
Log.e(MainActivity.TAG, "Error creating Twilio Conversations Client: " + errorInfo.getMessage());
348
}
349
};
350
351
352
private final ConversationListener mDefaultConversationListener = new ConversationListener() {
353
354
355
@Override
356
public void onMessageAdded(final Message message) {
357
Log.d(MainActivity.TAG, "Message added");
358
messages.add(message);
359
if (conversationsManagerListener != null) {
360
conversationsManagerListener.receivedNewMessage();
361
}
362
}
363
364
@Override
365
public void onMessageUpdated(Message message, Message.UpdateReason updateReason) {
366
Log.d(MainActivity.TAG, "Message updated: " + message.getMessageBody());
367
}
368
369
@Override
370
public void onMessageDeleted(Message message) {
371
Log.d(MainActivity.TAG, "Message deleted");
372
}
373
374
@Override
375
public void onParticipantAdded(Participant participant) {
376
Log.d(MainActivity.TAG, "Participant added: " + participant.getIdentity());
377
}
378
379
@Override
380
public void onParticipantUpdated(Participant participant, Participant.UpdateReason updateReason) {
381
Log.d(MainActivity.TAG, "Participant updated: " + participant.getIdentity() + " " + updateReason.toString());
382
}
383
384
@Override
385
public void onParticipantDeleted(Participant participant) {
386
Log.d(MainActivity.TAG, "Participant deleted: " + participant.getIdentity());
387
}
388
389
@Override
390
public void onTypingStarted(Conversation conversation, Participant participant) {
391
Log.d(MainActivity.TAG, "Started Typing: " + participant.getIdentity());
392
}
393
394
@Override
395
public void onTypingEnded(Conversation conversation, Participant participant) {
396
Log.d(MainActivity.TAG, "Ended Typing: " + participant.getIdentity());
397
}
398
399
@Override
400
public void onSynchronizationChanged(Conversation conversation) {
401
402
}
403
};
404
405
public ArrayList<Message> getMessages() {
406
return messages;
407
}
408
409
public void setListener(QuickstartConversationsManagerListener listener) {
410
this.conversationsManagerListener = listener;
411
}
412
}
413

Sending Messages to a Conversation

sending-messages-to-a-conversation page anchor

To send a message (with text content) to a conversation that a user has joined, you need to call the sendMessage() method on a Conversation object. To create a message, you can build one up with the Message.Options class.

Sending a Message to a Conversation

sending-a-message-to-a-conversation page anchor
1
package com.twilio.conversationsquickstart;
2
3
import android.content.Context;
4
import android.util.Log;
5
6
import com.google.gson.Gson;
7
import com.twilio.conversations.CallbackListener;
8
import com.twilio.conversations.Conversation;
9
import com.twilio.conversations.ConversationListener;
10
import com.twilio.conversations.ConversationsClient;
11
import com.twilio.conversations.ConversationsClientListener;
12
import com.twilio.conversations.ErrorInfo;
13
import com.twilio.conversations.Participant;
14
import com.twilio.conversations.Message;
15
import com.twilio.conversations.StatusListener;
16
import com.twilio.conversations.User;
17
18
import org.jetbrains.annotations.Nullable;
19
20
import java.io.IOException;
21
import java.util.ArrayList;
22
import java.util.List;
23
24
import okhttp3.OkHttpClient;
25
import okhttp3.Request;
26
import okhttp3.Response;
27
28
interface QuickstartConversationsManagerListener {
29
void receivedNewMessage();
30
void messageSentCallback();
31
void reloadMessages();
32
}
33
34
interface TokenResponseListener {
35
void receivedTokenResponse(boolean success, @Nullable Exception exception);
36
}
37
38
interface AccessTokenListener {
39
void receivedAccessToken(@Nullable String token, @Nullable Exception exception);
40
}
41
42
43
class QuickstartConversationsManager {
44
45
// This is the unique name of the conversation we are using
46
private final static String DEFAULT_CONVERSATION_NAME = "general";
47
48
final private ArrayList<Message> messages = new ArrayList<>();
49
50
private ConversationsClient conversationsClient;
51
52
private Conversation conversation;
53
54
private QuickstartConversationsManagerListener conversationsManagerListener;
55
56
private String tokenURL = "";
57
58
private class TokenResponse {
59
String token;
60
}
61
62
void retrieveAccessTokenFromServer(final Context context, String identity,
63
final TokenResponseListener listener) {
64
65
// Set the chat token URL in your strings.xml file
66
String chatTokenURL = context.getString(R.string.chat_token_url);
67
68
if ("https://YOUR_DOMAIN_HERE.twil.io/chat-token".equals(chatTokenURL)) {
69
listener.receivedTokenResponse(false, new Exception("You need to replace the chat token URL in strings.xml"));
70
return;
71
}
72
73
tokenURL = chatTokenURL + "?identity=" + identity;
74
75
new Thread(new Runnable() {
76
@Override
77
public void run() {
78
retrieveToken(new AccessTokenListener() {
79
@Override
80
public void receivedAccessToken(@Nullable String token,
81
@Nullable Exception exception) {
82
if (token != null) {
83
ConversationsClient.Properties props = ConversationsClient.Properties.newBuilder().createProperties();
84
ConversationsClient.create(context, token, props, mConversationsClientCallback);
85
listener.receivedTokenResponse(true,null);
86
} else {
87
listener.receivedTokenResponse(false, exception);
88
}
89
}
90
});
91
}
92
}).start();
93
}
94
95
void initializeWithAccessToken(final Context context, final String token) {
96
97
ConversationsClient.Properties props = ConversationsClient.Properties.newBuilder().createProperties();
98
ConversationsClient.create(context, token, props, mConversationsClientCallback);
99
}
100
101
private void retrieveToken(AccessTokenListener listener) {
102
OkHttpClient client = new OkHttpClient();
103
104
Request request = new Request.Builder()
105
.url(tokenURL)
106
.build();
107
try (Response response = client.newCall(request).execute()) {
108
String responseBody = "";
109
if (response != null && response.body() != null) {
110
responseBody = response.body().string();
111
}
112
Log.d(MainActivity.TAG, "Response from server: " + responseBody);
113
Gson gson = new Gson();
114
TokenResponse tokenResponse = gson.fromJson(responseBody,TokenResponse.class);
115
String accessToken = tokenResponse.token;
116
Log.d(MainActivity.TAG, "Retrieved access token from server: " + accessToken);
117
listener.receivedAccessToken(accessToken, null);
118
119
}
120
catch (IOException ex) {
121
Log.e(MainActivity.TAG, ex.getLocalizedMessage(),ex);
122
listener.receivedAccessToken(null, ex);
123
}
124
}
125
126
void sendMessage(String messageBody) {
127
if (conversation != null) {
128
Message.Options options = Message.options().withBody(messageBody);
129
Log.d(MainActivity.TAG,"Message created");
130
conversation.sendMessage(options, new CallbackListener<Message>() {
131
@Override
132
public void onSuccess(Message message) {
133
if (conversationsManagerListener != null) {
134
conversationsManagerListener.messageSentCallback();
135
}
136
}
137
});
138
}
139
}
140
141
142
private void loadChannels() {
143
if (conversationsClient == null || conversationsClient.getMyConversations() == null) {
144
return;
145
}
146
conversationsClient.getConversation(DEFAULT_CONVERSATION_NAME, new CallbackListener<Conversation>() {
147
@Override
148
public void onSuccess(Conversation conversation) {
149
if (conversation != null) {
150
if (conversation.getStatus() == Conversation.ConversationStatus.JOINED
151
|| conversation.getStatus() == Conversation.ConversationStatus.NOT_PARTICIPATING) {
152
Log.d(MainActivity.TAG, "Already Exists in Conversation: " + DEFAULT_CONVERSATION_NAME);
153
QuickstartConversationsManager.this.conversation = conversation;
154
QuickstartConversationsManager.this.conversation.addListener(mDefaultConversationListener);
155
QuickstartConversationsManager.this.loadPreviousMessages(conversation);
156
} else {
157
Log.d(MainActivity.TAG, "Joining Conversation: " + DEFAULT_CONVERSATION_NAME);
158
joinConversation(conversation);
159
}
160
}
161
}
162
163
@Override
164
public void onError(ErrorInfo errorInfo) {
165
Log.e(MainActivity.TAG, "Error retrieving conversation: " + errorInfo.getMessage());
166
createConversation();
167
}
168
169
});
170
}
171
172
private void createConversation() {
173
Log.d(MainActivity.TAG, "Creating Conversation: " + DEFAULT_CONVERSATION_NAME);
174
175
conversationsClient.createConversation(DEFAULT_CONVERSATION_NAME,
176
new CallbackListener<Conversation>() {
177
@Override
178
public void onSuccess(Conversation conversation) {
179
if (conversation != null) {
180
Log.d(MainActivity.TAG, "Joining Conversation: " + DEFAULT_CONVERSATION_NAME);
181
joinConversation(conversation);
182
}
183
}
184
185
@Override
186
public void onError(ErrorInfo errorInfo) {
187
Log.e(MainActivity.TAG, "Error creating conversation: " + errorInfo.getMessage());
188
}
189
});
190
}
191
192
193
private void joinConversation(final Conversation conversation) {
194
Log.d(MainActivity.TAG, "Joining Conversation: " + conversation.getUniqueName());
195
if (conversation.getStatus() == Conversation.ConversationStatus.JOINED) {
196
197
QuickstartConversationsManager.this.conversation = conversation;
198
Log.d(MainActivity.TAG, "Already joined default conversation");
199
QuickstartConversationsManager.this.conversation.addListener(mDefaultConversationListener);
200
return;
201
}
202
203
204
conversation.join(new StatusListener() {
205
@Override
206
public void onSuccess() {
207
QuickstartConversationsManager.this.conversation = conversation;
208
Log.d(MainActivity.TAG, "Joined default conversation");
209
QuickstartConversationsManager.this.conversation.addListener(mDefaultConversationListener);
210
QuickstartConversationsManager.this.loadPreviousMessages(conversation);
211
}
212
213
@Override
214
public void onError(ErrorInfo errorInfo) {
215
Log.e(MainActivity.TAG, "Error joining conversation: " + errorInfo.getMessage());
216
}
217
});
218
}
219
220
private void loadPreviousMessages(final Conversation conversation) {
221
conversation.getLastMessages(100,
222
new CallbackListener<List<Message>>() {
223
@Override
224
public void onSuccess(List<Message> result) {
225
messages.addAll(result);
226
if (conversationsManagerListener != null) {
227
conversationsManagerListener.reloadMessages();
228
}
229
}
230
});
231
}
232
233
private final ConversationsClientListener mConversationsClientListener =
234
new ConversationsClientListener() {
235
236
@Override
237
public void onConversationAdded(Conversation conversation) {
238
239
}
240
241
@Override
242
public void onConversationUpdated(Conversation conversation, Conversation.UpdateReason updateReason) {
243
244
}
245
246
@Override
247
public void onConversationDeleted(Conversation conversation) {
248
249
}
250
251
@Override
252
public void onConversationSynchronizationChange(Conversation conversation) {
253
254
}
255
256
@Override
257
public void onError(ErrorInfo errorInfo) {
258
259
}
260
261
@Override
262
public void onUserUpdated(User user, User.UpdateReason updateReason) {
263
264
}
265
266
@Override
267
public void onUserSubscribed(User user) {
268
269
}
270
271
@Override
272
public void onUserUnsubscribed(User user) {
273
274
}
275
276
@Override
277
public void onClientSynchronization(ConversationsClient.SynchronizationStatus synchronizationStatus) {
278
if (synchronizationStatus == ConversationsClient.SynchronizationStatus.COMPLETED) {
279
loadChannels();
280
}
281
}
282
283
@Override
284
public void onNewMessageNotification(String s, String s1, long l) {
285
286
}
287
288
@Override
289
public void onAddedToConversationNotification(String s) {
290
291
}
292
293
@Override
294
public void onRemovedFromConversationNotification(String s) {
295
296
}
297
298
@Override
299
public void onNotificationSubscribed() {
300
301
}
302
303
@Override
304
public void onNotificationFailed(ErrorInfo errorInfo) {
305
306
}
307
308
@Override
309
public void onConnectionStateChange(ConversationsClient.ConnectionState connectionState) {
310
311
}
312
313
@Override
314
public void onTokenExpired() {
315
316
}
317
318
@Override
319
public void onTokenAboutToExpire() {
320
retrieveToken(new AccessTokenListener() {
321
@Override
322
public void receivedAccessToken(@Nullable String token, @Nullable Exception exception) {
323
if (token != null) {
324
conversationsClient.updateToken(token, new StatusListener() {
325
@Override
326
public void onSuccess() {
327
Log.d(MainActivity.TAG, "Refreshed access token.");
328
}
329
});
330
}
331
}
332
});
333
}
334
};
335
336
private final CallbackListener<ConversationsClient> mConversationsClientCallback =
337
new CallbackListener<ConversationsClient>() {
338
@Override
339
public void onSuccess(ConversationsClient conversationsClient) {
340
QuickstartConversationsManager.this.conversationsClient = conversationsClient;
341
conversationsClient.addListener(QuickstartConversationsManager.this.mConversationsClientListener);
342
Log.d(MainActivity.TAG, "Success creating Twilio Conversations Client");
343
}
344
345
@Override
346
public void onError(ErrorInfo errorInfo) {
347
Log.e(MainActivity.TAG, "Error creating Twilio Conversations Client: " + errorInfo.getMessage());
348
}
349
};
350
351
352
private final ConversationListener mDefaultConversationListener = new ConversationListener() {
353
354
355
@Override
356
public void onMessageAdded(final Message message) {
357
Log.d(MainActivity.TAG, "Message added");
358
messages.add(message);
359
if (conversationsManagerListener != null) {
360
conversationsManagerListener.receivedNewMessage();
361
}
362
}
363
364
@Override
365
public void onMessageUpdated(Message message, Message.UpdateReason updateReason) {
366
Log.d(MainActivity.TAG, "Message updated: " + message.getMessageBody());
367
}
368
369
@Override
370
public void onMessageDeleted(Message message) {
371
Log.d(MainActivity.TAG, "Message deleted");
372
}
373
374
@Override
375
public void onParticipantAdded(Participant participant) {
376
Log.d(MainActivity.TAG, "Participant added: " + participant.getIdentity());
377
}
378
379
@Override
380
public void onParticipantUpdated(Participant participant, Participant.UpdateReason updateReason) {
381
Log.d(MainActivity.TAG, "Participant updated: " + participant.getIdentity() + " " + updateReason.toString());
382
}
383
384
@Override
385
public void onParticipantDeleted(Participant participant) {
386
Log.d(MainActivity.TAG, "Participant deleted: " + participant.getIdentity());
387
}
388
389
@Override
390
public void onTypingStarted(Conversation conversation, Participant participant) {
391
Log.d(MainActivity.TAG, "Started Typing: " + participant.getIdentity());
392
}
393
394
@Override
395
public void onTypingEnded(Conversation conversation, Participant participant) {
396
Log.d(MainActivity.TAG, "Ended Typing: " + participant.getIdentity());
397
}
398
399
@Override
400
public void onSynchronizationChanged(Conversation conversation) {
401
402
}
403
};
404
405
public ArrayList<Message> getMessages() {
406
return messages;
407
}
408
409
public void setListener(QuickstartConversationsManagerListener listener) {
410
this.conversationsManagerListener = listener;
411
}
412
}
413

Receiving and Displaying Messages

receiving-and-displaying-messages page anchor

Each Conversation object from the Conversations SDK represents an individual conversation between one or more users. Inside the Conversations Quickstart, we interact with the Conversation in the QuickstartConversationManager class. We use this approach to avoid having an activity or fragment class that does too much. After initializing the Conversations SDK with an access token, waiting for the client to synchronize, and then either creating or joining a conversation, we can start to engage with that conversation by sending or receiving messages. These messages are Message objects from the Conversations SDK.

Displaying Existing Messages

displaying-existing-messages page anchor

We retrieve the last messages using the getLastMessages() method on the Conversation class. This returns all of the previous messages (up to a limit, which you can set in code), and you can use that to initialize the display for your class. After loading in any existing messages, the QuickstartConversationsManager notifies its listener (the MainActivity) that there is a new batch of messages to display.

Loading Previous Messages

loading-previous-messages page anchor
1
package com.twilio.conversationsquickstart;
2
3
import android.content.Context;
4
import android.util.Log;
5
6
import com.google.gson.Gson;
7
import com.twilio.conversations.CallbackListener;
8
import com.twilio.conversations.Conversation;
9
import com.twilio.conversations.ConversationListener;
10
import com.twilio.conversations.ConversationsClient;
11
import com.twilio.conversations.ConversationsClientListener;
12
import com.twilio.conversations.ErrorInfo;
13
import com.twilio.conversations.Participant;
14
import com.twilio.conversations.Message;
15
import com.twilio.conversations.StatusListener;
16
import com.twilio.conversations.User;
17
18
import org.jetbrains.annotations.Nullable;
19
20
import java.io.IOException;
21
import java.util.ArrayList;
22
import java.util.List;
23
24
import okhttp3.OkHttpClient;
25
import okhttp3.Request;
26
import okhttp3.Response;
27
28
interface QuickstartConversationsManagerListener {
29
void receivedNewMessage();
30
void messageSentCallback();
31
void reloadMessages();
32
}
33
34
interface TokenResponseListener {
35
void receivedTokenResponse(boolean success, @Nullable Exception exception);
36
}
37
38
interface AccessTokenListener {
39
void receivedAccessToken(@Nullable String token, @Nullable Exception exception);
40
}
41
42
43
class QuickstartConversationsManager {
44
45
// This is the unique name of the conversation we are using
46
private final static String DEFAULT_CONVERSATION_NAME = "general";
47
48
final private ArrayList<Message> messages = new ArrayList<>();
49
50
private ConversationsClient conversationsClient;
51
52
private Conversation conversation;
53
54
private QuickstartConversationsManagerListener conversationsManagerListener;
55
56
private String tokenURL = "";
57
58
private class TokenResponse {
59
String token;
60
}
61
62
void retrieveAccessTokenFromServer(final Context context, String identity,
63
final TokenResponseListener listener) {
64
65
// Set the chat token URL in your strings.xml file
66
String chatTokenURL = context.getString(R.string.chat_token_url);
67
68
if ("https://YOUR_DOMAIN_HERE.twil.io/chat-token".equals(chatTokenURL)) {
69
listener.receivedTokenResponse(false, new Exception("You need to replace the chat token URL in strings.xml"));
70
return;
71
}
72
73
tokenURL = chatTokenURL + "?identity=" + identity;
74
75
new Thread(new Runnable() {
76
@Override
77
public void run() {
78
retrieveToken(new AccessTokenListener() {
79
@Override
80
public void receivedAccessToken(@Nullable String token,
81
@Nullable Exception exception) {
82
if (token != null) {
83
ConversationsClient.Properties props = ConversationsClient.Properties.newBuilder().createProperties();
84
ConversationsClient.create(context, token, props, mConversationsClientCallback);
85
listener.receivedTokenResponse(true,null);
86
} else {
87