UI Kit quickstart
Updated
A highly reliable global communication platform where users can chat one-to-one, in groups or in chat rooms.
Currently, there is no Chat UI Kit for this platform.
Currently, there is no Chat UI Kit for this platform.
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.
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
- Xcode, preferably the latest version.
- A simulator or a physical mobile device running iOS 11.0 or later.
- CocoaPods. Refer to Getting Started with CocoaPods if you have not installed CocoaPods.
Project setup
In order to create the environment necessary to integrate Chat UI Samples into your app, do the following:
-
Create a new project for an iOS app. Make sure you select Storyboard as the Interface.
If you have added any team information, you see the Add account... button. Click it, input your Apple ID, and click Next. Your team information is added to the project.
-
Enable automatic signing for your project.
-
Set the target devices to deploy your iOS app.
-
Add project permissions for microphone and camera usage.
Open info in the project navigation panel, and add the following properties to the Property List:
Key Type Value Privacy - Photo Library Usage DescriptionString For photo library access Privacy - Microphone Usage DescriptionString For microphone access Privacy - Camera Usage DescriptionString For camera access App Transport Security Settings > Allow Arbitrary LoadsBoolean YES -
Integrate the UI Samples into your project.
-
Create a Podfile for your project.
In the Terminal, navigate to the root directory of your Xcode project and run the following command:
pod init -
Add dependencies to the UI Samples in the Podfile.
Open Podfile and replace the content with the following:
platform :ios, '11.0' target 'EaseChatKitExample' do # Pods for EaseChatKitExample pod 'chat-uikit' pod 'Masonry' end
-
-
Install the UI Samples.
In the Terminal, navigate to the root directory of your Xcode project and run the following command:
pod install
The Chat UI Samples is now added into your project.
Implementation
Implement peer-to-peer messaging
This section shows how to use Chat UI Samples to rapidly implement peer-to-peer messaging in your app.
Initialize UI Samples
Follow the steps to initialize the UI Samples.
-
To import the header file, open
SceneDelegate.min Xcode and add the following lines to import the header file:#import #import "AgoraLoginViewController.h" #import -
Initialize the UI Samples by calling the
initWithAgoraChatOptionsmethod. InSceneDelegate.m, replace thewillConnectToSessionmethod with the following code:- (void)scene:(UIScene *)scene willConnectToSession:(UISceneSession *)session options:(UISceneConnectionOptions *)connectionOptions { // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). AgoraChatOptions *options = [AgoraChatOptions optionsWithAppkey:@"41117440#383391"]; options.enableConsoleLog = YES; options.usingHttpsOnly = YES; options.enableDeliveryAck = YES; options.isAutoLogin = NO; // Initialize chat-starter-kit [EaseChatKitManager initWithAgoraChatOptions:options]; UIWindowScene *windowScene = (UIWindowScene *)scene; self.window = [[UIWindow alloc] initWithWindowScene:windowScene]; self.window.frame = windowScene.coordinateSpace.bounds; // Go to the login view self.window.rootViewController = [[AgoraLoginViewController alloc]init]; [self.window makeKeyAndVisible]; }
Log into Chat
Take the following steps to log into Chat.
-
In Xcode, go to File > New > File, and create a Cocoa Touch Class file named
AgoraChatHttpRequest. Make sure to set Subclass of asNSObject. Save the file under AgoraChatUIKit. -
Open
AgoraChatHttpRequest.hand replace the code with the following:#import NS_ASSUME_NONNULL_BEGIN @interface AgoraChatHttpRequest : NSObject + (instancetype)sharedManager; - (void)registerToApperServer:(NSString *)uName pwd:(NSString *)pwd completion:(void (^)(NSInteger statusCode, NSString *response))aCompletionBlock; - (void)loginToApperServer:(NSString *)uName pwd:(NSString *)pwd completion:(void (^)(NSInteger statusCode, NSString *response))aCompletionBlock; @end NS_ASSUME_NONNULL_ENDTo register to the Chat app server, open
AgoraChatHttpRequest.mand replace the code with the following:#import "AgoraChatHttpRequest.h" @interface AgoraChatHttpRequest() @property (readonly, nonatomic, strong) NSURLSession *session; @end @implementation AgoraChatHttpRequest + (instancetype)sharedManager { static dispatch_once_t onceToken; static AgoraChatHttpRequest *sharedInstance; dispatch_once(&onceToken, ^{ sharedInstance = [[AgoraChatHttpRequest alloc] init]; }); return sharedInstance; } - (instancetype)init { if (self = [super init]) { NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration]; configuration.timeoutIntervalForRequest = 120; _session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:[NSOperationQueue mainQueue]]; } return self; } - (void)registerToApperServer:(NSString *)uName pwd:(NSString *)pwd completion:(void (^)(NSInteger statusCode, NSString *aUsername))aCompletionBlock { NSURL *url = [NSURL URLWithString:@"https://a41.chat.agora.io/app/chat/user/register"]; NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; request.HTTPMethod = @"POST"; NSMutableDictionary *headerDict = [[NSMutableDictionary alloc]init]; [headerDict setObject:@"application/json" forKey:@"Content-Type"]; request.allHTTPHeaderFields = headerDict; NSMutableDictionary *dict = [[NSMutableDictionary alloc]init]; [dict setObject:uName forKey:@"userAccount"]; [dict setObject:pwd forKey:@"userPassword"]; request.HTTPBody = [NSJSONSerialization dataWithJSONObject:dict options:0 error:nil]; NSURLSessionDataTask *task = [self.session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { NSString *responseData = data ? [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] : nil; if (aCompletionBlock) { aCompletionBlock(((NSHTTPURLResponse*)response).statusCode, responseData); } }]; [task resume]; } - (void)loginToApperServer:(NSString *)uName pwd:(NSString *)pwd completion:(void (^)(NSInteger statusCode, NSString *response))aCompletionBlock { NSURL *url = [NSURL URLWithString:@"https://a41.chat.agora.io/app/chat/user/login"]; NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; request.HTTPMethod = @"POST"; NSMutableDictionary *headerDict = [[NSMutableDictionary alloc]init]; [headerDict setObject:@"application/json" forKey:@"Content-Type"]; request.allHTTPHeaderFields = headerDict; NSMutableDictionary *dict = [[NSMutableDictionary alloc]init]; [dict setObject:uName forKey:@"userAccount"]; [dict setObject:pwd forKey:@"userPassword"]; request.HTTPBody = [NSJSONSerialization dataWithJSONObject:dict options:0 error:nil]; NSURLSessionDataTask *task = [self.session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { NSString *responseData = data ? [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] : nil; if (aCompletionBlock) { aCompletionBlock(((NSHTTPURLResponse*)response).statusCode, responseData); } }]; [task resume]; } - (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable))completionHandler { if([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]){ NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]; if(completionHandler) completionHandler(NSURLSessionAuthChallengeUseCredential,credential); } } @end -
Go to File > New > File and create a Cocoa Touch Class file named
AgoraLoginViewController. Make sure to set Subclass of asUIViewController. -
To import the header file, open
AgoraLoginViewController.mand add the following lines:#import "AgoraChatHttpRequest.h" // To send request to the Appserver #import //Chat SDK #import "ViewController.h"To sign up, add the following lines after
@implementation AgoraLoginViewController:// Register to the AppServer - (void)doSignUp { // Set the chat ID as vvv [[AgoraChatHttpRequest sharedManager] registerToApperServer:@"vvv" pwd:@"1" completion:^(NSInteger statusCode, NSString * _Nonnull response) { dispatch_async(dispatch_get_main_queue(),^{ if (response != nil) { NSData *responseData = [response dataUsingEncoding:NSUTF8StringEncoding]; NSDictionary *responsedict = [NSJSONSerialization JSONObjectWithData:responseData options:0 error:nil]; if (responsedict != nil) { NSString *result = [responsedict objectForKey:@"code"]; if ([result isEqualToString:@"RES_OK"]) { // Sign in Chat if registration succeeds [self doSignIn]; } } } }); }]; } - (void)doSignIn { // Sign in AppServer [[AgoraChatHttpRequest sharedManager] loginToApperServer:@"vvv" pwd:@"1" completion:^(NSInteger statusCode, NSString * _Nonnull response) { dispatch_async(dispatch_get_main_queue(), ^{ if (response && response.length > 0 && statusCode) { NSData *responseData = [response dataUsingEncoding:NSUTF8StringEncoding]; NSDictionary *responsedict = [NSJSONSerialization JSONObjectWithData:responseData options:0 error:nil]; NSString *token = [responsedict objectForKey:@"accessToken"]; NSString *loginName = [responsedict objectForKey:@"chatUserName"]; if (token && token.length > 0) { // Log into Chat SDK [[AgoraChatClient sharedClient] loginWithUsername:[loginName lowercaseString] agoraToken:token completion:^(NSString *aUsername, AgoraChatError *aError) { if (!aError) { ViewController *chatsVC = [[ViewController alloc] init]; UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:chatsVC]; navigationController.navigationBarHidden = YES; UIWindow *window = [[[UIApplication sharedApplication] windows] lastObject]; window.rootViewController = navigationController; } }]; } } }); }]; }Replace the
viewDidLoadfunction with the following code:- (void)viewDidLoad { [super viewDidLoad]; [self doSignIn]; // Do any additional setup after loading the view. }
Launch the chat storyboard
In this section, add an input box for getting username of the peer user, and a Chat button that loads the chat storyboard.
In ViewController.m, add the following code:
#define kIsBangsScreen ({\
BOOL isBangsScreen = NO; \
if (@available(iOS 11.0, *)) { \
UIWindow *window = [[UIApplication sharedApplication].windows firstObject]; \
isBangsScreen = window.safeAreaInsets.bottom > 0; \
} \
isBangsScreen; \
})
#define AgoraChatVIEWTOPMARGIN (kIsBangsScreen ? 34.f : 0.f)
#import "ViewController.h"
#import
#import
#import
@interface ViewController ()
@property (nonatomic, strong) EaseConversationModel *conversationModel;
@property (nonatomic, strong) AgoraChatConversation *conversation;
@property (nonatomic, strong) EaseChatViewController *chatController;
@property (nonatomic, strong) UITextField *conversationIdField;
@property (nonatomic, strong) UIButton *chatBtn;
@property (nonatomic, strong) UIButton *logoutBtn;
@endIn ViewController.m, replace the viewDidLoad method with the following code:
- (void)viewDidLoad {
[super viewDidLoad];
[self _setupChatSubviews];
}
- (void)viewWillAppear:(BOOL)animated
{
self.navigationController.navigationBarHidden = YES;
}
- (void)_setupChatSubviews
{
self.conversationIdField = [[UITextField alloc] init];
self.conversationIdField.backgroundColor = [UIColor systemGrayColor];
self.conversationIdField.delegate = self;
self.conversationIdField.borderStyle = UITextBorderStyleNone;
NSAttributedString *convAttrStr = [[NSAttributedString alloc] initWithString:@"single chat ID" attributes:@{NSForegroundColorAttributeName:[UIColor whiteColor]}];
self.conversationIdField.attributedPlaceholder = convAttrStr;
self.conversationIdField.font = [UIFont systemFontOfSize:17];
self.conversationIdField.textColor = [UIColor whiteColor];
self.conversationIdField.returnKeyType = UIReturnKeyDone;
self.conversationIdField.layer.cornerRadius = 5;
self.conversationIdField.layer.borderWidth = 1;
self.conversationIdField.layer.borderColor = [UIColor lightGrayColor].CGColor;
[self.view addSubview:self.conversationIdField];
[self.conversationIdField mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.view).offset(30);
make.top.equalTo(self.view).offset(30 + AgoraChatVIEWTOPMARGIN);
make.height.mas_equalTo(@50);
make.width.mas_equalTo(@320);
}];
self.chatBtn = [[UIButton alloc] init];
self.chatBtn.clipsToBounds = YES;
self.chatBtn.layer.cornerRadius = 5;
self.chatBtn.backgroundColor = [UIColor colorWithRed:((float) 78 / 255.0f) green:0 blue:((float) 234 / 255.0f) alpha:1];
self.chatBtn.titleLabel.font = [UIFont systemFontOfSize:19];
[self.chatBtn setTitle:@"chat" forState:UIControlStateNormal];
[self.chatBtn setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
[self.chatBtn addTarget:self action:@selector(chatAction) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:self.chatBtn];
[self.chatBtn mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.view).offset(30);
make.top.equalTo(self.conversationIdField.mas_bottom).offset(20);
make.height.mas_equalTo(@50);
make.width.mas_equalTo(@150);
}];
self.logoutBtn = [[UIButton alloc]init];
self.logoutBtn.backgroundColor = [UIColor redColor];
[self.logoutBtn setTitle:@"Log out" forState:UIControlStateNormal];
[self.logoutBtn addTarget:self action:@selector(logout) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:self.logoutBtn];
[self.logoutBtn mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.right.bottom.equalTo(self.view);
make.height.equalTo(@50);
}];
self.view.backgroundColor = [UIColor colorWithRed:242/255.0 green:242/255.0 blue:242/255.0 alpha:1.0];
}Add the following code in ViewController.m to create the input box and Chat button:
- (void)chatAction
{
[self.view endEditing:YES];
if (self.conversationIdField.text.length
);
}
}
export default App;-
Set the layout for the conversation.
Open
my-app/src/App.css, and replace the content with the following:/** App.css */ .container { height: 100%; width: 100% }
Test your app
When the app launches, you see the following interface and are logged into Chat.
To test the app, take the following steps:
- Enter the single chat ID. A single chat ID is the user ID of a peer user and for testing purposes, you can simply input
abc. - Click chat. An edit box pops up.
- Enter the message in the edit box and click Enter, the message is sent and displayed in the UI.
- You can also click + on the right of the edit box and try sending attachment messages.
Next steps
Reference
Agora provides the fully featured AgoraChat-ios demo app as an implementation reference.
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
- React 16.8.0 or later.
- React DOM 16.8.0 or later.
- A Windows or macOS computer that has a browser supported by the Agora Chat SDK:
- Internet Explorer 11 or later
- Firefox 10 or later
- Chrome 54 or later
- Safari 11 or later
- A valid Agora Account.
- An Agora project that has enabled the Chat service.
- An App key and a user token generated on your app server.
Project setup
This sections introduces how to create an app and add the Chat UI Samples to the project.
-
In your terminal, run the following command to create an app:
# Install CLI npm install create-react-app # Create an app named my-app npx create-react-app my-app cd my-appOnce you successfully create the app, the project structure is as follows:
my-app ├── package.json ├── public # The static directory for Webpack │ ├── favicon.ico │ ├── index.html # The default one-page app │ └── manifest.json ├── src │ ├── App.css # The css file of the app │ ├── App.js # The code of the app │ ├── App.test.js │ ├── index.css # The layout when launching the file │ ├── index.js # Launch the file │ ├── logo.svg │ └── serviceWorker.js └── yarn.lock -
Run one of the following commands to add the Chat UI Samples to your project.
To add the UI Samples using npm:
npm install agora-chat-uikit --saveTo add the UI Samples using yarn
yarn add agora-chat-uikit
Implementation
Implement peer-to-peer messaging
Test your app
In your terminal, run the following command to launch the app:
npm run startYou can see the app launch in your browser. Before you can send a message, refer to Add a contact or Join a chat group to add a contact or join a chat group.
Next steps
This section includes more advanced features you can implement in your project.
Customizable attributes
EaseChat provides the following attributes for customization. You can customize the features and layout by setting these attributes. To ensure the functionality of EaseChat, ensure that you set all the required parameters.
| Attribute | Type | Required | Description |
|---|---|---|---|
appkey | String | Yes | The unique identifier that the Chat service assigns to each app. The rule is $(OrgName)#{AppName}. |
username | String | Yes | The user ID. |
agoraToken | String | Yes | The Agora token. |
to | String | Yes | In one-to-one messaging, it is the user ID of the recipient; in group chat, it is the group ID. |
showByselfAvatar | Boolean | No | Whether to display the avatar of the current user. true: Yes (Default). false: No. |
easeInputMenu | String | No | The mode of the input menu. (Default) all: The complete mode. noAudio: No audio. noEmoji: No emoji. noAudioAndEmoji: No audio or emoji. onlyText: Only text. |
menuList | Array | No | The extensions of the input box on the right panel. (Default) menuList: [ {name:'Send a pic', value:'img'},{name:'Send a file', value:'file'}] |
handleMenuItem | function({item, key}) | No | The callback event triggered by clicking on the right panel of the input box. |
successLoginCallback | function(res) | No | The callback event for a successful login. |
failCallback | function(err) | No | The callback event for a failed method call. |
Add business logic
In use-cases where you want to add your own business logic, you can use the various callback events provided by the Chat UI Samples, as shown in the following steps:
-
Get the SDK instance
const WebIM = EaseApp.getSdk({ appkey: 'xxxx' }) -
Add callback events
Call
addEventHandlerto add the callback events.// Use nameSpace to differentiate the different business logics. You can add multiple callback events according to your needs.。 WebIM.conn.addEventHandler('nameSpace'),{ onOpend:()=>{}, onTextMessage:()=>{}, .... })Refer to EventHandlerType for the complete list of callback events you can add.
Reference
Agora Chat provides an open-source AgoraChat sample project on GitHub that has integrated the UI Samples. You can download the project to try it out or view the source code.
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
-
Flutter 2.10 or higher.
-
Dart 2.16 or higher.
-
If your target platform is iOS:
- macOS
- Xcode 12.4 or higher with Xcode Command Line Tools
- CocoaPods
- An iOS emulator or a physical iOS device running iOS 10.0 or higher
-
If your target platform is Android:
- macOS or Windows
- Android Studio 4.0 or higher with JDK 1.8 or higher
- An Android emulator or a physical Android device running Android SDK API 21 or higher
Project setup
Follow these steps to create the environment necessary to integrate Chat UI Kit into your app:
-
Create a new Flutter project for iOS and Android
-
In the terminal, run the following command:
flutter create uikit_demo --platforms=ios,android -i objc -a java -
Integrate the UI Kit using pub.dev (Recommended)
Execute the following commands in the
uikit_demodirectory:cd uikit_demo flutter pub add agora_chat_uikit flutter pub get
-
-
Add permissions for network and device access
-
For Android, 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 iOS, open info in the project navigation panel, and add the following properties to the Property List:
Key Type Value Privacy - Microphone Usage DescriptionString For microphone access Privacy - Camera Usage DescriptionString For camera access Privacy - Photo Library Usage DescriptionString For photo library access
-
-
Prevent code obfuscation
In the
example/android/app/proguard-rules.profile, add the following lines:-keep class com.hyphenate.** {*;} -dontwarn com.hyphenate.**
Implementation
This section shows how to use the Chat UI Kit to implement peer-to-peer messaging in your app.
The agora_chat_uikit library provides the logic and UI to implement the following Chat functions:
- Send, receive, and display messages
- Shows the unread message count, and clear messages. Text, image, emoji, file, and audio messages are supported
- Delete conversations and messages
- Customize the UI
Chat UI Kit widgets
| Widget | Function | Description |
|---|---|---|
ChatUIKit | Root widget | The root of all widgets in Chat UI Kit. |
ChatConversationsView | Conversation list | Presents avatar, nickname, last message, unread count, and message time. |
ChatConversationsView | Delete conversation | Deletes the conversation from the conversation list. |
ChatMessagesView | Message sender | Sends text, emoji, image, file, and voice messages. |
ChatMessagesView | Delete messages | Deletes messages. |
ChatMessagesView | Recall message | Recalls messages sent within 120 seconds. |
ChatMessagesView | Read mark | Receives a read receipt after a message is retrieved. |
ChatMessagesView | Message sent state | Displays the status after a message is sent. |
ChatMessagesView | Display message | Displays one-to-one and group messages with sender, content, time, and status information. |
Use the following UI Kit widgets to implement Chat functionality with the associated UI in your projects.
ChatUIKit
Place a ChatUIKit widget at the top of your widget tree to help refresh the ChatConversationsView when returning from ChatMessagesView.
ChatUIKit({
super.key,
this.child,
ChatUIKitTheme? theme,
});The ChatUIKit widget also enables you to customize the theme settings.
| Property | Description |
|---|---|
theme | Chat UI Kit theme for setting component styles. If this property is not set, the default style is used. |
ChatMessagesView
You use ChatMessagesView to manage text, images, emojis, files, and voice messages:
- Send and receive messages
- Delete messages
- Recall messages
| Property | Description |
|---|---|
conversation | The ChatConversation to which the messages belong. |
inputBarTextEditingController | Text input widget text editing controller. |
background | The background widget. |
inputBar | Text input component. If you don't pass in this property, ChatInputBar is used by default. |
onTap | Message bubble click callback. |
onBubbleLongPress | Callback for holding a message bubble. |
onBubbleDoubleTap | Callback for double-clicking a message bubble. |
avatarBuilder | Avatar component builder. |
nicknameBuilder | Nickname component builder. |
itemBuilder | Message bubble. If you don't set this property, the default bubble is used. |
moreItems | Action items displayed after a message bubble is held down. If you return null in onBubbleLongPress, moreItems is used. This property involves three default actions: copy, delete, and recall. |
messageListViewController | Message list controller. You are advised to use the default value. For details, see ChatMessageListController. |
willSendMessage | Text message pre-sending callback. This callback needs to return a ChatMessage object. |
onError | Error callback, such as no permissions. |
enableScrollBar | Whether to enable the scroll bar. The scroll bar is enabled by default. |
needDismissInputWidget | Callback for dismissing the input widget. If you use a custom input widget, dismiss the input widget when you receive this callback, for example, by calling FocusNode.unfocus. See ChatInputBar. |
inputBarMoreActionsOnTap | The callback for clicking the plus symbol next to the input box. You need to return the ChatBottomSheetItems list. |
ChatMessagesView({
required this.conversation,
this.inputBarTextEditingController,
this.background,
this.inputBar,
this.onTap,
this.onBubbleLongPress,
this.onBubbleDoubleTap,
this.avatarBuilder,
this.nicknameBuilder,
this.itemBuilder,
this.moreItems,
ChatMessageListController? messageListViewController,
this.willSendMessage,
this.onError,
this.enableScrollBar = true,
this.needDismissInputWidget,
this.inputBarMoreActionsOnTap,
super.key,
});ChatConversationsView
The 'ChatConversationsView' allows you to quickly display and manage the current conversations.
| Property | Description |
|---|---|
controller | The ChatConversationsView controller. |
itemBuilder | Conversation list item builder. Return a widget if you need to customize it. |
avatarBuilder | Avatar builder. If this property is not implemented or you return null, the default avatar is used. |
nicknameBuilder | Nickname builder. If you don't set this property or return null, the conversation ID is displayed. |
onItemTap | The callback of the click event of the conversation list item. |
backgroundWidgetWhenListEmpty | Background widget when the list is empty. |
enablePullReload | Whether to enable drop-down refresh. |
ChatConversationsView({
super.key,
this.onItemTap,
ChatConversationsViewController? controller,
this.itemBuilder,
this.avatarBuilder,
this.nicknameBuilder,
this.backgroundWidgetWhenListEmpty,
this.enablePullReload = false,
this.scrollController,
this.reverse = false,
this.primary,
this.physics,
this.shrinkWrap = false,
this.cacheExtent,
this.dragStartBehavior = DragStartBehavior.down,
this.keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
this.restorationId,
this.clipBehavior = Clip.hardEdge,
});Local integration
The recommended integration method for the Chat UI Kit is using pub.dev. You can also download the project manually for integration. To do this, open the uikit_demo project in your IDE and take the following steps:
-
Clone the Chat UIKit for Flutter repository.
git clone https://github.com/AgoraIO-Usecase/AgoraChat-UIKit-flutter.git -
Add the UI Kit dependency:
dependencies: agora_chat_uikit: path: `<#uikit path#>` -
Replace the code in
lib/main.dartwith the following:import 'package:flutter/material.dart'; import 'package:agora_chat_uikit/agora_chat_uikit.dart'; import 'messages_page.dart'; class ChatConfig { static const String appKey = ""; static const String userId = ""; static const String agoraToken = ''; } void main() async { assert(ChatConfig.appKey.isNotEmpty, "You need to configure AppKey information first."); WidgetsFlutterBinding.ensureInitialized(); final options = ChatOptions( appKey: ChatConfig.appKey, autoLogin: false, ); await ChatClient.getInstance.init(options); runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, ), builder: (context, child) { // ChatUIKit widget at the top of the widget return ChatUIKit(child: child!); }, localizationsDelegates: AppLocalizations.localizationsDelegates, supportedLocales: AppLocalizations.supportedLocales, home: const MyHomePage(title: 'Flutter Demo'), ); } } class MyHomePage extends StatefulWidget { const MyHomePage({super.key, required this.title}); final String title; @override State<MyHomePage> createState() => _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { ScrollController scrollController = ScrollController(); ChatConversation? conversation; String _chatId = ""; final List<String> _logText = []; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(widget.title), ), body: Container( padding: const EdgeInsets.only(left: 10, right: 10), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, mainAxisSize: MainAxisSize.max, children: [ const SizedBox(height: 10), const Text("login userId: ${ChatConfig.userId}"), const Text("agoraToken: ${ChatConfig.agoraToken}"), const SizedBox(height: 10), Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ Expanded( flex: 1, child: ElevatedButton( onPressed: () { _signIn(); }, child: const Text("SIGN IN"), ), ), const SizedBox(width: 10), Expanded( child: ElevatedButton( onPressed: () { _signOut(); }, child: const Text("SIGN OUT"), ), ), ], ), const SizedBox(height: 10), Row( children: [ Expanded( child: TextField( decoration: const InputDecoration( hintText: "Enter recipient's userId", ), onChanged: (chatId) => _chatId = chatId, ), ), const SizedBox(height: 10), ElevatedButton( onPressed: () { pushToChatPage(_chatId); }, child: const Text("START CHAT"), ), ], ), Flexible( child: ListView.builder( controller: scrollController, itemBuilder: (_, index) { return Text(_logText[index]); }, itemCount: _logText.length, ), ), ], ), ), ); } void pushToChatPage(String userId) async { if (userId.isEmpty) { _addLogToConsole('UserId is null'); return; } if (ChatClient.getInstance.currentUserId == null) { _addLogToConsole('user not login'); return; } ChatConversation? conv = await ChatClient.getInstance.chatManager.getConversation(userId); Future(() { Navigator.of(context).push(MaterialPageRoute(builder: (_) { return MessagesPage(conv!); })); }); } void _signIn() async { _addLogToConsole('begin sign in...'); if (ChatConfig.agoraToken.isNotEmpty) { try { await ChatClient.getInstance.loginWithAgoraToken( ChatConfig.userId, ChatConfig.agoraToken, ); } on ChatError catch (e) { _addLogToConsole('sign in fail: ${e.description}'); } } else { _addLogToConsole('sign in fail: The agoraToken cannot both be null.'); } } void _signOut() async { _addLogToConsole('begin sign out...'); try { await ChatClient.getInstance.logout(); _addLogToConsole('sign out success'); } on ChatError catch (e) { _addLogToConsole('sign out fail: ${e.description}'); } } void _addLogToConsole(String log) { _logText.add("$_timeString: $log"); setState(() { scrollController.jumpTo(scrollController.position.maxScrollExtent); }); } String get _timeString { return DateTime.now().toString().split(".").first; } } -
To create a
MessagesPagerefer to the following code:import 'package:agora_chat_uikit/agora_chat_uikit.dart'; import 'package:flutter/material.dart'; class MessagesPage extends StatefulWidget { const MessagesPage(this.conversation, {super.key}); final ChatConversation conversation; @override State<MessagesPage> createState() => _MessagesPageState(); } class _MessagesPageState extends State<MessagesPage> { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text(widget.conversation.id)), body: SafeArea( // Message page in uikit. child: ChatMessagesView( conversation: widget.conversation, ), ), ); } }
Test your app
To test the UI Kit Chat features example:
-
In
example/lib/main.dart, set theappKey,userId, andagoraTokento valid values.class ChatConfig { static const String appKey = ""; static const String userId = ""; static const String agoraToken = ''; } -
Click Sign In. You see a log message confirming sign-in success.
-
Run the app on another device or simulator. Ensure that the
userIdvalues are unique. -
On the first device or simulator, enter the user id you just created and click Start Chat. You can now start chatting between the two clients.
Reference
Refer to the fully featured AgoraChat-UIKit-flutter demo app as an implementation reference. To customize the UI Kit, refer to the following information.
Customize colors
You can set the color when adding ChatUIKit.
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
builder: (context, child) {
// ChatUIKit widget at the top of the widget
return ChatUIKit(
// ChatUIKitTheme
theme: ChatUIKitTheme(),
child: child!,
);
},
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}Add an avatar
class _MessagesPageState extends State<MessagesPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(widget.conversation.id)),
body: SafeArea(
// Message page in uikit.
child: ChatMessagesView(
conversation: widget.conversation,
avatarBuilder: (context, userId) {
// Returns the avatar widget that you want to display.
return Container(
width: 30,
height: 30,
color: Colors.red,
);
},
),
),
);
}
}Add a nickname
class _MessagesPageState extends State<MessagesPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(widget.conversation.id)),
body: SafeArea(
// Message page in uikit.
child: ChatMessagesView(
conversation: widget.conversation,
// Returns the nickname widget that you want to display.
nicknameBuilder: (context, userId) {
return Text(userId);
},
),
),
);
}
}Add the bubble click event
class _MessagesPageState extends State<MessagesPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(widget.conversation.id)),
body: SafeArea(
// Message page in uikit.
child: ChatMessagesView(
conversation: widget.conversation,
// item tap event
onTap: (context, message) {
bubbleClicked(message);
return true;
},
),
),
);
}
void bubbleClicked(ChatMessage message) {
debugPrint('bubble clicked');
}
}Customize the message item widget
class _MessagesPageState extends State<MessagesPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(widget.conversation.id)),
body: SafeArea(
// Message page in uikit.
child: ChatMessagesView(
conversation: widget.conversation,
itemBuilder: (context, model) {
if (model.message.body.type == MessageType.TXT) {
// Custom message bubble
return CustomTextItemWidget(
model: model,
onTap: (context, message) {
bubbleClicked(message);
return true;
},
);
}
},
),
),
);
}
void bubbleClicked(ChatMessage message) {
debugPrint('bubble clicked');
}
}
// Custom message bubble
class CustomTextItemWidget extends ChatMessageListItem {
const CustomTextItemWidget({super.key, required super.model, super.onTap});
@override
Widget build(BuildContext context) {
ChatTextMessageBody body = model.message.body as ChatTextMessageBody;
Widget content = Text(
body.content,
style: const TextStyle(
color: Colors.black,
fontSize: 50,
fontWeight: FontWeight.w400,
),
);
return getBubbleWidget(content);
}
}
Customize the input widget
class _MessagesPageState extends State<MessagesPage> {
late ChatMessageListController _msgController;
final TextEditingController _textController = TextEditingController();
final FocusNode _focusNode = FocusNode();
@override
void initState() {
super.initState();
_msgController = ChatMessageListController(widget.conversation);
}
@override
void dispose() {
_focusNode.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(widget.conversation.id)),
body: SafeArea(
// Message page in uikit.
child: ChatMessagesView(
conversation: widget.conversation,
messageListViewController: _msgController,
inputBar: customInputWidget(),
needDismissInputWidget: () {
_focusNode.unfocus();
},
),
),
);
}
// custom input widget
Widget customInputWidget() {
return SizedBox(
height: 50,
child: Row(
children: [
Expanded(
child: TextField(
focusNode: _focusNode,
controller: _textController,
),
),
ElevatedButton(
onPressed: () {
final msg = ChatMessage.createTxtSendMessage(
targetId: widget.conversation.id,
content: _textController.text);
_textController.text = '';
_msgController.sendMessage(msg);
},
child: const Text('Send'))
],
),
);
}
}
Delete all Messages in the current conversation
class _MessagesPageState extends State<MessagesPage> {
late ChatMessageListController _msgController;
@override
void initState() {
super.initState();
_msgController = ChatMessageListController(widget.conversation);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.conversation.id),
actions: [
TextButton(
onPressed: () {
_msgController.deleteAllMessages();
},
child: const Text(
'Clear',
style: TextStyle(color: Colors.white),
))
],
),
body: SafeArea(
// Message page in uikit.
child: ChatMessagesView(
conversation: widget.conversation,
messageListViewController: _msgController,
),
),
);
}
}Customize actions displayed upon a click of the plus symbol in the page
class _MessagesPageState extends State<MessagesPage> {
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.conversation.id),
),
body: SafeArea(
// Message page in uikit.
child: ChatMessagesView(
conversation: widget.conversation,
// Returns a list of custom events
inputBarMoreActionsOnTap: (items) {
ChatBottomSheetItem item = ChatBottomSheetItem(
type: ChatBottomSheetItemType.normal,
onTap: customMoreAction,
label: 'more',
);
return items + [item];
},
),
),
);
}
Future<void> customMoreAction() async {
debugPrint('custom action');
Navigator.of(context).pop();
}
}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
Android
If your target platform is Android:
- macOS 10.15.7 or later, or Windows 10 or later
- Android Studio 2021.3.1 or later, including JDK 1.8 or later
- Visual Studio Code latest
- React Native 0.63.5 or later
- CocoaPods package management tool if your operating system is macOS.
- Powershell 5.1 or later if your operating system is Windows.
- Node.js 16.18.0 or later, including npm package management tool (Homebrew installation is recommended)
- TypeScript 4.0 or later
- Yarn 1.22.19 or later
- Watchman debugging tool
- npm and related tools
- Expo 6.0.0 or later
- A physical or virtual mobile device running Android 6.0 or later
iOS
If your target platform is iOS:
- macOS 10.15.7 or later
- Xcode 13.4 or later, including command line tools
- Objective-C 2.0 or later
- Visual Studio Code latest
- React Native 0.63.5 or later
- Node.js 16.18.0 or later, including npm package management tool (Homebrew installation is recommended)
- TypeScript 4.0 or later
- CocoaPods package management tool
- Yarn 1.22.19 or later
- Watchman debugging tool
- npm and related tools
- Expo 6.0.0 or later
- A physical or virtual mobile device running iOS 10.0 or later
For more information, see Setting up the environment.
Project setup
To create a new project using the React Native UI Kit, take the following steps:
-
Create a new React-Native application project
npx react-native init RNUIkitQuickExamle --version 0.71.11 -
Initialize your project
yarn && yarn run env -
Add the required dependencies:
yarn add @react-native-async-storage/async-storage \ @react-native-camera-roll/camera-roll \ @react-native-clipboard/clipboard \ @react-native-firebase/app \ @react-native-firebase/messaging \ react-native-audio-recorder-player \ react-native-agora-chat \ react-native-agora-chat-uikit \ react-native-create-thumbnail \ react-native-document-picker \ react-native-fast-image \ react-native-file-access \ react-native-get-random-values \ react-native-image-picker \ react-native-permissions \ react-native-safe-area-context \ react-native-screens \ react-native-video -
Configure the iOS platform.
Add the following content to the
ios/Podfilefile:target 'RNUIkitQuickExamle' do pod 'GoogleUtilities', :modular_headers => true pod 'FirebaseCore', :modular_headers => true permissions_path = File.join(File.dirname(`node --print "require.resolve('react-native-permissions/package.json')"`), "ios") pod 'Permission-Camera', :path => "#{permissions_path}/Camera" pod 'Permission-MediaLibrary', :path => "#{permissions_path}/MediaLibrary" pod 'Permission-Microphone', :path => "#{permissions_path}/Microphone" pod 'Permission-Notifications', :path => "#{permissions_path}/Notifications" pod 'Permission-PhotoLibrary', :path => "#{permissions_path}/PhotoLibrary" endAdd the following content to the
ios/RNUIkitQuickExamle/Info.plistfile:<dict> <key>NSCameraUsageDescription</key> <string></string> <key>NSMicrophoneUsageDescription</key> <string></string> <key>NSPhotoLibraryUsageDescription</key> <string></string> </dict> -
Configure the Android platform.
Add the following content to the
android/build.gradlefile:buildscript { ext { kotlinVersion = '1.6.10' if (findProperty('android.kotlinVersion')) { kotlinVersion = findProperty('android.kotlinVersion') } } dependencies { classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion") } }Add the following content to the
android/app/src/main/AndroidManifest.xmlfile:<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.RECORD_AUDIO" /> </manifest>
Implementation
To initialize the UI Kit, log in to the server, and enter the chat page, take the following steps:
-
Initialize the UI Kit
export const App = () => { return ( <UikitContainer option={{ appKey: appKey, autoLogin: autoLogin, debugModel: debugModel, }} > <NavigationContainer> <Root.Navigator initialRouteName="Main"> <Root.Screen name="Main" component={MainScreen} /> <Root.Screen name="Chat" component={ChatScreen} /> </Root.Navigator> </NavigationContainer> </UikitContainer> ); }; -
Display Chat details
export function ChatScreen({ route, }: NativeStackScreenProps<typeof RootParamsList>): JSX.Element { return ( <ScreenContainer mode="padding" edges={['right', 'left', 'bottom']}> <ChatFragment screenParams={{ params: route.params as any, }} /> </ScreenContainer> ); }
Test your app
To set up and run the React Native Chat UI Kit example app:
-
Clone the Chat UI Kit for React Native repository.
git clone https://github.com/AgoraIO-Usecase/AgoraChat-UIKit-rn.git -
Initialize the project by running the following commands in a terminal:
yarn && yarn run example-env && yarn run sdk-version -
Configure the necessary parameters.
-
In the
exampleproject, openexample/src/env.tsand fill in the information.export const test = false; // test mode export const appKey = ''; // from Agora console export const id = ''; // default user id export const ps = ''; // default password or token export const accountType = 'agora'; -
For the
examples/callkit-exampleproject, addappKeyand other values to theexamples/callkit-example/src/env.tsfile.
-
-
Configure the FCM file.
-
In the
exampleproject, make the following changes for each platform:- For Android, put the google-services.json file under the
examples/android/appfolder. - For iOS, move GoogleService-Info.plist to the
example/iOS/ChatUikitExamplefolder.
- For Android, put the google-services.json file under the
-
In the
examples/callkit-exampleproject:- For Android, move the file google-services.json to the
examples/callkit-example/android/appfolder. - For iOS, place GoogleService-Info.plist under the
examples/callkit-example/iOS/ChatCallkitExamplefolder.
- For Android, move the file google-services.json to the
-
-
Run the sample project
Execute the following inside a terminal to compile and run the React Native Chat UI Kit example app:
-
For Android:
cd example && yarn run Android -
For iOS:
cd example && yarn run pods && yarn run iOS
-
Reference
-
UI Kit Details
Take a look at the Quick Start for UIKit project to experience the fastest and easiest integration.
-
CallKit Details
Take a look at the Quick Start for CallKit project to experience the fastest and easiest integration.
