Skip to content

Kotlin Extensions

Couchbase Lite — Kotlin support

Introduction

In addition to implementing the full Couchbase Lite Java SDK API, Kotbase also provides the additional APIs available in the Couchbase Lite Android KTX SDK, which includes a number of Kotlin-specific extensions.

This includes:

Additionally, while not available in the Java SDK, as Java doesn't support operator overloading, Kotbase adds support for Fragment subscript APIs, similar to Couchbase Lite Swift, Objective-C, and .NET.

Configuration Factories

Couchbase Lite provides a set of configuration factories. These allow use of named parameters to specify property settings.

This makes it simple to create variant configurations, by simply overriding named parameters:

Example of overriding configuration

val listener8080 = URLEndpointListenerConfigurationFactory.newConfig(
    networkInterface = "en0",
    port = 8080
)
val listener8081 = listener8080.newConfig(port = 8081)

Database

Use DatabaseConfigurationFactory to create a DatabaseConfiguration object, overriding the receiver’s values with the passed parameters.

val database = Database(
    "getting-started",
    DatabaseConfigurationFactory.newConfig()
)
val DatabaseConfigurationFactory: DatabaseConfiguration? = null

fun DatabaseConfiguration?.newConfig(
    databasePath: String? = null, 
    encryptionKey: EncryptionKey? = null
): DatabaseConfiguration

Replication

Use ReplicatorConfigurationFactory to create a ReplicatorConfiguration object, overriding the receiver’s values with the passed parameters.

val replicator = Replicator(
    ReplicatorConfigurationFactory.newConfig(
        collections = mapOf(db.collections to null),
        target = URLEndpoint("ws://localhost:4984/getting-started-db"),
        type = ReplicatorType.PUSH_AND_PULL,
        authenticator = BasicAuthenticator("sync-gateway", "password".toCharArray())
    )
)
val ReplicatorConfigurationFactory: ReplicatorConfiguration? = null

public fun ReplicatorConfiguration?.newConfig(
    target: Endpoint? = null,
    collections: Map<out kotlin.collections.Collection<Collection>, CollectionConfiguration?>? = null,
    type: ReplicatorType? = null,
    continuous: Boolean? = null,
    authenticator: Authenticator? = null,
    headers: Map<String, String>? = null,
    pinnedServerCertificate: ByteArray? = null,
    maxAttempts: Int? = null,
    maxAttemptWaitTime: Int? = null,
    heartbeat: Int? = null,
    enableAutoPurge: Boolean? = null,
    acceptOnlySelfSignedServerCertificate: Boolean? = null,
    acceptParentDomainCookies: Boolean? = null
): ReplicatorConfiguration

Use FullTextIndexConfigurationFactory to create a FullTextIndexConfiguration object, overriding the receiver’s values with the passed parameters.

collection.createIndex(
    "overviewFTSIndex",
    FullTextIndexConfigurationFactory.newConfig("overview")
)
val FullTextIndexConfigurationFactory: FullTextIndexConfiguration? = null

fun FullTextIndexConfiguration?.newConfig(
    vararg expressions: String = emptyArray(), 
    language: String? = null, 
    ignoreAccents: Boolean? = null
): FullTextIndexConfiguration

Indexing

Use ValueIndexConfigurationFactory to create a ValueIndexConfiguration object, overriding the receiver’s values with the passed parameters.

collection.createIndex(
    "TypeNameIndex",
    ValueIndexConfigurationFactory.newConfig("type", "name")
)
val ValueIndexConfigurationFactory: ValueIndexConfiguration? = null

fun ValueIndexConfiguration?.newConfig(vararg expressions: String = emptyArray()): ValueIndexConfiguration

Logs

Use LogFileConfigurationFactory to create a LogFileConfiguration object, overriding the receiver’s values with the passed parameters.

