Client - Android - Guide

Before reading this document, please first take a look at Realtime Engine Guide.

API Documentation

This guide only provides a brief integration description. For more details on the usage, see API Documentation.

Android Version Compatibility

Android 3.0 (SDK API 11)and above is supported.

Installation

Use JCenter

  1. Open the build.gradle file of the project.
  2. Add compile 'com.tuisongbao.android:realtime-engine:2.3.0' to dependencies.
  3. Sync up the dependencies, and will be ready when download completes.

Others

  1. Download SDK
  2. Unzip the SDK, and introduce all the dependencies under the libs directory to the project.

Configure and Establish Connection

Configure AndroidManifest.xml

  • Declare the version of SDK:

      <uses-sdk android:minSdkVersion="11" android:targetSdkVersion="xxx" />
    
  • Add permission declarations:

      <!-- Network connection. -->
      <uses-permission android:name="android.permission.INTERNET" />
      <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    
      <!-- When new messages are received, the application process needs to be woken up -->
      <uses-permission android:name="android.permission.WAKE_LOCK" />
    

Initialize Engine

Usually you need to intialize an Engine instance when enabling an application and keep global singleton references. Please see below:

public class DemoApplication extends Application {
    public static Engine engine;

    @Override
    public void onCreate() {
        super.onCreate();

        // Initialize EngineOptions
        // The appId is the ID assigned when you register the application on the TuiSongBao official website; authUrl is used for authentication
        EngineOptions options = new EngineOptions(appId , authUrl);

        // Initialize Engine
        engine = new Engine(this, options);
    }
}

The connection is automatically established after instantiation, and you can use the Engine instance returned for later various operations.

Connection Event

Through engine.getConnection(), you can get the Connection instance, on which you can listen for Event to get the connection state. Please see below:

Emitter.Listener stateListener = new Emitter.Listener() {
    @Override
    public void call(Object... args) {
        Connection.State state = (Connection.State)args[0];
        Log.i(TAG, "Connection state changed to: " + state.toString());
    }
};

connection.bind(Connection.State.Initialized, stateListener);
connection.bind(Connection.State.Connecting, stateListener);
connection.bind(Connection.State.Connected, stateListener);
connection.bind(Connection.State.Disconnected, stateListener);

// This is only the callback of state transition, and doesn't tell the reason. You need to listen for Connection.EVENT_ERROR Event to get the reason for failure
connection.bind(Connection.State.Failed, stateListener);

connection.bind(Connection.EVENT_CONNECT_IN, new Emitter.Listener() {
    @Override
    public void call(Object... args) {
        Log.i(TAG, "Connect in " + args[0] + " seconds");
    }
});
connection.bind(Connection.EVENT_STATE_CHANGED, new Emitter.Listener() {
    @Override
    public void call(Object... args) {
        Log.i(TAG, "Connection state changed from " + args[0] + " to " + args[1]);
    }
});
connection.bind(Connection.EVENT_ERROR, new Emitter.Listener() {
    @Override
    public void call(Object... args) {
        Log.i(TAG, "Connection error: " + args[0]);
    }
});

Get SocketId

connection.bind(Connection.State.Connected, new Emitter.Listener() {
    @Override
    public void call(Object... args) {
        String socketId = connection.getSocketId();
    }
});

Disconnect

Call disconnect on the connection object:

connection.disconnect();

To restore the connection, call connect:

connection.connect();

Pub/Sub

The Pub/Sub related functions are completed through ChannelManager, which can be obtained through engine.getChannelManager().

Subscribe Channel

Channel channel = channelManager.subscribe("demo", null);

Calling the above method will return the Channel instance, which can be used for the bind operation.

Unsubscribe through the unsubscribe method:

channelManager.unsubscribe(channelName)

Bind/Unbind Event Callback Function

Emitter.Listener listener = new Emitter.Listener() {
    @Override
    public void call(Object... args) {
        String data = args[0].toString();
        Log.i(TAG, "Get " + data);
    }
};

