Start session

Sessions

Initialisation through the Comapi (or RxComapi) class gives you a client object that you use for any further communication with the SDK.

Learn more in Initialise.

In order to communicate with our services you must start a session in the SDK. You only need to do this once per user log-in. After that, the SDK re-authenticates the session until you explicitly end it.

💡

Where to call this. Call startSession() once your ChallengeHandler is able to produce a valid JWT. For apps with sign-in, that's typically after the user signs in — the sub claim in the JWT identifies the profile. For apps that support anonymous users, the sub can be a stable device-scoped identifier (e.g. a GUID stored locally), in which case you can start the session as soon as the SDK is initialised. See Create a JWT for the rules around the sub claim.

📦

Imports note. The reactive surface in this SDK uses RxJava 1 types — rx.Observer, 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.

📦

Getting the client. Comapi.getShared() and RxComapi.getShared() return the non-null singleton once initialiseShared(...) has been called — the client instance is created synchronously inside initialiseShared(...), before the async network init finishes. They only throw RuntimeException if you call them before initialiseShared(...) has run at all (e.g. from a ContentProvider that runs ahead of Application.onCreate()). With init in Application.onCreate(), getShared() is safe to call from any Activity, Service, or BroadcastReceiver.


Start

To create a session (log a user in to our services):

client.service().session().startSession(new Callback<Session>() {
    @Override
    public void success(Session session) {
        // Session started — SDK can now send and receive on behalf of this user
        if (session.isSuccessfullyCreated()) {
            Log.d("Comapi", "Session ready for profile " + session.getProfileId());
        }
    }
    @Override
    public void error(Throwable t) {
        Log.e("Comapi", "startSession failed", t);
    }
});
rxClient.service().session().startSession()
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(new Observer<Session>() {
        @Override public void onNext(Session session) {
            // Session started
        }
        @Override public void onCompleted() { }
        @Override public void onError(Throwable t) {
            Log.e("Comapi", "startSession failed", t);
        }
    });
client.service().session().startSession(object : Callback<Session> {
    override fun success(session: Session) {
        if (session.isSuccessfullyCreated) {
            Log.d("Comapi", "Session ready for profile ${session.profileId}")
        }
    }
    override fun error(t: Throwable) {
        Log.e("Comapi", "startSession failed", t)
    }
})
rxClient.service().session().startSession()
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(object : Observer<Session> {
        override fun onNext(session: Session) {
            // Session started
        }
        override fun onCompleted() { }
        override fun onError(t: Throwable) {
            Log.e("Comapi", "startSession failed", t)
        }
    })

This asks ComapiAuthenticator (provided when initialising) for a JWT token, then the SDK creates a session server-side for the profile ID obtained from the token. Any subsequent call to the services uses the same authentication details.

The Session result

Callback<Session> delivers a com.comapi.Session (not the network model class of the same name):

// Profile ID of the user the session was created for
session.getProfileId();
// true if the server returned all the fields a valid session needs (sessionId, profileId, accessToken, expiresOn)
session.isSuccessfullyCreated();
session.profileId
session.isSuccessfullyCreated

Treat isSuccessfullyCreated == false as a soft failure — the callback's success path can still fire even if the server returned an incomplete session. Always check the flag before treating the SDK as authenticated.

Kotlin: starting a session with Coroutines

For Kotlin codebases, wrap startSession in a suspend function so callers can await the result without nesting callbacks:

suspend fun ComapiClient.startSessionAwait(): Session =
    suspendCancellableCoroutine { cont ->
        service().session().startSession(object : Callback<Session> {
            override fun success(session: Session) {
                if (cont.isActive) cont.resume(session) {}
            }
            override fun error(t: Throwable) {
                if (cont.isActive) cont.resumeWithException(t)
            }
        })
    }

// Usage from a ViewModel after the user signs in:
viewModelScope.launch {
    try {
        val session = Comapi.getShared().startSessionAwait()
        if (session.isSuccessfullyCreated) {
            // SDK is now authenticated
        } else {
            Log.w("Comapi", "Session returned without required fields")
        }
    } catch (t: Throwable) {
        Log.e("Comapi", "startSession failed", t)
    }
}
📘

The try / catch in the example above covers the rare case where Comapi.getShared() is called before Comapi.initialiseShared(...) — it throws RuntimeException in that case. If you always initialise in Application.onCreate(), this catch is belt-and-braces.


Stop

To close a currently opened session — for example when the user signs out, or when you want to switch users on the same device:

client.service().session().endSession(new Callback<ComapiResult<Void>>() {
    @Override
    public void success(ComapiResult<Void> result) {
        // Session ended
    }
    @Override
    public void error(Throwable t) {
        Log.e("Comapi", "endSession failed", t);
    }
});
rxClient.service().session().endSession()
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(new Observer<ComapiResult<Void>>() {
        @Override public void onNext(ComapiResult<Void> result) {
            // Session ended
        }
        @Override public void onCompleted() { }
        @Override public void onError(Throwable t) {
            Log.e("Comapi", "endSession failed", t);
        }
    });
client.service().session().endSession(object : Callback<ComapiResult<Void>> {
    override fun success(result: ComapiResult<Void>) {
        // Session ended
    }
    override fun error(t: Throwable) {
        Log.e("Comapi", "endSession failed", t)
    }
})
rxClient.service().session().endSession()
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(object : Observer<ComapiResult<Void>> {
        override fun onNext(result: ComapiResult<Void>) {
            // Session ended
        }
        override fun onCompleted() { }
        override fun onError(t: Throwable) {
            Log.e("Comapi", "endSession failed", t)
        }
    })

Kotlin: ending a session with Coroutines

suspend fun ComapiClient.endSessionAwait(): ComapiResult<Void> =
    suspendCancellableCoroutine { cont ->
        service().session().endSession(object : Callback<ComapiResult<Void>> {
            override fun success(result: ComapiResult<Void>) {
                if (cont.isActive) cont.resume(result) {}
            }
            override fun error(t: Throwable) {
                if (cont.isActive) cont.resumeWithException(t)
            }
        })
    }

// Usage on sign-out:
viewModelScope.launch {
    runCatching { Comapi.getShared().endSessionAwait() }
        .onFailure { t -> Log.e("Comapi", "endSession failed", t) }
}
🧭

Switching users. When a different user signs in on the same device, end the current session first, then update the sub claim your ChallengeHandler returns (so the new JWT identifies the new user), then call startSession() again. Don't try to swap users without an endSession() in between — the server-side session is still bound to the previous profile until you close it.