Skip to main content

Android Kotlin

Convex Android client library enables your Android application to interact with your Convex backend. It allows your frontend code to:

  1. Call your queriesmutations and actions
  2. Authenticate users using Auth0

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.

tip

Use serializable Kotlin Data classes to automatically convert Convex objects to Kotlin model classes.

caution

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'll 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))
tip

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:

  1. Embrace Flows and unidirectional data flow
  2. Have a clear data layer (use Repository classes with ConvexClient as your data source)
  3. 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.