diff --git a/CHANGELOG.md b/CHANGELOG.md index 34d03811d..91a024575 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ x.x.x Release notes (yyyy-MM-dd) * Support change notifications on query results. ([208](https://github.com/realm/realm-dart/pull/208)) * Added support checking if Realm lists and Realm objects are valid. ([#183](https://github.com/realm/realm-dart/pull/183)) * Support query on lists of realm objects. ([239](https://github.com/realm/realm-dart/pull/239)) +* Added support for opening Realm in read-only mode. ([#260](https://github.com/realm/realm-dart/pull/260)) ### Compatibility * Dart ^2.15 on Windows, MacOS and Linux diff --git a/lib/src/configuration.dart b/lib/src/configuration.dart index 45a4ecd9a..f59e8df9f 100644 --- a/lib/src/configuration.dart +++ b/lib/src/configuration.dart @@ -36,11 +36,17 @@ class Configuration { RealmSchema get schema => _schema; /// Creates a [Configuration] with schema objects for opening a [Realm]. - Configuration(List schemaObjects) + /// [readOnly] controls whether a [Realm] is opened as readonly. + /// This allows opening it from locked locations such as resources, + /// bundled with an application. The realm file must already exists. + Configuration(List schemaObjects, {bool readOnly = false}) : _schema = RealmSchema(schemaObjects), _handle = realmCore.createConfig() { schemaVersion = 0; path = defaultPath; + if (readOnly) { + isReadOnly = true; + } realmCore.setSchema(this); } @@ -53,7 +59,7 @@ class Configuration { } /// The platform dependent path to the default realm file - `default.realm`. - /// + /// /// If set it should contain the name of the realm file. Ex. /mypath/myrealm.realm static String get defaultPath => _defaultPath ??= _initDefaultPath(); static set defaultPath(String value) => _defaultPath = value; @@ -83,10 +89,18 @@ class Configuration { /// If omitted the [defaultPath] for the platform will be used. String get path => realmCore.getConfigPath(this); set path(String value) => realmCore.setConfigPath(this, value); + + /// Gets or sets a value indicating whether a [Realm] is opened as readonly. + /// This allows opening it from locked locations such as resources, + /// bundled with an application. + /// + /// The realm file must already exists at [path] + bool get isReadOnly => realmCore.getConfigReadOnly(this); + set isReadOnly(bool value) => realmCore.setConfigReadOnly(this, value); } /// A collection of properties describing the underlying schema of a [RealmObject]. -/// +/// /// {@category Configuration} class SchemaObject { /// Schema object type. @@ -103,7 +117,7 @@ class SchemaObject { } /// Describes the complete set of classes which may be stored in a `Realm` -/// +/// /// {@category Configuration} class RealmSchema extends Iterable { ///@nodoc diff --git a/lib/src/native/realm_core.dart b/lib/src/native/realm_core.dart index 953d2a77f..42a4f86c9 100644 --- a/lib/src/native/realm_core.dart +++ b/lib/src/native/realm_core.dart @@ -155,6 +155,16 @@ class _RealmCore { _realmLib.realm_config_set_schema_version(config.handle._pointer, version); } + bool getConfigReadOnly(Configuration config) { + int mode = _realmLib.realm_config_get_schema_mode(config.handle._pointer); + return mode == realm_schema_mode.RLM_SCHEMA_MODE_READ_ONLY; + } + + void setConfigReadOnly(Configuration config, bool value) { + int mode = value ? realm_schema_mode.RLM_SCHEMA_MODE_READ_ONLY : realm_schema_mode.RLM_SCHEMA_MODE_AUTOMATIC; + _realmLib.realm_config_set_schema_mode(config.handle._pointer, mode); + } + ConfigHandle createConfig() { final configPtr = _realmLib.realm_config_new(); return ConfigHandle._(configPtr); diff --git a/test/realm_test.dart b/test/realm_test.dart index cc671c21a..d8fa1fb7d 100644 --- a/test/realm_test.dart +++ b/test/realm_test.dart @@ -202,6 +202,45 @@ Future main([List? args]) async { config.schemaVersion = 3; expect(config.schemaVersion, equals(3)); }); + + test('Configuration readOnly - opening non existing realm throws', () { + Configuration config = Configuration([Car.schema], readOnly: true); + expect(() => Realm(config), throws("Message: No such table exists")); + }); + + test('Configuration readOnly - open existing realm with read-only config', () { + Configuration config = Configuration([Car.schema]); + var realm = Realm(config); + realm.close(); + + // Open an existing realm as readonly. + config = Configuration([Car.schema], readOnly: true); + realm = Realm(config); + realm.close(); + }); + + test('Configuration readOnly - reading is possible', () { + Configuration config = Configuration([Car.schema]); + var realm = Realm(config); + realm.write(() => realm.add(Car("Mustang"))); + realm.close(); + + config.isReadOnly = true; + realm = Realm(config); + var cars = realm.all(); + realm.close(); + }); + + test('Configuration readOnly - writing on read-only Realms throws', () { + Configuration config = Configuration([Car.schema]); + var realm = Realm(config); + realm.close(); + + config = Configuration([Car.schema], readOnly: true); + realm = Realm(config); + expect(() => realm.write(() {}), throws("Can't perform transactions on read-only Realms.")); + realm.close(); + }); }); group('RealmClass tests:', () { @@ -898,15 +937,15 @@ Future main([List? args]) async { var config = Configuration([Dog.schema, Person.schema]); var realm = Realm(config); - realm.write(() { + realm.write(() { realm.add(Dog("Lassy")); }); var callbackCalled = false; - final subscription = realm.all().changes.listen((changes) { + final subscription = realm.all().changes.listen((changes) { callbackCalled = true; }); - + await Future.delayed(Duration(milliseconds: 10)); expect(callbackCalled, true); @@ -930,10 +969,10 @@ Future main([List? args]) async { var realm = Realm(config); var callbackCalled = false; - final subscription = realm.all().changes.listen((changes) { + final subscription = realm.all().changes.listen((changes) { callbackCalled = true; }); - + await Future.delayed(Duration(milliseconds: 10)); expect(callbackCalled, true); @@ -951,8 +990,7 @@ Future main([List? args]) async { realm.add(Dog("Lassy1")); }); await Future.delayed(Duration(milliseconds: 10)); - expect(callbackCalled,true); - + expect(callbackCalled, true); await subscription.cancel(); await Future.delayed(Duration(milliseconds: 10)); @@ -1610,7 +1648,6 @@ Future main([List? args]) async { realm.close(); }); - test('Realm adding objects graph', () { var studentMichele = Student(1) ..name = "Michele Ernesto"