Moxtra Chat SDK for Android

Use the Moxtra Chat SDK module to add rich collaboration to your app.

What features you will get with the SDK:

  • Ability to do one-on-one and/or group chats
  • Share files
  • Layer visual & voice annotations on top of files
  • Conduct real-time meetings

Let's get started by using a sample app "Moxie Chat". In this tutorial, you will learn how to integrate the Moxtra Chat SDK in your app.

The diagram below explains how the sample app "Moxie Chat" will interact with Moxtra.

App Architecture

Setup

Asynchronous programming

You should have some basic knowledge of asynchronous programming. This type of programming involves calling an API with a XXXListener parameter and then waiting for the listener callback before continuing with next steps.

Register

Before proceeding further, ensure you have registered your app with Moxtra.

Clone the sample project from github

Please clone the sample project MoxieChat from here:

git clone https://github.com/Moxtra/moxtra-chat-android-sample MoxieChat

  • If you don't have git installed, you can download and install git from http://git-scm.com.

Step 0: Create an empty project in Android Studio

This tutorial uses Android Studio to create the sample app 'Moxie Chat'. Ensure you have installed Android Studio and the Android SDK (launch the SDK Manager in Android Studio and follow the setup wizard to install) before proceeding further.

You can start by creating an empty project using Android Studio.

Step 1: Add the Moxtra Chat SDK dependency

After creating the MoxieChat empty project, it's time to add the Moxtra Chat SDK dependency to it.

First, we need to add the Moxtra Maven Repo URL to let gradle know where do download the dependencies.

Edit the build.gradle under the MoxieChat and add the following code snippet:

maven() {
    url "https://maven.moxtra.com/nexus/content/groups/public"
}

And add the following dependency to the build.gradle file in the app folder:

compile "com.moxtra:chat-sdk:${CHAT_SDK_VERSION}"

You can now sync the project with gradle files:

Tools -> Android -> Sync Project with Gradle Files

Make sure you do not have any compile errors before proceeding further.

Step 2: Build scaffold and adding test data

In this step you will build the scaffold for your app and add some user data for testing.

The Moxtra Chat SDK has NO user management feature and it's up to you to define it. In this tutorial, you can use some test data.

Step 3: Implement the login and logout functionality

This tutorial covers only the login/setup user flow related to Moxtra Chat SDK shown below.

The login flow looks like this:

Login Flow

You should initialize ChatClient when starting your app. For the purpose of this tutorial, you can initialize it in the Application as shown in the code snippet below:

@Override
public void onCreate() {
    super.onCreate();
    ChatClient.initialize(this);
}

The linkXXX is an asynchronous operation and requires to pass a callback to it. The callback interface can be implemented as shown below:

ChatClient.linkWithUniqueID(uniqueId, clientId, clientSecret, orgId, baseDomain,
    new ApiCallback() {
        @Override
        public void onCompleted(ChatClientDelegate ccd) {
            Log.i(TAG, "Linked to Moxtra account successfully.");
            PreferenceUtil.saveUser(mContext, uniqueId);
            startChatListActivity();
        }
        
        @Override
        public void onError(int errorCode, String errorMsg) {
            Log.e(TAG, "Failed to link to Moxtra account, errorCode=" +
            errorCode + ", errorMsg=" + errorMsg);
            showProgress(false);
        }
});

We pass clientId and clientSecret (2nd and 3rd parameters, you should replace it with your clientId and clientSecret when building your app). And set the baseDomain to development environment (sandbox.moxtra.com). When you are ready to move into production (www.moxtra.com), contact us.

The clientSecret should be confidential and you'd better to store it on your server side. It has two advantages:

  • Relatively secure and hard to hack in.
  • You can change the secret later and it will not impact your current distributted app client.

After the user has successfully logged out from your app, unlink the Moxtra account. In MoxieChat (or in this tutorial, it is implemented in the BaseActivity Action Toolbar so the user can logout from any activity that extends BaseActivity).

ChatClient.unlink(new ApiCallback() {
    @Override
    public void onCompleted(Void result) {
        Log.i(TAG, "Unlink Moxtra account successfully.");
    }

    @Override
    public void onError(int errorCode, String errorMsg) {
        Log.e(TAG, "Failed to unlink Moxtra account, errorCode=" + errorCode + ", errorMsg=" + errorMsg);
    }
});

Step 4: Show the chat list

Two scenarios need to be considered when showing the chat list:

  • Initializing the chat list when a user opens your app
  • Updating the user of any updates to the chats when the user is inside the chat list UI of your app.

Now let's build the chat list using the ChatRepo API. To get an instance of the ChatRepo, you can use the following code snippet:

mChatRepo = mChatClientDelegate.createChatRepo();

When a user opens the chat list UI, you can call the following API to get all the chats:

List chatList = mChatRepo.getList();

