Android

The following section takes you through using the App Messaging Foundation Android SDK, which can be found in Github here:

There is also a repository containing some sample apps here:

Installing the SDK

compile 'com.comapi:foundation:1.3.0'
<dependency>
    <groupId>com.comapi</groupId>
    <artifactId>foundation</artifactId>
    <version>1.3.0</version>
</dependency>

Then

  • initialise SDK with API Space id and authentication token provider
  • start session when you obtain token from your authentication provider
  • listen for realtime events, and push messages
  • call message and profile services

Initialise

To initialise the SDK, you will need a few pre-requisites ...

  1. Setup the Push / App Messaging configuration is Engagement Cloud, see the instructions here for more information on setting up App Messaging.

  2. Your API Space Id for your configuration, see the instructions here for more information

  3. An authentication provider that can generate a JWT that matches the authorisation scheme configured for your API Space.

Note: The generated JWT must include the provided nonce as a claim in the generated JWT

The Auth Challenge function you pass when initializing the SDK needs to generate and return a JWT via the answerAuthenticationChallenge method.

There are two ways of creating a JWT for users, you can:

Use the Session API to create a JWT

This is a convenient way to create an anonymous user JWT, simply call the Session API to generate an anonymous JWT. You can pass a user id, which is unique per user or device, and a nonce to ensure the JWT is the one you requested.

Self Issued JWT

With this option you must create a valid JWT matching the settings you entered into the App Messaging configuration; you have total control of the user id and claims. There are plenty of code examples of how to create JWT online, and a lot of good information and code libraries can be found at the JWT home page.

The JWT

The following parameters are needed when generating a JWT for the SDK and they are:

  • Issuer
  • Audience
  • Shared Secret
  • ID Claim

A cryptographic nonce is used as part of the authentication flow. This is passed into the authChallenge (options.nonce) and needs to be added as a claim in the generated JWT.

These parameters can be defined and obtained from within the portal in the Push channel profile settings area. See the Push / App Messaging setup pages for more information.

The code snippet shows how to initialize the SDK passing your auth challenge function:

Set both the authentication provider and API Space Id when building configuration object. This is the only required configuration setup. To use the SDK you will also need to provide couple event listeners.

ComapiConfig config = new ComapiConfig()
   .apiSpaceId("<API_SPACE_ID>")
   .authenticator(new ChallengeHandler());

e.g. of the authentication challenge handler (needs to extend ComapiAuthenticator class)

public class ChallengeHandler extends ComapiAuthenticator {
    @Override
    public void onAuthenticationChallenge(AuthClient authClient, 
       ChallengeOptions options) {
          authClient.authenticateWithToken(/*get token using options.getNonce()*/));
    }
}

🚧

onCreate in Application

The initialisation needs to be performed in onCreate method of Android Application class.

You can use APIs in two versions reactive and with callbacks. Callback version initialise through Comapi class and the access to APIs is through ComapiClient. For reactive java alternative use RxComapi and RxComapiClient classes.

public class MyApplication extends Application {

    @Override
    public void onCreate()
    {
        super.onCreate();
      
        Comapi.initialiseShared(this, config, new Callback<ComapiClient>() {
            @Override
            public void success(ComapiClient client) {
               //Use client instance to communicate with services
            }

            @Override
            public void error(Throwable t) {
               //Error
            }
        });
    }
}
public class MyApplication extends Application {

    @Override
    public void onCreate()
    {
        super.onCreate();
      
        RxComapi.initialiseShared(this, config)
           .subscribeOn(Schedulers.io())
           .observeOn(AndroidSchedulers.mainThread())
           .subscribe(new Action1<RxComapiClient>() {
                    @Override
                    public void call(RxComapiClient client) {
                       //Use client instance to communicate with services
                    }
                }, new Action1<Throwable>() {
                    @Override
                    public void call(Throwable t) {
                        //Error
                    }
                });
    }
}

Both of the above calls will create singleton client instances that you can obtain through

