diff --git a/CHANGELOG.md b/CHANGELOG.md index 6dafb035a..ae5e48ffe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +0.6.3 (2020-05-07) +------------------ +* Update FlatBuffers to 1.12.0 +* Provide error hinting when DB can't be created (e.g. when an app docs directory isn't passed properly on Flutter). + 0.6.2 (2020-03-09) ------------------ * Support large object arrays on 32-bit platforms/emulators. diff --git a/README.md b/README.md index 396fc06c7..fe0aefee0 100644 --- a/README.md +++ b/README.md @@ -16,11 +16,11 @@ Installation Add the following dependencies to your `pubspec.yaml`: ```yaml dependencies: - objectbox: ^0.6.2 + objectbox: ^0.6.3 dev_dependencies: build_runner: ^1.0.0 - objectbox_generator: ^0.6.2 + objectbox_generator: ^0.6.3 ``` Proceed based on whether you're developing a Flutter app or a standalone dart program: diff --git a/example/README.md b/example/README.md index 492c51821..c69c02239 100644 --- a/example/README.md +++ b/example/README.md @@ -37,18 +37,45 @@ Finally, you will create a `Box` which gives you a typed interface for sto import 'objectbox.g.dart'; // this file will be generated by ObjectBox after running `pub run build_runner build` void main() { - var store = Store(getObjectBoxModel()); // Note: getObjectBoxModel() is generated for you in objectbox.g.dart - var box = Box(store); - - var note = Note(text: "Hello"); - note.id = box.put(note); - print("new note got id ${note.id}"); - print("refetched note: ${box.get(note.id)}"); - - store.close(); + var store = Store(getObjectBoxModel()); // Note: getObjectBoxModel() is generated for you in objectbox.g.dart + var box = Box(store); + + var note = Note(text: "Hello"); + note.id = box.put(note); + print("new note got id ${note.id}"); + print("refetched note: ${box.get(note.id)}"); + + store.close(); } ``` -See also +Flutter -------- -* sample [Flutter android app](flutter/objectbox_demo) - requires Flutter 1.12 +* See a [Flutter example app](flutter/objectbox_demo) - requires Flutter 1.12 + +As opposed to a plain Dart app which runs directly on your PC, there are more restrictions where your Flutter app can +write data. Therefore, you should give ObjectBox a full path to a per-app documents directory, where to store the data +even when a user closes your app. + +If you didn't specify this path to ObjectBox, it would try to use a default "objectbox" directory where the app is +currently running, but it doesn't have permissions to write there: `failed to create store: 10199 Dir does not exist: objectbox (30)`. + +To configure ObjectBox properly, you can use `getApplicationDocumentsDirectory()` from the `path_provider` package. +See [Flutter: read & write files](https://flutter.dev/docs/cookbook/persistence/reading-writing-files) for more info. +Have a look how it's done in the Flutter example app: +```dart +import 'package:path_provider/path_provider.dart'; + +class _MyHomePageState extends State { + Store _store; + + @override + void initState() { + super.initState(); + + getApplicationDocumentsDirectory().then((dir) { + _store = Store(getObjectBoxModel(), directory: dir.path + "/objectbox"); + }); + } +} +``` \ No newline at end of file diff --git a/example/flutter/objectbox_demo/pubspec.yaml b/example/flutter/objectbox_demo/pubspec.yaml index b401b20d6..8ed210a61 100644 --- a/example/flutter/objectbox_demo/pubspec.yaml +++ b/example/flutter/objectbox_demo/pubspec.yaml @@ -11,13 +11,13 @@ dependencies: cupertino_icons: ^0.1.2 path_provider: any intl: any - objectbox: ^0.6.2 + objectbox: ^0.6.3 dev_dependencies: flutter_test: sdk: flutter build_runner: ^1.0.0 - objectbox_generator: ^0.6.2 + objectbox_generator: ^0.6.3 flutter: uses-material-design: true diff --git a/example/flutter/objectbox_demo_desktop/pubspec.yaml b/example/flutter/objectbox_demo_desktop/pubspec.yaml index 1d44abd33..540e6a3f0 100644 --- a/example/flutter/objectbox_demo_desktop/pubspec.yaml +++ b/example/flutter/objectbox_demo_desktop/pubspec.yaml @@ -11,13 +11,13 @@ dependencies: flutter: sdk: flutter cupertino_icons: ^0.1.0 - objectbox: ^0.6.2 + objectbox: ^0.6.3 dev_dependencies: flutter_test: sdk: flutter build_runner: ^1.0.0 - objectbox_generator: ^0.6.2 + objectbox_generator: ^0.6.3 flutter: uses-material-design: true diff --git a/generator/pubspec.yaml b/generator/pubspec.yaml index 0f4439534..241d966c2 100644 --- a/generator/pubspec.yaml +++ b/generator/pubspec.yaml @@ -1,5 +1,5 @@ name: objectbox_generator -version: 0.6.2 +version: 0.6.3 repository: https://github.com/objectbox/objectbox-dart homepage: https://objectbox.io description: ObjectBox binding code generator - finds annotated entities and adds them to the ObjectBox DB model. @@ -8,7 +8,7 @@ environment: sdk: ">=2.5.0 <3.0.0" dependencies: - objectbox: 0.6.2 + objectbox: 0.6.3 build: ^1.0.0 source_gen: ^0.9.0 analyzer: ">=0.35.0 <0.100.0" diff --git a/lib/src/bindings/helpers.dart b/lib/src/bindings/helpers.dart index 1384d5313..e89082c3f 100644 --- a/lib/src/bindings/helpers.dart +++ b/lib/src/bindings/helpers.dart @@ -5,27 +5,26 @@ import "bindings.dart"; import "constants.dart"; import "../common.dart"; -checkObx(errorCode) { - if (errorCode != OBXError.OBX_SUCCESS) throw ObjectBoxException(lastObxErrorString(errorCode)); +checkObx(int code) { + if (code != OBXError.OBX_SUCCESS) throw latestNativeError(codeIfMissing: code); } -Pointer checkObxPtr(Pointer ptr, String msg) { +Pointer checkObxPtr(Pointer ptr, String dartMsg) { if (ptr == null || ptr.address == 0) { - final info = lastObxErrorString(); - throw ObjectBoxException(info.isEmpty ? msg : "$msg: $info"); + throw latestNativeError(dartMsg: dartMsg); } return ptr; } -String lastObxErrorString([int err = 0]) { +ObjectBoxException latestNativeError({String dartMsg, int codeIfMissing}) { int code = bindings.obx_last_error_code(); String text = cString(bindings.obx_last_error_message()); if (code == 0 && text.isEmpty) { - return (err != 0) ? "code $err" : "unknown native error"; + return ObjectBoxException(dartMsg: dartMsg, nativeCode: codeIfMissing, nativeMsg: 'unknown native error'); } - return code == 0 ? text : "$code $text"; + return ObjectBoxException(dartMsg: dartMsg, nativeCode: code, nativeMsg: text); } String cString(Pointer charPtr) { diff --git a/lib/src/bindings/structs.dart b/lib/src/bindings/structs.dart index a4bdd75e6..d893cdd55 100644 --- a/lib/src/bindings/structs.dart +++ b/lib/src/bindings/structs.dart @@ -57,8 +57,9 @@ class OBX_bytes extends Struct { int length; /// Get access to the data (no-copy) - Uint8List get data => - isEmpty ? throw ObjectBoxException("can't access data of empty OBX_bytes") : _dataPtr.asTypedList(length); + Uint8List get data => isEmpty + ? throw ObjectBoxException(dartMsg: "can't access data of empty OBX_bytes") + : _dataPtr.asTypedList(length); bool get isEmpty => length == 0 || _dataPtr.address == 0; diff --git a/lib/src/box.dart b/lib/src/box.dart index d0d6316be..e8aca0c10 100644 --- a/lib/src/box.dart +++ b/lib/src/box.dart @@ -59,7 +59,7 @@ class Box { int id = propVals[_modelEntity.idProperty.name]; if (id == null || id == 0) { id = bindings.obx_box_id_for_put(_cBox, 0); - if (id == 0) throw ObjectBoxException(lastObxErrorString()); + if (id == 0) throw latestNativeError(); propVals[_modelEntity.idProperty.name] = id; } diff --git a/lib/src/common.dart b/lib/src/common.dart index fdc1bc248..5c8dd93f8 100644 --- a/lib/src/common.dart +++ b/lib/src/common.dart @@ -28,12 +28,20 @@ Version versionLib() { } class ObjectBoxException implements Exception { - final String message; - final String msg; - - ObjectBoxException(msg) - : message = "ObjectBoxException: " + msg, - msg = msg; - - String toString() => message; + final String dartMsg; + final int nativeCode; + final String nativeMsg; + + ObjectBoxException({String dartMsg, int nativeCode, String nativeMsg}) + : dartMsg = dartMsg, + nativeCode = nativeCode, + nativeMsg = nativeMsg; + + @override + String toString() { + var result = 'ObjectBoxException: '; + if (dartMsg != null) result += dartMsg + ': '; + if (nativeCode != 0) result += '$nativeCode '; + return result + nativeMsg; + } } diff --git a/lib/src/model.dart b/lib/src/model.dart index 039e6d230..31f7d5983 100644 --- a/lib/src/model.dart +++ b/lib/src/model.dart @@ -33,7 +33,7 @@ class Model { int code = bindings.obx_model_error_code(_cModel); String text = cString(bindings.obx_model_error_message(_cModel)); - throw ObjectBoxException("$code $text"); + throw ObjectBoxException(nativeCode: code, nativeMsg: text); } void addEntity(ModelEntity entity) { diff --git a/lib/src/query/builder.dart b/lib/src/query/builder.dart index 0839c44bf..4cad2532e 100644 --- a/lib/src/query/builder.dart +++ b/lib/src/query/builder.dart @@ -13,7 +13,7 @@ class QueryBuilder { void _throwExceptionIfNecessary() { if (bindings.obx_qb_error_code(_cBuilder) != OBXError.OBX_SUCCESS) { final msg = cString(bindings.obx_qb_error_message(_cBuilder)); - throw ObjectBoxException("$msg"); + throw ObjectBoxException(nativeMsg: msg); } } diff --git a/lib/src/store.dart b/lib/src/store.dart index 5e9229591..51bdbca18 100644 --- a/lib/src/store.dart +++ b/lib/src/store.dart @@ -4,6 +4,7 @@ import "bindings/bindings.dart"; import "bindings/helpers.dart"; import "modelinfo/index.dart"; import "model.dart"; +import "common.dart"; enum TxMode { Read, @@ -40,7 +41,23 @@ class Store { rethrow; } _cStore = bindings.obx_store_open(opt); - checkObxPtr(_cStore, "failed to create store"); + + try { + checkObxPtr(_cStore, "failed to create store"); + } on ObjectBoxException catch (e) { + // Recognize common problems when trying to open/create a database + // 10199 = OBX_ERROR_STORAGE_GENERAL + if (e.nativeCode == 10199 && e.nativeMsg != null && e.nativeMsg.contains('Dir does not exist')) { + // 13 = permissions denied, 30 = read-only filesystem + if (e.nativeMsg.endsWith(' (13)') || e.nativeMsg.endsWith(' (30)')) { + final msg = e.nativeMsg + + " - this usually indicates a problem with permissions; if you're using Flutter you may need to use " + + "getApplicationDocumentsDirectory() from the path_provider package, see example/README.md"; + throw ObjectBoxException(dartMsg: e.dartMsg, nativeCode: e.nativeCode, nativeMsg: msg); + } + } + rethrow; + } } /// Closes this store. diff --git a/pubspec.yaml b/pubspec.yaml index 921ba5f4d..e6b62130e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: objectbox -version: 0.6.2 +version: 0.6.3 repository: https://github.com/objectbox/objectbox-dart homepage: https://objectbox.io description: ObjectBox is a super-fast NoSQL ACID compliant object database. @@ -9,7 +9,7 @@ environment: dependencies: # take care updating flatbuffers - keep aligned with other bindings - flat_buffers: 1.11.0 + flat_buffers: 1.12.0 ffi: ^0.1.3 dev_dependencies: diff --git a/test/basics_test.dart b/test/basics_test.dart index 3ee292c6a..a85420195 100644 --- a/test/basics_test.dart +++ b/test/basics_test.dart @@ -11,7 +11,7 @@ void main() { // sanity check - the result is a null pointer expect(cStore, isA().having((ptr) => ptr.address, "address", equals(0))); - final error = lastObxErrorString(); - expect(error, matches('Argument .+ must not be null')); + final error = latestNativeError(); + expect(error.nativeMsg, matches('Argument .+ must not be null')); }); }