After getting a list of all chat sessions, you have to listen for changes so the UI can be updated automatically.

mChatRepo.setOnChangedListener(new BaseRepo.OnRepoChangedListener() {
    @Override
    public void onCreated(List items) {
        mAdapter.updateChats(mChatRepo.getList());
    }

    @Override
    public void onUpdated(List items) {
        mAdapter.updateChats(mChatRepo.getList());
    }

    @Override
    public void onDeleted(List items) {
        mAdapter.updateChats(mChatRepo.getList());
    }
});

Step 5: Start a new chat

In the sample app, you now have an empty chat list.

You have to build the UI to select another user to chat by calling the following API:

mChatRepo.createGroupChat(topic, new ApiCallback() {
    @Override
    public void onCompleted(Chat chat) {
        Log.i(TAG, "Create group chat successfully.");
        mChat = chat;
        mChat.inviteMembers(orgId, uniqueIdList, new ApiCallback() {
            @Override
            public void onCompleted(Void result) {
                Log.i(TAG, "Invite members successfully.");
            }

            @Override
            public void onError(int errorCode, String errorMsg) {
                Log.e(TAG, "Failed to invite members, errorCode=" + errorCode + ", errorMsg=" + errorMsg);
            }
        });
        showChatFragment();
    }

    @Override
    public void onError(int errorCode, String errorMsg) {
        Log.e(TAG, "Failed to create group chat, errorCode=" + errorCode + ", errorMsg=" + errorMsg);
        finishInMainThread();
    }
});

As you may have noticed from the last parameter in the API call, the apiCallback is an asynchronous operation.

In the Moxtra Chat SDK, all operations with a XXXCallback are asynchronous operations. The result of this asynchronous operation is not known until a callback is fired.

Step 6: Open an existing chat

To open an existing chat from the chat list UI by calling the following API:

mChatController = mChatClientDelegate.createChatController(mChat);
Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.chat_frame);
if (fragment == null) {
    fragment = mChatController.createChatFragment();
    getSupportFragmentManager().beginTransaction().add(R.id.chat_frame,
    fragment).commit();
}

Step 7: Start a meet

With Moxtra Chat SDK, you can easily start an audio/video meeting with a few lines of code. You can aslo share your screen and annotate documents inside the meeting.

This step is required only if you implement the start meet functionality outside the chat UI.

To start a meeting outside the Chat UI, use the following code snippet:

mMeetRepo = mChatClientDelegate.createMeetRepo();
mMeetRepo.startMeetWithTopic(topic, new ApiCallback() {
    @Override
    public void onCompleted(MeetSession meetSession) {
        Log.i(TAG, "Start meet successfully.");
        mMeetSession = meetSession;
        mMeetSession.inviteParticipants(userList, new ApiCallback() {
            @Override
            public void onCompleted(Void result) {
                Log.i(TAG, "Invite participants successfully.");
            }

            @Override
            public void onError(int errorCode, String errorMsg) {
                Log.i(TAG, "Failed to invite participants, errorCode=" + errorCode + ", errorMsg=" + errorMsg);
            }
        });
        mMeetSessionController = mChatClientDelegate.createMeetSessionController(mMeetSession);
        showMeetFragment();
    }

    @Override
    public void onError(int errorCode, String errorMsg) {
        Log.e(TAG, "Failed to start meet, errorCode=" + errorCode + ", errorMsg=" + errorMsg);
        finishInMainThread();
    }
});

Please implement the callback interface to determine if the meeting was started successfully.

After that, you are able to show meet fragment in your activity,

Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.meet_frame);
if (fragment == null) {
    fragment = mMeetSessionController.createMeetFragment();
    getSupportFragmentManager().beginTransaction().add(R.id.meet_frame,
    fragment).commit();
}

Step 8: Join a meet

Invited users should be able to join a meeting. Use this code snippet to implement the join meeting functionality.

mMeetRepo.joinMeet(meet.getID(), new ApiCallback() {
    @Override
    public void onCompleted(MeetSession meetSession) {
        Log.i(TAG, "Join meet successfully.");
        mMeetSession = meetSession;
        mMeetSessionController = mChatClientDelegate.createMeetSessionController(mMeetSession);
        showMeetFragment();
    }

    @Override
    public void onError(int errorCode, String errorMsg) {
        Log.e(TAG, "Failed to join meet, errorCode=" + errorCode + ", errorMsg=" + errorMsg);
        finishInMainThread();
    }
});

Step 9: Delete Chat

Every chat that is created is a persistent chat. Chat messages are saved over time, so new and old chat members can see all the chat history at any time.

If you have a chat that is no longer required, then it can be deleted. Use this code snippet to implement the Delete Chat functionality.

