This document covers the setup required to use cr-sqlite in Android instrumentation tests with Room.
Before cr-sqlite can be tested in instrumentation tests, several APK packaging configurations are required.
In AndroidManifest.xml, native libraries must be extracted to the filesystem:
<application
android:extractNativeLibs="true"
...>Without this, the native library directory will be empty at runtime, and dlopen will fail.
In app/build.gradle, legacy packaging must be enabled:
packagingOptions {
jniLibs {
useLegacyPackaging = true
}
pickFirst '**/*.so'
}useLegacyPackaging = trueis required whenextractNativeLibs="true"pickFirst '**/*.so'resolves conflicts when multiple libraries have the same name
The project has two cr-sqlite builds:
- React Native's version (
crsqlite.so) - bundled via op-sqlite/PowerSync for the RN side - Our custom build (
crsqlite_requery.so) - built specifically for requery's SQLite version
These are not interchangeable. The React Native version is built against op-sqlite's embedded SQLite, while our custom version is built for requery's SQLite 3.45.0.
To avoid Gradle's pickFirst choosing the wrong one, we renamed our build to crsqlite_requery.so.
override fun createConfiguration(
path: String?,
@OpenFlags openFlags: Int
): SQLiteDatabaseConfiguration {
val config = super.createConfiguration(path, openFlags)
config.customExtensions.add(
SQLiteCustomExtension("crsqlite_requery", "sqlite3_crsqlite_init")
)
return config
}This uses requery's internal nativeLoadExtension() which works correctly.
// DON'T DO THIS - fails with "error during initialization"
db.execSQL("SELECT load_extension('crsqlite_requery', 'sqlite3_crsqlite_init');")Even though the library exists and is accessible, calling load_extension() from SQL fails with a generic "error during initialization" error. The root cause is unclear, but requery's native loading mechanism works while the SQL function does not.
After the database is opened, verify cr-sqlite functions are available:
db.rawQuery("SELECT crsql_db_version()", null).use { cursor ->
if (cursor.moveToFirst()) {
Log.d(TAG, "cr-sqlite loaded! db_version = ${cursor.getString(0)}")
}
}Note: Use crsql_db_version(), not crsql_version() - the latter doesn't exist in this build.
We use requery's built-in Room support (docs):
Room → CrSqliteRoomFactory → RequerySQLiteOpenHelperFactory → cr-sqlite
CrSqliteRoomFactory- Our factory that adds cr-sqlite viaConfigurationOptionsCrSqliteFinalizeWrapper- Thin wrapper that callscrsql_finalize()before closeRequerySQLiteOpenHelperFactory- Requery's built-in Room-compatible factory
ConfigurationOptions.apply()addsSQLiteCustomExtension("crsqlite_requery", ...)CrSqliteFinalizeWrapperensurescrsql_finalize()is called before closeunderlyingDatabaseexposes the requery SQLiteDatabase for cr-sqlite function access
The cr-sqlite extension isn't loading. Check:
extractNativeLibs="true"in AndroidManifest.xmluseLegacyPackaging = truein build.gradle- The
.sofile exists injniLibs/<arch>/ - Clean build:
./gradlew clean
Don't use SQL load_extension(). Use SQLiteCustomExtension instead (see above).
The library isn't being extracted. Check extractNativeLibs and useLegacyPackaging settings.
Same as above - the APK packaging isn't extracting native libraries.
If you see unexpected behavior, verify the correct .so is packaged:
unzip -l app/build/outputs/apk/*/debug/*.apk | grep crsqliteYou should see crsqlite_requery.so in the lib directories.
This warning appears during database close:
E SQLiteConnection: sqlite3_close(0x...) failed: 5
E SQLiteConnectionPool: unable to close due to unfinalized statements
This is expected and not a test failure. Room/Requery cache prepared statements for performance, and SQLite complains when closing with cached statements. The GC cleans these up.
CrSqliteSupportHelper tries to mitigate this by:
- Setting SQL cache size to 0 before close
- Catching the close exception gracefully
The important thing is that crsql_finalize() is called before close, which it is.