Usage Examples
This page provides practical examples of how to use the Solibo SDK in various scenarios.
Kotlin (Multiplatform / JVM / Android)
Full Application Setup (Android)
A typical integration in an Android application involves configuring the SDK with platform-specific implementations for Fingerprinter and PushTokenProvider. See the advanced setup guide for detailed implementation examples. Using a Dependency Injection framework like Koin is recommended.
ApiModule.kt
import no.solibo.oss.sdk.SoliboSDK
import no.solibo.oss.sdk.auth.Auth
import org.koin.dsl.module
val apiModule = module {
single<SoliboSDK> {
SoliboSDK.createMobile(
userPoolId = "eu-west-1_...",
clientId = "...",
settings = get(), // Multiplatform Settings (e.g., SharedPreferences)
fingerprinter = get(), // Platform implementation
pushTokenProvider = get(), // e.g., Firebase Messaging
configure = {
onRefreshFailure = {
// Global refresh failure handler (e.g., redirect to login)
get<Auth>().clearSession()
}
// Optional: custom Ktor HttpClient configuration
httpClientConfig = {
install(Logging) {
level = LogLevel.INFO
}
}
}
)
}
// Expose sub-APIs for easier injection
single { get<SoliboSDK>().api }
single { get<SoliboSDK>().auth }
}
MainApplication.kt
import android.app.Application
import org.koin.android.ext.koin.androidContext
import org.koin.core.context.startKoin
class MainApplication : Application() {
override fun onCreate() {
super.onCreate()
startKoin {
androidContext(this@MainApplication)
modules(
platformModule, // Module containing Fingerprinter and PushTokenProvider
apiModule
)
}
}
}
Full Application Setup (iOS)
For iOS applications using Swift, you can initialize the SDK through a shared KMP helper or directly. Below is an example of initializing the SDK in your App struct or AppDelegate.
iOSApp.swift
import SwiftUI
import Shared // Your shared KMP module
@main
struct SoliboHomeApp: App {
init() {
// Initialize Koin and the SDK with platform implementations
HelperKt.doInitKoin(
fingerprinter: iOSFingerprinter(),
pushTokenProvider: iOSPushTokenProvider(),
userPoolId: "eu-west-1_...",
clientId: "..."
)
}
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
The
HelperKtanddoInitKoinmethods are typically defined in your shared KMP module to bridge between Swift and Kotlin. For platform-specific implementations, see the advanced setup guide.
Fetching User Profile
import no.solibo.oss.sdk.SoliboSDK
import kotlinx.coroutines.runBlocking
fun main() = runBlocking {
val sdk = SoliboSDK.createMobile(
userPoolId = "eu-west-1_...",
clientId = "..."
)
// sdk.api.setInitialBaseUrl("https://home.solibo.no/api") // Optional, default is https://api.home.solibo.no
try {
val user = sdk.api.users.showUser()
println("Hello, ${user.name?.given}!")
} catch (e: Exception) {
println("Error fetching user: ${e.message}")
}
}
Updating a Board Member (Admin)
import no.solibo.oss.sdk.SoliboSDK
import no.solibo.oss.sdk.api.gen.models.UpdateBoardMemberCommand
suspend fun updateMember(sdk: SoliboSDK, boardId: String, memberId: String) {
sdk.api.board.updateBoardMember(
boardId = boardId,
memberId = memberId,
updateBoardMemberCommand = UpdateBoardMemberCommand(
role = "Chairman"
)
)
}
Real-time Chat Subscription (Event Bus)
import no.solibo.oss.sdk.SoliboSDK
import no.solibo.oss.sdk.api.gen.models.ConversationMessagePayload
fun subscribeToMessages(sdk: SoliboSDK, conversationId: Long) {
sdk.eventBus.onEvent { event ->
val payload = event.payload
if (payload is ConversationMessagePayload && payload.conversationId == conversationId) {
println("New message: ${payload.message.text}")
}
}
}
MFA Management
import no.solibo.oss.sdk.SoliboSDK
import no.solibo.oss.sdk.api.gen.models.MfaType
suspend fun setupMfa(sdk: SoliboSDK) {
// 1. Associate TOTP
val association = sdk.auth.createTOTPMfa()
println("Scan this secret in your app: ${association.secretCode}")
// 2. Verify and enable
sdk.auth.verifyCreateTOTPMfa(code = "123456")
sdk.auth.createMfaPreference(MfaType.TOTP)
}
suspend fun handleMfaChallenge(sdk: SoliboSDK, code: String) {
// The SDK automatically handles challenge type tracking
val result = sdk.auth.answerMfaChallenge(code)
if (result.tokens != null) {
println("MFA successful!")
}
}
Web (React / TypeScript)
Full Application Setup (React)
A typical integration in a React application involves configuring the SDK and wrapping the app with SoliboProvider. This should be placed inside your QueryClientProvider.
sdk.ts
import { CreateSdkArgs } from '@solibo/solibo-query';
export const sdkConfig: CreateSdkArgs = {
// domain: 'home.solibo.no', // Optional, default is api.home.solibo.no
cognitoUserPoolId: 'eu-west-1_...',
// Fingerprinter for Cognito Advanced Security (optional)
fingerprinter: {
print: (username: string) => {
// Access your platform's fingerprinting logic (e.g., AmazonCognitoAdvancedSecurityData)
return (window as any).AmazonCognitoAdvancedSecurityData?.getData(
username,
'user-pool-id',
'client-id'
);
},
},
// Global refresh failure handler (e.g., redirect to login)
refreshFailureHandler: {
onRefreshFailure: () => {
localStorage.removeItem('user_session');
window.location.href = '/auth/login';
},
},
};
App.tsx
import { StrictMode } from 'react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { SoliboProvider } from '@solibo/solibo-query';
import { sdkConfig } from './sdk';
const queryClient = new QueryClient();
export default function App() {
return (
<StrictMode>
<QueryClientProvider client={queryClient}>
<SoliboProvider config={sdkConfig}>
<YourRoutes />
</SoliboProvider>
</QueryClientProvider>
</StrictMode>
);
}
Using a Query Hook
The @solibo/solibo-query package provides hooks that integrate with TanStack React Query.
Fetching current user
import { useUser } from '@solibo/solibo-query'
function UserProfile() {
const { data: user, isPending, isError } = useUser()
if (isPending) return <div>Loading...</div>
if (isError || !user) return <div>Error: Could not load user</div>
return (
<div>
<h1>Welcome, {user.name.given}!</h1>
<p>Email: {user.email}</p>
</div>
)
}
Fetching Board Members
import { useBoardMembers } from '@solibo/solibo-query'
function BoardMembersList({ boardId }: { boardId: bigint }) {
const { data: members, isLoading, error } = useBoardMembers(boardId)
if (isLoading) return <div>Loading...</div>
if (error) return <div>Error: {error.message}</div>
return (
<ul>
{members?.map(member => (
<li key={member.id}>{member.name.given} {member.name.family}</li>
))}
</ul>
)
}
Using a Mutation Hook
import { useCreateBoardMember } from '@solibo/solibo-query'
function AddMemberForm({ boardId }: { boardId: bigint }) {
const mutation = useCreateBoardMember()
const handleSubmit = (event: React.FormEvent) => {
event.preventDefault()
// .mutateAsync for promise based mutations
mutation.mutate({
boardId,
createBoardMemberCommand: {
userId: '...',
role: 'Member'
}
})
}
return (
<form onSubmit={handleSubmit}>
{/* form fields */}
<button type="submit" disabled={mutation.isPending}>
Add Member
</button>
{mutation.isError && <div>Error: {mutation.error.message}</div>}
</form>
)
}
Document Upload and Download (React / TypeScript)
For React applications, the document wrappers live in @solibo/solibo-react. The matching framework-agnostic option factories live in @solibo/solibo-query.
Uploading a private company document
import { useUploadDocument } from '@solibo/solibo-react'
import { DocumentType } from '@solibo/solibo-sdk'
function UploadButton({ companyId, file }: { companyId: number; file: File }) {
const uploadDocument = useUploadDocument()
const handleUpload = async () => {
await uploadDocument.mutateAsync({
companyId,
documentType: DocumentType.Other,
file,
})
}
return <button onClick={handleUpload}>Upload document</button>
}
The upload wrappers automatically handle the backend’s signed upload flow for you. You pass a File, and the wrapper creates the document, uploads the bytes, verifies the upload, and cleans up on failure.
Uploading a document linked to another resource
import { useUploadDocumentBelongsTo } from '@solibo/solibo-react'
import { DocumentType } from '@solibo/solibo-sdk'
function UploadMeetingAttachment(props: {
companyId: number
meetingId: number
file: File
}) {
const uploadDocument = useUploadDocumentBelongsTo()
const handleUpload = async () => {
await uploadDocument.mutateAsync({
companyId: props.companyId,
documentType: DocumentType.Meeting,
belongsToId: props.meetingId,
file: props.file,
})
}
return <button onClick={handleUpload}>Upload attachment</button>
}
Creating a directory
Use useCreateDocumentDirectory() for folders instead of the file upload wrappers:
import { useCreateDocumentDirectory } from '@solibo/solibo-react'
import { DocumentType } from '@solibo/solibo-sdk'
function CreateFolder({ companyId }: { companyId: number }) {
const createDirectory = useCreateDocumentDirectory()
const handleCreate = async () => {
await createDirectory.mutateAsync({
companyId,
documentType: DocumentType.Board,
file: new File(['directory'], 'Board Documents'),
})
}
return <button onClick={handleCreate}>Create folder</button>
}
Resolving a private document download URL
import { useGetDocumentURL } from '@solibo/solibo-react'
function DownloadLink(props: { companyId: number; documentId: number }) {
const { data, isPending } = useGetDocumentURL({
companyId: props.companyId,
documentId: props.documentId,
})
if (isPending) return <span>Loading...</span>
if (!data?.url) return <span>Missing URL</span>
return (
<a href={data.url} target="_blank" rel="noreferrer">
Open document
</a>
)
}
useGetDocumentURL() returns a UrlWrapper body with a signed url. It does not auto-redirect, which lets your app decide whether to open, preview, or download the file.
Resolving a public document URL
import { usePublicGetDocumentURL } from '@solibo/solibo-react'
function PublicDocumentLink({ documentId }: { documentId: number }) {
const { getDocumentURL } = usePublicGetDocumentURL({
companyIdOverride: 42,
})
const handleOpen = async () => {
const { url } = await getDocumentURL(documentId)
window.open(url, '_blank', 'noopener')
}
return <button onClick={handleOpen}>Open public document</button>
}
For public conversation documents, call getDocumentURL(conversationId, documentId) and provide slug in the hook params.
MFA Management (React)
The SDK provides hooks for managing Multi-factor Authentication. The useAnswerMfaChallenge hook automatically tracks the current challenge type, so you don’t need to pass it explicitly if you just initiated an auth flow.
import { useState } from 'react'
import {
useRemoveMfa,
useCreateMfaPreference,
useAnswerMfaChallenge,
useInitiateAuth
} from '@solibo/solibo-query'
import { MfaType, AuthChallengeType } from '@solibo/solibo-sdk'
function Login() {
const loginMutation = useInitiateAuth()
const verifyMutation = useAnswerMfaChallenge()
const [mfaChallenge, setMfaChallenge] = useState<AuthChallengeType | null>(null)
const [code, setCode] = useState('')
const handleLogin = async (e: React.FormEvent) => {
e.preventDefault()
const result = await loginMutation.mutateAsync({
creds: { login: '...', pwd: '...' },
fcmDevice: 'WEB'
})
if (result.challenge) {
setMfaChallenge(result.challenge.type)
}
}
const handleVerify = () => {
// No need to pass challengeType! The SDK tracks it.
verifyMutation.mutate({ code })
}
if (mfaChallenge) {
return (
<div>
<p>Enter your {mfaChallenge} code:</p>
<input value={code} onChange={e => setCode(e.target.value)} />
<button onClick={handleVerify}>Verify</button>
</div>
)
}
return (
<form onSubmit={handleLogin}>
{/* login fields */}
<button type="submit">Login</button>
</form>
)
}
function MfaSettings() {
const removeMfa = useRemoveMfa()
const setPreference = useCreateMfaPreference()
return (
<div>
<button onClick={() => removeMfa.mutate()}>Disable MFA</button>
<button onClick={() => setPreference.mutate({ mfaType: MfaType.SMS })}>
Use SMS MFA
</button>
</div>
)
}
function TotpSetup() {
const startTotp = useCreateTOTPMfa()
const verifyTotp = useVerifyCreateTOTPMfa()
const [secret, setSecret] = useState<string | null>(null)
const [code, setCode] = useState('')
const handleStart = async () => {
const res = await startTotp.mutateAsync()
setSecret(res.secretCode!)
}
const handleVerify = () => {
verifyTotp.mutate({ code, deviceName: 'My Phone' })
}
if (secret) {
return (
<div>
<p>Your TOTP Secret: {secret}</p>
<input value={code} onChange={e => setCode(e.target.value)} />
<button onClick={handleVerify}>Verify & Enable</button>
</div>
)
}
return <button onClick={handleStart}>Enroll in TOTP</button>
}
Real-time Notifications (Event Bus)
import { useEffect } from 'react'
import { useSoliboApi } from '@solibo/solibo-query'
function NotificationListener() {
const { eventBus } = useSoliboApi()
useEffect(() => {
// onEvent returns an unsubscribe function
const unsubscribe = eventBus.onEvent(({ payload }) => {
// Discriminator-based type check
if (payload.type === 'solibo.common.infrastructure.websockets.payload.ConversationMessagePayload') {
alert('New message received!')
}
})
return () => unsubscribe()
}, [eventBus])
return <div>Monitoring for live updates...</div>
}
Machine-to-Machine (M2M)
For backend services, scripts, or other non-interactive clients, the SDK supports M2M authentication using OAuth 2.0 Client Credentials flow.
Setup (Kotlin)
import no.solibo.oss.sdk.SoliboSDK
import kotlinx.coroutines.runBlocking
fun main() = runBlocking {
val sdk = SoliboSDK.createM2M(
clientId = "your-client-id",
clientSecret = "your-client-secret",
scopes = listOf("api.read", "api.write")
)
val user = sdk.api.users.showUser()
println("Service acting as: ${user.name?.given}")
}
Setup (TypeScript)
import { SoliboSDKJsBuilder } from '@solibo/solibo-query';
async function run() {
const sdk = await SoliboSDKJsBuilder.createM2M(
'your-client-id',
'your-client-secret',
['api.read', 'api.write']
);
const user = await sdk.api.users.showUser();
console.log(`Service acting as: ${user.name?.given}`);
}