// Bind the callback function of the event named 'cool-event'
channel.bind("cool-event", listener);

// Unbind a certain callback function of the 'cool-event' event
channel.unbind("cool-event", listener);

// Unbind all the callback functions of the 'cool-event' event
channel.unbind("cool-event");

Channel Event

All the Channels can handle the subscription result through listening for the EVENT_SUBSCRIPTION_SUCCESS and EVENT_SUBSCRIPTION_ERROR Event:

channel.bind(Channel.EVENT_SUBSCRIPTION_SUCCESS, new Emitter.Listener() {
    @Override
    public void call(Object... args) {
        Log.i(TAG, "Subscribe succeeded");
    }
});
channel.bind(Channel.EVENT_SUBSCRIPTION_ERROR, new Emitter.Listener() {
    @Override
    public void call(Object... args) {
        String message = args[0].toString();
        Log.i(TAG, "Subscribe failed with error: " + message);
    }
});

As for Presence Channel and EVENT_SUBSCRIPTION_SUCCESS, the callback function will get an additional parameter, i.e., the list of current online users:

presenceChannel.bind(Channel.EVENT_SUBSCRIPTION_SUCCESS, new Emitter.Listener() {
    @Override
    public void call(Object... args) {
        List<OnlineUser> users = (List)args[0];
        Log.i(TAG, "Subscribe succeeded");
    }
});

Additionally, you can listen for EVENT_USER_ADDED and EVENT_USER_REMOVED Event on the Presence Channel object to handle notifications of users’ going online and offline:

presenceChannel.bind(PresenceChannel.EVENT_USER_ADDED, new Emitter.Listener() {
    @Override
    public void call(Object... args) {
        User user = (User)args[0];
        String id = user.id;
        String info = user.info;
    }
});

presenceChannel.bind(PresenceChannel.EVENT_USER_REMOVED, new Emitter.Listener() {
    @Override
    public void call(Object... args) {
        User user = (User)args[0];
        String id = user.id;
        String info = user.info;
    }
});

Chat

The Chat messages can only be received when the application is opened, otherwise, unable to be received normally. If necessary, the Push SDK can offer the offline notification function. Please refer to Android Push Guide for integration, after which the offline messages will automatically reach user’s mobiles through the Push* service. No additional configuration is required for the realtime engine.

Configure AndroidManifest.xml

  • Add the following permission:
<!--Audio Message-->
<uses-permission android:name="android.permission.RECORD_AUDIO" />

Get ChatManager

The Chat relate functions are completed through ChatManager, which can be obtained through engine.getChatManager().

ChatManager Event

chatManager.bind(ChatManager.EVENT_LOGIN_SUCCEEDED, new Emitter.Listener() {
    @Override
    public void call(Object... args) {
        ChatUser user = (ChatUser)args[0];
        Log.i(TAG, "Login succeeded");
    }
});
chatManager.bind(ChatManager.EVENT_LOGIN_FAILED, new Emitter.Listener() {
    @Override
    public void call(Object... args) {
        Log.i(TAG, "Login failed");
    }
});
chatManager.bind(ChatManager.EVENT_PRESENCE_CHANGED, new Emitter.Listener() {
    @Override
    public void call(Object... args) {
        ChatUserPresence data = (ChatUserPresence)args[0];
        Log.i(TAG, data.getUserId() + " changed to " + data.getChangedTo());
    }
});
chatManager.bind(ChatManager.EVENT_MESSAGE_NEW, new Emitter.Listener() {
    @Override
    public void call(Object... args) {
        ChatMessage message = (ChatMessage)args[0];
    }
});
Note that when the network is not stable, SDK will automatically resign in, and the corresponding Events will be retriggered. That developers refresh the conversation list and so on in the ChatManager.EVENT_LOGIN_SUCCEEDED Event is suggested so as to sync up the updates during network disconnection.