Comapi.getShared(); 
// OR
RxComapi.getShared();

If you don't want SDK to keep the reference to the client instance use

Comapi.initialise(...);
// OR
RxComapi.initialise(...);

instead and store the client yourself.

Add listeners

When your app is in the foreground it keeps socket connection open and listens for live update of your profile and conversations for which you are a participant. Register for the incoming events with the ComapiConfig object and pass it to registration method.

ComapiConfig config = new ComapiConfig()
   .apiSpaceId("<API_SPACE_ID>") //required
   .authenticator(new ChallengeHandler()) //required
   .pushMessageListener(/* extends PushMessageListener */)
   .messagingListener(/* extends MessagingListener */)
   .profileListener(/* extends ProfileListener */)
   .stateListener(/* extends StateListener */);

Advanced options

FCM

If you don't want SDK to manage FCM registration and token

config.fcmEnabled(false);

Logging

You can set the logging level separately for internal file and console output and network calls to OFF, FATAL, ERROR, WARNING, INFO, DEBUG. By default they are set to warning.

ComapiConfig config = new ComapiConfig().logConfig(
new LogConfig()
   .setFileLevel(LogLevel.DEBUG)
   .setConsoleLevel(LogLevel.DEBUG)
   .setNetworkLevel(LogLevel.DEBUG));

You can also set custom limit for the size of internal log files

new ComapiConfig().logSizeLimitKilobytes(limit);

Proxy

If your test app connects through a proxy provide the Uri like this

new ComapiConfig().apiConfiguration(new APIConfig()
       .proxy("http://xx.xxx.xx.xxx:xxxx"));

Start a session

Initialisation through RxComapi class gives you access to client object used for any further communication with the SDK. See initialisation for details.

In order to communicate with App Messaging services you need to start session in SDK. This is needed only once per user log in, after that SDK will be re-authenticating session until you end session.

Start

To create or log in user to App Messaging services use:

client.service().session().startSession(new Callback<Session>(){/* implement */});
rxClient.service().session().startSession()
   .subscribe(new Observer<Session>(){/* implement */});

This will ask ComapiAuthenticator (provided when initialising) for a JWT token then SDK will create session server side for profile id obtained from the token. Any subsequent call to services will use same authentication details.

Stop

To close currently opened session use

client.service().session().endSession(
   new Callback<ComapiResult<Void>>(){/* implement */});
rxClient.service().session().endSession()
   .subscribe(new Observer<ComapiResult<Void>>(){/* implement */});

Client APIs

You can use the client instance obtained in Initialisation to access SDK APIs.

session

client.getSession().getProfileId();
client.getSession().isSuccessfullyCreated();

Services

// Messaging related service calls
client.service().messaging();

// User profile related service calls
client.service().profile();

📘

APIs

Depending on the client you initialised ComapiClient or RxComapiClient you will have access to callbacks or reactive APIs. Reactive version returns Observables you need to subscribe to. For callback version pass callback as the last parameter and the request will be performed in the background delivering result in UI thread.

All service calls will return ComapiResult objects that give access to

// True if {@link #getCode()} ()} is in the range [200..300).
result.isSuccessful()

// Response data
result.getResult()

// HTTP status message
result.getMessage()

// service call error details
result.getErrorBody()

// HTTP status code.
result.getCode()

// ETag describing version of the data.
result.getETag()

// list of validation failures in request
result.getValidationFailures();

Logs

client.getLogs().subscribe(new Action1<String>() {
                    @Override
                    public void call(String logs) {
                       //Internal logs
                    }
                }, new Action1<Throwable>() {
                    @Override
                    public void call(Throwable t) {
                        //Error
                    }
                });

For large logs data a good alternative is to copy them to a provided file from which they can be read gradually or e.g. send for inspection

client.copyLogs(file).subscribe(new Action1<File>() {
                    @Override
                    public void call(File logs) {
                       //Internal logs
                    }
                }, new Action1<Throwable>() {
                    @Override
                    public void call(Throwable t) {
                        //Error
                    }
                });

SDK state

client.getState();

You can find the list of codes in GlobalState class.

Listen for events

Realtime events are delivered to the SDK via a websocket. These events can be subscribed to via the following methods

ComapiConfig config = new ComapiConfig()
   .messagingListener(listener /* extends MessagingListener */)
   .profileListener(listener /* extends ProfileListener */);
client.addListener(listener /* extends MessagingListener */);
client.addListener(listener /* extends ProfileListener */);
client.removeListener(listener /* extends MessagingListener */);
client.removeListener(listener /* extends ProfileListener */);

MessagingListener and ProfileListener are abstract with empty implementation of onEventName methods. Override some or all of them to receive socket events from foundation SDK. Real time events are available only when the application is in foreground (visible to the user). If app is put to the background use FCM to communicate messages to the user. Update the internal state after app is put back to foreground using services APIs.

Available events

Event NameDescription
ProfileUpdateEventSent when a user's profile is updated.
MessageSentEventSent when a new message appeared in a conversation. This event will also be delivered to the message sender.
MessageDeliveredEventSent when one of participants updated the message status to 'delivered'.
MessageReadEventSent when one of participants updated the message status to 'read'.
ParticipantAddedEventSent when a participant is added to a conversation. When a conversation is created, this event will also fire with the owner's profileId.
ParticipantUpdatedEventSent when a participant role is updated in a conversation.
ParticipantRemovedEventSent when a participant is removed from a conversation.
ConversationDeleteEventSent when a conversation is deleted.
ConversationUpdateEventSent when a conversation details were updated.
ConversationUndeleteEventSent when a conversation is restored.
ParticipantTypingEvent (from v1.0.2)Sent when 'isTyping' method has been called informing conversation participants that the user started typing a new message
ParticipantTypingOffEvent (from v1.0.2)Sent when 'isTyping' method has been called informing conversation participants that the user stopped typing a new message
// Event unique identifier.
getEventId()

// Event name/type.
getName()

In addition specific event type

// Profile unique identifier.
getProfileId();

//Time when the update event was published.
getPublishedOn();

//Revision of the profile details on the server.
getRevision();

//Gets profileId of an user that performed this update.
getCreatedBy();

//Gets profile update details.
getPayload();

// Tag specifying server data version.
getETag();
// Message unique identifier
getMessageId();

// Unique, monotonically increasing number of event for this conversation
getConversationEventId()

// Custom message metadata (sent when client sends a message)
getMetadata();

// Message sender
getFromWhom();

// Message sender defined internally on server (shouldn't be visible inside the app)
getSentBy();

// When the message was sent
getSentOn();

// Conversation unique identifier
getConversationId();

// Parts of the message with data, type, name and size 
getParts();

// Alert definitions with FCM and APNS push platforms
getAlert();
// Message unique identifier
getMessageId();

// Conversation unique identifier
getConversationId();

// Profile unique identifier
getProfileId();

// When the message was marked as delivered
getTimestamp();

// Unique, monotonically increasing number of event for this conversation
getConversationEventId();
// Same as MessageDeliveredEvent
// Conversation unique identifier
getConversationId();

// Profile unique identifier
getProfileId();

// Role definition for this participant in this conversation
getRole();
// Same as ParticipantAddedEvent
// Same as ParticipantAddedEvent
// Conversation unique identifier
getConversationId() 

// Conversation name
getConversationName();

// Conversation decription
getDescription();

// Role definitions for 'owner' and 'participant'
getRoles();

// Tag specifying server data version.
getETag();
// Conversation unique identifier
getConversationId();

// When the conversation was deleted
getDeletedOn();

// Tag specifying server data version.
getETag();
// Conversation unique identifier
getConversationId();

// Conversation details, same as ConversationUpdateEvent
getConversation();

// Tag specifying server data version.
getETag();
// Conversation unique identifier
getConversationId();

// Get profile id of an participant who started typing a new message
getProfileId();
// Conversation unique identifier
getConversationId();

// Get profile id of an participant who stopped typing a new message
getProfileId();

Push messages

The SDK automatically registers your app to receive push messages through FCM.

Should you need access to the raw push messages to process some custom logic around the data in the push you can obtain it by either registering a PushMessageListener when initializing SDK, or the System Tray Intent used to open Launcher Activity.

App stateNotificationDataBoth
ForegroundPushMessageListenerPushMessageListenerPushMessageListener
BackgroundSystem trayPushMessageListenerNotification: system tray; Data: in extras of the intent.

PushMessageListeners simply redirects messages from internally registered FirebaseMessagingService (see Firebase)

📘

Deep links

If a push notification is delivered while the app is in the foreground, you can build your own in the 'onMessageReceived()` method that creates a link to a particular activity in your app.

public class PushHandler implements PushMessageListener {

    @Override
    public void onMessageReceived(RemoteMessage message) {
      // Check if message contains a notification payload.
        if (message.getNotification() != null) {
        Log.i("TAG", "Push notification: " + message.getNotification().getBody());
      // Do something with the message to notify the user
          // Example
          createNotificationChannel(context);
          Notification.Builder b = new Notification.Builder(context);
          b.setAutoCancel(true)
            .setDefaults(NotificationCompat.DEFAULT_ALL)
            .setWhen(System.currentTimeMillis())
            .setSmallIcon(R.drawable.common_google_signin_btn_icon_dark)
            .setContentTitle("foreground message")
            .setContentText(body)
            .setContentInfo("INFO");
          if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            b.setChannelId("test_channel_id1");
          }
          NotificationManager nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
          nm.notify(1, b.build());
        }
    }
}

private void createNotificationChannel(Context context) {
        // Create the NotificationChannel, but only on devices that have the Google API 26+ because the NotificationChannel class is new and not in supported in older libraries
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            CharSequence name = "test channel";
            String description = "some description";
            int importance = NotificationManager.IMPORTANCE_DEFAULT;
            NotificationChannel channel = new NotificationChannel("test_channel_id1", name, importance);
            channel.setDescription(description);
            // Register the channel with the system; you can't change the importance
            // or other notification behaviors after this
            NotificationManager notificationManager = context.getSystemService(NotificationManager.class);
            notificationManager.createNotificationChannel(channel);
        }
}
  1. Pass your class to the pushMessageListener() on the ComapiConfig object
config.pushMessageListener(new PushHandler());

Messaging service

service = client.service().messaging();

Depending on the client you initialised ComapiClient or RxComapiClient you will have access to callbacks or reactive profile APIs. Reactive version returns Observables you need to subscribe to. For callback version pass callback as the last parameter and the request will be performed in the background delivering result in UI thread.

Data consistency

In order to manage concurrent updates of a conversation from many devices/websites use ETag. When obtaining conversation details from Comapi services you can find an ETag in ComapiResult object. It describes what version of server data you got. When you want to update this data pass the same ETag with the new details. If the server data will change before your next update the services will not allow such modification until you obtain the recent version of the data and pass correct ETag with the next update. This way you can keep consistency of the data across many devices.

Messaging service API

create conversation

ConversationCreate conversation = ConversationCreate.builder()
   // Unique conversation identifier.
   .setId("1234")
   // Description
   .setDescription("This is my first conversation")
   // Name 
   .setName("Awesome chat")
   // Is this conversation visible to users not being participants.
   .setPublic(false)
   /* Sets what permissions 'owner' and 'participant' will have in this conversation
      You can set if they will can: add new participants, remove participants, send messages.
      By default 'owner' and 'participant' can send messages, can add participants, cannot remove participants.
      Here we added CanRemoveParticipants permission for the 'owner'. */
   .setRoles(new Roles(Role.builder().setCanRemoveParticipants().build(), new Role()))
   .build();

