diff --git a/build.gradle.kts b/build.gradle.kts
index e80a02d8..9c175ec9 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -8,11 +8,13 @@ plugins {
alias(libs.plugins.kmmbridge) apply false
alias(libs.plugins.skie) apply false
alias(libs.plugins.kotlin.jvm) apply false
+ alias(libs.plugins.kotlin.android) apply false
alias(libs.plugins.sqldelight) apply false
alias(libs.plugins.grammarKitComposer) apply false
alias(libs.plugins.mavenPublishPlugin) apply false
alias(libs.plugins.downloadPlugin) apply false
alias(libs.plugins.kotlinter) apply false
+ alias(libs.plugins.keeper) apply false
}
// Having different versions of this lead to the issue mentioned here
diff --git a/core-tests-android/.gitignore b/core-tests-android/.gitignore
new file mode 100644
index 00000000..42afabfd
--- /dev/null
+++ b/core-tests-android/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/core-tests-android/build.gradle.kts b/core-tests-android/build.gradle.kts
new file mode 100644
index 00000000..46c4fad2
--- /dev/null
+++ b/core-tests-android/build.gradle.kts
@@ -0,0 +1,54 @@
+import com.slack.keeper.optInToKeeper
+
+plugins {
+ alias(libs.plugins.androidApplication)
+ alias(libs.plugins.kotlin.android)
+ alias(libs.plugins.keeper)
+}
+
+dependencies {
+ implementation(projects.core)
+ implementation(libs.androidx.core)
+ implementation(libs.androidx.appcompat)
+ implementation(libs.androidx.material)
+
+ androidTestImplementation(libs.androidx.junit)
+ androidTestImplementation(libs.androidx.espresso.core)
+ androidTestImplementation(libs.test.coroutines)
+ androidTestImplementation(libs.test.turbine)
+}
+
+android {
+ namespace = "com.powersync.testing"
+ compileSdk = libs.versions.android.compileSdk.get().toInt()
+
+ defaultConfig {
+ applicationId = "com.powersync.testing"
+ minSdk = libs.versions.android.minSdk.get().toInt()
+ targetSdk = libs.versions.android.targetSdk.get().toInt()
+ versionCode = 1
+ versionName = "1.0"
+
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+ }
+
+ buildTypes {
+ release {
+ isMinifyEnabled = true
+ proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
+ signingConfig = signingConfigs.getByName("debug")
+ }
+ }
+ testBuildType = "release"
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_11
+ targetCompatibility = JavaVersion.VERSION_11
+ }
+ kotlinOptions {
+ jvmTarget = "11"
+ }
+}
+
+androidComponents {
+ beforeVariants { it.optInToKeeper() }
+}
diff --git a/core-tests-android/proguard-rules.pro b/core-tests-android/proguard-rules.pro
new file mode 100644
index 00000000..481bb434
--- /dev/null
+++ b/core-tests-android/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/core-tests-android/src/androidTest/java/com/powersync/AndroidDatabaseTest.kt b/core-tests-android/src/androidTest/java/com/powersync/AndroidDatabaseTest.kt
new file mode 100644
index 00000000..0ca5d9ea
--- /dev/null
+++ b/core-tests-android/src/androidTest/java/com/powersync/AndroidDatabaseTest.kt
@@ -0,0 +1,94 @@
+package com.powersync
+
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import app.cash.turbine.turbineScope
+import com.powersync.db.schema.Schema
+import com.powersync.testutils.UserRow
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.runTest
+import org.junit.After
+
+import org.junit.Test
+import org.junit.runner.RunWith
+
+import org.junit.Assert.*
+import org.junit.Before
+
+@RunWith(AndroidJUnit4::class)
+class AndroidDatabaseTest {
+ private lateinit var database: PowerSyncDatabase
+
+ @Before
+ fun setupDatabase() {
+ database =
+ PowerSyncDatabase(
+ factory = DatabaseDriverFactory(InstrumentationRegistry.getInstrumentation().targetContext),
+ schema = Schema(UserRow.table),
+ dbFilename = "testdb",
+ )
+
+ runBlocking {
+ database.disconnectAndClear(true)
+ }
+ }
+
+ @After
+ fun tearDown() {
+ runBlocking { database.disconnectAndClear(true) }
+ }
+
+ @Test
+ fun testLinksPowerSync() =
+ runTest {
+ database.get("SELECT powersync_rs_version() AS r;") { it.getString(0)!! }
+ }
+
+ @Test
+ fun testTableUpdates() =
+ runTest {
+ turbineScope {
+ val query = database.watch("SELECT * FROM users") { UserRow.from(it) }.testIn(this)
+
+ // Wait for initial query
+ assertEquals(0, query.awaitItem().size)
+
+ database.execute(
+ "INSERT INTO users (id, name, email) VALUES (uuid(), ?, ?)",
+ listOf("Test", "test@example.org"),
+ )
+ assertEquals(1, query.awaitItem().size)
+
+ database.writeTransaction {
+ it.execute(
+ "INSERT INTO users (id, name, email) VALUES (uuid(), ?, ?)",
+ listOf("Test2", "test2@example.org"),
+ )
+ it.execute(
+ "INSERT INTO users (id, name, email) VALUES (uuid(), ?, ?)",
+ listOf("Test3", "test3@example.org"),
+ )
+ }
+
+ assertEquals(3, query.awaitItem().size)
+
+ try {
+ database.writeTransaction {
+ it.execute("DELETE FROM users;")
+ it.execute("syntax error, revert please")
+ }
+ } catch (e: Exception) {
+ // Ignore
+ }
+
+ database.execute(
+ "INSERT INTO users (id, name, email) VALUES (uuid(), ?, ?)",
+ listOf("Test4", "test4@example.org"),
+ )
+ assertEquals(4, query.awaitItem().size)
+
+ query.expectNoEvents()
+ query.cancel()
+ }
+ }
+}
\ No newline at end of file
diff --git a/core-tests-android/src/androidTest/java/com/powersync/testutils/UserRow.kt b/core-tests-android/src/androidTest/java/com/powersync/testutils/UserRow.kt
new file mode 100644
index 00000000..caf65765
--- /dev/null
+++ b/core-tests-android/src/androidTest/java/com/powersync/testutils/UserRow.kt
@@ -0,0 +1,23 @@
+package com.powersync.testutils
+
+import com.powersync.db.SqlCursor
+import com.powersync.db.getString
+import com.powersync.db.schema.Column
+import com.powersync.db.schema.Table
+
+data class UserRow(
+ val id: String,
+ val name: String,
+ val email: String,
+) {
+ companion object {
+ fun from(cursor: SqlCursor): UserRow =
+ UserRow(
+ id = cursor.getString("id"),
+ name = cursor.getString("name"),
+ email = cursor.getString("email"),
+ )
+
+ val table = Table(name = "users", columns = listOf(Column.text("name"), Column.text("email")))
+ }
+}
diff --git a/core-tests-android/src/main/AndroidManifest.xml b/core-tests-android/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..eaf882a7
--- /dev/null
+++ b/core-tests-android/src/main/AndroidManifest.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/core-tests-android/src/main/res/drawable/ic_launcher_background.xml b/core-tests-android/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 00000000..956b344d
--- /dev/null
+++ b/core-tests-android/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,74 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/core-tests-android/src/main/res/drawable/ic_launcher_foreground.xml b/core-tests-android/src/main/res/drawable/ic_launcher_foreground.xml
new file mode 100644
index 00000000..1ee14938
--- /dev/null
+++ b/core-tests-android/src/main/res/drawable/ic_launcher_foreground.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/core-tests-android/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/core-tests-android/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 00000000..50ec8862
--- /dev/null
+++ b/core-tests-android/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/core-tests-android/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/core-tests-android/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 00000000..50ec8862
--- /dev/null
+++ b/core-tests-android/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/core-tests-android/src/main/res/mipmap-hdpi/ic_launcher.webp b/core-tests-android/src/main/res/mipmap-hdpi/ic_launcher.webp
new file mode 100644
index 00000000..c209e78e
Binary files /dev/null and b/core-tests-android/src/main/res/mipmap-hdpi/ic_launcher.webp differ
diff --git a/core-tests-android/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/core-tests-android/src/main/res/mipmap-hdpi/ic_launcher_round.webp
new file mode 100644
index 00000000..b2dfe3d1
Binary files /dev/null and b/core-tests-android/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ
diff --git a/core-tests-android/src/main/res/mipmap-mdpi/ic_launcher.webp b/core-tests-android/src/main/res/mipmap-mdpi/ic_launcher.webp
new file mode 100644
index 00000000..4f0f1d64
Binary files /dev/null and b/core-tests-android/src/main/res/mipmap-mdpi/ic_launcher.webp differ
diff --git a/core-tests-android/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/core-tests-android/src/main/res/mipmap-mdpi/ic_launcher_round.webp
new file mode 100644
index 00000000..62b611da
Binary files /dev/null and b/core-tests-android/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ
diff --git a/core-tests-android/src/main/res/mipmap-xhdpi/ic_launcher.webp b/core-tests-android/src/main/res/mipmap-xhdpi/ic_launcher.webp
new file mode 100644
index 00000000..948a3070
Binary files /dev/null and b/core-tests-android/src/main/res/mipmap-xhdpi/ic_launcher.webp differ
diff --git a/core-tests-android/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/core-tests-android/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
new file mode 100644
index 00000000..1b9a6956
Binary files /dev/null and b/core-tests-android/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ
diff --git a/core-tests-android/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/core-tests-android/src/main/res/mipmap-xxhdpi/ic_launcher.webp
new file mode 100644
index 00000000..28d4b77f
Binary files /dev/null and b/core-tests-android/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ
diff --git a/core-tests-android/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/core-tests-android/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
new file mode 100644
index 00000000..9287f508
Binary files /dev/null and b/core-tests-android/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ
diff --git a/core-tests-android/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/core-tests-android/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
new file mode 100644
index 00000000..aa7d6427
Binary files /dev/null and b/core-tests-android/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ
diff --git a/core-tests-android/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/core-tests-android/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
new file mode 100644
index 00000000..9126ae37
Binary files /dev/null and b/core-tests-android/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ
diff --git a/core-tests-android/src/main/res/values-night/themes.xml b/core-tests-android/src/main/res/values-night/themes.xml
new file mode 100644
index 00000000..62fbedd2
--- /dev/null
+++ b/core-tests-android/src/main/res/values-night/themes.xml
@@ -0,0 +1,16 @@
+
+
+
+
\ No newline at end of file
diff --git a/core-tests-android/src/main/res/values/colors.xml b/core-tests-android/src/main/res/values/colors.xml
new file mode 100644
index 00000000..f8c6127d
--- /dev/null
+++ b/core-tests-android/src/main/res/values/colors.xml
@@ -0,0 +1,10 @@
+
+
+ #FFBB86FC
+ #FF6200EE
+ #FF3700B3
+ #FF03DAC5
+ #FF018786
+ #FF000000
+ #FFFFFFFF
+
\ No newline at end of file
diff --git a/core-tests-android/src/main/res/values/strings.xml b/core-tests-android/src/main/res/values/strings.xml
new file mode 100644
index 00000000..859dfd76
--- /dev/null
+++ b/core-tests-android/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+
+ Core Android tests
+
\ No newline at end of file
diff --git a/core-tests-android/src/main/res/values/themes.xml b/core-tests-android/src/main/res/values/themes.xml
new file mode 100644
index 00000000..25692b5b
--- /dev/null
+++ b/core-tests-android/src/main/res/values/themes.xml
@@ -0,0 +1,16 @@
+
+
+
+
\ No newline at end of file
diff --git a/core/build.gradle.kts b/core/build.gradle.kts
index 413e3749..4f4ce091 100644
--- a/core/build.gradle.kts
+++ b/core/build.gradle.kts
@@ -276,6 +276,7 @@ android {
libs.versions.android.minSdk
.get()
.toInt()
+ consumerProguardFiles("proguard-rules.pro")
@Suppress("UnstableApiUsage")
externalNativeBuild {
diff --git a/core/proguard-rules.pro b/core/proguard-rules.pro
new file mode 100644
index 00000000..aeed3990
--- /dev/null
+++ b/core/proguard-rules.pro
@@ -0,0 +1,11 @@
+# If the app calls the JNI method to initialize driver bindings, keep that method
+# (so that it can be linked through JNI) and the other methods called from native
+# code.
+-if class com.powersync.DatabaseDriverFactory {
+ private void setupSqliteBinding();
+}
+-keep class com.powersync.DatabaseDriverFactory {
+ private void setupSqliteBinding();
+ private void onTableUpdate(java.lang.String);
+ private void onTransactionCommit(boolean);
+}
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 73d20659..a4db612f 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -38,6 +38,7 @@ download-plugin = "5.5.0"
grammerKit = "0.1.12"
mokkery = "2.4.0"
kotlinter = "4.4.1"
+keeper = "0.16.1"
# Sample - Android
androidx-core = "1.13.1"
@@ -46,7 +47,9 @@ androidx-activity-compose = "1.9.2"
androidx-appcompat = "1.7.0"
androidx-espresso-core = "3.6.1"
androidx-material = "1.12.0"
-androidx-test-junit = "1.2.1"
+androidx-test-runner = "1.6.2"
+androidx-test-rules = "1.6.1"
+junitVersion = "1.2.1"
[libraries]
configuration-annotations = { module = "co.touchlab.skie:configuration-annotations", version.ref = "configurationAnnotations" }
@@ -56,7 +59,8 @@ powersync-sqlite-core-android = { module = "co.powersync:powersync-sqlite-core",
mavenPublishPlugin = { module = "com.vanniktech:gradle-maven-publish-plugin", version.ref = "maven-publish" }
test-junit = { group = "junit", name = "junit", version.ref = "junit" }
-test-junitKtx = { module = "androidx.test.ext:junit-ktx", version.ref = "androidx-test-junit" }
+test-android-runner = { module = "androidx.test:runner", version.ref = "androidx-test-runner" }
+test-android-rules = { module = "androidx.test:rules", version.ref = "androidx-test-rules" }
test-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "coroutines" }
test-turbine = { module = "app.cash.turbine:turbine", version.ref = "turbine" }
kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" }
@@ -101,6 +105,7 @@ androidx-activity-compose = { module = "androidx.activity:activity-compose", ver
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "androidx-espresso-core" }
androidx-material = { group = "com.google.android.material", name = "material", version.ref = "androidx-material" }
compose-ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview", version.ref = "compose-preview" }
+androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
[plugins]
androidApplication = { id = "com.android.application", version.ref = "android-gradle-plugin" }
@@ -119,6 +124,8 @@ mavenPublishPlugin = { id = "com.vanniktech.maven.publish", version.ref = "maven
downloadPlugin = { id = "de.undercouch.download", version.ref = "download-plugin" }
mokkery = { id = "dev.mokkery", version.ref = "mokkery" }
kotlinter = { id = "org.jmailen.kotlinter", version.ref = "kotlinter" }
+keeper = { id = "com.slack.keeper", version.ref = "keeper" }
+kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
[bundles]
sqldelight = [
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 06426d2a..23fcbe5a 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -21,6 +21,7 @@ dependencyResolutionManagement {
rootProject.name = "powersync-root"
include(":core")
+include(":core-tests-android")
include(":connectors:supabase")
include(":dialect")