commonMain.io.realm.RealmConfiguration.kt Maven / Gradle / Ivy
/*
* Copyright 2020 Realm Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.realm
import io.realm.internal.Mediator
import io.realm.internal.PlatformHelper
import io.realm.internal.REPLACED_BY_IR
import io.realm.internal.RealmObjectCompanion
import io.realm.internal.RealmObjectInternal
import io.realm.internal.singleThreadDispatcher
import io.realm.interop.NativePointer
import io.realm.interop.RealmInterop
import io.realm.interop.SchemaMode
import io.realm.log.LogLevel
import io.realm.log.RealmLogger
import kotlinx.coroutines.CoroutineDispatcher
import kotlin.reflect.KClass
/**
* Configuration for log events created by a Realm instance.
*/
public data class LogConfiguration(
/**
* The [LogLevel] for which all log events of equal or higher priority will be reported.
*/
public val level: LogLevel,
/**
* Any loggers to install. They will receive all log events with a priority equal to or higher than
* the value defined in [LogConfiguration.level].
*/
public val loggers: List
)
/**
* A _Realm Configuration_ defining specific setup and configuration for a Realm instance.
*
* The RealmConfiguration can, for simple uses cases, be created directly through the constructor.
* More advanced setup requires building the RealmConfiguration through
* [RealmConfiguration.Builder.build].
*
* @see Realm
* @see RealmConfiguration.Builder
*/
@Suppress("LongParameterList")
public class RealmConfiguration private constructor(
companionMap: Map, RealmObjectCompanion>,
path: String?,
name: String,
schema: Set>,
logConfig: LogConfiguration,
maxNumberOfActiveVersions: Long,
notificationDispatcher: CoroutineDispatcher,
writeDispatcher: CoroutineDispatcher,
schemaVersion: Long,
deleteRealmIfMigrationNeeded: Boolean,
) {
// Public properties making up the RealmConfiguration
// TODO Add more elaborate KDoc for all of these
/**
* Path to the realm file.
*/
public val path: String
/**
* Filename of the realm file.
*/
public val name: String
/**
* The set of classes included in the schema for the realm.
*/
public val schema: Set>
/**
* The log configuration used for the realm instance.
*/
public val log: LogConfiguration
/**
* Maximum number of active versions.
*
* Holding references to objects from previous version of the data in the realm will also
* require keeping the data in the actual file. This can cause growth of the file. See
* [Builder.maxNumberOfActiveVersions] for details.
*/
public val maxNumberOfActiveVersions: Long
/**
* The coroutine dispatcher for internal handling of notification registration and delivery.
*/
public val notificationDispatcher: CoroutineDispatcher
/**
* The coroutine dispatcher used for all write operations.
*/
public val writeDispatcher: CoroutineDispatcher
/**
* The schema version.
*/
public val schemaVersion: Long
/**
* Flag indicating whether the realm will be deleted if the schema has changed in a way that
* requires schema migration.
*/
public val deleteRealmIfMigrationNeeded: Boolean
// Internal properties used by other Realm components, but does not make sense for the end user to know about
internal var mapOfKClassWithCompanion: Map, RealmObjectCompanion>
internal var mediator: Mediator
internal val nativeConfig: NativePointer = RealmInterop.realm_config_new()
init {
this.path = if (path == null || path.isEmpty()) {
val directory = PlatformHelper.appFilesDirectory()
// FIXME Proper platform agnostic file separator: File.separator is not available for Kotlin/Native
// https://github.com/realm/realm-kotlin/issues/75
"$directory/$name"
} else path
this.name = name // FIXME Should read name from end of path
this.schema = schema
this.mapOfKClassWithCompanion = companionMap
this.log = logConfig
this.maxNumberOfActiveVersions = maxNumberOfActiveVersions
this.notificationDispatcher = notificationDispatcher
this.writeDispatcher = writeDispatcher
this.schemaVersion = schemaVersion
this.deleteRealmIfMigrationNeeded = deleteRealmIfMigrationNeeded
RealmInterop.realm_config_set_path(nativeConfig, this.path)
when (deleteRealmIfMigrationNeeded) {
true -> SchemaMode.RLM_SCHEMA_MODE_RESET_FILE
false -> SchemaMode.RLM_SCHEMA_MODE_AUTOMATIC
}.also { schemaMode ->
RealmInterop.realm_config_set_schema_mode(nativeConfig, schemaMode)
}
RealmInterop.realm_config_set_schema_version(config = nativeConfig, version = schemaVersion)
val nativeSchema = RealmInterop.realm_schema_new(mapOfKClassWithCompanion.values.map { it.`$realm$schema`() })
RealmInterop.realm_config_set_schema(nativeConfig, nativeSchema)
RealmInterop.realm_config_set_max_number_of_active_versions(nativeConfig, maxNumberOfActiveVersions)
mediator = object : Mediator {
override fun createInstanceOf(clazz: KClass<*>): RealmObjectInternal = (
mapOfKClassWithCompanion[clazz]?.`$realm$newInstance`()
?: error("$clazz not part of this configuration schema")
) as RealmObjectInternal
override fun companionOf(clazz: KClass): RealmObjectCompanion =
mapOfKClassWithCompanion[clazz]
?: error("$clazz not part of this configuration schema")
}
}
/**
* Short-hand for creating common variants of RealmConfigurations.
*
* @param path full path to the Realm file. If set, [RealmConfiguration.name] is ignored.
* @param name name of the Realm file being created if no [RealmConfiguration.path] is configured. Realm files are
* placed in the default location for the platform. On Android this is in `getFilesDir()`
* @param schema set of classes that make up the schema for the Realm. Identified by their class literal `T::class`.
*/
// This constructor is never used at runtime, all calls to it are being rewired by the Realm Compiler Plugin to call
// the internal secondary constructor with all schema classes mapped to their RealmCompanion.
public constructor(
path: String? = null,
name: String = Realm.DEFAULT_FILE_NAME,
schema: Set>
) : this(path, name, mapOf()) // REPLACED_BY_IR
// Called by the compiler plugin, with a populated companion map.
// Default values should match what happens when calling `RealmConfiguration.Builder(schema = setOf(...)).build()`
internal constructor(
path: String? = null,
name: String = Realm.DEFAULT_FILE_NAME,
schema: Map, RealmObjectCompanion>
) : this(
schema,
path,
name,
schema.keys,
LogConfiguration(
LogLevel.WARN,
listOf(PlatformHelper.createDefaultSystemLogger(Realm.DEFAULT_LOG_TAG))
),
Long.MAX_VALUE,
singleThreadDispatcher(name),
singleThreadDispatcher(name),
0,
false
)
/**
* Used to create a [RealmConfiguration]. For common use cases, a [RealmConfiguration] can be created directly
* using the [RealmConfiguration] constructor.
*/
class Builder(
var path: String? = null, // Full path for Realm (directory + name)
var name: String = Realm.DEFAULT_FILE_NAME, // Optional Realm name (default is 'default')
var schema: Set> = setOf()
) {
private var logLevel: LogLevel = LogLevel.WARN
private var removeSystemLogger: Boolean = false
private var userLoggers: List = listOf()
private var maxNumberOfActiveVersions: Long = Long.MAX_VALUE
private var notificationDispatcher: CoroutineDispatcher? = null
private var writeDispatcher: CoroutineDispatcher? = null
private var deleteRealmIfMigrationNeeded: Boolean = false
private var schemaVersion: Long = 0
/**
* Sets the absolute path of the realm file.
*/
fun path(path: String) = apply { this.path = path }
/**
* Sets the filename of the realm file.
*/
fun name(name: String) = apply { this.name = name }
/**
* Sets the classes of the schema.
*
* The elements of the set must be direct class literals.
*
* @param classes The set of classes that the schema consists of.
*/
fun schema(classes: Set>) = apply { this.schema = classes }
/**
* Sets the classes of the schema.
*
* The `classes` arguments must be direct class literals.
*
* @param classes The classes that the schema consists of.
*/
fun schema(vararg classes: KClass) =
apply { this.schema = setOf(*classes) }
/**
* Sets the maximum number of live versions in the Realm file before an [IllegalStateException] is thrown when
* attempting to write more data.
*
* Realm is capable of concurrently handling many different versions of Realm objects, this can e.g. happen if
* a flow is slow to process data from the database while a fast writer is putting data into the Realm.
*
* Under normal circumstances this is not a problem, but if the number of active versions grow too large, it
* will have a negative effect on the file size on disk. Setting this parameters can therefore be used to
* prevent uses of Realm that can result in very large file sizes.
*
* @param number the maximum number of active versions before an exception is thrown.
* @see [FAQ](https://realm.io/docs/java/latest/.faq-large-realm-file-size)
*/
fun maxNumberOfActiveVersions(maxVersions: Long = 8) = apply {
if (maxVersions < 1) {
throw IllegalArgumentException("Only positive numbers above 0 are allowed. Yours was: $maxVersions")
}
this.maxNumberOfActiveVersions = maxVersions
}
/**
* Configure how Realm will report log events.
*
* @param level all events at this level or higher will be reported.
* @param customLoggers any custom loggers to send log events to. A default system logger is
* installed by default that will redirect to the common logging framework on the platform, i.e.
* LogCat on Android and NSLog on iOS.
*/
fun log(level: LogLevel = LogLevel.WARN, customLoggers: List = emptyList()) =
apply {
this.logLevel = level
this.userLoggers = customLoggers
}
/**
* Dispatcher used to run background writes to the Realm.
*
* Defaults to a single threaded dispatcher started when the configuration is built.
*
* NOTE On Android the dispatcher's thread must have an initialized
* [Looper](https://developer.android.com/reference/android/os/Looper#prepare()).
*
* @param dispatcher Dispatcher on which writes are run. It is required to be backed by a
* single thread only.
*/
public fun notificationDispatcher(dispatcher: CoroutineDispatcher) = apply {
this.notificationDispatcher = dispatcher
}
/**
* Dispatcher on which Realm notifications are run. It is possible to listen for changes to
* Realm objects from any thread, but the underlying logic will run on this dispatcher
* before any changes are returned to the caller thread.
*
* Defaults to a single threaded dispatcher started when the configuration is built.
*
* NOTE On Android the dispatcher's thread must have an initialized
* [Looper](https://developer.android.com/reference/android/os/Looper#prepare()).
*
* @param dispatcher Dispatcher on which notifications are run. It is required to be backed
* by a single thread only.
*/
public fun writeDispatcher(dispatcher: CoroutineDispatcher) = apply {
this.writeDispatcher = dispatcher
}
/**
* Setting this will change the behavior of how migration exceptions are handled. Instead of throwing an
* exception the on-disc Realm will be cleared and recreated with the new Realm schema.
*
* **WARNING!** This will result in loss of data.
*/
fun deleteRealmIfMigrationNeeded() = apply { this.deleteRealmIfMigrationNeeded = true }
/**
* Sets the schema version of the Realm. This must be equal to or higher than the schema version of the existing
* Realm file, if any. If the schema version is higher than the already existing Realm, a migration is needed.
*/
fun schemaVersion(schemaVersion: Long) =
apply { this.schemaVersion = validateSchemaVersion(schemaVersion) }
/**
* TODO Evaluate if this should be part of the public API. For now keep it internal.
*
* Removes the default system logger from being installed. If no custom loggers have
* been configured, no log events will be reported, regardless of the configured
* log level.
*
* @see [RealmConfiguration.Builder.log]
*/
internal fun removeSystemLogger() = apply { this.removeSystemLogger = true }
/**
* Creates the RealmConfiguration based on the builder properties.
*
* @return the created RealmConfiguration.
*/
fun build(): RealmConfiguration {
REPLACED_BY_IR()
}
// Called from the compiler plugin
internal fun build(companionMap: Map, RealmObjectCompanion>): RealmConfiguration {
val allLoggers = mutableListOf()
if (!removeSystemLogger) {
allLoggers.add(PlatformHelper.createDefaultSystemLogger(Realm.DEFAULT_LOG_TAG))
}
allLoggers.addAll(userLoggers)
return RealmConfiguration(
companionMap,
path,
name,
schema,
LogConfiguration(logLevel, allLoggers),
maxNumberOfActiveVersions,
notificationDispatcher ?: singleThreadDispatcher(name),
writeDispatcher ?: singleThreadDispatcher(name),
schemaVersion,
deleteRealmIfMigrationNeeded
)
}
private fun validateSchemaVersion(schemaVersion: Long): Long {
if (schemaVersion < 0) {
throw IllegalArgumentException("Realm schema version numbers must be 0 (zero) or higher. Yours was: $schemaVersion")
}
return schemaVersion
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy