UI Kit quickstart
Updated
A highly reliable global communication platform where users can chat one-to-one, in groups or in chat rooms.
Instant messaging connects people wherever they are and allows them to communicate with others in real time. Agora offers an open-source Chat UI Kit project on GitHub. With built-in user interfaces for key Chat features, the Agora Chat UI Kit enables you to quickly embed real-time messaging into your app without requiring extra effort on the UI.
For the latest Agora Chat UIKit documentation, refer to the UIKit 2.x Documentation GitHub repository.
Legacy UIkit 1.x documentation
This page shows sample code to add peer-to-peer messaging into your app by using the Agora Chat UI Kit.
Understand the tech
The following figure shows the workflow of how clients send and receive peer-to-peer messages:
Chat UI kit workflow
- Clients retrieve a token from your app server.
- Client A and Client B log in to Agora Chat.
- Client A sends a message to Client B. The message is sent to the Agora Chat server, and the server delivers the message to Client B. When Client B receives the message, the SDK triggers an event. Client B listens for the event and gets the message.
Prerequisites
- An Android simulator or a physical Android device.
- Android Studio 3.2 or higher.
- Java Development Kit (JDK). You can refer to the User Guide of Android for applicable versions.
Project setup
Follow the steps to create the environment necessary to add video call into your app.
-
For new projects, in Android Studio, create a Phone and Tablet Android project with an Empty Activity.
After creating the project, Android Studio automatically starts gradle sync. Ensure that the sync succeeds before you continue.
-
Integrate the Chat SDK into your project with Maven Central.
-
In
/Gradle Scripts/build.gradle(Project: <projectname>), add the following lines to add the Maven Central dependency:buildscript { repositories { ... mavenCentral() } } allprojects { repositories { ... mavenCentral() } } -
In
/Gradle Scripts/build.gradle(Module: <projectname>.app), add the following lines to integrate the Chat UI Samples into your Android project:android { defaultConfig { // The Android OS version should be 21 or higher. minSdkVersion 21 } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } } dependencies { ... // Replace X.Y.Z with the latest version of the Chat UI Samples. // For the latest version, go to https://search.maven.org/. implementation 'io.agora.rtc:chat-uikit:X.Y.Z' }
-
-
Add permissions for network and device access.
In
/app/Manifests/AndroidManifest.xml, add the following permissions after</application>:<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> <uses-permission android:name="android.permission.WAKE_LOCK"/> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.CAMERA"/> <uses-permission android:name="android.permission.RECORD_AUDIO"/> <!-- For Android 12, you need to add the following line to apply for the alarm clock permission. For Agora Chat 1.0.9 or later, this permission is optional.--> <uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />These are the minimum permissions you need to add to start Chat. You can also add other permissions according to your use case.
-
Prevent code obfuscation.
In
/Gradle Scripts/proguard-rules.pro, add the following line:-keep class io.agora.** {*;} -dontwarn io.agora.**
Implementation
Implement peer-to-peer messaging
This section shows how to use the Chat UI Samples to implement peer-to-peer messaging in your app step by step.
Create the UI
-
To add the text strings used by the UI, open
app/res/values/strings.xmland replace the content with the following:<resources> <string name="app_name">AgoraChatUIKitQuickstart</string> <string name="username_or_pwd_miss">Username or password is empty</string> <string name="sign_up_success">Sign up success!</string> <string name="sign_in_success">Sign in success!</string> <string name="sign_out_success">Sign out success!</string> <string name="send_message_success">Send message success!</string> <string name="enter_username">Enter username</string> <string name="enter_password">Enter password</string> <string name="sign_in">Sign in</string> <string name="sign_out">Sign out</string> <string name="sign_up">Sign up</string> <string name="enter_to_username">Enter to username</string> <string name="start_chat">Start chat</string> <string name="enter_content">Enter content</string> <string name="log_hint">Show log area...</string> <string name="has_login_before">An account has been signed in, please sign out first and then sign in</string> <string name="sign_in_first">Please sign in first</string> <string name="not_find_send_name">Please enter the username who you want to send first!</string> <string name="app_key">41117440#383391</string> </resources> -
To add the UI framework, open
app/res/layout/activity_main.xmland replace the content with the following:<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context=".MainActivity"> <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1"> <EditText android:id="@+id/et_username" android:layout_width="0dp" android:layout_height="wrap_content" android:hint="@string/enter_username" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toLeftOf="@id/et_pwd" app:layout_constraintTop_toTopOf="parent" android:layout_marginStart="20dp" android:layout_marginEnd="20dp" android:layout_marginTop="20dp"/> <EditText android:id="@+id/et_pwd" android:layout_width="0dp" android:layout_height="wrap_content" android:hint="@string/enter_password" android:inputType="textPassword" app:layout_constraintLeft_toRightOf="@id/et_username" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="@id/et_username" android:layout_marginEnd="20dp"/> <Button android:id="@+id/btn_sign_in" android:layout_width="0dp" android:layout_height="wrap_content" android:textSize="12sp" android:text="@string/sign_in" android:onClick="signInWithToken" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintTop_toBottomOf="@id/et_pwd" app:layout_constraintRight_toLeftOf="@id/btn_sign_out" android:layout_marginStart="10dp" android:layout_marginEnd="5dp" android:layout_marginTop="10dp"/> <Button android:id="@+id/btn_sign_out" android:layout_width="0dp" android:layout_height="wrap_content" android:textSize="12sp" android:text="@string/sign_out" android:onClick="signOut" app:layout_constraintLeft_toRightOf="@id/btn_sign_in" app:layout_constraintTop_toBottomOf="@id/et_pwd" app:layout_constraintRight_toLeftOf="@id/btn_sign_up" android:layout_marginStart="5dp" android:layout_marginEnd="5dp" android:layout_marginTop="10dp"/> <Button android:id="@+id/btn_sign_up" android:layout_width="0dp" android:layout_height="wrap_content" android:text="@string/sign_up" android:textSize="12sp" android:onClick="signUp" app:layout_constraintLeft_toRightOf="@id/btn_sign_out" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toBottomOf="@id/et_pwd" app:layout_constraintTop_toTopOf="@id/btn_sign_in" android:layout_marginStart="5dp" android:layout_marginEnd="10dp"/> <EditText android:id="@+id/et_to_username" android:layout_width="0dp" android:layout_height="wrap_content" android:hint="@string/enter_to_username" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toLeftOf="@id/btn_start_chat" app:layout_constraintTop_toBottomOf="@id/btn_sign_up" app:layout_constraintHorizontal_weight="2" android:layout_marginStart="20dp" android:layout_marginEnd="20dp" android:layout_marginTop="10dp" android:layout_marginBottom="10dp"/> <Button android:id="@+id/btn_start_chat" android:layout_width="0dp" android:layout_height="wrap_content" android:text="@string/start_chat" android:onClick="startChat" android:textSize="12sp" app:layout_constraintLeft_toRightOf="@id/et_to_username" app:layout_constraintRight_toRightOf="parent" app:layout_constraintBottom_toBottomOf="@id/et_to_username" app:layout_constraintHorizontal_weight="1" android:layout_marginEnd="10dp"/> <FrameLayout android:id="@+id/fl_fragment" android:layout_width="match_parent" android:layout_height="0dp" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toBottomOf="@id/btn_start_chat" app:layout_constraintBottom_toBottomOf="parent"/> </androidx.constraintlayout.widget.ConstraintLayout> <TextView android:id="@+id/tv_log" android:layout_width="match_parent" android:layout_height="100dp" android:hint="@string/log_hint" android:scrollbars="vertical" android:padding="10dp"/> </LinearLayout>
Implementation
To enable your app to send and receive messages between individual users, do the following:
-
Implement sending and receiving messages.
In
app/java/io.agora.agorachatquickstart/MainActivity, replace the code with the following:package io.agora.chatuikitquickstart; import android.Manifest; import android.os.Bundle; import android.text.TextUtils; import android.text.method.ScrollingMovementMethod; import android.view.MotionEvent; import android.view.View; import android.widget.EditText; import android.widget.TextView; import android.widget.Toast; import androidx.appcompat.app.AppCompatActivity; import androidx.core.content.ContextCompat; import org.json.JSONObject; import java.util.HashMap; import java.util.Map; import io.agora.CallBack; import io.agora.ConnectionListener; import io.agora.Error; import io.agora.chat.ChatClient; import io.agora.chat.ChatMessage; import io.agora.chat.ChatOptions; import io.agora.chat.uikit.EaseUIKit; import io.agora.chat.uikit.chat.EaseChatFragment; import io.agora.chat.uikit.chat.interfaces.OnChatExtendMenuItemClickListener; import io.agora.chat.uikit.chat.interfaces.OnChatInputChangeListener; import io.agora.chat.uikit.chat.interfaces.OnChatRecordTouchListener; import io.agora.chat.uikit.chat.interfaces.OnMessageSendCallBack; import io.agora.chatuikitquickstart.utils.LogUtils; import io.agora.chatuikitquickstart.utils.PermissionsManager; import io.agora.cloud.HttpClientManager; import io.agora.cloud.HttpResponse; import io.agora.util.EMLog; import static io.agora.cloud.HttpClientManager.Method_POST; public class MainActivity extends AppCompatActivity { private static final String NEW_LOGIN = "NEW_LOGIN"; private static final String RENEW_TOKEN = "RENEW_TOKEN"; private static final String LOGIN_URL = "https://a41.chat.agora.io/app/chat/user/login"; private static final String REGISTER_URL = "https://a41.chat.agora.io/app/chat/user/register"; private EditText et_username; private TextView tv_log; private ConnectionListener connectionListener; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initView(); requestPermissions(); initSDK(); addConnectionListener(); } private void initView() { et_username = findViewById(R.id.et_username); tv_log = findViewById(R.id.tv_log); tv_log.setMovementMethod(new ScrollingMovementMethod()); } private void requestPermissions() { checkPermissions(Manifest.permission.WRITE_EXTERNAL_STORAGE, 110); } //=================== init SDK start ======================== private void initSDK() { ChatOptions options = new ChatOptions(); // Set your appkey applied from Agora Console String sdkAppkey = getString(R.string.app_key); if(TextUtils.isEmpty(sdkAppkey)) { Toast.makeText(MainActivity.this, "You should set your AppKey first!", Toast.LENGTH_SHORT).show(); return; } // Set your appkey to options options.setAppKey(sdkAppkey); // Set whether confirmation of delivery is required by the recipient. Default: false options.setRequireDeliveryAck(true); // Set not to log in automatically options.setAutoLogin(false); // Use UI Samples to initialize Chat SDK EaseUIKit.getInstance().init(this, options); // Make Chat SDK debuggable ChatClient.getInstance().setDebugMode(true); } //=================== init SDK end ======================== //================= SDK listener start ==================== private void addConnectionListener() { connectionListener = new ConnectionListener() { @Override public void onConnected() { } @Override public void onDisconnected(int error) { if (error == Error.USER_REMOVED) { onUserException("account_removed"); } else if (error == Error.USER_LOGIN_ANOTHER_DEVICE) { onUserException("account_conflict"); } else if (error == Error.SERVER_SERVICE_RESTRICTED) { onUserException("account_forbidden"); } else if (error == Error.USER_KICKED_BY_CHANGE_PASSWORD) { onUserException("account_kicked_by_change_password"); } else if (error == Error.USER_KICKED_BY_OTHER_DEVICE) { onUserException("account_kicked_by_other_device"); } else if(error == Error.USER_BIND_ANOTHER_DEVICE) { onUserException("user_bind_another_device"); } else if(error == Error.USER_DEVICE_CHANGED) { onUserException("user_device_changed"); } else if(error == Error.USER_LOGIN_TOO_MANY_DEVICES) { onUserException("user_login_too_many_devices"); } } @Override public void onTokenExpired() { //login again signInWithToken(null); LogUtils.showLog(tv_log,"ConnectionListener onTokenExpired"); } @Override public void onTokenWillExpire() { getTokenFromAppServer(RENEW_TOKEN); LogUtils.showLog(tv_log, "ConnectionListener onTokenWillExpire"); } }; // Call removeConnectionListener(connectionListener) when the activity is destroyed ChatClient.getInstance().addConnectionListener(connectionListener); } //================= SDK listener end ==================== //=================== click event start ======================== /** * Sign up with username and password. */ public void signUp(View view) { String username = et_username.getText().toString().trim(); String pwd = ((EditText) findViewById(R.id.et_pwd)).getText().toString().trim(); if(TextUtils.isEmpty(username) || TextUtils.isEmpty(pwd)) { LogUtils.showErrorToast(this, tv_log, getString(R.string.username_or_pwd_miss)); return; } execute(()-> { try { Map headers = new HashMap<>(); headers.put("Content-Type", "application/json"); JSONObject request = new JSONObject(); request.putOpt("userAccount", username); request.putOpt("userPassword", pwd); LogUtils.showErrorLog(tv_log,"begin to signUp..."); HttpResponse response = HttpClientManager.httpExecute(REGISTER_URL, headers, request.toString(), Method_POST); int code= response.code; String responseInfo = response.content; if (code == 200) { if (responseInfo != null && responseInfo.length() > 0) { JSONObject object = new JSONObject(responseInfo); String resultCode = object.getString("code"); if(resultCode.equals("RES_OK")) { LogUtils.showToast(MainActivity.this, tv_log, getString(R.string.sign_up_success)); }else{ String errorInfo = object.getString("errorInfo"); LogUtils.showErrorLog(tv_log,errorInfo); } } else { LogUtils.showErrorLog(tv_log,responseInfo); } } else { LogUtils.showErrorLog(tv_log,responseInfo); } } catch (Exception e) { e.printStackTrace(); LogUtils.showErrorLog(tv_log, e.getMessage()); } }); } /** * Log in with token. */ public void signInWithToken(View view) { getTokenFromAppServer(NEW_LOGIN); } /** * Sign out. */ public void signOut(View view) { if(ChatClient.getInstance().isLoggedInBefore()) { ChatClient.getInstance().logout(true, new CallBack() { @Override public void onSuccess() { LogUtils.showToast(MainActivity.this, tv_log, getString(R.string.sign_out_success)); } @Override public void onError(int code, String error) { LogUtils.showErrorToast(MainActivity.this, tv_log, "Sign out failed! code: "+code + " error: "+error); } @Override public void onProgress(int progress, String status) { } }); } } public void startChat(View view) { EditText et_to_username = findViewById(R.id.et_to_username); String toChatUsername = et_to_username.getText().toString().trim(); // check username if(TextUtils.isEmpty(toChatUsername)) { LogUtils.showErrorToast(this, tv_log, getString(R.string.not_find_send_name)); return; } // 1: single chat; 2: group chat; 3: chat room EaseChatFragment fragment = new EaseChatFragment.Builder(toChatUsername, EaseChatType.SINGLE_CHAT) .useHeader(false) .setOnChatExtendMenuItemClickListener(new OnChatExtendMenuItemClickListener() { @Override public boolean onChatExtendMenuItemClick(View view, int itemId) { if(itemId == io.agora.chat.uikit.R.id.extend_item_take_picture) { return !checkPermissions(Manifest.permission.CAMERA, 111); }else if(itemId == io.agora.chat.uikit.R.id.extend_item_picture || itemId == io.agora.chat.uikit.R.id.extend_item_file || itemId == io.agora.chat.uikit.R.id.extend_item_video) { return !checkPermissions(Manifest.permission.READ_EXTERNAL_STORAGE, 112); } return false; } }) .setOnChatRecordTouchListener(new OnChatRecordTouchListener() { @Override public boolean onRecordTouch(View v, MotionEvent event) { return !checkPermissions(Manifest.permission.RECORD_AUDIO, 113); } }) .setOnMessageSendCallBack(new OnMessageSendCallBack() { @Override public void onSuccess(ChatMessage message) { LogUtils.showLog(tv_log, "Send success: message type: " + message.getType().name()); } @Override public void onError(int code, String errorMsg) { LogUtils.showErrorLog(tv_log, "Send failed: error code: "+code + " errorMsg: "+errorMsg); } }) .build(); getSupportFragmentManager().beginTransaction().replace(R.id.fl_fragment, fragment).commit(); } //=================== click event end ======================== //=================== get token from server start ======================== private void getTokenFromAppServer(String requestType) { if(ChatClient.getInstance().getOptions().getAutoLogin() && ChatClient.getInstance().isLoggedInBefore()) { LogUtils.showErrorLog(tv_log, getString(R.string.has_login_before)); return; } String username = et_username.getText().toString().trim(); String pwd = ((EditText) findViewById(R.id.et_pwd)).getText().toString().trim(); if(TextUtils.isEmpty(username) || TextUtils.isEmpty(pwd)) { LogUtils.showErrorToast(MainActivity.this, tv_log, getString(R.string.username_or_pwd_miss)); return; } execute(()-> { try { Map headers = new HashMap<>(); headers.put("Content-Type", "application/json"); JSONObject request = new JSONObject(); request.putOpt("userAccount", username); request.putOpt("userPassword", pwd); LogUtils.showErrorLog(tv_log,"begin to getTokenFromAppServer ..."); HttpResponse response = HttpClientManager.httpExecute(LOGIN_URL, headers, request.toString(), Method_POST); int code = response.code; String responseInfo = response.content; if (code == 200) { if (responseInfo != null && responseInfo.length() > 0) { JSONObject object = new JSONObject(responseInfo); String token = object.getString("accessToken"); if(TextUtils.equals(requestType, NEW_LOGIN)) { ChatClient.getInstance().loginWithAgoraToken(username, token, new CallBack() { @Override public void onSuccess() { LogUtils.showToast(MainActivity.this, tv_log, getString(R.string.sign_in_success)); } @Override public void onError(int code, String error) { LogUtils.showErrorToast(MainActivity.this, tv_log, "Login failed! code: " + code + " error: " + error); } @Override public void onProgress(int progress, String status) { } }); }else if(TextUtils.equals(requestType, RENEW_TOKEN)) { ChatClient.getInstance().renewToken(token); } } else { LogUtils.showErrorToast(MainActivity.this, tv_log, "getTokenFromAppServer failed! code: " + code + " error: " + responseInfo); } } else { LogUtils.showErrorToast(MainActivity.this, tv_log, "getTokenFromAppServer failed! code: " + code + " error: " + responseInfo); } } catch (Exception e) { e.printStackTrace(); LogUtils.showErrorToast(MainActivity.this, tv_log, "getTokenFromAppServer failed! code: " + 0 + " error: " + e.getMessage()); } }); } //=================== get token from server end ======================== /** * Check and request permission * @param permission * @param requestCode * @return */ private boolean checkPermissions(String permission, int requestCode) { if(!PermissionsManager.getInstance().hasPermission(this, permission)) { PermissionsManager.getInstance().requestPermissions(this, new String[]{permission}, requestCode); return false; } return true; } /** * user met some exception: conflict, removed or forbidden, goto login activity */ protected void onUserException(String exception) { LogUtils.showLog(tv_log, "onUserException: " + exception); ChatClient.getInstance().logout(false, null); } public void execute(Runnable runnable) { new Thread(runnable).start(); } @Override protected void onDestroy() { super.onDestroy(); if(connectionListener != null) { ChatClient.getInstance().removeConnectionListener(connectionListener); } } } -
Add LogUtils and PermissionsManager.
To make troubleshooting less time-consuming, this quickstart also uses
LogUtilsclass for logs. Navigate toapp/java/io.agora.agorachatquickstart/, create a folder namedutils. In this new folder, create a.javafile, name itLogUtils, and copy the following codes into the file.package io.agora.chatuikitquickstart.utils; import android.app.Activity; import android.text.TextUtils; import android.util.Log; import android.widget.TextView; import android.widget.Toast; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; public class LogUtils { private static final String TAG = LogUtils.class.getSimpleName(); public static void showErrorLog(TextView tvLog, String content) { showLog(tvLog, content); } public static void showNormalLog(TextView tvLog, String content) { showLog(tvLog, content); } public static void showLog(TextView tvLog, String content) { if(TextUtils.isEmpty(content) || tvLog == null) { return; } String preContent = tvLog.getText().toString().trim(); StringBuilder builder = new StringBuilder(); builder.append(formatCurrentTime()) .append(" ") .append(content) .append("\n") .append(preContent); tvLog.post(()-> { tvLog.setText(builder); }); } public static void showErrorToast(Activity activity, TextView tvLog, String content) { if(activity == null || activity.isFinishing()) { Log.e(TAG, "Context is null..."); return; } if(TextUtils.isEmpty(content)) { return; } activity.runOnUiThread(()-> { Toast.makeText(activity, content, Toast.LENGTH_SHORT).show(); showErrorLog(tvLog,content); }); } public static void showToast(Activity activity, TextView tvLog, String content) { if(TextUtils.isEmpty(content) || activity == null || activity.isFinishing()) { return; } activity.runOnUiThread(()-> { Toast.makeText(activity, content, Toast.LENGTH_SHORT).show(); showNormalLog(tvLog, content); }); } private static String formatCurrentTime() { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()); return sdf.format(new Date()); } }When your app launches, check if the permissions necessary to insert real-time chat into the app are granted. In the
utilsfile, create a.javafile, name itPermissionsManager, and copy the following codes into the file.package io.agora.chatuikitquickstart.utils; import android.app.Activity; import android.content.Context; import android.content.pm.PackageManager; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.app.ActivityCompat; public class PermissionsManager { private static PermissionsManager mInstance = null; public static PermissionsManager getInstance() { if (mInstance == null) { mInstance = new PermissionsManager(); } return mInstance; } private PermissionsManager() {} /** * Check if has permission * @param context * @param permission * @return */ @SuppressWarnings("unused") public synchronized boolean hasPermission(@Nullable Context context, @NonNull String permission) { return context != null && ActivityCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED; } /** * Request permissions * @param activity * @param permissions * @param requestCode */ public synchronized void requestPermissions(Activity activity, String[] permissions, int requestCode) { ActivityCompat.requestPermissions(activity, permissions, requestCode); } } -
To send image and file messages, take the following configurations:
Under
/app/src/main/res/, create a folder, name itxml, and create an xml file namedfile_paths.xmlunderxml. Openfile_paths.xml, replace the code with the following:<?xml version="1.0" encoding="utf-8"?> <paths> <external-path path="Android/data/io/agora/chatuikitquickstart/" name="files_root" /> <external-path path="." name="external_storage_root" /> </paths>In
/app/Manifests/AndroidManifest.xml, add the following lines before</application>:<!-- After android 7.0, you should add --> <provider android:name="androidx.core.content.FileProvider" android:authorities="${applicationId}.fileProvider" android:grantUriPermissions="true" android:exported="false"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" /> </provider> -
Click
Sync Project with Gradle Filesto sync your project. Now you are ready to test your app.
Test your app
To validate the peer-to-peer messaging you have just integrated into your app using Chat:
-
In Android Studio, click
Run 'app'.You see the following interface on your simulator or physical device:
-
Create a user account and click SIGN UP. Click Sign in and you will see a log that says Sign in success.
-
Run the app on another Android device or simulator and create another user account. Ensure that the usernames you created are unique.
-
On the first device or simulator, enter the username you just created and click START CHAT. You can now start chatting between the two clients.
Next steps
For demonstration purposes, Chat provides an app server that enables you to quickly retrieve a token using the App Key given in this guide. In a production context, the best practice is for you to deploy your own token server, use your own App Key to generate a token, and retrieve the token on the client side to log in to Agora. To see how to implement a server that generates and serves tokens on request, see Authenticate your users with tokens.
Reference
Agora provides the fully featured AgoraChat-Starter-Kit-Android demo app as an implementation reference.