Database.log.file.apply {
    config = LogFileConfigurationFactory.newConfig(
        directory = "path/to/temp/logs",
        maxSize = 10240,
        maxRotateCount = 5,
        usePlainText = false
    )
    level = LogLevel.INFO
}
val LogFileConfigurationFactory: LogFileConfiguration? = null

fun LogFileConfiguration?.newConfig(
    directory: String? = null,
    maxSize: Long? = null,
    maxRotateCount: Int? = null,
    usePlainText: Boolean? = null
): LogFileConfiguration

Change Flows

These wrappers use Flows to monitor for changes.

Collection Change Flow

Use the Collection.collectionChangeFlow() to monitor collection change events.

scope.launch {
    collection.collectionChangeFlow()
        .map { it.documentIDs }
        .collect { docIds: List<String> ->
            // handle changes
        }
}
fun Collection.collectionChangeFlow(
    coroutineContext: CoroutineContext? = null
): Flow<CollectionChange>

Document Change Flow

Use Collection.documentChangeFlow() to monitor changes to a document.

scope.launch {
    collection.documentChangeFlow("1001")
        .map { it.collection.getDocument(it.documentID)?.getString("lastModified") }
        .collect { lastModified: String? ->
            // handle document changes
        }
}
fun Collection.documentChangeFlow(
    documentId: String, 
    coroutineContext: CoroutineContext? = null
): Flow<DocumentChange>

Replicator Change Flow

Use Replicator.replicatorChangeFlow() to monitor replicator changes.

scope.launch {
    repl.replicatorChangesFlow()
        .map { it.status.activityLevel }
        .collect { activityLevel: ReplicatorActivityLevel ->
            // handle replicator changes
        }
}
fun Replicator.replicatorChangesFlow(
    coroutineContext: CoroutineContext? = null
): Flow<ReplicatorChange>

Document Replicator Change Flow

Use Replicator.documentReplicationFlow() to monitor document changes during replication.

scope.launch {
    repl.documentReplicationFlow()
        .map { it.documents }
        .collect { docs: List<ReplicatedDocument> ->
            // handle replicated documents
        }
}
fun Replicator.documentReplicationFlow(
    coroutineContext: CoroutineContext? = null
): Flow<DocumentReplication>

Query Change Flow

Use Query.queryChangeFlow() to monitor changes to a query.

scope.launch {
    query.queryChangeFlow()
        .mapNotNull { change ->
            val err = change.error
            if (err != null) {
                throw err
            }
            change.results?.allResults()
        }
        .collect { results: List<Result> ->
            // handle query results
        }
}
fun Query.queryChangeFlow(
    coroutineContext: CoroutineContext? = null
): Flow<QueryChange>

Fragment Subscripts

Kotbase uses Kotlin's indexed access operator to implement Couchbase Lite's Fragment subscript APIs for Database, Collection, Document, Array, Dictionary, and Result, for concise, type-safe, and null-safe access to arbitrary values in a nested JSON object. MutableDocument, MutableArray, and MutableDictionary also support the MutableFragment APIs for mutating values.

Supported types can get Fragment or MutableFragment objects by either index or key. Fragment objects represent an arbitrary entry in a key path, themselves supporting subscript access to nested values.

Finally, the typed optional value at the end of a key path can be accessed or set with the Fragment properties, e.g. array, dictionary, string, int, date, etc.

Subscript API examples

val db = Database("db")
val coll = db.defaultCollection
val doc = coll["doc-id"]       // DocumentFragment
doc.exists                     // true or false
doc.document                   // "doc-id" Document from Database
doc["array"].array             // Array value from "array" key
doc["array"][0].string         // String value from first Array item
doc["dict"].dictionary         // Dictionary value from "dict" key
doc["dict"]["num"].int         // Int value from Dictionary "num" key
coll["milk"]["exp"].date       // Instant value from "exp" key from "milk" Document
val newDoc = MutableDocument("new-id")
newDoc["name"].value = "Sally" // set "name" value