Java
Sariska provides a Java API and easy-to-integrate SDKs for web, mobile, front-end, and cloud infrastructure to add real-time features in your applications.
Key Features:
Real-time messaging for in-app chats and instant messaging
Easy installation
Socket creation and management
Channel creation, joining, and leaving
Sending messages, poll votes, and message replies
Presence management (track: typing, joining and leaving users)
History management (fetching chat history and specific messages)
Installation
Step 1 : Install the Phoenix Client
JavaPhoenixChannels
is hosted on jitpack.io, so you'll need to add it as a repository to your project.
Add Jitpack to your repositories
# Declare Maven repository for Jitpack
repositories {
maven{
url "https://jitpack.io" # Jitpack repository URL
}
}
Add
JavaPhoenixChannels
dependency
dependencies {
implementation ('com.github.dipak140:JavaPhoenixChannels:v1.0.3')
{
exclude module: 'groovy-all'
}
}
Step 2 : Create Socket
Establish a WebSocket connection to the Sariska server to join channels, receive events, and send messages.
// Store your Sariska API token
String token = "{your-token}";
// Create a socket connection and attempt to connect
Uri.Builder url = Uri.parse( "wss://api.sariska.io/api/v1/messaging/websocket" ).buildUpon();
url.appendQueryParameter( "token", token );
try {
socket = new Socket(url.build().toString());
socket.connect();
System.out.println("Socket Connected");
} catch (IOException e) {
e.printStackTrace();
}
// Handles socket opening event
socket.onOpen(new ISocketOpenCallback() {
@Override
public void onOpen() {
Log.i("Connected");
}
});
// Handles socket closing event
socket.onClose(new ISocketCloseCallback() {
@Override
public void onClose() {
Log.i("Closed");
}
});
// Handles socket error events
socket.onError(new IErrorCallback() {
@Override
public void onError(final String reason) {
Log.i("Error");
}
});
Disconnect Socket
Close the WebSocket connection to the Sariska server. This will terminate all active channels and prevent further communication with the server.
// Close connection to the server
socket.disconnect();
Step 3 : Create Channel
Channels cannot be created directly; instead, they are created through the socket object by calling socket.channel(topic)
with the topic of the channel. The topic is a string that identifies the channel, and it can be any string you like.
Channel Prefix
Each channel name starts with a prefix that determines the message serialization format and persistence behavior.
chat:
Use this prefix for persisting messages to the database. This prefix requires a fixed schema and is ideal for chat applications.
channel = socket.channel("chat:room123");
rtc:
Use this prefix for scenarios where message persistence is not necessary. This prefix allows sending arbitrary JSON data, making it suitable for events in multiplayer games, IoT applications, and others.
channel = socket.channel("rtc:room123");
sariska:
Use this prefix for performance-critical applications that utilize Flatbuffers as the serialization format and do not require message persistence. This prefix provides zero-copy serialization/deserialization, significantly enhancing performance.
channel = socket.channel("sariska:room123");
Handle Errors
When an error occurs on the channel, the ERROR
callback is triggered. The callback receives the error information in payload, if available.
channel.on(ChannelEvent.ERROR.getPhxEvent(), new IMessageCallback() {
@Override
public void onMessage(Envelope envelope) {
Log.i("ERROR: " + envelope.toString());
}
});
Close Channel
When the channel is closed, the CLOSE
callback is invoked. This signifies that the communication on the channel has ended and no further data can be exchanged.
channel.on(ChannelEvent.CLOSE.getPhxEvent(), new IMessageCallback() {
@Override
public void onMessage(Envelope envelope) {
Log.i("CLOSED: " + envelope.toString());
}
});
Step 4 : Join and Leave Channel
To join a channel, call the join()
method on the channel object. The join()
method returns a promise that resolves when the client has successfully joined the channel. When sending data, you can utilize the .receive()
hook to monitor the status of the outgoing push message.
// Join the channel
channel.join()
// Ignore any messages with the "ignore" label
.receive("ignore", new IMessageCallback() {
@Override
public void onMessage(Envelope envelope) {
// Log the received "ignore" message
Log.i("IGNORE");
}
})
// Upon receiving a message with the "ok" label, assume the join request was successful
.receive("ok", new IMessageCallback() {
@Override
public void onMessage(Envelope envelope) {
// Log the join confirmation message
Log.i("JOINED with " + envelope.toString());
}
});
// Leave the Channel
channel.leave();
Channel New Message
channel.on("new_message", new IMessageCallback() {
@Override
public void onMessage(Envelope envelope) {
final ChatMessage message;
System.out.println("NEW MESSAGE: " + envelope.toString());
try {
message = objectMapper.treeToValue(envelope.getPayload(), ChatMessage.class);
// Retrieve the user ID from the message
message.getUserId();
// Log the message details for information
Log.i(TAG, "MESSAGE: " + message);
addToList(message);
} catch (JsonProcessingException e) {
// Log the error
Log.e(TAG, "Unable to parse message", e);
}
}
});
Send Message
Once you've established a connection to a channel, you can start sending messages to other connected clients. To send a message, use the push()
method on the channel object.
// Create a new HashMap to store the message payload
Map<String, Object> payload = new HashMap<>();
// Add the message content to the payload
payload.put("hi, from nick", content);
// Send the message to the channel
channel.push("new_message", payload)
// Register a callback for the "ok" response
.receive("ok", new IMessageCallback() {
@Override
public void onMessage(Envelope envelope) {
Log.d(TAG, "success " + envelope.toString());
}
})
// Register a callback for the "error" response
.receive("error", new IMessageCallback() {
@Override
public void onMessage(Envelope envelope) {
Log.d(TAG, "error " + envelope.toString());
}
});
Send Message Reply
// Create a new HashMap to store the payload data
Map<String, Object> payload = new HashMap<>();
// Add the message content
payload.put("hi, from nick", content);
// Add the message ID as a key with the value
payload.put(1, message_id);
// Send the payload to the channel
channel.push("new_message_reply", payload)
// Listen for the "ok" message that signifies successful delivery
.receive("ok", new IMessageCallback() {
@Override
public void onMessage(Envelope envelope) {
Log.d(TAG, "success " + envelope.toString());
}
})
// Listen for the "error" message in case of any issues
.receive("error", new IMessageCallback() {
@Override
public void onMessage(Envelope envelope) {
Log.d(TAG, "error " + envelope.toString());
}
})
Channel Poll Vote
// Define the poll question
String pollQuestion = "What is your favorite color?";
// Define a list of available poll options
List<String> pollOptions = List.of("Red", "Blue", "Green");
// Build the payload with the poll question, content type, and options
Map<String, Object> payload = new HashMap<>();
payload.put("content", pollQuestion);
payload.put("content_type", "poll");
payload.put("options", pollOptions);
// Send the new_vote event with the payload
channel.push("new_vote", payload)
// Receive a confirmation message on success
.receive("ok", new IMessageCallback() {
@Override
public void onMessage(Envelope response) {
System.out.println("Poll Vote Sent: " + response.toString());
}
})
// Handle any errors that occur
.receive("error", new IMessageCallback() {
@Override
public void onMessage(Envelope response) {
System.out.println("Error: " + response.toString());
}
});
Attach Media Files to Chat Messages
Attaching media files to chat messages involves obtaining a presigned URL, uploading the file to the presigned URL, and then sending the file information to the chat server.
Obtain a Presigned URL
To obtain a presigned URL, make a POST request to the API endpoint. The request payload should be empty, and the Authorization
header should contain your bearer token.
OkHttpClient client = new OkHttpClient();
final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
String URL = "https://api.sariska.io/api/v1/misc/get-presigned";
// Add bearer token to the Authorization header
String payload = "{\"Authorizaton\":\"Bearer your-token\"}";
RequestBody body = RequestBody.create(JSON, json);
Request request = new Request.Builder()
.url(url)
.post(body)
.addHeader("Content-Type", "application/json")
.build();
try(Response response = client.newCall(request).execute()){
String responseString = response.body().string();
responseString = "[" + responseString + "]";
JSONArray array = new JSONArray(responseString);
String finalResponse = null;
for(int i=0; i < array.length(); i++) {
JSONObject responseObject = array.getJSONObject(i);
}
} catch (JSONException e) {
e.printStackTrace();
System.out.println("Cannot send files");
}
Upload the File
After obtaining the presigned URL, the file can be uploaded to the URL using the PUT method.
File file = event.target.files[0];
// Extract the presigned URL from the response object
String signedURL = responseObject.get("presignedURL");
Request signedRequest = new Request.Builder()
// Set the URL to the presigned URL
.url(signedURL)
// Set the HTTP method to PUT
.PUT(file)
// Add an ACL header to allow public read access
.addHeader("ACL", "public-read")
// Add a Content-Disposition header to indicate the file is an attachment
.addHeader("Content-Disposition","attachment")
// Build the request object
.build();
// Create a new call using the client and the signed request
client.newCall(signedRequest).execute();
Chat History
Retrieve the chat history using two methods:
By Subscribing to Events
Subscribe to the
archived_message
event to receive the last 1000 messages in reverse chronological order.
import java.util.Map;
// Subscribe to the "archived_message" event
channel.on("archived_message", payload -> {
// Check if the "messages" field in the payload is a Map
if (payload.get("messages") instanceof Map) {
// Cast the "messages" field to a Map
Map<?, ?> messages = (Map<?, ?>) payload.get("messages");
// Print a message indicating archived messages were received
System.out.println("Received archived messages: " + messages);
}
});
Subscribe to the
archived_message_count
event to get the total number of messages in the chat history.
import java.util.Map;
// Subscribe to the "archived_message_count" event
channel.on("archived_message_count", payload -> {
// Check if the "page" property is a Map
if (payload.get("page") instanceof Map) {
// Cast the "page" property to a Map
Map<?, ?> page = (Map<?, ?>) payload.get("page");
// Check if the "count" property is an Integer
if (page.get("count") instanceof Integer) {
// Cast the "count" property to an Integer
int count = (Integer) page.get("count");
// Print a message indicating archived messages count is received
System.out.println("Total message count: " + count);
}
}
});
To retrieve a list of messages in the chat history, trigger the
archived_message
event to obtain the messages. Specify thesize
parameter to determine the number of messages you wish to fetch, and set theoffset
parameter as the starting index of the messages andgroup_by_date
to group messages by date.
import java.util.HashMap;
import java.util.Map;
// Create a map to configure the page information for the archived message
Map<String, Object> payload = new HashMap<>();
// Start retrieving messages from the 21st message
// Retrieve a maximum of 20 messages
// Do not group messages by date
payload.put("page", Map.of("offset", 20, "size", 20, "group_by_date": false));
// Push the payload to the channel
channel.push("archived_message", payload);
To receive the total count of messages at any given time, initiate the
archived_message_count
trigger and subscribe to the corresponding event by listening forarchived_message_count
.
channel.push("archived_message_count");
Using the Messages API
Make a GET request to the API endpoint to fetch the chat history for a specific room.
func fetchChatHistory(completion: @escaping (Result<[String: Any], Error>) -> Void) {
// Construct the request URL
let url = URL(string: "https://api.sariska.io/api/v1/messaging/rooms/{room_name}/messages")!
// Modify the request to use the GET method
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.addValue("Bearer your-token", forHTTPHeaderField: "Authorization")
// Send the GET request to the API endpoint
URLSession.shared.dataTask(with: request) { (data, response, error) in
// Check for errors
if let error = error {
completion(.failure(error))
return
}
// Check for successful response
guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else {
let statusCode = (response as? HTTPURLResponse)?.statusCode ?? -1
let errorMessage = "Failed with status code \(statusCode)"
let error = NSError(domain: "APIError", code: statusCode, userInfo: [NSLocalizedDescriptionKey: errorMessage])
completion(.failure(error))
return
}
// Parse the response data
do {
if let data = data {
let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any]
completion(.success(json ?? [:]))
} else {
completion(.success([:]))
}
} catch {
completion(.failure(error))
}
}.resume()
}
fetchChatHistory { result in
switch result {
case .success(let chatHistory):
print("Chat history: \(chatHistory)")
case .failure(let error):
print("Error fetching chat history: \(error)")
}
}
Fetch Specific Message
Retrieve any specific message from a room. It takes the room ID
and message ID
as parameters and sends a GET request to the Sariska API to fetch the specified message.
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
public class MessageFetcher {
public static void fetchSpecificMessage(String messageID, Callback<Map<String, Object>> completion) {
try {
// Construct the request URL
String urlString = "https://api.sariska.io/api/v1/messaging/rooms/{room_name}/messages/" + messageID;
URL url = new URL(urlString);
// Construct the request
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setRequestProperty("Authorization", "Bearer your-token");
// Send the GET request to the API endpoint
int responseCode = connection.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
String responseData = reader.lines().collect(Collectors.joining());
reader.close();
// Parse the response data
Map<String, Object> json = new ObjectMapper().readValue(responseData, Map.class);
completion.onSuccess(json != null ? json : new HashMap<>());
} else {
String errorMessage = "Failed with status code " + responseCode;
completion.onFailure(new Exception(errorMessage));
}
} catch (IOException e) {
completion.onFailure(e);
}
}
public static void main(String[] args) {
String messageId = "your-message-id";
fetchSpecificMessage(messageId, result -> {
if (result.isSuccess()) {
Map<String, Object> specificMessage = result.get();
System.out.println("Specific message: " + specificMessage);
} else {
Exception error = result.getError();
System.out.println("Error fetching specific message: " + error);
}
});
}
interface Callback<T> {
void onSuccess(T result);
void onFailure(Exception error);
}
}
Delete Chat History
Delete chat history for a specific room.
Delete Single or Multiple Messages
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class ChatEmptyer {
public static void emptyChat(List<String> messageIDs, String roomName, Callback<Map<String, Object>> completion) {
try {
// Construct the request URL
String urlString = "https://api.sariska.io/api/v1/messaging/rooms/" + roomName + "/messages";
URL url = new URL(urlString);
// Construct the request payload
Map<String, Object> payload = new HashMap<>();
payload.put("method", "DELETE");
Map<String, String> headers = new HashMap<>();
headers.put("Authorization", "Bearer your-token");
payload.put("headers", headers);
Map<String, Object> body = new HashMap<>();
body.put("message_ids", messageIDs);
body.put("is_empty", false);
payload.put("body", body);
// Convert payload to JSON
String jsonPayload = new ObjectMapper().writeValueAsString(payload);
// Convert payload to Data
byte[] jsonData = jsonPayload.getBytes(StandardCharsets.UTF_8);
// Construct the request
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("DELETE");
connection.setRequestProperty("Authorization", "Bearer your-token");
connection.setRequestProperty("Content-Type", "application/json");
connection.setDoOutput(true);
connection.getOutputStream().write(jsonData);
// Send the DELETE request to the API endpoint
int responseCode = connection.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
String responseData = reader.lines().collect(Collectors.joining());
reader.close();
// Parse the response data
Map<String, Object> json = new ObjectMapper().readValue(responseData, Map.class);
completion.onSuccess(json != null ? json : new HashMap<>());
} else {
String errorMessage = "Failed with status code " + responseCode;
completion.onFailure(new Exception(errorMessage));
}
} catch (IOException e) {
completion.onFailure(e);
}
}
public static void main(String[] args) {
List<String> messageIDs = List.of("message_id_1", "message_id_2");
String roomName = "your-room-name";
emptyChat(messageIDs, roomName, result -> {
if (result.isSuccess()) {
Map<String, Object> response = result.get();
System.out.println("Empty chat response: " + response);
} else {
Exception error = result.getError();
System.out.println("Error emptying chat: " + error);
}
});
}
interface Callback<T> {
void onSuccess(T result);
void onFailure(Exception error);
}
}
Delete All Chats
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
public class ChatEmptier {
public static void emptyChat(String roomName, Callback<Map<String, Object>> completion) {
try {
// Construct the request URL
String urlString = "https://api.sariska.io/api/v1/messaging/rooms/" + roomName + "/messages";
URL url = new URL(urlString);
// Construct the request payload
Map<String, Object> payload = new HashMap<>();
payload.put("method", "DELETE");
Map<String, String> headers = new HashMap<>();
headers.put("Authorization", "Bearer your-token");
payload.put("headers", headers);
Map<String, Object> body = new HashMap<>();
body.put("message_ids", new ArrayList<>());
body.put("is_empty", true);
payload.put("body", body);
// Convert payload to JSON
String jsonPayload = new ObjectMapper().writeValueAsString(payload);
// Convert payload to Data
byte[] jsonData = jsonPayload.getBytes(StandardCharsets.UTF_8);
// Construct the request
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("DELETE");
connection.setRequestProperty("Authorization", "Bearer your-token");
connection.setRequestProperty("Content-Type", "application/json");
connection.setDoOutput(true);
connection.getOutputStream().write(jsonData);
// Send the DELETE request to the API endpoint
int responseCode = connection.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
String responseData = reader.lines().collect(Collectors.joining());
reader.close();
// Parse the response data
Map<String, Object> json = new ObjectMapper().readValue(responseData, Map.class);
completion.onSuccess(json != null ? json : new HashMap<>());
} else {
String errorMessage = "Failed with status code " + responseCode;
completion.onFailure(new Exception(errorMessage));
}
} catch (IOException e) {
completion.onFailure(e);
}
}
public static void main(String[] args) {
String roomName = "your-room-name";
emptyChat(roomName, result -> {
if (result.isSuccess()) {
Map<String, Object> response = result.get();
System.out.println("Empty chat response: " + response);
} else {
Exception error = result.getError();
System.out.println("Error emptying chat: " + error);
}
});
}
interface Callback<T> {
void onSuccess(T result);
void onFailure(Exception error);
}
}
Presence
Create a Presence Instance
To establish presence synchronization, instantiate a Presence object and provide the channel to track presence lifecycle events:
Channel channel = socket.chan("chat:room123");
Presence presence = new Presence(channel);
State Synchronization
Utilize the presence.onSync
callback to respond to state changes initiated by the server. For instance, to dynamically render the user list upon every list modification, implement the following:
presence.onSync(() -> {
myRenderUsersFunction(presence.list());});
Handle Individual Join and Leave Events
The presence.onJoin
and presence.onLeave
callbacks allow for handling individual user join and leave events. Here is an instance:
Presence presence = new Presence(channel);
// Detect if a user has joined for the 1st time or from another tab/device
presence.onJoin(userJoinedCallback(id, current, newPres));
void userJoinedCallback(int id, boolean current, Presence newPres){
if(!current){
System.out.println("user has entered for the first time "+ newPres);
}else{
System.out.println("user additional presence "+ newPres);
}
}
// Detect if a user has left from all tabs/devices, or is still a present
presence.onLeave(userLeftCallback(int id, boolean current, Presence newPres));
void userLeftCallback(int id, boolean current, Presence newPres){
if(!current){
System.out.println("user has entered for the first time "+ newPres);
}else{
System.out.println("user additional presence "+ newPres);
}
}
// Receive presence data from server
presence.onSync(() -> {
presence.list(); //List of users
});
Retrieve Presence Information
The presence.list(by:)
method retrieves a list of presence information based on the local metadata state. By default, it returns the entire presence metadata. Alternatively, a listBy
function can be provided to filter the metadata for each presence.
For instance, if a user is online from multiple devices, each with a metadata status of "online," but their status is set to "away" on another device, the application might prefer to display the "away" status in the UI.
The following example defines a listBy
function that prioritizes the first metadata registered for each user, representing the first tab or device they used to connect:
List onlineUsers = presence.list();
User Typing
Send information about a user who is typing. This information can be used to update the chat interface, such as displaying a "user is typing" indicator next to the user's name.
Send User Typing Event
When a user starts typing, the following code sends a typing event to other peers.
Map<String, Object> payload = new HashMap<>();
payload.put("typing", true);
channel.push("user_typing", payload)
.receive("ok", new IMessageCallback() {
@Override
public void onMessage(Envelope envelope) {
Log.d(TAG, "success " + envelope.toString());
}
})
.receive("error", new IMessageCallback() {
@Override
public void onMessage(Envelope envelope) {
Log.d(TAG, "error " + envelope.toString());
}
});
Receive User Typing Event
Other peers in the chat can listen for the typing event on the same channel.
channel.on("user_typing", new IMessageCallback() {
@Override
public void onMessage(Envelope envelope) {
final ChatMessage message;
System.out.println("NEW MESSAGE: " + envelope.toString());
try {
message = objectMapper.treeToValue(envelope.getPayload(), ChatMessage.class);
message.getUserId();
Log.i(TAG, "MESSAGE: " + message);
addToList(message);
} catch (JsonProcessingException e) {
Log.e(TAG, "Unable to parse message", e);
}
}
});
For more detailed information on the APIs, check out the Github repository.
For detailed real-time messaging API's, refer to Phoenix documentation.
For detailed management of chat and room APIs, refer to Sariska Swagger documentation.
Last updated