Using the client
You can use the client instance obtained in Initialisation to access SDK APIs. The samples below assume you've already captured the client (either from the init callback/Observable or via Comapi.getShared() / RxComapi.getShared()) and have it in a variable named client or rxClient.
Imports note. The reactive surface in this SDK — includinggetLogs()andcopyLogs()below — uses RxJava 1 types:rx.functions.Action1,rx.Observable,rx.schedulers.Schedulers, andrx.android.schedulers.AndroidSchedulers. Make sure your IDE imports therx.*packages rather thanio.reactivex.*(RxJava 2) orio.reactivex.rxjava3.*(RxJava 3), otherwise the samples below won't compile.
Comapi.getShared()is safe to call afterinitialiseShared(...). The client instance is created synchronously insideinitialiseShared(...), before the async network init finishes — so as long as you've calledComapi.initialiseShared(...)(typically inApplication.onCreate()),getShared()returns the non-null singleton from any subsequent Activity, Service, or BroadcastReceiver. It throwsRuntimeExceptiononly if you call it beforeinitialiseShared(...)has run at all. Either way, it never returnsnull— Kotlin?:Elvis fallbacks against it will never fire.
Session
Check which profile is currently registered in the SDK, or whether a session has been successfully created:
client.getSession().getProfileId();client.getSession().isSuccessfullyCreated();val profileId: String? = client.session.profileIdval isCreated: Boolean = client.session.isSuccessfullyCreatedgetSession() always returns a non-null Session. Before startSession() has run, the returned object's isSuccessfullyCreated() is false and getProfileId() is null — always check the flag before treating the SDK as authenticated.
Services
The service() accessor exposes the SDK's REST services — messaging and profile — grouped by area:
// Messaging related service calls
client.service().messaging();
// User profile related service calls
client.service().profile();
// Profile API with typed ComapiProfile getters/setters
client.service().profileWithDefaults();
// Session management calls
client.service().session();// Messaging related service calls
val messaging = client.service().messaging()
// User profile related service calls
val profile = client.service().profile()
// Profile API with typed ComapiProfile getters/setters
val profileTyped = client.service().profileWithDefaults()
// Session management calls
val sessions = client.service().session()
Callback vs reactive. Depending on the client you initialised,ComapiClientorRxComapiClient, you have access to callbacks or reactive APIs. The reactive version returns RxJava 1.xObservableinstances you need to subscribe to. For the callback version, pass aCallbackas the last parameter — the request is performed in the background and the result is delivered on the UI thread.
ComapiResult
All service calls deliver their data wrapped in a ComapiResult object:
// True if getCode() is in the range [200..300)
result.isSuccessful();
// Response data (the payload type depends on the call)
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 the request
result.getValidationFailures();// True if code is in the range [200..300)
result.isSuccessful
// Response data (the payload type depends on the call)
result.result
// HTTP status message
result.message
// Service call error details
result.errorBody
// HTTP status code
result.code
// ETag describing version of the data
result.eTag
// List of validation failures in the request
result.validationFailuresgetValidationFailures() returns a list of ComapiValidationFailure items, each with getParamName() and getMessage(), that the server populates when a request fails validation (HTTP 400). Use them to surface field-level errors back to the user.
ETag and concurrent updates. When you read a resource (e.g. conversation details),ComapiResult.getETag()describes the version of the server data you received. When you later update that resource, pass the same ETag back — if the server data has changed in the meantime, the update is rejected and you need to re-fetch before retrying. This keeps concurrent edits across devices consistent.
Logs
The SDK keeps internal logs that you can read for debugging or send for inspection.
Read logs into memory
client.getLogs()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
new Action1<String>() {
@Override
public void call(String logs) {
if (logs != null) {
// Internal logs
}
}
},
new Action1<Throwable>() {
@Override
public void call(Throwable t) {
Log.e("Comapi", "Failed to read logs", t);
}
}
);client.logs
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
Action1<String?> { logs ->
logs?.let { /* internal logs */ }
},
Action1<Throwable> { t ->
Log.e("Comapi", "Failed to read logs", t)
}
)The emitted value is null if the SDK hasn't finished initialising yet — the samples above guard against this. Otherwise it's the concatenated contents of the SDK's internal log files.
Copy logs to a file
For large log data, copying to a file is a better choice — the file can then be read incrementally or attached to a support email.
File file = new File(getExternalFilesDir(null), "comapi-logs.txt");
client.copyLogs(file)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
new Action1<File>() {
@Override
public void call(File logs) {
if (logs != null) {
// logs is the file the SDK wrote to
}
}
},
new Action1<Throwable>() {
@Override
public void call(Throwable t) {
Log.e("Comapi", "Failed to copy logs", t);
}
}
);val file = File(getExternalFilesDir(null), "comapi-logs.txt")
client.copyLogs(file)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
Action1<File?> { written ->
written?.let { /* `it` is the file the SDK wrote to */ }
},
Action1<Throwable> { t ->
Log.e("Comapi", "Failed to copy logs", t)
}
)
Scoped storage on Android 10+ (API 29).getExternalFilesDir(null)returns a path inside your app's own external-files folder, which doesn't require any storage permission. If you need to write the log file to a user-visible location (e.g.Downloads) on Android 10+, use the MediaStore API or the system file picker — direct file-path access to shared storage is restricted.
Kotlin: reading logs with Coroutines
If you're working in a Kotlin codebase, you can wrap either log call in a suspend function to avoid the Rx subscription dance:
suspend fun ComapiClient.getLogsAwait(): String? =
suspendCancellableCoroutine { cont ->
val subscription = logs
.subscribeOn(Schedulers.io())
.subscribe(
Action1<String?> { value -> if (cont.isActive) cont.resume(value) {} },
Action1<Throwable> { t -> if (cont.isActive) cont.resumeWithException(t) }
)
cont.invokeOnCancellation { subscription.unsubscribe() }
}
suspend fun ComapiClient.copyLogsAwait(target: File): File? =
suspendCancellableCoroutine { cont ->
val subscription = copyLogs(target)
.subscribeOn(Schedulers.io())
.subscribe(
Action1<File?> { file -> if (cont.isActive) cont.resume(file) {} },
Action1<Throwable> { t -> if (cont.isActive) cont.resumeWithException(t) }
)
cont.invokeOnCancellation { subscription.unsubscribe() }
}
// Usage:
viewModelScope.launch {
runCatching { client.getLogsAwait() }
.onSuccess { logs -> logs?.let { /* show or share */ } }
.onFailure { t -> Log.e("Comapi", "log read failed", t) }
}
Privacy reminder. Internal logs atDEBUGorINFOlevel may include request URLs, headers, and bodies. Before sharing logs with anyone — including your support team — scan for tokens, personal data, or other sensitive content, and scrub anything that shouldn't leave the device.
SDK state
Get the SDK's internal state code:
int state = client.getState();val state: Int = client.stateThe numeric values are defined as constants in com.comapi.GlobalState. Compare against the constants rather than hard-coded integers — the numeric values are not part of the public contract.
| Constant | Meaning |
|---|---|
GlobalState.NOT_INITIALISED | SDK has not been initialised. |
GlobalState.INITIALISING | initialiseShared(...) is in progress. |
GlobalState.INITIALISED | SDK is initialised, no session loaded. |
GlobalState.SESSION_OFF | A session is loaded but not active (e.g. expired or explicitly ended). |
GlobalState.SESSION_STARTING | The SDK is creating or re-authenticating a session. |
GlobalState.SESSION_ACTIVE | A valid session is in place — service calls can be made. |
if (client.getState() == GlobalState.SESSION_ACTIVE) {
// Safe to call service APIs
}if (client.state == GlobalState.SESSION_ACTIVE) {
// Safe to call service APIs
}