Then pass this object to Comapi services

service.createConversation(conversation,
   new Callback<ComapiResult<ConversationDetails>>(){/* implement */});
rxService.createConversation(conversation)
   .subscribe(new Observer<ComapiResult<ConversationDetails>>(){/* implement */});

update conversation

ConversationUpdate update = ConversationUpdate.builder()
                .setDescription("New description")
                .setName("Different name")
                .build();

Then pass this object to Comapi services

service.updateConversation(conversationId, update, eTag,
   new Callback<ComapiResult<ConversationDetails>>(){/* implement */});
rxService.updateConversation(conversationId, update, eTag)
   .subscribe(new Observer<ComapiResult<ConversationDetails>>(){/* implement */});

delete conversation

service.deleteConversation(conversationId, eTag,
   new Callback<ComapiResult<Void>>(){/* implement */});
rxService.deleteConversation(conversationId, eTag)
   .subscribe(new Observer<ComapiResult<Void>>(){/* implement */});

get convesartion

To obtain conversation details call

service.getConversation(conversationId,
  new Callback<ComapiResult<ConversationDetails>>(){/* implement */});
rxService.getConversation(conversationId)
   .subscribe(new Observer<ComapiResult<ConversationDetails>>(){/* implement */});
// is the conversation public or private
isPublic();

// unique identification number
getId();

// name of the conversation
getName();

// description of the conversation
getDescription();

// privileges of owner and participants in this conversation
getRoles();

query conversations

You can also obtain the list of all conversations:

service.getConversations(isPublic,
   new Callback<ComapiResult<List<Conversation>>>(){/* implement */});
rxService.getConversations(isPublic)
   .subscribe(new Observer<ComapiResult<List<Conversation>>>(){/* implement */});
// is the conversation public or private
isPublic();

// unique identification number
getId();

// name of the conversation
getName();

// description of the conversation
getDescription();

// privileges of owner and participants in this conversation
getRoles();

// eTag to compare if local version of the data is the same as the one the server side
getETag();

// number of participant in conversation
getParticipantCount();

// latest event id of sent message in the conversation. If null there are no messages in the conversation
getLatestSentEventId();

remove conversation participants

Provide list of profile ids to be removed from conversation.

service.removeParticipants(conversationId, ids,
   new Callback<ComapiResult<Void>>(){/* implement */});
rxService.removeParticipants(conversationId, ids)
   .subscribe(new Observer<ComapiResult<Void>>(){/* implement */});

query conversation participants

Query all participants for particular conversation

service.getParticipants(conversationId, 
   new Callback<ComapiResult<List<Participant>>>(){/* implement */});
rxService.getParticipants(conversationId)
   .subscribe(new Observer<ComapiResult<List<Participant>>>(){/* implement */});

add participants to conversation

Add new participants to conversation

service.addParticipants(conversationId, participants,
   new Callback<ComapiResult<Void>>(){/* implement */});
rxService.addParticipants(conversationId, participants)
   .subscribe(new Observer<ComapiResult<Void>>(){/* implement */});

send message in conversation

service.sendMessage(conversationId, body,
   new Callback<ComapiResult<MessageSentResponse>>(){/* implement */});
rxService.sendMessage(conversationId, body)
   .subscribe(new Observer<ComapiResult<MessageSentResponse>>(){/* implement */});

You can update the message status later using conversationId from ComapiResult result.

// globaly unique message identifier
getId();

// unique for conversation event identifier, monotonically increasing, can be used to order messages
getEventId();

The more advanced way of constructing a new message is through

Map<String, Object> data = new HashMap<>();
data.put("key","value");

Map<String, Object> fcm = new HashMap<>();
fcm.put("data", data);
fcm.put("notification", "{ \"title\":\"Message\", \"body\":\"Hi!\" } ");

Map<String, Object> apns = new HashMap<>();
apns.put("alert", "Hi!");

