Android Kotlin
Convex Android client library enables your Android application to interact with your Convex backend. It allows your frontend code to:
The library is open source and available on GitHub.
Follow the Android Quickstart to get started.
Installation
You'll need to make the following changes to your app's build.gradle[.kts]
file.
plugins {
// ... existing plugins
kotlin("plugin.serialization") version "1.9.0"
}
dependencies {
// ... existing dependencies
implementation("dev.convex:android-convexmobile:0.4.1@aar") {
isTransitive = true
}
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3")
}
After that, sync Gradle to pick up those changes. Your app will now have access to the Convex for Android library as well as Kotlin's JSON serialization which is used to communicate between your code and the Convex backend.
Connecting to a backend
The ConvexClient
is used to establish and maintain a connect between your application and the Convex backend. First you need to create an instance of the client by giving it your backend deployment URL:
package com.example.convexapp
import dev.convex.android.ConvexClient
val convex = ConvexClient("https://<your domain here>.convex.cloud")
You should create and use one instance of the ConvexClient
for the lifetime of your application process. It can be convenient to create a custom Android Application
subclass and initialize it there:
package com.example.convexapp
import android.app.Application
import dev.convex.android.ConvexClient
class MyApplication : Application() {
lateinit var convex: ConvexClient
override fun onCreate() {
super.onCreate()
convex = ConvexClient("https://<your domain here>.convex.cloud")
}
}
Once you've done that, you can access the client from a Jetpack Compose @Composable
function like this:
val convex = (application as MyApplication).convex
Fetching data
Convex for Android gives you access to the Convex reactor, which enables real-time subscriptions to query results. You subscribe to queries with the subscribe
method on ConvexClient
which returns a Flow
. The contents of the Flow
will change over time as the underlying data backing the query changes.
All methods on ConvexClient
suspend, and need to be called from a CoroutineScope
or another suspend
function. A simple way to consume a query that returns a list of strings from a @Composable
is to use a combination of mutable state containing a list and LaunchedEffect
:
var workouts: List<String> by remember { mutableStateOf(listOf()) }
LaunchedEffect("onLaunch") {
client.subscribe<List<String>>("workouts:get").collect { result ->
result.onSuccess { receivedWorkouts ->
workouts = receivedWorkouts
}
}
}
Any time the data that powers the backend "workouts:get"
query changes, a new Result<List<String>>
will be emitted into the Flow
and the workouts
list will refresh with the new data. Any UI that uses workouts
will then rebuild, giving you a fully reactive UI.
Note: you may prefer to put the subscription logic wrapped a Repository as described in the Android architecture patterns.
Query arguments
You can pass arguments to subscribe
and they will be supplied to the associated backend query
function. The arguments are typed as Map<String, Any?>
. The values in the map must be primitive values or other maps and lists.
val favoriteColors = mapOf("favoriteColors" to listOf("blue", "red"))
client.subscribe<List<String>>("users:list", args = favoriteColors)
Assuming a backend query that accepts a favoriteColors
argument, the value can be received and used to perform logic in the query function.
Use serializable Kotlin Data classes to automatically convert Convex objects to Kotlin model classes.
- There are important gotchas when sending and receiving numbers between Kotlin and Convex.
_
is a used to signify private fields in Kotlin. If you want to use a_creationTime
and_id
Convex fields directly without warnings you'll have to convert the field name in Kotlin.- Depending on your backend functions, you may need to deal with reserved Kotlin keywords.
Subscription lifetime
The Flow
returned from subscribe
will persist as long as something is waiting to consume results from it. When a @Composable
or ViewModel
with a subscription goes out of scope, the underlying query subscription to Convex will be canceled.
Editing data
You can use the mutation
method on ConvexClient
to trigger a backend mutation.
You'll need to use it in another suspend
function or a CoroutineScope
. Mutations can return a value or not. If you expect a type in the response, indicate it in the call signature.
Mutations can also receive arguments, just like queries. Here's an example of returning a type from a mutation with arguments:
val recordsDeleted = convex.mutation<@ConvexNum Int>(
"messages:cleanup",
args = mapOf("keepLatest" to 100)
)
If an error occurs during a call to mutation
, it will throw an exception. Typically you may want to catch ConvexError
and ServerError
and handle them however is appropriate in your application. See documentation on error handling for more details.
Calling third-party APIs
You can use the action
method on ConvexClient
to trigger a backend action.
Calls to action
can accept arguments, return values and throw exceptions just like calls to mutation
.
Even though you can call actions from Android, it's not always the right choice. See the action docs for tips on calling actions from clients.
Authentication with Auth0
You can use ConvexClientWithAuth
in place of ConvexClient
to configure authentication with Auth0. You'll need the convex-android-auth0
library to do that, as well as an Auth0 account and application configuration.
See the README in the convex-android-auth0
repo for more detailed setup instructions, and the Workout example app which is configured for Auth0. The overall Convex authentication docs are a good resource as well.
It should also be possible to integrate other similar OpenID Connect authentication providers. See the AuthProvider
interface in the convex-mobile
repo for more info.
Production and dev deployments
When you're ready to move toward production for your app, you can setup your Android build system to point different builds or flavors of your application to different Convex deployments. One fairly simple way to do it is by passing different values (e.g. deployment URL) to different build targets or flavors.
Here's a simple example that shows using different deployment URLs for release and debug builds:
// In the android section of build.gradle.kts:
buildTypes {
release {
// Snip various other config like ProGuard ...
resValue("string", "convex_url", "YOUR_PROD.convex.cloud")
}
debug {
resValue("string", "convex_url", "YOUR_DEV.convex.cloud")
}
}
Then you can build your ConvexClient
using a single resource in code, and it will get the right value at compile time.
val convex = ConvexClient(context.getString(R.string.convex_url))
You may not want these urls checked into your repository. One pattern is to create a custom my_app.properties
file that is configured to be ignored in your .gitignore
file. You can then read this file in your build.gradle.kts
file. You can see this pattern in use in the workout sample app.
Structuring your application
The examples shown in this guide are intended to be brief, and don't provide guidance on how to structure a whole application.
The official Android application architecture docs cover best practices for building applications, and Convex also has a sample open source application that attempts to demonstrate what a small multi-screen application might look like.
In general, do the following:
- Embrace Flows and unidirectional data flow
- Have a clear data layer (use Repository classes with
ConvexClient
as your data source) - Hold UI state in a ViewModel
Testing
ConvexClient
is an open
class so it can be mocked or faked in unit tests. If you want to use more of the real client, you can pass a fake MobileConvexClientInterface
in to the ConvexClient
constructor. Just be aware that you'll need to provide JSON in Convex's undocumented JSON format.
You can also use the full ConvexClient
in Android instrumentation tests. You can setup a special backend instance for testing or run a local Convex server and run full integration tests.
Under the hood
Convex for Android is built on top of the official Convex Rust client. It handles maintaining a WebSocket connection with the Convex backend and implements the full Convex protocol.
All method calls on ConvexClient
are handled via a Tokio async runtime on the Rust side and are safe to call from the application's main thread.
ConvexClient
also makes heavy use of Kotlin's serialization framework, and most of the functionality in that framework is available for you to use in your applications. Internally, ConvexClient
enables the JSON ignoreUnknownKeys
and allowSpecialFloatingPointValues
features.