Client APIs

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 — including getLogs() and copyLogs() below — uses RxJava 1 types: rx.functions.Action1, rx.Observable, rx.schedulers.Schedulers, and rx.android.schedulers.AndroidSchedulers. Make sure your IDE imports the rx.* packages rather than io.reactivex.* (RxJava 2) or io.reactivex.rxjava3.* (RxJava 3), otherwise the samples below won't compile.

📦

Comapi.getShared() is safe to call after initialiseShared(...). The client instance is created synchronously inside initialiseShared(...), before the async network init finishes — so as long as you've called Comapi.initialiseShared(...) (typically in Application.onCreate()), getShared() returns the non-null singleton from any subsequent Activity, Service, or BroadcastReceiver. It throws RuntimeException only if you call it before initialiseShared(...) has run at all. Either way, it never returns null — 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.profileId
val isCreated: Boolean = client.session.isSuccessfullyCreated

getSession() 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, ComapiClient or RxComapiClient, you have access to callbacks or reactive APIs. The reactive version returns RxJava 1.x Observable instances you need to subscribe to. For the callback version, pass a Callback as 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.validationFailures

getValidationFailures() 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 at DEBUG or INFO level 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.state

The 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.

ConstantMeaning
GlobalState.NOT_INITIALISEDSDK has not been initialised.
GlobalState.INITIALISINGinitialiseShared(...) is in progress.
GlobalState.INITIALISEDSDK is initialised, no session loaded.
GlobalState.SESSION_OFFA session is loaded but not active (e.g. expired or explicitly ended).
GlobalState.SESSION_STARTINGThe SDK is creating or re-authenticating a session.
GlobalState.SESSION_ACTIVEA 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
}