Part part = Part.builder()
               .setData("Hi")
               .setName("body")
               .setSize("Hi".length())
               .setType("text/plain")
               .build();

MessageToSend message = MessageToSend.builder()
   .setAlert(fcm, apns)
   .setMetadata(data)
   .addPart(part)
   .build();

In this way you can set details of FCM/APNS notifications to be sent to participants devices, some custom metadata, add more message parts with e.g. Base64 encoded images etc.

Then call

service.sendMessage(conversationId, message, 
   new Callback<ComapiResult<MessageSentResponse>>(){/* implement */});
rxService.sendMessage(conversationId, message)
   .subscribe(new Observer<ComapiResult<MessageSentResponse>>(){/* implement */});

You can update the message status later using conversationId from ComapiResult result.

update message status

MessageStatusUpdate update = MessageStatusUpdate.builder()
   .addMessageId("id")
   .setStatus(MessageStatus.delivered)
   .build();

List<MessageStatusUpdate> updates = new ArrayList<>();
updates.add();

Then pass the list with 'delivered' and 'read' statuses to the service

service.updateMessageStatus(conversationId, updates,
   new Callback<ComapiResult<Void>>(){/* implement */});
rxService.updateMessageStatus(conversationId, updates)
   .subscribe(new Observer<ComapiResult<Void>>(){/* implement */});

query conversation events

Introduced in v1.0.2.

Conversation events - sending a message and updating a message status can be obtained providing conversation event id to start from (and going forward) and an upper limit of events you are interested in.

service.queryConversationEvents(conversationId, from, limit,
   new Callback<ComapiResult<ConversationEventsResponse>>(){/* implement */});
rxService.queryConversationEvents(conversationId, from, limit)
   .subscribe(new Observer<ComapiResult<ConversationEventsResponse>>(){
      /* implement */});

In response you will get

/**
 * Gets Events in the order in which they were received.
 * This collection can contain following conversation events:
 * MessageSentEvent - new message received in the conversation
 * MessageDeliveredEvent - message status marked 'delivered'
 * MessageReadEvent - message status marked 'read'
 * Cast elements of this collection to one of these.
 */
getEventsInOrder();

// Parsed message sent events
getMessageSent();

// Parsed message status 'delivered' events
getMessageDelivered();

// Parsed message status 'read' events
getMessageRead();

See socket events for details.

query messages

List of messages in a conversation can be obtained providing conversation event id to start from (and going backwards) and an upper limit of messages you are interested in. This interface allows you to get messages from the end of the conversation one page at a time. This will allow you to implement the classic pull down interface to load more messages.

service.queryMessages(conversationId, from, limit, 
   new Callback<ComapiResult<MessagesQueryResponse>>(){/* implement */});
rxService.queryMessages(conversationId, from, limit)
   .subscribe(new Observer<ComapiResult<MessagesQueryResponse>>(){/* implement */});

The result is a list of messages and orphaned events associated with them. The orphaned events are the events updating messages for conversation event id higher then 'from' - so for messages obtained e.g. in previous 'paging calls'. This events need to be applied to this newer messages.

// Messages conforming to the query.
getMessages();

/* Latest event id in this conversation that 
was taken into account constructing the query result. */
getLatestEventId();

/* Earliest event id in this conversation that 
was taken into account constructing the query result. */
getEarliestEventId();

// Events updating messages for conversation event id later/higher then 'from'.
getOrphanedEvents();

Getter messagesQueryResponse.getMessages() returns list of MessageReceived:

// Message unique identifier.
getMessageId();

/* Unique, monotonically increasing number of event 
of this message and conversation. */
getSentEventId();

// Message sender.
getFromWhom();

/* Message sender defined internally on server 
(shouldn't be visible inside the app). */
getSentBy();

// When the message was sent.
getSentOn();

// Conversation unique identifier
getConversationId();

// Key is the profile identifier the value is either 'delivered' or 'read'
getStatusUpdate();

Getter messagesQueryResponse.getOrphanedEvents() returns list of OrphanedEvent:

// Conversation event id.
getConversationEventId()

// Event name.
getName();

// Unique event identifier.
getEventId()

// Id of the updated message.
getMessageId();

// Id of the conversation for which message was updated.
getConversationId();

// Profile id of the user that changed the message status.
getProfileId();

// Gets time when the message status changed.
getTimestamp();

// True if this event is of type message delivered.
isEventTypeDelivered()

// True if this event is of type message read.
isEventTypeRead();

send user is typing event

Introduced in v1.0.2.

To send an information to conversation participants that the user started or finished typing a new message call

service.isTyping(conversationId, isTyping, 
   new Callback<ComapiResult<Void>>(){/* implement */});
rxService.isTyping(conversationId, isTyping)
   .subscribe(new Observer<ComapiResult<Void>>(){/* implement */});

Upload content data

Introduced in v1.1.1

To upload content data

service.uploadContent(folder, contentData,
   new Callback<ComapiResult<UploadContentResponse>>(){/* implement */});
rxService.uploadContent(folder, contentData)
   .subscribe(new Observer<ComapiResult<UploadContentResponse>>(){
     /* implement */});

The content data can be created based on a file, byte array and base64 string

contentData = ContentData.create(file, type, name);
contentData = ContentData.create(byte[], type, name);
contentData = ContentData.create(base64, type, name);

The UploadContentResponse will contain the full url to the file in the cloud. You can for e.g. send a message with a message Part containing this url pointing to an attachment data.

Profile Service

service = client.service().profileWithDefaults();

Depending on the client you initialised ComapiClient or RxComapiClient you will have access to callbacks or reactive profile APIs. Reactive version returns Observables you need to subscribe to. For callback version pass callback as the last parameter and the request will be performed in the background delivering result in UI thread.

Data consistency

In order to manage concurrent updates of an profile from many devices/websites use ETag. When obtaining profile details from COMAPI services you can find an ETag in ComapiResult object. It describes what version of server data you got. When you want to update this data pass the same ETag with the new profile details. If the server data will change before your next update the services will not allow such modification until you obtain the recent version of the data and pass correct ETag with the next update. This way you can keep consistency of the data across many devices.

Profile Service API

profile details

Get profile details of an user registered in the same API Space as the SDK

service.getProfile(profileId,
   new Callback<ComapiResult<ComapiResult<ComapiProfile>>>(){/* implement */});
rxService.getProfile(profileId)
   .subscribe(new Observer<ComapiResult<ComapiProfile>>() {/* implement */});

Result contains containing a string-object map of properties associated with a given user profile.

query profiles

Query profiles registered on API Space

service.queryProfiles(queryString,
   new Callback<ComapiResult<List<ComapiProfile>>>(){/* implement */});
rxService.queryProfiles(queryString)
   .subscribe(new Observer<ComapiResult<List<ComapiProfile>>>() {/* implement */});

In result you will get a ComapiProfile objects with a list of profile properties, each associated with a user profile conforming to provided query.

See query syntax. You can use QueryBuilder helper class to construct valid query string.

e.g.

String queryString = new QueryBuilder().addExists("email").build();

and queryProfiles will look for all profiles containing a field named 'email'.

update profile

Update your user profile providing custom map of values that should be associated with it.

service.updateProfile(profileDetails, eTag,
   new Callback<ComapiResult<ComapiProfile>>(){/* implement */});
rxService.updateProfile(profileDetails, eTag)
   .subscribe(new Observer<ComapiResult<ComapiProfile>>(){/* implement */});

result contains updated details.

patch profile

Applies patch for an user profile. This method won't erase previous profile details with different keys.

service.patchProfile(profileId, profileDetails, eTag,
   new Callback<ComapiResult<ComapiProfile>>(){/* implement */});
rxService.patchProfile(profileId, profileDetails, eTag)
   .subscribe(new Observer<ComapiResult<ComapiProfile>>(){/* implement */});