service = client.service().profileWithDefaults();val service = client.service().profileWithDefaults()Depending on the client you initialised — ComapiClient or RxComapiClient — you have access to either a callback or a reactive profile API.
The reactive flavour returns Observables you subscribe to. The callback flavour takes a Callback<T> as the last argument; the request runs on a background I/O thread and the result is delivered on the main (UI) thread.
Two flavours of the profile API
client.service().profileWithDefaults()returns aComapiProfilewrapper that exposes typed getters/setters for well-known fields (firstName, lastName, email, phoneNumber, etc.) plus an escape hatch for custom properties.There is also
client.service().profile()which operates directly on aMap<String, Object>. Use this if you don't need the typed conveniences. The method signatures are identical otherwise. The rest of this page usesprofileWithDefaults().
Data consistency
To manage concurrent updates of a profile from many devices or websites, use the ETag.
When obtaining profile details from our services, you can find an ETag in the ComapiResult object (via result.getETag()). It describes the version of the server data you received. Pass the same ETag with your next update; if the server data has changed in the meantime, the service rejects the modification until you obtain the latest version and supply its ETag. This keeps the profile consistent across many devices.
You can pass null for the ETag to skip the version check, but doing so re-introduces the risk of overwriting concurrent changes.
Profile details
Get the profile details of a user registered in the same API space as the SDK:
service.getProfile(profileId,
new Callback<ComapiResult<ComapiProfile>>() { /* implement */ });service.getProfile(profileId,
object : Callback<ComapiResult<ComapiProfile>> { /* implement */ })rxService.getProfile(profileId)
.subscribe(new Observer<ComapiResult<ComapiProfile>>() { /* implement */ });rxService.getProfile(profileId)
.subscribe(object : Observer<ComapiResult<ComapiProfile>> { /* implement */ })The result wraps a ComapiProfile describing the user's properties — typed accessors for well-known fields plus a custom-property map. See Working with ComapiProfile below.
Query profiles
Query profiles registered in an API space:
service.queryProfiles(queryString,
new Callback<ComapiResult<List<ComapiProfile>>>() { /* implement */ });service.queryProfiles(queryString,
object : Callback<ComapiResult<List<ComapiProfile>>> { /* implement */ })rxService.queryProfiles(queryString)
.subscribe(new Observer<ComapiResult<List<ComapiProfile>>>() { /* implement */ });rxService.queryProfiles(queryString)
.subscribe(object : Observer<ComapiResult<List<ComapiProfile>>> { /* implement */ })The result contains a list of ComapiProfiles — one for each user profile matching the query.
You can use the QueryBuilder helper class to construct a valid query string.
Learn more about query syntax.
Example
String queryString = new QueryBuilder()
.addExists("email")
.build();val queryString = QueryBuilder()
.addExists("email")
.build()queryProfiles will then return every profile that has an email field set.
QueryBuilder supports the operators you'd expect:
| Builder method | Matches |
|---|---|
addEqual(key, value) | key == value |
addUnequal(key, value) | key != value |
addGreaterThan(key, value) | key > value |
addGreaterOrEqualThan(key, value) | key >= value |
addLessThan(key, value) / addLessOrEqualThan | key < value / key <= value |
addStartsWith(key, value) | string prefix match |
addEndsWith(key, value) | string suffix match |
addContains(key, value) | substring match |
addExists(key) / addNotExists(key) | the field is set / is not set |
Calls can be chained; the resulting query is an AND of every clause you add.
Update profile
Replace the user profile properties with the supplied values. The session's own profile is updated.
service.updateProfile(profileDetails, eTag,
new Callback<ComapiResult<ComapiProfile>>() { /* implement */ });service.updateProfile(profileDetails, eTag,
object : Callback<ComapiResult<ComapiProfile>> { /* implement */ })rxService.updateProfile(profileDetails, eTag)
.subscribe(new Observer<ComapiResult<ComapiProfile>>() { /* implement */ });rxService.updateProfile(profileDetails, eTag)
.subscribe(object : Observer<ComapiResult<ComapiProfile>> { /* implement */ })The result contains the updated profile details.
Patch profile
Apply a patch to a user profile by its id.
Patch vs. updateA patch only changes the keys you supply — properties on the server that you didn't include in the patch are left untouched. An update replaces the whole document.
service.patchProfile(profileId, profileDetails, eTag,
new Callback<ComapiResult<ComapiProfile>>() { /* implement */ });service.patchProfile(profileId, profileDetails, eTag,
object : Callback<ComapiResult<ComapiProfile>> { /* implement */ })rxService.patchProfile(profileId, profileDetails, eTag)
.subscribe(new Observer<ComapiResult<ComapiProfile>>() { /* implement */ });rxService.patchProfile(profileId, profileDetails, eTag)
.subscribe(object : Observer<ComapiResult<ComapiProfile>> { /* implement */ })patchProfile requires permission to modify the target profile. If the patch only needs to apply to the current session's own profile, use patchMyProfile instead — it always operates on the current session.
service.patchMyProfile(profileDetails, eTag,
new Callback<ComapiResult<ComapiProfile>>() { /* implement */ });service.patchMyProfile(profileDetails, eTag,
object : Callback<ComapiResult<ComapiProfile>> { /* implement */ })rxService.patchMyProfile(profileDetails, eTag)
.subscribe(new Observer<ComapiResult<ComapiProfile>>() { /* implement */ });rxService.patchMyProfile(profileDetails, eTag)
.subscribe(object : Observer<ComapiResult<ComapiProfile>> { /* implement */ })Working with ComapiProfile
ComapiProfileComapiProfile is the typed wrapper returned by every method on profileWithDefaults(). It exposes typed getters/setters for the well-known fields that the Portal understands, plus a custom-property bag for everything else.
ComapiProfile profile = new ComapiProfile()
.setFirstName("Ada")
.setLastName("Lovelace")
.setEmail("[email protected]")
.setPhoneNumberCountryCode("44")
.setPhoneNumber("7700900123")
.setGender("F")
.setProfilePicture("https://example.com/avatar.png");
// Custom properties (anything outside the well-known set):
profile.add("favouriteColour", "blue");
profile.add("loyaltyPoints", 1200);val profile = ComapiProfile()
.setFirstName("Ada")
.setLastName("Lovelace")
.setEmail("[email protected]")
.setPhoneNumberCountryCode("44")
.setPhoneNumber("7700900123")
.setGender("F")
.setProfilePicture("https://example.com/avatar.png")
profile.add("favouriteColour", "blue")
profile.add("loyaltyPoints", 1200)Read it back the same way:
String firstName = profile.getFirstName();
String lastName = profile.getLastName();
String email = profile.getEmail();
String phone = profile.getPhoneNumber();
String country = profile.getPhoneNumberCountryCode();
String gender = profile.getGender();
String picture = profile.getProfilePicture();
String id = profile.getId();
Object colour = profile.get("favouriteColour");
Map<String, Object> flat = profile.asMap(); // defaults + custom merged into one mapval firstName: String? = profile.firstName
val lastName: String? = profile.lastName
val email: String? = profile.email
val phone: String? = profile.phoneNumber
val country: String? = profile.phoneNumberCountryCode
val gender: String? = profile.gender
val picture: String? = profile.profilePicture
val id: String? = profile.id
val colour: Any? = profile["favouriteColour"]
val flat: Map<String, Any> = profile.asMap()
Avoid the default keys as custom keys
add(key, value)is for properties outside the well-known set. Don't pass"firstName","lastName","email","gender","phoneNumber","phoneNumberCountryCode","profilePicture", or"id"toadd(...)— use the dedicated setters instead. Keys beginning with an underscore are reserved by the service and are ignored when constructing aComapiProfilefrom a server map.
Calling the profile API from Kotlin coroutines
The reactive flavour uses RxJava 1.x (rx.Observable), which has no maintained Kotlin coroutine bridge. The cleanest path for coroutine codebases is to wrap the callback flavour:
suspend fun <T> awaitResult(
call: (Callback<T>) -> Unit
): T = suspendCancellableCoroutine { cont ->
call(object : Callback<T> {
override fun success(result: T) {
if (cont.isActive) cont.resume(result) {}
}
override fun error(t: Throwable) {
if (cont.isActive) cont.resumeWithException(t)
}
})
}
// Usage:
val result: ComapiResult<ComapiProfile> = awaitResult { cb ->
service.getProfile(profileId, cb)
}
val profile: ComapiProfile? = result.result
val eTag: String? = result.eTagThe same wrapper works for queryProfiles, updateProfile, patchProfile, and patchMyProfile.