Sign In

This operation is asynchronous. Please refer to the above example to listen for the related events to get the result.

chatManager.login(userData);

Sign Out

chatManager.logout(new EngineCallback<String>() {
    @Override
    public void onSuccess(String t) {
        Log.i(TAG, "Successfully logout");
    }

    @Override
    public void onError(ResponseError error) {
        Log.i(TAG, "Failed to logout");
    }
});

After signing out, SDK will unbind all the ChatManager related Events.

Cache

// Enable cache
chatManager.enableCache()

// Disable cache
chatManager.disableCache()

Get ConversationManager through chatManager.getConversationManager() to perform conversation related operations.

Get Conversation List

Filter through session type and target:

conversationManager.getList(type, target, new EngineCallback<List<ChatConversation>>() {
    @Override
    public void onSuccess(final List<ChatConversation> t) {
        Log.d(TAG, "Get " + t.size() + " conversations");
    }

    @Override
    public void onError(ResponseError error) {
        activity.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                Toast.makeText(getActivity(), "Failed to get conversation list, try later", Toast.LENGTH_LONG).show();
            }
        });
    }
});

Get ChatConversation Instance

Developers can call the methods on the ChatConversation instance to send messages, delete conversations, reset unread messages, and so forth. It can be obtained from the coversation list as mentioned in the above example, or you can create a specific conversation through the following method:

ChatConversation conversation = new ChatConversation(engine);
conversation.setTarget(targetId);
conversation.setType(ChatType.SingleChat);

conversation.sendMessage(message, callback);

Get Conversation History

conversation.getMessages(startMessageId, null, 20, new EngineCallback<List<ChatMessage>>() {
    @Override
    public void onSuccess(final List<ChatMessage> t) {
        Log.d(TAG, "Get " + t.size() + " messages");
    }

    @Override
    public void onError(ResponseError error) {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                Toast.makeText(ChatConversationActivity.this,
                        "Failed to get messages, try later", Toast.LENGTH_LONG).show();
            }
        });
    }
});

Reset Unread Messages of a Conversation as 0

conversation.resetUnread(new EngineCallback<String>() {
    @Override
    public void onSuccess(String t) {
        Log.i(TAG, "Successfully reset unread count");
    }

    @Override
    public void onError(ResponseError error) {
        Log.i(TAG, "Failed to reset unread count");
    }
});

Delete Conversations

conversation.delete(new EngineCallback<String>() {
    @Override
    public void onSuccess(String t) {
        Log.i(TAG, "Successfully delete conversation");
    }

    @Override
    public void onError(ResponseError error) {
        Log.i(TAG, "Failed to delete conversation");
    }
});

Listen for New Messages

Emitter.Listener messageNewListener = new Emitter.Listener() {
    @Override
    public void call(Object... args) {
        ChatMessage message = (ChatMessage)args[0];
    }
};

chatManager.bind(ChatManager.EVENT_MESSAGE_NEW, messageNewListener);

Send Messages

The sendings of callback of all message types are the same. Please see below:

private EngineCallback<ChatMessage> sendMessageCallback = new EngineCallback<ChatMessage>() {
    @Override
    public void onSuccess(final ChatMessage message) {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                // Refresh message list
            }
        });
    }

    @Override
    public void onError(final ResponseError error) {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                // Notify the user of operation failure
            }
        });
    }
};

Send messages by calling the sendMessage method on the ChatConversation object. This method will return ChatMessage for developers to refresh UI.

  • Simple Text Message

      ChatMessageContent content = new ChatMessageContent();
      content.setText("Hello~");
      ChatMessage sentMessage = conversation.sendMessage(content, sendMessageCallback);
    
  • Multimedia Message

    Multimedia messages refer to image, audio and video messages. Developers need to call setFilePath to specify the absolute path of the multimedia file.

      // Use ChatMessageImageContent when sending image messages, and ChatMessageVoiceContent when sending audio messages
      ChatMessageVideoContent content = new ChatMessageVideoContent();
      content.setFilePath(videoPath);
      ChatMessage sentMessage = conversation.sendMessage(content, sendMessageCallback);
    

