Quickstart with the Kotlin API client
On this page
Supported platforms
The Algolia Kotlin API client supports Kotlin version 1.6 or later. The API client supports both JVM (backend) and Android (frontend) projects.
Install
Install the Kotlin API client by adding the following dependencies to your build.gradle.kts
file:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
dependencies {
// for Gradle version 6.0 and later
implementation("com.algolia:algoliasearch-client-kotlin:2.0.0")
// Choose one of the following HTTP clients
// For JVM:
implementation("io.ktor:ktor-client-apache:2.0.1")
implementation("io.ktor:ktor-client-java:2.0.1")
implementation("io.ktor:ktor-client-jetty:2.0.1")
// For JVM/Android
implementation("io.ktor:ktor-client-okhttp:2.0.1")
implementation("io.ktor:ktor-client-android:2.0.1")
implementation("io.ktor:ktor-client-cio:2.0.1")
}
For serializing and deserializing your records, add the following plugin to your build.gradle.kts
file:
1
2
3
4
plugins {
// ...
kotlin("plugin.serialization") version "1.6.21"
}
Stack
This client provides a Kotlin implentation for using AlgoliaSearch. It is built using the official Kotlin stack:
- Kotlin multiplatform.
- Kotlinx serialization for json parsing.
- Kotlinx coroutines for asynchronous operations.
- Ktor HTTP client.
Integrated documentation
The Kotlin client integrates the actual Algolia documentation in each source file: Request parameters, response fields, methods and concepts; all are documented and link to the corresponding url of the Algolia doc website.
Type safety
Response and parameters objects are typed to provide extensive compile-time safety coverage.
Example for creating a Client instance without mixing the application ID and the API key.
1
2
3
4
val appID = ApplicationID("YourApplicationID")
val apiKey = APIKey("YourWriteAPIKey")
val client = ClientSearch(appID, apiKey)
Example for attributes:
1
2
3
val color = Attribute("color")
val category = Attribute("category")
val query = Query(attributesToRetrieve = listOf(color, category))
Sealed classes are used to represent enumerated types. It allows to quickly discover possible values for each type thanks to IDE autocomplete support.
Each enumerated type has an Other
case to pass a custom value.
1
2
3
4
5
val query = Query()
query.sortFacetsBy = SortFacetsBy.Count
// query.sortFacetsBy = SortFacetsBy.Alpha
// query.sortFacetsBy = SortFacetsBy.Other("custom value")
DSL
Extensive DSL coverage is provided to encourage a declarative style which retains the benefits of compile time safety while being more lightweight.
Example for query parameters:
1
2
3
4
5
6
val query = query {
attributesToRetrieve {
+"color"
+"category"
}
}
Example for settings:
1
2
3
4
5
val settings = settings {
attributesToSnippet {
+"content"(10)
}
}
Example for filters:
1
2
3
4
5
6
7
8
9
10
11
12
val query = query {
filters {
and {
facet("color", "red")
facet("category", "shirt")
}
orNumeric {
range("price", 0 until 10)
comparison("price", Equals, 15)
}
}
}
Json serialization
The Kotlin client relies on the kotlinx serialization library.
Search Response
Deserialize hits from a search response using the deserialize
extension functions.
1
2
3
4
5
6
7
8
9
@Serializable
data class Contact(
val firstname: String,
val lastname: String
)
val response = index.search()
val contacts: List<Contact> = response.hits.deserialize(Contact.serializer())
GetObject
Deserialize data from a getObject
call by passing a serializer
as parameter.
1
2
3
4
5
6
7
8
9
10
@Serializable
data class Contact(
val firstname: String,
val lastname: String,
override val objectID: ObjectID
) : Indexable
val objectID = ObjectID("myID1")
val contact: Contact = index.getObject(Contact.serializer(), objectID)
General
A JsonObject
can be transformed at any moment using the library standard methods.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Serializable
data class Contact(
val firstname: String,
val lastname: String
)
val json: JsonObject = buildJsonObject {
put("firstname", "Jimmie")
put("lastname", "Barninger")
}
val contact: Contact = Json.decodeFromJsonElement(Contact.serializer(), json)
// Or configure your own Json instance:
val JsonNonStrict = Json {
ignoreUnknownKeys = true
isLenient = true
allowSpecialFloatingPointValues = true
}
val contactNonStrict: Contact = JsonNonStrict.decodeFromJsonElement(Contact.serializer(), json)
Learn more about kotlinx serialization.
Exception handling
In case of success, an HTTP call returns the appropriate typed object.
1
val response: ResponseSearch = index.search()
However, an HTTP exception can occur. Handle it with a try / catch block.
1
2
3
4
5
6
7
8
try {
val response = index.search()
} catch (exception: AlgoliaApiException) {
when (exception.httpErrorCode) {
404 -> TODO()
400 -> TODO()
}
}
Other kinds of exceptions can occur. Handle them appropriately.
1
2
3
4
5
6
7
8
9
try {
val response = index.search()
} catch (exception: AlgoliaRuntimeException) {
TODO()
} catch (exception: IOException) {
TODO()
} catch (exception: Exception) {
TODO()
}
Coroutines
All methods performing HTTP calls in the Kotlin client are suspending functions. This means these functions can only be called from a coroutine scope.
In the example below, a coroutine is launched in the main thread. The context is switched to a thread pool to perform the search HTTP call off the main thread. The response can be manipulated from the main thread.
1
2
3
4
5
6
7
8
9
10
class Searcher : CoroutineScope {
override val coroutineContext = SupervisorJob()
fun search() {
launch(Dispatchers.Main) {
val response = withContext(Dispatchers.Default) { index.search() }
}
}
}
The developer is responsible for implementing the asynchronous logic that matches their specific requirements. The Kotlin client doesn’t execute HTTP calls on any particular thread, it is up to the developer to define it explicitly using coroutines. Learn more about coroutines.
Waiting for operations
Waiting for an asynchronous server task is made available via a function literal with receiver.
Use the apply or run functions on your index or client.
1
2
3
4
5
6
index.apply {
setSettings(Settings()).wait()
}
client.run {
multipleBatchObjects(listOf<BatchOperationIndex>()).waitAll()
}
The wait
functions are suspending, and should only be called from a coroutine.
Multithreading
The client is designed to be thread-safe. You can use SearchClient
, AnalyticsClient
, and InsightsClient
in a multithreaded environment.
Initialize the client
To start, you need to initialize the client. To do this, you need your Application ID and API Key. You can find both on your Algolia account.
1
2
3
4
5
6
7
val client = ClientSearch(
applicationID = ApplicationID("YourApplicationID"),
apiKey = APIKey("YourWriteAPIKey")
)
val indexName = IndexName("your_index_name")
client.initIndex(indexName)
The API key displayed here is your Admin API key. To maintain security, never use your Admin API key on your frontend, nor share it with anyone. In your frontend, only use the search-only API key or any other key that has search-only rights.
If you’re building a native app on mobile, don’t include the search API key directly in the source code. You should instead consider fetching the key from your servers during the app’s startup.
Push data
Without any prior configuration, you can start indexing contacts in the contacts
index using the following code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Serializable
data class Contact(
val firstname: String,
val lastname: String,
val followers: Int,
val company: String
)
val contacts = listOf(
Contact("Jimmie", "Barninger", 93, "California Paint"),
Contact("Warren", "Speach", 42, "Norwalk Crmc")
)
val index = client.initIndex(IndexName("contacts"))
index.apply {
saveObjects(Contact.serializer(), contacts).wait()
}
Configure
You can customize settings to fine-tune the search behavior. For example, you can add a custom ranking by number of followers to further enhance the built-in relevance:
1
2
3
4
5
6
7
val settings = settings {
customRanking {
+Desc("followers")
}
}
index.setSettings(settings)
You can also configure the list of attributes you want to index by order of importance (most important first).
Algolia suggests results as you type, which means you’ll generally search by prefix. In this case, the order of attributes is crucial in deciding which hit is the best.
1
2
3
4
5
6
7
8
9
val settings = settings {
searchableAttributes {
+"lastname"
+"firstname"
+"company"
}
}
index.setSettings(settings)
Search
Once configured, you can search for contacts by attributes such as firstname
, lastname
, or company
(even with typos):
1
2
3
4
5
6
7
8
// Search for a first name
index.search(Query("jimmie"))
// Search for a first name with typo
index.search(Query("jimie"))
// Search for a company
index.search(Query("california paint"))
// Search for a first name and a company
index.search(Query("jimmie paint"))