I have a relatively large db that may take 1 to 2 minutes to initialise, is it possible to load a pre-populated db when using sqldelight (kotlin multiplatform) instead of initialising the db on app launch?
Sorry, but I can't add any comments yet, which would be more appropriate...
Although not directly answering your question, 1 to 2 minutes is really, really long for sqlite. What are you doing? I would first make sure you're using transactions properly. 1-2 minutes of inserting data would (probably) result in a huge db file.
Alternatively, my problem due to which I had to use a pre-populated database was associated with the large size of .sq files (more than 30 MB text of INSERTs per table), and SqlDeLight silently interrupted the generation, without displaying error messages.
You'll need to put the db file in assets on android and in a bundle on iOS and copy them to their respective folders before initializing sqldelight.
Having to load a db from resources on both android and ios feels a lot of work + it means the shared project wont be the only place where the data is initialised.
Kotlin MultiPlatform library Moko-resources solves the issue of a single source for a database in a shared module. It works for KMM the same way for Android and iOS.
Unfortunately, using this feature are almost not presented in the samples of library. I added a second method (getDriver) to the expected class DatabaseDriverFactory to open the prepared database, and implemented it on the platform. For example, for androidMain:
actual class DatabaseDriverFactory(private val context: Context) {
actual fun createDriver(schema: SqlDriver.Schema, fileName: String): SqlDriver {
return AndroidSqliteDriver(schema, context, fileName)
}
actual fun getDriver(schema: SqlDriver.Schema, fileName: String): SqlDriver {
val database: File = context.getDatabasePath(fileName)
if (!database.exists()) {
val inputStream = context.resources.openRawResource(MR.files.dbfile.rawResId)
val outputStream = FileOutputStream(database.absolutePath)
inputStream.use { input: InputStream ->
outputStream.use { output: FileOutputStream ->
input.copyTo(output)
}
}
}
return AndroidSqliteDriver(schema, context, fileName)
}
}
MR.files.fullDb is the FileResource from the class generated by the library, it is associated with the name of the file located in the resources/MR/files directory of the commonMain module. It property rawResId represents the platform-side resource ID.
I have converted the code by @Denis Luttcev to the ios target side and it works perfectly in my kotlin multiplatform project. Had some troubles figuring out how to make it work, so in case anyone else will need it here is what I got:
actual class DriverFactory {
actual fun createDriver(schema: SqlDriver.Schema, fileName: String): SqlDriver {
val fileManager = NSFileManager.defaultManager
val documentsPath = NSSearchPathForDirectoriesInDomains(
directory = NSApplicationSupportDirectory,
domainMask = NSUserDomainMask,
expandTilde = true
).first() as NSString
val dbDirectoryPath = documentsPath.stringByAppendingPathComponent("databases")
val targetDbPath = documentsPath.stringByAppendingPathComponent("databases/$fileName")
val sourceDbPath = MR.files.dbfile.path
val directoryExists = fileManager.fileExistsAtPath(dbDirectoryPath)
val databaseExists = fileManager.fileExistsAtPath(targetDbPath)
if (databaseExists.not()) {
memScoped {
// you can use it to log errors
val error: ObjCObjectVar<NSError?> = alloc()
if (directoryExists.not()) {
val createSuccess = fileManager.createDirectoryAtPath(
path = dbDirectoryPath,
withIntermediateDirectories = true,
attributes = null,
error = error.ptr
)
}
val copySuccess = fileManager.copyItemAtPath(
srcPath = sourceDbPath,
toPath = targetDbPath,
error = error.ptr
)
}
}
return NativeSqliteDriver(schema, fileName)
}
}
Yes, but it can be tricky. Not just for "Multiplatform". You need to copy the db to the db folder before trying to init sqldelight. That probably means i/o on the main thread when the app starts.
There is no standard way to do this now. You'll need to put the db file in assets on android and in a bundle on iOS and copy them to their respective folders before initializing sqldelight. Obviously you'll want to check if the db exists first, or have some way of knowing this is your first app run.
If you're planning on shipping updates that will have newer databases, you'll need to manage versions outside of just a check for the existance of the db.
Although not directly answering your question, 1 to 2 minutes is really, really long for sqlite. What are you doing? I would first make sure you're using transactions properly. 1-2 minutes of inserting data would (probably) result in a huge db file.
The only thing you need is to specify the path to the DB file using the driver.
Let's assume your DB lies in /mnt/my_best_app_dbs/super.db
. Now, pass the path in the name
property of the Driver. Something like this:
val sqlDriver: SqlDriver = AndroidSqliteDriver(Schema, context, "/mnt/my_best_app_dbs/best.db")
Keep in mind that you might need to have permissions that allow you to read a given storage type.
© 2022 - 2024 — McMap. All rights reserved.