Receive Image Messages

SDK provides the downloadThumb and download methods for downloading thumbnail and original images respectively. The methods can be called repeatedly. SDK will automatically check whether there is a local cache, and will download from the server if no.

message.getContent().downloadThumb(new EngineCallback<String>() {
    @Override
    public void onSuccess(final String filePath) {
        ((ChatConversationActivity) context).runOnUiThread(new Runnable() {
            @Override
            public void run() {
                Bitmap bmp = BitmapFactory.decodeFile(filePath);
                imageView.setImageBitmap(bmp);
            }
        });
    }

    @Override
    public void onError(ResponseError error) {
        ((ChatConversationActivity) context).runOnUiThread(new Runnable() {
            @Override
            public void run() {
                textView.setText("Failed to load image");
            }
        });
    }
}, new ProgressCallback() {
    @Override
    public void progress(final int percent) {
        ((ChatConversationActivity) context).runOnUiThread(new Runnable() {
            @Override
            public void run() {、
                // Download progress prompt
                textView.setText(percent + "%");
            }
        });
    }
});

Record Audio Messages

SDK offers ChatVoiceRecorder for recording audios easily. Developers can also implement it themselves.

// Initializing ChatVoiceRecorder in onCreate is suggested
recorder = new ChatVoiceRecorder();
recorder.setMinDuration(2 * 1000, new Emitter.Listener() {
    @Override
    public void call(final Object... args) {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                Toast.makeText(getApplicationContext(),
                    "duration is " + args[0] + ", less then min duration " + args[1], Toast.LENGTH_SHORT).show();
            }
        });
    }
});
recorder.setMaxDuration(10 * 1000, new Emitter.Listener() {
    @Override
    public void call(final Object... args) {
        Toast.makeText(getApplicationContext(),
            "Reach max duration " + args[0] + ", send message automatically", Toast.LENGTH_SHORT).show();

        // Call recorder.stop(), and send messages after getting the path of the audio file
        onRecordFinished();
        // Call recorder.start(), and continue to record audios
        onRecordStart();
    }
});

// Start recording
recorder.start();

// Stop recording, and return the path of the audio file for sending messages
String filePath = recorder.stop();

// Cancel recording
recorder.cancel();

// Calling this interface in onDestroy is suggested to release resources
recorder.release();

Receive Audio Messages

SDK offers ChatVoicePlayer exclusively for playing the audio resources of TuiSongBao easily. Developers can also impelement it themselves. After calling the start method, SDK will automatically download the audio file, and automatically play it when download completes, and will execute the onStop callback when playing is stopped; will execute the onError callback if an error occurs while downloading or playing the audio, and also tell the reason of the error. This method can be called repeatedly:

ChatVoicePlayer player = ChatVoicePlayer.getInstance();
player.start(message, new ChatVoicePlayer.OnStopListener() {
    @Override
    public void onStop() {
        Log.d(TAG, "onStop");
    }
}, new ChatVoicePlayer.OnErrorListener() {
    @Override
    public void onError(String error) {
        Log.d(TAG, "Failed to play this voice, because " + error);
    }
}, new ProgressCallback() {
    @Override
    public void progress(final int percent) {
        ((ChatConversationActivity)context).runOnUiThread(new Runnable() {
            @Override
            public void run() {
                // Download progress prompt
            }
        });
    }
});

Video Message

  • Because recording video messages needs to use SurfaceView, which has not been integrated well in SDK, developers need to record videos themselves, and then send messages through the absolute path of the file.
  • Video message provides the first frame of the thumbnail. You can call message.getContent().downloadThumb() to get it, and call message.getContent().download() to download the video file.

Location Data Message

SDK contains the helper class to make it easy to get the current location:

