Profile service

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 a ComapiProfile wrapper 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 a Map<String, Object>. Use this if you don't need the typed conveniences. The method signatures are identical otherwise. The rest of this page uses profileWithDefaults().


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 methodMatches
addEqual(key, value)key == value
addUnequal(key, value)key != value
addGreaterThan(key, value)key > value
addGreaterOrEqualThan(key, value)key >= value
addLessThan(key, value) / addLessOrEqualThankey < 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. update

A 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

ComapiProfile 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 map
val 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" to add(...) — use the dedicated setters instead. Keys beginning with an underscore are reserved by the service and are ignored when constructing a ComapiProfile from 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.eTag

The same wrapper works for queryProfiles, updateProfile, patchProfile, and patchMyProfile.