mChatRepo.deleteOrLeaveChat(chat, new ApiCallback() {
    @Override
    public void onCompleted(Void result) {
        Log.i(TAG, "Leave or delete chat successfully.");
    }

    @Override
    public void onError(int errorCode, String errorMsg) {
        Log.e(TAG, "Failed to leave or delete chat, errorCode=" + errorCode + ", errorMsg=" + errorMsg);
    }
});

Step 10: Enable notifications

Notifications are an essential feature for real-time communication apps. We recommend the use of Firebase for this purpose, and have implemented it into our sample app as an example. (If for some reason Firebase cannot be used, long-connections are an alternative means of providing notifications support: click here.)

To set up Firebase for your app, please reference: https://firebase.google.com/docs/android/setup. Use of Android Studio 2.2 or later is recommended, as its inbuilt Firebase Assistant greatly expedites the process.

Next, to set up Firebase Cloud Messaging, please reference: https://firebase.google.com/docs/android/setup.

Once basic FCM functionality has been set up, please take the following steps to integrate it with Moxtra.

After a user logs on, implement the following method to allow the Moxtra Notification Manager access to handle Firebase notifications:


private void initNotification() {
    ChatClientDelegate ccd = ChatClient.getClientDelegate();
    if (ccd == null) return;

    NotificationManager nm = ccd.getNotificationManager();
    if (nm == null) return;

    if (GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(this) != ConnectionResult.SUCCESS) {
        nm.useBuiltInPushNotification(MoxtraNotificationService.class.getName());
    } else {
        FirebaseMessaging.getInstance().setAutoInitEnabled(true);
        String token = FirebaseInstanceId.getInstance().getToken();
        if (!TextUtils.isEmpty(token)) nm.setNotificationDeviceToken(token);
    }
}

After a user logs off, make sure to disconnect from Firebase by adding the following snippet of code to your logout logic:


FirebaseMessaging.getInstance().setAutoInitEnabled(false);
new AsyncTask() {
    @Override
    protected Void doInBackground(Void... voids) {
        try {
            FirebaseInstanceId.getInstance().deleteInstanceId();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
}.execute();

The Moxtra Notification Manager must be notified whenever the Firebase registration token is refreshed.

Therefore, create a subclass of FirebaseInstanceIdService and override onTokenRefresh as follows:


public class FcmInstanceIDService extends FirebaseInstanceIdService {
    @Override
    public void onTokenRefresh() {
        super.onTokenRefresh();
        String token = FirebaseInstanceId.getInstance().getToken();
        if (!TextUtils.isEmpty(token)) {
            ChatClientDelegate ccd = ChatClient.getClientDelegate();
            if (ccd == null) return;

            NotificationManager nm = ccd.getNotificationManager();
            if (nm == null) return;

            nm.setNotificationDeviceToken(token);
        }
    }
}

Whenever a notification is received from Firebase, it is handled by the method onMessageReceived in FirebaseMessagingService. Override this method in your subclass of FirebaseMessagingService and Moxtra's isValidRemoteNotification to determine if it is a Moxtra message:


public class FcmService extends FirebaseMessagingService {

    //...

    @Override
    public void onMessageReceived(RemoteMessage remoteMessage) {
        super.onMessageReceived(remoteMessage);

        //isValidRemoteNotification takes an Intent as a parameter, which must be
        //  packaged in the following way:
        Intent intent = new Intent();
        Map data = remoteMessage.getData();
        for (String key : data.keySet()) {
            intent.putExtra(key, data.get(key));
        }

        //note: NotificationHelper =/= NotificationManager
        boolean handled = NotificationHelper.isValidRemoteNotification(intent);
        if (handled) {
            //this is a Moxtra Chat SDK message, and can be handled as you wish
            //this method gets the text of the notification
            String title = NotificationHelper.getNotificationMessageText(this, intent);

            //you can now do whatever you want to react to this Moxtra message
            //  as an example, reference the sample app's sendChatSDKNotification method in
            //  FcmService, which will construct a notification to display on the screen with a sound
        } else {
            //not a chat sdk message; can be ignored
        }
        Log.i(TAG, "Received: " + intent.getExtras().toString());
    }

    //...

}

Step 11: Prepare for release

In order to prepare your app for release, you should set up the signing configuration. Don't forget to update the proguard rules.

API Reference

This tutorial covers only a few APIs. For a full list of APIs, please refer to our Android Chat SDK: API Reference Guide.

Current Version - V 5.2.8 (March 29, 2019)

Bug Fixes
- Fixed UI issue with emoji when using reply.
- Fixed issue with URL display in chat message.

Previous Versions

V 5.2.6 (February 28, 2019)

Bug Fixes
- Fixed typo from "Pages" to "Files" in search results.
- Updated to show both first name and last name in push notification and timeline feed.
- Added warning message when user try to upload a file beyond the size limitation.
- Minor UI fix for opening web page inside the app.

V 5.2.4 (November 30, 2018)

- Initial 5.x SDK release