// Set time-out with unit "sec"
int timeout = 5;
ChatLocationManager.getInstance().getCurrentLocation(new EngineCallback<Location>() {
    @Override
    public void onSuccess(Location location) {
        Toast.makeText(getApplicationContext(), "Successfully located, and sent the current location", Toast.LENGTH_LONG).show();
        // API Please refer to API for other constructor methods
        ChatMessageLocationContent content = new ChatMessageLocationContent(location);
        mConversation.sendMessage(content, sendMessageCallback, null);
    }

    @Override
    public void onError(ResponseError error) {
        Toast.makeText(getApplicationContext(), "Failed to locate", Toast.LENGTH_LONG).show();
    }
}, timeout);
Note that acquiring the location function needs the support of mobile. GPS is not availbe indoors, and the acquisition through network on certain Android devices can be supported after manual configuration. Please go to “Settings”->”Location”, and check “Allow to determine location using WLAN and mobile network” [?????] (The name will be different on different devices, and please make your own judgement), and it’s better to restart your mobile phone after setting it to ensure that the setting can take effect.

Get location content:

content = chatMessage.getContent();
if (content.getType() == TYPE.LOCATION) {
    ChatMessageLocationContent location = (ChatMessageLocationContent)content;
    String locationDescription = String.format("longitude and latitude:(%s, %s), point of interest: %s"
            , location.getLongitude(), location.getLatitude(), location.getPointOfInterest());
}

Perform the group related operations through ChatGroupManager, which can be obtained by calling the following method:

chatManager.getGroupManager()

Get Group List

groupManager.getList(null, new EngineCallback<List<ChatGroup>>() {
    @Override
    public void onSuccess(List<ChatGroup> t) {
        Log.i(TAG, "Get " + t.size() + " groups");
    }

    @Override
    public void onError(ResponseError error) {
        Log.i(TAG, "Failed to get groups, because " + error.getMessage());
    }
});

Create Groups

groupManager.create(members, true, false, new EngineCallback<ChatGroup>() {
        @Override
        public void onSuccess(ChatGroup t) {
            Log.i(TAG, "Create group successfully, " + t);
        }

        @Override
        public void onError(ResponseError error) {
            Log.i(TAG, "Failed to create group, because " + error.getMessage());
        }
});

Get Group User List

group.getUsers(new EngineCallback<List<ChatUserPresence>>() {
    @Override
    public void onSuccess(List<ChatUserPresence> t) {
        Log.i(TAG, "There are " + t.size() " members in this group");
    }

    @Override
    public void onError(ResponseError error) {
        Log.i(TAG, "Failed to get members, because " + error.getMessage());
    }
});

Other APIS

Please see API Documentation

Notes

  • All the callbacks are executed in the background thread. To refresh UI, call back to the main thread.

      groupManager.getList(null, new EngineCallback<List<ChatGroup>>() {
          @Override
          public void onSuccess(List<ChatGroup> t) {
              runOnUiThread(new Runnable() {
                  @Override
                  public void run() {
                      // Refresh group list
                  }
              });
          }
    
          @Override
          public void onError(ResponseError error) {
              runOnUiThread(new Runnable() {
                  @Override
                  public void run() {
                      // Notify the user of operation failure
                  }
              });
          }
      });
    
  • After integrating with Push SDK, the onCreate method of the Applciation will be called again when Remote Service is enabled, so you need to prevent the Engine SDK from being instantiated twice through the following method:

      @Override
      public void onCreate() {
          super.onCreate();
    
          PushManager.init(this);
    
          // Return directly when the process already exists
          String processName = getProcessName(android.os.Process.myPid());
          if (processName == null || !processName.equalsIgnoreCase("com.tuisongbao.engine.demo")) {
              return;
          }
    
          // Intialize EngineOptions
          // appId The appId is the ID assigned when you register the application on the official website of TuiSongBao; authUrl is used for authentication
          EngineOptions options = new EngineOptions(appId , authUrl);
    
          // Initialize Engine
          engine = new Engine(this, options);
      }
    
      private String getProcessName(int pid) {
          String processName = null;
          ActivityManager am = (ActivityManager) this.getSystemService(ACTIVITY_SERVICE);
          List<ActivityManager.RunningAppProcessInfo> l = am.getRunningAppProcesses();
          Iterator<ActivityManager.RunningAppProcessInfo> i = l.iterator();
          while (i.hasNext()) {
              ActivityManager.RunningAppProcessInfo info = (i.next());
              try {
                  if (info.pid == pid) {
                      processName = info.processName;
                      return processName;
                  }
              } catch (Exception e) {
              }
          }
          return processName;
      }
    
  • Thebindandunbindoperations ofEventEmittermust be used in pairs. Maintaining event binding throughHashMap` is recommended, and this can be integrated into Android life cycle events easily:

      private Map<String, Emitter.Listener> listenersMap = new HashMap<>();
    
      private void initAllListeners() {
          listenersMap.put(ChatManager.EVENT_LOGIN_SUCCEEDED, new Emitter.Listener() {
              @Override
              public void call(Object... args) {
                  showToaster("Auto login success");
              }
          });
          listenersMap.put(ChatManager.EVENT_LOGIN_FAILED, new Emitter.Listener() {
              @Override
              public void call(Object... args) {
                  showToaster("Auto login failed");
              }
          });
          listenersMap.put(ChatManager.EVENT_PRESENCE_CHANGED, new Emitter.Listener() {
              @Override
              public void call(Object... args) {
                  ChatUserPresence data = (ChatUserPresence) args[0];
                  showToaster(data.getUserId() + " changed to " + data.getChangedTo());
              }
          });
          listenersMap.put(ChatManager.EVENT_MESSAGE_NEW, new Emitter.Listener() {
              @Override
              public void call(Object... args) {
                  if (currentPage != 0) {
                      conversationTextView.setTextColor(getResources().getColor(R.color.red));
                  }
              }
          });
      }
    
      private void manageListeners(boolean isBind) {
          Iterator<String> events = listenersMap.keySet().iterator();
          while (events.hasNext()) {
              String event = events.next();
              Emitter.Listener listener = listenersMap.get(event);
              if (isBind) {
                  chatManager.bind(event, listener);
              } else {
                  chatManager.unbind(event, listener);
              }
          }
      }
    

    Then call manageListeners() in due time.

  • Both ChatMessage and ChatGroup can be serialized as String by calling the serialize() method to be the Extra delivery of Intent easily:

      ChatConversation conversation = new ChatConversation(engine);
      conversation.setTarget(friendsList.get(arg2).getUserId());
      conversation.setType(ChatType.SingleChat);
    
      intent.putExtra(ChatConversationActivity.EXTRA_CONVERSATION, conversation.serialize());
      startActivity(intent);
    

    You can restore by calling the deserialize() method in ChatConversationActivity. Because the reference to Engine is not able to be passed, you have to specify:

      String conversationString = getIntent().getStringExtra(EXTRA_CONVERSATION);
      conversation = ChatConversation.deserialize(engine, conversationString);
    

Upgrade Steps

If no special notes, just replacing the SDK would be enough.

v2.0.0 to v2.1.0

  • Replace the SDK
  • Adjust accordingly in line with the document

v1.x to v2.0.0

  • Replace the SDK
  • Added messages, conversations and groups to local cache. For more details on these, see Cache
  • Added multimedia messages, including image and audio messages. The following permission needs to be enabled for recording audio messsages:

      <uses-permission android:name="android.permission.RECORD_AUDIO" />
    
  • Abstracted ChatGroup and ChatConversation for developers to directly call the related methods on these classes to develop easily.

  • Removed the name and description fields in the ChatGroup structure, so the corresponding interface invocations need to be changed accordingly.
  Back To Top