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'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))
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.