diff --git a/.github/workflows/dart.yml b/.github/workflows/dart.yml index c998937aa..10748f94d 100644 --- a/.github/workflows/dart.yml +++ b/.github/workflows/dart.yml @@ -10,10 +10,10 @@ jobs: steps: - uses: actions/checkout@v1 - name: Install dependencies - working-directory: bin/objectbox_model_generator + working-directory: generator run: pub get - name: Run tests - working-directory: bin/objectbox_model_generator + working-directory: generator run: pub run test lib: diff --git a/CHANGELOG.md b/CHANGELOG.md index bb51940e7..1e2677e17 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,14 +1,28 @@ +0.4.0 (2019-10-31) +------------------ +* Flutter Android support +* Queries for all currently supported types + (thanks [Jasm Sison](https://github.com/Buggaboo) for [#27](https://github.com/objectbox/objectbox-dart/pull/27) and [#46](https://github.com/objectbox/objectbox-dart/pull/46)] +* More Box functions (count, isEmpty, contains, remove and their bulk variants) + (thanks [liquidiert](https://github.com/liquidiert) for [#42](https://github.com/objectbox/objectbox-dart/pull/42) and [#45](https://github.com/objectbox/objectbox-dart/pull/45)] +* Explicit write transactions + (thanks [liquidiert](https://github.com/liquidiert) for [#50](https://github.com/objectbox/objectbox-dart/pull/50)] +* Resolved linter issues + (thanks [Gregory Sech](https://github.com/GregorySech) for [#31](https://github.com/objectbox/objectbox-dart/pull/31)] +* Updated to objectbox-c 0.7.2 +* First release on pub.dev + 0.3.0 (2019-10-15) ------------------ * ID/UID generation and model persistence (objectbox-model.json) * CI tests using GitHub Actions * Code cleanup, refactoring and formatting - (thanks [Buggaboo](https://github.com/Buggaboo) for [#20](https://github.com/objectbox/objectbox-dart/pull/20) & [#21](https://github.com/objectbox/objectbox-dart/pull/21)] + (thanks [Jasm Sison](https://github.com/Buggaboo) for [#20](https://github.com/objectbox/objectbox-dart/pull/20) & [#21](https://github.com/objectbox/objectbox-dart/pull/21)] 0.2.0 (2019-09-11) ------------------- +------------------Buggaboo * UTF-8 support for Store and Box - (thanks to [Buggaboo](https://github.com/Buggaboo) for [#14](https://github.com/objectbox/objectbox-dart/pull/14)!) + (thanks to [Jasm Sison](https://github.com/Buggaboo) for [#14](https://github.com/objectbox/objectbox-dart/pull/14)!) * Bulk put and get functions (getMany, getAll, putMany) * Updated to objectbox-c 0.7 * Basic Store options diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..c5bf8d42b --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,30 @@ +Contributing +------------------ +This project is completely managed on GitHub using its [issue tracker](https://github.com/objectbox/objectbox-dart/issues) and [project boards](https://github.com/objectbox/objectbox-dart/projects). + +Anyone can contribute, be it by coding, improving docs or just proposing a new feature. +Look for tasks having a [**"help wanted"**](https://github.com/objectbox/objectbox-dart/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22) tag. +When picking up an existing issue, please let us know in the issue comment. +Don't hesitate to reach out for guidance or to discuss a solution proposal! + +### Code contributions +When creating a Pull Request for code changes, please check that you cover the following: +* Include tests for the changes you introduce. See the [test folder](test) for examples. +* Formatted the code using `dartfmt -l 120`. You can configure your IDE to do this automatically, + e.g. VS Code needs the project-specific settings `"editor.defaultFormatter": "Dart-Code.dart-code"` and `"dart.lineLength": 120`. + +### Project workflow +Issues on the [project board](https://github.com/objectbox/objectbox-dart/projects/3) are referred to as "cards" which move from left to right: + +* New cards start in the "To Do" column. + Within the column, cards are ordered: more important tasks should be above less important ones. +* Once somebody starts on a task, the according card is moved to "In progress". + Also, please assign yourself to the issue. +* Once a task is considered complete (e.g. PR is made), put it in the "Review" column. +* Once another person had a look and is happy, the task is finally moved to "Done" and its closed. + +### Basic technical approach +ObjectBox offers a [C API](https://github.com/objectbox/objectbox-c) which can be called by [Dart FFI](https://dart.dev/server/c-interop). +The C API is is also used by the ObjectBox language bindings for [Go](https://github.com/objectbox/objectbox-go), [Swift](https://github.com/objectbox/objectbox-swift), and [Python](https://github.com/objectbox/objectbox-python). +These language bindings currently serve as an example for this Dart implementation. +Internally, ObjectBox uses [FlatBuffers](https://google.github.io/flatbuffers/) to store objects. \ No newline at end of file diff --git a/README.md b/README.md index a47cb76ea..c24f9798a 100644 --- a/README.md +++ b/README.md @@ -2,56 +2,50 @@ ObjectBox for Dart/Flutter ========================== ObjectBox for Dart is a standalone database storing Dart objects locally, with strong ACID semantics. -Help wanted ------------ -ObjectBox for Dart is still in a prototype stage supporting only the most basic database tasks, like putting and getting objects. -However, the ObjectBox core supports many more features, e.g. queries, indexing, async operations, transaction control. -To bring all these features to Dart, we're asking the community to help out. PRs are more than welcome! -The ObjectBox team will try its best to guide you and answer questions. - -Contributing ------------------- -This project is completely managed here on GitHub using its [issue tracker](https://github.com/objectbox/objectbox-dart/issues) and [project boards](https://github.com/objectbox/objectbox-dart/projects). - -To prepare an upcoming version, we create a (Kanban like) board for it. -Once it is decided which features and fixes go into the version, the according issues are added to the board. -Issues on the board are referred to as "cards" which move from left to right: - -* New cards start in the "To Do" column. - Within the column, cards are ordered: more important tasks should be above less important ones. -* Once somebody starts on a task, the according card is moved to "In progress". - Also, please assign yourself to the issue. -* Once a task is considered complete (e.g. PR is made), put it in the "Review" column. -* Once another person had a look and is happy, the task is finally moved to "Done" - -Anyone can contribute, be it by coding, improving docs or just proposing a new feature. -Look for tasks having a **"help wanted"** tag. - -#### Feedback -Also, please let us know your feedback by opening an issue: -for example, if you experience errors or if you have ideas for how to improve the API. -Thanks! +Installation +------------ +Add the following dependencies to your `pubspec.yaml`: +```yaml +dependencies: + objectbox: ^0.4.0 + +dev_dependencies: + build_runner: ^1.0.0 + objectbox_generator: ^0.4.0 +``` -#### Code style -Please make sure that all code submitted via Pull Request is formatted using `dartfmt -l 120`. -You can configure your IDE to do this automatically, e.g. VS Code needs the project-specific settings -`"editor.defaultFormatter": "Dart-Code.dart-code"` and `"dart.lineLength": 120`. +Proceed based on whether you're developing a Flutter app or a standalone dart program: +1. **Flutter** only steps: + * Install the packages `flutter pub get` + * Add `objectbox-android` dependency to your `android/app/build.gradle` + ``` + dependencies { + implementation "io.objectbox:objectbox-android:2.4.1" + ... + ``` + * iOS coming soon +1. **Dart standalone programs**: + * Install the packages `pub get` + * Install [objectbox-c](https://github.com/objectbox/objectbox-c) system-wide: + * macOS/Linux: execute the following command (answer Y when it asks about installing to /usr/lib) + ```shell script + bash <(curl -s https://raw.githubusercontent.com/objectbox/objectbox-c/master/download.sh) 0.7.2 + ``` + * macOS: if dart later complains that it cannot find the `libobjectbox.dylib` you probably have to unsign the + `dart` binary (source: [dart issue](https://github.com/dart-lang/sdk/issues/38314#issuecomment-534102841)): + ```shell script + sudo xcode --remove-signature $(which dart) + ``` + * Windows: use "Git Bash" or similar to execute the following command + ```shell script + bash <(curl -s https://raw.githubusercontent.com/objectbox/objectbox-c/master/download.sh) 0.7.2 + ``` + Then copy the downloaded `lib/objectbox.dll` to `C:\Windows\System32\` (requires admin privileges). + +ObjectBox generates code binding code for classes you want stored based using build_runner. +After you've defined your persisted entities (see below), run `pub run build_runner build` or `flutter pub run build_runner build`. Getting started ---------------- -To try out the demo code in this repository, follow these steps: - -1. Install [objectbox-c](https://github.com/objectbox/objectbox-c) system-wide: - * macOS/Linux: `bash <(curl -s https://raw.githubusercontent.com/objectbox/objectbox-c/master/download.sh) 0.7` (answer Y when it asks about installing to /usr/lib). - * Windows: - * use "Git Bash" or similar to execute `bash <(curl -s https://raw.githubusercontent.com/objectbox/objectbox-c/master/download.sh) 0.7` - * copy the downloaded `lib/objectbox.dll` to `C:\Windows\System32\` (requires admin privileges) -2. Back in this repository, run `pub get`. -3. Execute `pub run build_runner build`. This regenerates the ObjectBox model to make it usable in Dart - (i.e. the file `test/test.g.dart`) and is necessary each time you add or change a class annotated with `@Entity(...)`. -4. Finally run `pub run test` to run the unit tests. - -Dart integration ---------------- In general, Dart class annotations are used to mark classes as ObjectBox entities and provide meta information. Note that right now, only a limited set of types is supported; this will be expanded upon in the near future. @@ -98,20 +92,70 @@ print("refetched note: ${box.get(note.id)}"); store.close(); ``` -Basic technical approach ------------------------- -ObjectBox offers a [C API](https://github.com/objectbox/objectbox-c) which can be called by [Dart FFI](https://dart.dev/server/c-interop). -The C API is is also used by the ObjectBox language bindings for [Go](https://github.com/objectbox/objectbox-go), [Swift](https://github.com/objectbox/objectbox-swift), and [Python](https://github.com/objectbox/objectbox-python). -These language bindings currently serve as an example for this Dart implementation. +### Query and QueryBuilder + +Basic querying can be done with e.g.: + +```dart +// var store ... +// var box ... + +box.putMany([Note(), Note(), Note()]); +box.put(Note.construct("Hello world!")); + +final queryNullText = box.query(Note_.text.isNull()).build(); + +assert(queryNullText.count() == 3); + +queryNullText.close(); // We have to manually close queries and query builders. +``` + +More complex queries can be constructed using `and/or` operators. +Also there is basic operator overloading support for `equal`, `greater`, `less`, `and` and `or`, +respectively `==`, `>`, `<`, `&`, `|`. + +```dart +// final box ... -Internally, ObjectBox uses [FlatBuffers](https://google.github.io/flatbuffers/) to store objects. -There are two basic ways to make the conversion: generated binding code, or implicit FlatBuffers conversion. -The latter is used at the moment (helped us to get started quickly). -A future version will exchange that with code generation. +box.query(value.greaterThan(10).or(date.IsNull())).build(); + +// equivalent to + +final overloaded = (value > 10) | date.IsNull(); +box.query(overloaded as Condition).build(); // the cast is necessary due to the type analyzer +``` + +### Ordering + +The results from a query can be ordered using the `order` method, e.g. + +```dart +final q = box.query(Entity_.number > 0) + .order(Type_.number) + .build(); + +// ... + +final qt = box.query(Entity_.text.notNull()) + .order(Entity_.text, flags: OBXOrderFlag.DESCENDING | OBXOrderFlag.CASE_SENSITIVE) + .build(); +``` + +Help wanted +----------- +ObjectBox for Dart is still in an early stage with limited feature set (compared to other languages). +To bring all these features to Dart, we're asking the community to help out. PRs are more than welcome! +The ObjectBox team will try its best to guide you and answer questions. + +### Feedback +Also, please let us know your feedback by opening an issue: +for example, if you experience errors or if you have ideas for how to improve the API. +Thanks! -Changelog +See also --------- -[CHANGELOG.md](CHANGELOG.md) +* [Changelog](CHANGELOG.md) +* [Contribution guidelines](CONTRIBUTING.md) License ------- diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 000000000..da2849a9b --- /dev/null +++ b/analysis_options.yaml @@ -0,0 +1,11 @@ +include: package:pedantic/analysis_options.yaml + +linter: + rules: + # additional rules used by pub.dev + - non_constant_identifier_names + +analyzer: + exclude: + - example/** + - generator/** # needs to be checked separately because its a separate package \ No newline at end of file diff --git a/bin/objectbox_model_generator/lib/src/code_chunks.dart b/bin/objectbox_model_generator/lib/src/code_chunks.dart deleted file mode 100644 index a927fee03..000000000 --- a/bin/objectbox_model_generator/lib/src/code_chunks.dart +++ /dev/null @@ -1,46 +0,0 @@ -import "package:objectbox/src/modelinfo/index.dart"; - -class CodeChunks { - static String modelInfoLoader() => """ - Map _allOBXModelEntities = null; - - void _loadOBXModelEntities() { - if (FileSystemEntity.typeSync("objectbox-model.json") == FileSystemEntityType.notFound) - throw Exception("objectbox-model.json not found"); - - _allOBXModelEntities = {}; - ModelInfo modelInfo = ModelInfo.fromMap(json.decode(new File("objectbox-model.json").readAsStringSync())); - modelInfo.entities.forEach((e) => _allOBXModelEntities[e.id.uid] = e); - } - - ModelEntity _getOBXModelEntity(int entityUid) { - if (_allOBXModelEntities == null) _loadOBXModelEntities(); - if (!_allOBXModelEntities.containsKey(entityUid)) - throw Exception("entity uid missing in objectbox-model.json: \$entityUid"); - return _allOBXModelEntities[entityUid]; - } - """; - - static String instanceBuildersReaders(ModelEntity readEntity) { - String name = readEntity.name; - return """ - ModelEntity _${name}_OBXModelGetter() { - return _getOBXModelEntity(${readEntity.id.uid}); - } - - $name _${name}_OBXBuilder(Map members) { - $name r = new $name(); - ${readEntity.properties.map((p) => "r.${p.name} = members[\"${p.name}\"];").join()} - return r; - } - - Map _${name}_OBXReader($name inst) { - Map r = {}; - ${readEntity.properties.map((p) => "r[\"${p.name}\"] = inst.${p.name};").join()} - return r; - } - - const ${name}_OBXDefs = EntityDefinition<${name}>(_${name}_OBXModelGetter, _${name}_OBXReader, _${name}_OBXBuilder); - """; - } -} diff --git a/bin/objectbox_model_generator/pubspec.yaml b/bin/objectbox_model_generator/pubspec.yaml deleted file mode 100644 index ba9edfb56..000000000 --- a/bin/objectbox_model_generator/pubspec.yaml +++ /dev/null @@ -1,14 +0,0 @@ -name: objectbox_model_generator -version: 0.3.0 -description: >- - A preprocessor for Dart source files containing ObjectBox entity definitions. -environment: - sdk: ">=2.5.0-dev.2.0 <3.0.0" -dependencies: - build: ">=0.12.0 <2.0.0" - source_gen: ^0.9.0 - objectbox: - path: ../.. -dev_dependencies: - build_runner: ">=0.9.0 <0.11.0" - test: ^1.0.0 diff --git a/bin/objectbox_model_generator/test/cases/single_entity/single_entity.g.dart_expected b/bin/objectbox_model_generator/test/cases/single_entity/single_entity.g.dart_expected deleted file mode 100644 index ee31d526e..000000000 --- a/bin/objectbox_model_generator/test/cases/single_entity/single_entity.g.dart_expected +++ /dev/null @@ -1,46 +0,0 @@ -// ************************************************************************** -// EntityGenerator -// ************************************************************************** - -Map _allOBXModelEntities = null; - -void _loadOBXModelEntities() { - if (FileSystemEntity.typeSync("objectbox-model.json") == - FileSystemEntityType.notFound) - throw Exception("objectbox-model.json not found"); - - _allOBXModelEntities = {}; - ModelInfo modelInfo = ModelInfo.fromMap( - json.decode(new File("objectbox-model.json").readAsStringSync())); - modelInfo.entities.forEach((e) => _allOBXModelEntities[e.id.uid] = e); -} - -ModelEntity _getOBXModelEntity(int entityUid) { - if (_allOBXModelEntities == null) _loadOBXModelEntities(); - if (!_allOBXModelEntities.containsKey(entityUid)) - throw Exception("entity uid missing in objectbox-model.json: $entityUid"); - return _allOBXModelEntities[entityUid]; -} - -ModelEntity _SingleEntity_OBXModelGetter() { - return _getOBXModelEntity(1234); -} - -SingleEntity _SingleEntity_OBXBuilder(Map members) { - SingleEntity r = new SingleEntity(); - r.id = members["id"]; - r.texta = members["texta"]; - return r; -} - -Map _SingleEntity_OBXReader(SingleEntity inst) { - Map r = {}; - r["id"] = inst.id; - r["texta"] = inst.texta; - return r; -} - -const SingleEntity_OBXDefs = EntityDefinition( - _SingleEntity_OBXModelGetter, - _SingleEntity_OBXReader, - _SingleEntity_OBXBuilder); diff --git a/doc/code-generation.md b/doc/code-generation.md new file mode 100644 index 000000000..f957ecd47 --- /dev/null +++ b/doc/code-generation.md @@ -0,0 +1,37 @@ +# ObjectBox Dart – Code generation + +To make it possible to read and write ObjectBox entity instances as easily as possible, wrapper code needs to be generated. Such code is only generated for Dart classes which have been annotated to indicate that they represent an ObjectBox entity (i.e. using [`@Entity`](/lib/src/annotations.dart#L1)). For a Dart source file called `myentity.dart`, which contains an entity definition, a file called `myentity.g.dart` is generated in the same directory by invoking the command `pub run build_runner build`. + +Unfortunately, only few documentation exists on how to generate code using Dart's `build`, `source_gen` and `build_runner`, so the approach taken here by `objectbox_generator` is documented in the following. + +## Basics + +In order to set up code generation, a new package needs to be created exclusively for this task. Here, it it called `objectbox_generator`. This package needs to contain a file called [`build.yaml`](/generator/build.yaml) as well as an entry point for the builder, [`objectbox_generator.dart`](/generator/lib/objectbox_generator.dart), and a generator specifically for one annotation class, [`generator.dart`](/generator/lib/src/generator.dart). The latter needs to contain a class which extends `GeneratorForAnnotation` and overrides `Future generateForAnnotatedElement(Element elementBare, ConstantReader annotation, BuildStep buildStep)`, which returns a string containing the generated code for a single annotation instance. + +It is then possible to traverse through the annotated class in `generateForAnnotatedElement` and e.g. determine all class member fields and their types. Additionally, such member fields can be annotated themselves, but because here, only the `@Entity` annotation is explicitly handled using a separate generator class, member annotations can be read and processed in line. + +## Merging + +After a class, e.g. `TestEntity` in [box_test.dart](/test/box_test.dart#L6), has been fully read, it needs to be compared against and merged with the existing model definition of this class from `objectbox-model.json`. This is done by the function `mergeEntity` in [`merge.dart`](/generator/lib/src/merge.dart). This function takes the parameters `modelInfo`, the existing JSON model, and `readEntity`, the model class definition currently read from a user-provided Dart source file. For some more information on the merging process, see the existing documentation on [Data Model Updates](https://docs.objectbox.io/advanced/data-model-updates); it should also be helpful to refer to the comments in `merge.dart`. + +Also note that in this step, IDs and UIDs are generated automatically for new instances. UIDs are always random, IDs are assigned in ascending order. UIDs may never be reused, e.g. after a property has been removed. This is why `ModelInfo` contains, among others, a member variable called `retiredPropertyUids`, which contains an array of all UIDs which have formerly been assigned to properties, and which are now unavailable to all entities. + +Eventually, `mergeEntity` either throws an error in case the model cannot be merged (e.g. because of ambiguities) or, after having returned normally, it has modified its `modelInfo` parameter to include the entity changes. + +## Testing + +For accomplishing actually automated testing capabilities for `objectbox_generator`, various wrapper classes are needed, as the `build` package is only designed to generate output _files_; yet, during testing, it is necessary to dump generated code to string variables, so they can be compared easily by Dart's `test` framework. + +The entry function for generator testing is the main function of [`generator_test.dart`](/generator/test/generator_test.dart). It makes sure that any existing file called `objectbox-model.json` is removed before every test, because we want a fresh start each time. + +### Helper classes + +The `build` package internally uses designated classes for reading from and writing to files or, to be more general, any kind of _assets_. In this case, we do not want to involve any kind of files as output and only very specific files as input, so it is necessary to create our own versions of the so-called `AssetReader` and `AssetWriter`. + +In [`helpers.dart`](/generator/test/helpers.dart), `_InMemoryAssetWriter` is supposed to receive a single output string and store it in memory. Eventually, the string it stores will be the output of [`EntityGenerator`](/generator/lib/src/generator.dart#L15). + +On the other hand, `_SingleFileAssetReader` shall read a single input Dart source file from the [`test/cases`](/generator/test/cases) directory. Note that currently, test cases have the rather ugly file extension `.dart_testcase`, such as [`single_entity.dart_testcase`](/generator/test/cases/single_entity/single_entity.dart_testcase). This is a workaround, because otherwise, running `pub run build_runner build` in the repository's root directory would generate `.g.dart` files from _all_ `.dart` files in the repository. An option to exclude certain directories from `build_runner` is yet to be found. + +### Executing the tests + +Eventually, the function `runBuilder` [can be executed](/generator/test/helpers.dart#L62), which is part of the `build` package. It encapsulates everything related to generating the final output. Thus, after it is finished and in case generation was successful, the `_InMemoryAssetWriter` instance contains the generated code, which can then be compared against the expected code. diff --git a/docs/modelinfo.md b/doc/modelinfo.md similarity index 83% rename from docs/modelinfo.md rename to doc/modelinfo.md index 0e131aefc..5e38f02db 100644 --- a/docs/modelinfo.md +++ b/doc/modelinfo.md @@ -14,4 +14,4 @@ Nonetheless, the concrete implementation in this repository is documented in the - [`ModelEntity`](/lib/src/modelinfo/modelentity.dart) describes an entity of a model and consists of instances of `ModelProperty` as well as an id, name and last property id - [`ModelInfo`](/lib/src/modelinfo/modelinfo.dart) logically contains an entire ObjectBox model file like [this one](/objectbox-model.json) and thus consists of an array of `ModelEntity` as well as various meta information for ObjectBox and model version information -Such model meta information is only actually needed when generating `objectbox-model.json`, i.e. when `objectbox_model_generator` is invoked. This is the case in [`generator.dart`](/bin/objectbox_model_generator/lib/src/generator.dart#L24). In [generated code](/bin/objectbox_model_generator/lib/src/code_chunks.dart#L12), the JSON file is loaded in the same way, but only the `ModelEntity` instances are kept. +Such model meta information is only actually needed when generating `objectbox-model.json`, i.e. when `objectbox_generator` is invoked. This is the case in [`generator.dart`](/generator/lib/src/generator.dart#L24). In [generated code](/generator/lib/src/code_chunks.dart#L12), the JSON file is loaded in the same way, but only the `ModelEntity` instances are kept. diff --git a/docs/code-generation.md b/docs/code-generation.md deleted file mode 100644 index 18717124f..000000000 --- a/docs/code-generation.md +++ /dev/null @@ -1,37 +0,0 @@ -# ObjectBox Dart – Code generation - -To make it possible to read and write ObjectBox entity instances as easily as possible, wrapper code needs to be generated. Such code is only generated for Dart classes which have been annotated to indicate that they represent an ObjectBox entity (i.e. using [`@Entity`](/lib/src/annotations.dart#L1)). For a Dart source file called `myentity.dart`, which contains an entity definition, a file called `myentity.g.dart` is generated in the same directory by invoking the command `pub run build_runner build`. - -Unfortunately, only few documentation exists on how to generate code using Dart's `build`, `source_gen` and `build_runner`, so the approach taken here by `objectbox_model_generator` is documented in the following. - -## Basics - -In order to set up code generation, a new package needs to be created exclusively for this task. Here, it it called `objectbox_model_generator`. This package needs to contain a file called [`build.yaml`](/bin/objectbox_model_generator/build.yaml) as well as an entry point for the builder, [`builder.dart`](/bin/objectbox_model_generator/lib/builder.dart), and a generator specifically for one annotation class, [`generator.dart`](/bin/objectbox_model_generator/lib/src/generator.dart). The latter needs to contain a class which extends `GeneratorForAnnotation` and overrides `Future generateForAnnotatedElement(Element elementBare, ConstantReader annotation, BuildStep buildStep)`, which returns a string containing the generated code for a single annotation instance. - -It is then possible to traverse through the annotated class in `generateForAnnotatedElement` and e.g. determine all class member fields and their types. Additionally, such member fields can be annotated themselves, but because here, only the `@Entity` annotation is explicitly handled using a separate generator class, member annotations can be read and processed in line. - -## Merging - -After a class, e.g. `TestEntity` in [box_test.dart](/test/box_test.dart#L6), has been fully read, it needs to be compared against and merged with the existing model definition of this class from `objectbox-model.json`. This is done by the function `mergeEntity` in [`merge.dart`](/bin/objectbox_model_generator/lib/src/merge.dart). This function takes the parameters `modelInfo`, the existing JSON model, and `readEntity`, the model class definition currently read from a user-provided Dart source file. For some more information on the merging process, see the existing documentation on [Data Model Updates](https://docs.objectbox.io/advanced/data-model-updates); it should also be helpful to refer to the comments in `merge.dart`. - -Also note that in this step, IDs and UIDs are generated automatically for new instances. UIDs are always random, IDs are assigned in ascending order. UIDs may never be reused, e.g. after a property has been removed. This is why `ModelInfo` contains, among others, a member variable called `retiredPropertyUids`, which contains an array of all UIDs which have formerly been assigned to properties, and which are now unavailable to all entities. - -Eventually, `mergeEntity` either throws an error in case the model cannot be merged (e.g. because of ambiguities) or, after having returned normally, it has modified its `modelInfo` parameter to include the entity changes. - -## Testing - -For accomplishing actually automated testing capabilities for `objectbox_model_generator`, various wrapper classes are needed, as the `build` package is only designed to generate output _files_; yet, during testing, it is necessary to dump generated code to string variables, so they can be compared easily by Dart's `test` framework. - -The entry function for generator testing is the main function of [`generator_test.dart`](/bin/objectbox_model_generator/test/generator_test.dart). It makes sure that any existing file called `objectbox-model.json` is removed before every test, because we want a fresh start each time. - -### Helper classes - -The `build` package internally uses designated classes for reading from and writing to files or, to be more general, any kind of _assets_. In this case, we do not want to involve any kind of files as output and only very specific files as input, so it is necessary to create our own versions of the so-called `AssetReader` and `AssetWriter`. - -In [`helpers.dart`](/bin/objectbox_model_generator/test/helpers.dart), `_InMemoryAssetWriter` is supposed to receive a single output string and store it in memory. Eventually, the string it stores will be the output of [`EntityGenerator`](/bin/objectbox_model_generator/lib/src/generator.dart#L15). - -On the other hand, `_SingleFileAssetReader` shall read a single input Dart source file from the [`test/cases`](/bin/objectbox_model_generator/test/cases) directory. Note that currently, test cases have the rather ugly file extension `.dart_testcase`, such as [`single_entity.dart_testcase`](/bin/objectbox_model_generator/test/cases/single_entity/single_entity.dart_testcase). This is a workaround, because otherwise, running `pub run build_runner build` in the repository's root directory would generate `.g.dart` files from _all_ `.dart` files in the repository. An option to exclude certain directories from `build_runner` is yet to be found. - -### Executing the tests - -Eventually, the function `runBuilder` [can be executed](/bin/objectbox_model_generator/test/helpers.dart#L62), which is part of the `build` package. It encapsulates everything related to generating the final output. Thus, after it is finished and in case generation was successful, the `_InMemoryAssetWriter` instance contains the generated code, which can then be compared against the expected code. diff --git a/example/README.md b/example/README.md new file mode 100644 index 000000000..64c59d3f4 --- /dev/null +++ b/example/README.md @@ -0,0 +1,4 @@ +ObjectBox Examples +========================== + +* [Flutter android app](flutter/objectbox_demo) - requires Flutter 1.9 diff --git a/example/flutter/objectbox_demo/.gitignore b/example/flutter/objectbox_demo/.gitignore new file mode 100644 index 000000000..ac228b8c1 --- /dev/null +++ b/example/flutter/objectbox_demo/.gitignore @@ -0,0 +1,38 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +.dart_tool/ +.flutter-plugins +.packages +.pub-cache/ +.pub/ +/build/ + +# Web related +lib/generated_plugin_registrant.dart + +# Exceptions to above rules. +!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages + +objectbox diff --git a/example/flutter/objectbox_demo/.metadata b/example/flutter/objectbox_demo/.metadata new file mode 100644 index 000000000..21ebdba99 --- /dev/null +++ b/example/flutter/objectbox_demo/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: a4d5266b769e92286a48f6b6714e538f2c4578dc + channel: master + +project_type: app diff --git a/example/flutter/objectbox_demo/README.md b/example/flutter/objectbox_demo/README.md new file mode 100644 index 000000000..d5141ddb3 --- /dev/null +++ b/example/flutter/objectbox_demo/README.md @@ -0,0 +1,5 @@ +# objectbox_demo + +## Getting Started + +This project contains the Flutter version of the main example from the [objectbox-examples](https://github.com/objectbox/objectbox-examples) repository. diff --git a/example/flutter/objectbox_demo/android/.gitignore b/example/flutter/objectbox_demo/android/.gitignore new file mode 100644 index 000000000..bc2100d8f --- /dev/null +++ b/example/flutter/objectbox_demo/android/.gitignore @@ -0,0 +1,7 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java diff --git a/example/flutter/objectbox_demo/android/app/build.gradle b/example/flutter/objectbox_demo/android/app/build.gradle new file mode 100644 index 000000000..096e35bb0 --- /dev/null +++ b/example/flutter/objectbox_demo/android/app/build.gradle @@ -0,0 +1,67 @@ +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } +} + +def flutterRoot = localProperties.getProperty('flutter.sdk') +if (flutterRoot == null) { + throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") +} + +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + flutterVersionCode = '1' +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + flutterVersionName = '1.0' +} + +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" + +android { + compileSdkVersion 28 + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + lintOptions { + disable 'InvalidPackage' + } + + defaultConfig { + applicationId "io.objectbox.flutterexample" + minSdkVersion 16 + targetSdkVersion 28 + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig signingConfigs.debug + } + } +} + +flutter { + source '../..' +} + +dependencies { + implementation "io.objectbox:objectbox-android:2.4.1" + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + testImplementation 'junit:junit:4.12' + androidTestImplementation 'androidx.test:runner:1.1.1' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' +} diff --git a/example/flutter/objectbox_demo/android/app/src/debug/AndroidManifest.xml b/example/flutter/objectbox_demo/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 000000000..876ad1518 --- /dev/null +++ b/example/flutter/objectbox_demo/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/example/flutter/objectbox_demo/android/app/src/main/AndroidManifest.xml b/example/flutter/objectbox_demo/android/app/src/main/AndroidManifest.xml new file mode 100644 index 000000000..24e04306e --- /dev/null +++ b/example/flutter/objectbox_demo/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + diff --git a/example/flutter/objectbox_demo/android/app/src/main/kotlin/com/example/objectbox_demo/MainActivity.kt b/example/flutter/objectbox_demo/android/app/src/main/kotlin/com/example/objectbox_demo/MainActivity.kt new file mode 100644 index 000000000..c2819210c --- /dev/null +++ b/example/flutter/objectbox_demo/android/app/src/main/kotlin/com/example/objectbox_demo/MainActivity.kt @@ -0,0 +1,12 @@ +package io.objectbox.flutterexample + +import android.os.Bundle +import io.flutter.app.FlutterActivity +import io.flutter.plugins.GeneratedPluginRegistrant + +class MainActivity: FlutterActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + GeneratedPluginRegistrant.registerWith(this) + } +} diff --git a/example/flutter/objectbox_demo/android/app/src/main/res/drawable/launch_background.xml b/example/flutter/objectbox_demo/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 000000000..304732f88 --- /dev/null +++ b/example/flutter/objectbox_demo/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/example/flutter/objectbox_demo/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/example/flutter/objectbox_demo/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..db77bb4b7 Binary files /dev/null and b/example/flutter/objectbox_demo/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/example/flutter/objectbox_demo/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/example/flutter/objectbox_demo/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..17987b79b Binary files /dev/null and b/example/flutter/objectbox_demo/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/example/flutter/objectbox_demo/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/example/flutter/objectbox_demo/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..09d439148 Binary files /dev/null and b/example/flutter/objectbox_demo/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/example/flutter/objectbox_demo/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/example/flutter/objectbox_demo/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..d5f1c8d34 Binary files /dev/null and b/example/flutter/objectbox_demo/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/example/flutter/objectbox_demo/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/example/flutter/objectbox_demo/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..4d6372eeb Binary files /dev/null and b/example/flutter/objectbox_demo/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/example/flutter/objectbox_demo/android/app/src/main/res/values/styles.xml b/example/flutter/objectbox_demo/android/app/src/main/res/values/styles.xml new file mode 100644 index 000000000..00fa4417c --- /dev/null +++ b/example/flutter/objectbox_demo/android/app/src/main/res/values/styles.xml @@ -0,0 +1,8 @@ + + + + diff --git a/example/flutter/objectbox_demo/android/app/src/profile/AndroidManifest.xml b/example/flutter/objectbox_demo/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 000000000..876ad1518 --- /dev/null +++ b/example/flutter/objectbox_demo/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/example/flutter/objectbox_demo/android/build.gradle b/example/flutter/objectbox_demo/android/build.gradle new file mode 100644 index 000000000..3100ad2d5 --- /dev/null +++ b/example/flutter/objectbox_demo/android/build.gradle @@ -0,0 +1,31 @@ +buildscript { + ext.kotlin_version = '1.3.50' + repositories { + google() + jcenter() + } + + dependencies { + classpath 'com.android.tools.build:gradle:3.5.0' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +allprojects { + repositories { + google() + jcenter() + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(':app') +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/example/flutter/objectbox_demo/android/gradle.properties b/example/flutter/objectbox_demo/android/gradle.properties new file mode 100644 index 000000000..38c8d4544 --- /dev/null +++ b/example/flutter/objectbox_demo/android/gradle.properties @@ -0,0 +1,4 @@ +org.gradle.jvmargs=-Xmx1536M +android.enableR8=true +android.useAndroidX=true +android.enableJetifier=true diff --git a/example/flutter/objectbox_demo/android/gradle/wrapper/gradle-wrapper.properties b/example/flutter/objectbox_demo/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..296b146b7 --- /dev/null +++ b/example/flutter/objectbox_demo/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Fri Jun 23 08:50:38 CEST 2017 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip diff --git a/example/flutter/objectbox_demo/android/settings.gradle b/example/flutter/objectbox_demo/android/settings.gradle new file mode 100644 index 000000000..5a2f14fb1 --- /dev/null +++ b/example/flutter/objectbox_demo/android/settings.gradle @@ -0,0 +1,15 @@ +include ':app' + +def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() + +def plugins = new Properties() +def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') +if (pluginsFile.exists()) { + pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } +} + +plugins.each { name, path -> + def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() + include ":$name" + project(":$name").projectDir = pluginDirectory +} diff --git a/example/flutter/objectbox_demo/ios/.gitignore b/example/flutter/objectbox_demo/ios/.gitignore new file mode 100644 index 000000000..e96ef602b --- /dev/null +++ b/example/flutter/objectbox_demo/ios/.gitignore @@ -0,0 +1,32 @@ +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/example/flutter/objectbox_demo/ios/Flutter/AppFrameworkInfo.plist b/example/flutter/objectbox_demo/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 000000000..6b4c0f78a --- /dev/null +++ b/example/flutter/objectbox_demo/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 8.0 + + diff --git a/example/flutter/objectbox_demo/ios/Flutter/Debug.xcconfig b/example/flutter/objectbox_demo/ios/Flutter/Debug.xcconfig new file mode 100644 index 000000000..592ceee85 --- /dev/null +++ b/example/flutter/objectbox_demo/ios/Flutter/Debug.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/example/flutter/objectbox_demo/ios/Flutter/Release.xcconfig b/example/flutter/objectbox_demo/ios/Flutter/Release.xcconfig new file mode 100644 index 000000000..592ceee85 --- /dev/null +++ b/example/flutter/objectbox_demo/ios/Flutter/Release.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/example/flutter/objectbox_demo/ios/Runner.xcodeproj/project.pbxproj b/example/flutter/objectbox_demo/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 000000000..cd42f519a --- /dev/null +++ b/example/flutter/objectbox_demo/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,518 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; }; + 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; }; + 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */, + 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */, + 3B80C3941E831B6300D905FE /* App.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B80C3931E831B6300D905FE /* App.framework */, + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEBA1CF902C7004384FC /* Flutter.framework */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 97C146F11CF9000F007C117D /* Supporting Files */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; + 97C146F11CF9000F007C117D /* Supporting Files */ = { + isa = PBXGroup; + children = ( + ); + name = "Supporting Files"; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1020; + ORGANIZATIONNAME = "The Chromium Authors"; + TargetAttributes = { + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin"; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + PRODUCT_BUNDLE_IDENTIFIER = io.objectbox.flutterexample; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + PRODUCT_BUNDLE_IDENTIFIER = io.objectbox.flutterexample; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + PRODUCT_BUNDLE_IDENTIFIER = io.objectbox.flutterexample; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/examples/flutter/objectbox_demo_desktop/macos/Runner.xcworkspace/contents.xcworkspacedata b/example/flutter/objectbox_demo/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata similarity index 100% rename from examples/flutter/objectbox_demo_desktop/macos/Runner.xcworkspace/contents.xcworkspacedata rename to example/flutter/objectbox_demo/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata diff --git a/example/flutter/objectbox_demo/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/flutter/objectbox_demo/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 000000000..a28140cfd --- /dev/null +++ b/example/flutter/objectbox_demo/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/example/flutter/objectbox_demo/ios/Runner.xcworkspace/contents.xcworkspacedata b/example/flutter/objectbox_demo/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..1d526a16e --- /dev/null +++ b/example/flutter/objectbox_demo/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/example/flutter/objectbox_demo/ios/Runner/AppDelegate.swift b/example/flutter/objectbox_demo/ios/Runner/AppDelegate.swift new file mode 100644 index 000000000..70693e4a8 --- /dev/null +++ b/example/flutter/objectbox_demo/ios/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import UIKit +import Flutter + +@UIApplicationMain +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/example/flutter/objectbox_demo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/example/flutter/objectbox_demo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 000000000..d36b1fab2 --- /dev/null +++ b/example/flutter/objectbox_demo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/example/flutter/objectbox_demo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/example/flutter/objectbox_demo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 000000000..dc9ada472 Binary files /dev/null and b/example/flutter/objectbox_demo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/example/flutter/objectbox_demo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/example/flutter/objectbox_demo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 000000000..28c6bf030 Binary files /dev/null and b/example/flutter/objectbox_demo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/example/flutter/objectbox_demo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/example/flutter/objectbox_demo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 000000000..2ccbfd967 Binary files /dev/null and b/example/flutter/objectbox_demo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/example/flutter/objectbox_demo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/example/flutter/objectbox_demo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 000000000..f091b6b0b Binary files /dev/null and b/example/flutter/objectbox_demo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/example/flutter/objectbox_demo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/example/flutter/objectbox_demo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 000000000..4cde12118 Binary files /dev/null and b/example/flutter/objectbox_demo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/example/flutter/objectbox_demo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/example/flutter/objectbox_demo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 000000000..d0ef06e7e Binary files /dev/null and b/example/flutter/objectbox_demo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/example/flutter/objectbox_demo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/example/flutter/objectbox_demo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 000000000..dcdc2306c Binary files /dev/null and b/example/flutter/objectbox_demo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/example/flutter/objectbox_demo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/example/flutter/objectbox_demo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 000000000..2ccbfd967 Binary files /dev/null and b/example/flutter/objectbox_demo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/example/flutter/objectbox_demo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/example/flutter/objectbox_demo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 000000000..c8f9ed8f5 Binary files /dev/null and b/example/flutter/objectbox_demo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/example/flutter/objectbox_demo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/example/flutter/objectbox_demo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 000000000..a6d6b8609 Binary files /dev/null and b/example/flutter/objectbox_demo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/example/flutter/objectbox_demo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/example/flutter/objectbox_demo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 000000000..a6d6b8609 Binary files /dev/null and b/example/flutter/objectbox_demo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/example/flutter/objectbox_demo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/example/flutter/objectbox_demo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 000000000..75b2d164a Binary files /dev/null and b/example/flutter/objectbox_demo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/example/flutter/objectbox_demo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/example/flutter/objectbox_demo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 000000000..c4df70d39 Binary files /dev/null and b/example/flutter/objectbox_demo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/example/flutter/objectbox_demo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/example/flutter/objectbox_demo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 000000000..6a84f41e1 Binary files /dev/null and b/example/flutter/objectbox_demo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/example/flutter/objectbox_demo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/example/flutter/objectbox_demo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 000000000..d0e1f5853 Binary files /dev/null and b/example/flutter/objectbox_demo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/example/flutter/objectbox_demo/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/example/flutter/objectbox_demo/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 000000000..0bedcf2fd --- /dev/null +++ b/example/flutter/objectbox_demo/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/example/flutter/objectbox_demo/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/example/flutter/objectbox_demo/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 000000000..9da19eaca Binary files /dev/null and b/example/flutter/objectbox_demo/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/example/flutter/objectbox_demo/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/example/flutter/objectbox_demo/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 000000000..9da19eaca Binary files /dev/null and b/example/flutter/objectbox_demo/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/example/flutter/objectbox_demo/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/example/flutter/objectbox_demo/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 000000000..9da19eaca Binary files /dev/null and b/example/flutter/objectbox_demo/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/example/flutter/objectbox_demo/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/example/flutter/objectbox_demo/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 000000000..89c2725b7 --- /dev/null +++ b/example/flutter/objectbox_demo/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/example/flutter/objectbox_demo/ios/Runner/Base.lproj/LaunchScreen.storyboard b/example/flutter/objectbox_demo/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 000000000..f2e259c7c --- /dev/null +++ b/example/flutter/objectbox_demo/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/example/flutter/objectbox_demo/ios/Runner/Base.lproj/Main.storyboard b/example/flutter/objectbox_demo/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 000000000..f3c28516f --- /dev/null +++ b/example/flutter/objectbox_demo/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/example/flutter/objectbox_demo/ios/Runner/Info.plist b/example/flutter/objectbox_demo/ios/Runner/Info.plist new file mode 100644 index 000000000..43cf18a9a --- /dev/null +++ b/example/flutter/objectbox_demo/ios/Runner/Info.plist @@ -0,0 +1,45 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + objectbox_demo + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + + diff --git a/example/flutter/objectbox_demo/ios/Runner/Runner-Bridging-Header.h b/example/flutter/objectbox_demo/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 000000000..7335fdf90 --- /dev/null +++ b/example/flutter/objectbox_demo/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" \ No newline at end of file diff --git a/example/flutter/objectbox_demo/lib/main.dart b/example/flutter/objectbox_demo/lib/main.dart new file mode 100644 index 000000000..ffd6686da --- /dev/null +++ b/example/flutter/objectbox_demo/lib/main.dart @@ -0,0 +1,181 @@ +import 'package:flutter/material.dart'; +import 'package:objectbox/objectbox.dart'; +import 'package:flutter/services.dart' show rootBundle; +import 'package:intl/intl.dart'; +import 'package:path_provider/path_provider.dart'; +part "main.g.dart"; + +@Entity() +class Note { + @Id() + int id; + + String text; + String comment; + int date; // TODO: use DateTime class + + Note(); + Note.construct(this.text) { + date = DateTime.now().millisecondsSinceEpoch; + print("constructed date: $date"); + } +} + +void main() => runApp(OBDemoApp()); + +class OBDemoApp extends StatelessWidget { + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'OB Example', + theme: ThemeData(primarySwatch: Colors.blue), + home: OBDemoHomePage(title: 'OB Example'), + ); + } +} + +class OBDemoHomePage extends StatefulWidget { + OBDemoHomePage({Key key, this.title}) : super(key: key); + + final String title; + + @override + _OBDemoHomePageState createState() => _OBDemoHomePageState(); +} + +class _OBDemoHomePageState extends State { + final _noteInputController = TextEditingController(); + Store _store; + Box _box; + List _notes = []; + + void _addNote() { + if (_noteInputController.text.isEmpty) return; + Note newNote = Note.construct(_noteInputController.text); + newNote.id = _box.put(newNote); + setState(() => _notes.add(newNote)); + _noteInputController.text = ""; + } + + void _removeNote(int index) { + _box.remove(_notes[index].id); + setState(() => _notes.removeAt(index)); + } + + @override + void initState() { + getApplicationDocumentsDirectory().then((dir) { + _store = Store([Note_OBXDefs], directory: dir.path + "/objectbox"); + _box = Box(_store); + List notesFromDb = _box.getAll(); + setState(() => _notes = notesFromDb); + // TODO: don't show UI before this point + }); + + super.initState(); + } + + @override + void dispose() { + _noteInputController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(widget.title), + ), + body: Column( + children: [ + Padding( + padding: EdgeInsets.all(20.0), + child: Row( + children: [ + Expanded( + child: Column( + children: [ + Padding( + padding: EdgeInsets.only(right: 10.0), + child: TextField( + decoration: InputDecoration(hintText: 'Enter new note'), + controller: _noteInputController, + ), + ), + Padding( + padding: EdgeInsets.only(top: 10.0, right: 10.0), + child: Align( + alignment: Alignment.centerRight, + child: Text( + "Click a note to remove it", + style: TextStyle( + fontSize: 11.0, + color: Colors.grey, + ), + ), + ), + ), + ], + ), + ), + Column( + children: [ + RaisedButton( + onPressed: this._addNote, + child: Text("Add"), + ) + ], + ) + ], + ), + ), + Expanded( + child: ListView.builder( + shrinkWrap: true, + padding: EdgeInsets.symmetric(horizontal: 20.0), + itemCount: _notes.length, + itemBuilder: (BuildContext context, int index) { + return GestureDetector( + onTap: () => this._removeNote(index), + child: Row( + children: [ + Expanded( + child: Container( + child: Padding( + padding: EdgeInsets.symmetric(vertical: 18.0, horizontal: 10.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + _notes[index].text, + style: TextStyle( + fontSize: 15.0, + ), + ), + Padding( + padding: EdgeInsets.only(top: 5.0), + child: Text( + "Added on ${DateFormat('dd.MM.yyyy hh:mm:ss').format(DateTime.fromMillisecondsSinceEpoch(_notes[index].date))}", + style: TextStyle( + fontSize: 12.0, + ), + ), + ), + ], + ), + ), + decoration: BoxDecoration(border: Border(bottom: BorderSide(color: Colors.black12))), + ), + ), + ], + ), + ); + }, + ), + ), + ], + ), + ); + } +} diff --git a/example/flutter/objectbox_demo/objectbox-model.json b/example/flutter/objectbox_demo/objectbox-model.json new file mode 100644 index 000000000..fb99e5d8d --- /dev/null +++ b/example/flutter/objectbox_demo/objectbox-model.json @@ -0,0 +1,46 @@ +{ + "_note1": "KEEP THIS FILE! Check it into a version control system (VCS) like git.", + "_note2": "ObjectBox manages crucial IDs for your object model. See docs for details.", + "_note3": "If you have VCS merge conflicts, you must resolve them according to ObjectBox docs.", + "entities": [ + { + "id": "1:2802681814019499133", + "lastPropertyId": "4:6451339597165131221", + "name": "Note", + "properties": [ + { + "id": "1:3178873177797362769", + "name": "id", + "type": 6, + "flags": 1 + }, + { + "id": "2:4285343053028527696", + "name": "text", + "type": 9 + }, + { + "id": "3:2606273611209948020", + "name": "comment", + "type": 9 + }, + { + "id": "4:6451339597165131221", + "name": "date", + "type": 6 + } + ] + } + ], + "lastEntityId": "1:2802681814019499133", + "lastIndexId": "0:0", + "lastRelationId": "0:0", + "lastSequenceId": "0:0", + "modelVersion": 5, + "modelVersionParserMinimum": 5, + "retiredEntityUids": [], + "retiredIndexUids": [], + "retiredPropertyUids": [], + "retiredRelationUids": [], + "version": 1 +} \ No newline at end of file diff --git a/example/flutter/objectbox_demo/pubspec.yaml b/example/flutter/objectbox_demo/pubspec.yaml new file mode 100644 index 000000000..4ad068ce9 --- /dev/null +++ b/example/flutter/objectbox_demo/pubspec.yaml @@ -0,0 +1,25 @@ +name: objectbox_demo +description: An example project for the objectbox-dart binding. +version: 0.3.0+1 + +environment: + sdk: ">=2.1.0 <3.0.0" + +dependencies: + flutter: + sdk: flutter + cupertino_icons: ^0.1.2 + path_provider: any + intl: any + objectbox: + path: ../../.. + +dev_dependencies: + flutter_test: + sdk: flutter + build_runner: ^1.0.0 + objectbox_generator: + path: ../../../generator + +flutter: + uses-material-design: true diff --git a/examples/flutter/objectbox_demo_desktop/.gitignore b/example/flutter/objectbox_demo_desktop/.gitignore similarity index 100% rename from examples/flutter/objectbox_demo_desktop/.gitignore rename to example/flutter/objectbox_demo_desktop/.gitignore diff --git a/examples/flutter/objectbox_demo_desktop/README.md b/example/flutter/objectbox_demo_desktop/README.md similarity index 100% rename from examples/flutter/objectbox_demo_desktop/README.md rename to example/flutter/objectbox_demo_desktop/README.md diff --git a/examples/flutter/objectbox_demo_desktop/fonts/Roboto/LICENSE.txt b/example/flutter/objectbox_demo_desktop/fonts/Roboto/LICENSE.txt similarity index 100% rename from examples/flutter/objectbox_demo_desktop/fonts/Roboto/LICENSE.txt rename to example/flutter/objectbox_demo_desktop/fonts/Roboto/LICENSE.txt diff --git a/examples/flutter/objectbox_demo_desktop/fonts/Roboto/Roboto-Black.ttf b/example/flutter/objectbox_demo_desktop/fonts/Roboto/Roboto-Black.ttf similarity index 100% rename from examples/flutter/objectbox_demo_desktop/fonts/Roboto/Roboto-Black.ttf rename to example/flutter/objectbox_demo_desktop/fonts/Roboto/Roboto-Black.ttf diff --git a/examples/flutter/objectbox_demo_desktop/fonts/Roboto/Roboto-Bold.ttf b/example/flutter/objectbox_demo_desktop/fonts/Roboto/Roboto-Bold.ttf similarity index 100% rename from examples/flutter/objectbox_demo_desktop/fonts/Roboto/Roboto-Bold.ttf rename to example/flutter/objectbox_demo_desktop/fonts/Roboto/Roboto-Bold.ttf diff --git a/examples/flutter/objectbox_demo_desktop/fonts/Roboto/Roboto-Light.ttf b/example/flutter/objectbox_demo_desktop/fonts/Roboto/Roboto-Light.ttf similarity index 100% rename from examples/flutter/objectbox_demo_desktop/fonts/Roboto/Roboto-Light.ttf rename to example/flutter/objectbox_demo_desktop/fonts/Roboto/Roboto-Light.ttf diff --git a/examples/flutter/objectbox_demo_desktop/fonts/Roboto/Roboto-Medium.ttf b/example/flutter/objectbox_demo_desktop/fonts/Roboto/Roboto-Medium.ttf similarity index 100% rename from examples/flutter/objectbox_demo_desktop/fonts/Roboto/Roboto-Medium.ttf rename to example/flutter/objectbox_demo_desktop/fonts/Roboto/Roboto-Medium.ttf diff --git a/examples/flutter/objectbox_demo_desktop/fonts/Roboto/Roboto-Regular.ttf b/example/flutter/objectbox_demo_desktop/fonts/Roboto/Roboto-Regular.ttf similarity index 100% rename from examples/flutter/objectbox_demo_desktop/fonts/Roboto/Roboto-Regular.ttf rename to example/flutter/objectbox_demo_desktop/fonts/Roboto/Roboto-Regular.ttf diff --git a/examples/flutter/objectbox_demo_desktop/fonts/Roboto/Roboto-Thin.ttf b/example/flutter/objectbox_demo_desktop/fonts/Roboto/Roboto-Thin.ttf similarity index 100% rename from examples/flutter/objectbox_demo_desktop/fonts/Roboto/Roboto-Thin.ttf rename to example/flutter/objectbox_demo_desktop/fonts/Roboto/Roboto-Thin.ttf diff --git a/examples/flutter/objectbox_demo_desktop/lib/main.dart b/example/flutter/objectbox_demo_desktop/lib/main.dart similarity index 98% rename from examples/flutter/objectbox_demo_desktop/lib/main.dart rename to example/flutter/objectbox_demo_desktop/lib/main.dart index 3e015dcef..1c9a7705b 100644 --- a/examples/flutter/objectbox_demo_desktop/lib/main.dart +++ b/example/flutter/objectbox_demo_desktop/lib/main.dart @@ -1,6 +1,5 @@ import 'package:flutter/foundation.dart' show debugDefaultTargetPlatformOverride; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import "package:objectbox/objectbox.dart"; part "main.g.dart"; diff --git a/examples/flutter/objectbox_demo_desktop/linux/.gitignore b/example/flutter/objectbox_demo_desktop/linux/.gitignore similarity index 100% rename from examples/flutter/objectbox_demo_desktop/linux/.gitignore rename to example/flutter/objectbox_demo_desktop/linux/.gitignore diff --git a/examples/flutter/objectbox_demo_desktop/linux/Makefile b/example/flutter/objectbox_demo_desktop/linux/Makefile similarity index 100% rename from examples/flutter/objectbox_demo_desktop/linux/Makefile rename to example/flutter/objectbox_demo_desktop/linux/Makefile diff --git a/examples/flutter/objectbox_demo_desktop/linux/flutter/generated_plugin_registrant.cc b/example/flutter/objectbox_demo_desktop/linux/flutter/generated_plugin_registrant.cc similarity index 100% rename from examples/flutter/objectbox_demo_desktop/linux/flutter/generated_plugin_registrant.cc rename to example/flutter/objectbox_demo_desktop/linux/flutter/generated_plugin_registrant.cc diff --git a/examples/flutter/objectbox_demo_desktop/linux/flutter/generated_plugin_registrant.h b/example/flutter/objectbox_demo_desktop/linux/flutter/generated_plugin_registrant.h similarity index 100% rename from examples/flutter/objectbox_demo_desktop/linux/flutter/generated_plugin_registrant.h rename to example/flutter/objectbox_demo_desktop/linux/flutter/generated_plugin_registrant.h diff --git a/examples/flutter/objectbox_demo_desktop/linux/main.cc b/example/flutter/objectbox_demo_desktop/linux/main.cc similarity index 100% rename from examples/flutter/objectbox_demo_desktop/linux/main.cc rename to example/flutter/objectbox_demo_desktop/linux/main.cc diff --git a/examples/flutter/objectbox_demo_desktop/macos/.gitignore b/example/flutter/objectbox_demo_desktop/macos/.gitignore similarity index 100% rename from examples/flutter/objectbox_demo_desktop/macos/.gitignore rename to example/flutter/objectbox_demo_desktop/macos/.gitignore diff --git a/examples/flutter/objectbox_demo_desktop/macos/Flutter/Flutter-Debug.xcconfig b/example/flutter/objectbox_demo_desktop/macos/Flutter/Flutter-Debug.xcconfig similarity index 100% rename from examples/flutter/objectbox_demo_desktop/macos/Flutter/Flutter-Debug.xcconfig rename to example/flutter/objectbox_demo_desktop/macos/Flutter/Flutter-Debug.xcconfig diff --git a/examples/flutter/objectbox_demo_desktop/macos/Flutter/Flutter-Release.xcconfig b/example/flutter/objectbox_demo_desktop/macos/Flutter/Flutter-Release.xcconfig similarity index 100% rename from examples/flutter/objectbox_demo_desktop/macos/Flutter/Flutter-Release.xcconfig rename to example/flutter/objectbox_demo_desktop/macos/Flutter/Flutter-Release.xcconfig diff --git a/examples/flutter/objectbox_demo_desktop/macos/Flutter/GeneratedPluginRegistrant.swift b/example/flutter/objectbox_demo_desktop/macos/Flutter/GeneratedPluginRegistrant.swift similarity index 100% rename from examples/flutter/objectbox_demo_desktop/macos/Flutter/GeneratedPluginRegistrant.swift rename to example/flutter/objectbox_demo_desktop/macos/Flutter/GeneratedPluginRegistrant.swift diff --git a/examples/flutter/objectbox_demo_desktop/macos/Runner.xcodeproj/project.pbxproj b/example/flutter/objectbox_demo_desktop/macos/Runner.xcodeproj/project.pbxproj similarity index 100% rename from examples/flutter/objectbox_demo_desktop/macos/Runner.xcodeproj/project.pbxproj rename to example/flutter/objectbox_demo_desktop/macos/Runner.xcodeproj/project.pbxproj diff --git a/examples/flutter/objectbox_demo_desktop/macos/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/example/flutter/objectbox_demo_desktop/macos/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata similarity index 100% rename from examples/flutter/objectbox_demo_desktop/macos/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata rename to example/flutter/objectbox_demo_desktop/macos/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata diff --git a/examples/flutter/objectbox_demo_desktop/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/example/flutter/objectbox_demo_desktop/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from examples/flutter/objectbox_demo_desktop/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to example/flutter/objectbox_demo_desktop/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/examples/flutter/objectbox_demo_desktop/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/flutter/objectbox_demo_desktop/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme similarity index 100% rename from examples/flutter/objectbox_demo_desktop/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme rename to example/flutter/objectbox_demo_desktop/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme diff --git a/example/flutter/objectbox_demo_desktop/macos/Runner.xcworkspace/contents.xcworkspacedata b/example/flutter/objectbox_demo_desktop/macos/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..1d526a16e --- /dev/null +++ b/example/flutter/objectbox_demo_desktop/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/examples/flutter/objectbox_demo_desktop/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/example/flutter/objectbox_demo_desktop/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from examples/flutter/objectbox_demo_desktop/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to example/flutter/objectbox_demo_desktop/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/examples/flutter/objectbox_demo_desktop/macos/Runner/AppDelegate.swift b/example/flutter/objectbox_demo_desktop/macos/Runner/AppDelegate.swift similarity index 100% rename from examples/flutter/objectbox_demo_desktop/macos/Runner/AppDelegate.swift rename to example/flutter/objectbox_demo_desktop/macos/Runner/AppDelegate.swift diff --git a/examples/flutter/objectbox_demo_desktop/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/example/flutter/objectbox_demo_desktop/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json similarity index 100% rename from examples/flutter/objectbox_demo_desktop/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json rename to example/flutter/objectbox_demo_desktop/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json diff --git a/examples/flutter/objectbox_demo_desktop/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/example/flutter/objectbox_demo_desktop/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png similarity index 100% rename from examples/flutter/objectbox_demo_desktop/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png rename to example/flutter/objectbox_demo_desktop/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png diff --git a/examples/flutter/objectbox_demo_desktop/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/example/flutter/objectbox_demo_desktop/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png similarity index 100% rename from examples/flutter/objectbox_demo_desktop/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png rename to example/flutter/objectbox_demo_desktop/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png diff --git a/examples/flutter/objectbox_demo_desktop/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/example/flutter/objectbox_demo_desktop/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png similarity index 100% rename from examples/flutter/objectbox_demo_desktop/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png rename to example/flutter/objectbox_demo_desktop/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png diff --git a/examples/flutter/objectbox_demo_desktop/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/example/flutter/objectbox_demo_desktop/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png similarity index 100% rename from examples/flutter/objectbox_demo_desktop/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png rename to example/flutter/objectbox_demo_desktop/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png diff --git a/examples/flutter/objectbox_demo_desktop/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/example/flutter/objectbox_demo_desktop/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png similarity index 100% rename from examples/flutter/objectbox_demo_desktop/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png rename to example/flutter/objectbox_demo_desktop/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png diff --git a/examples/flutter/objectbox_demo_desktop/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/example/flutter/objectbox_demo_desktop/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png similarity index 100% rename from examples/flutter/objectbox_demo_desktop/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png rename to example/flutter/objectbox_demo_desktop/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png diff --git a/examples/flutter/objectbox_demo_desktop/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/example/flutter/objectbox_demo_desktop/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png similarity index 100% rename from examples/flutter/objectbox_demo_desktop/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png rename to example/flutter/objectbox_demo_desktop/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png diff --git a/examples/flutter/objectbox_demo_desktop/macos/Runner/Base.lproj/MainMenu.xib b/example/flutter/objectbox_demo_desktop/macos/Runner/Base.lproj/MainMenu.xib similarity index 100% rename from examples/flutter/objectbox_demo_desktop/macos/Runner/Base.lproj/MainMenu.xib rename to example/flutter/objectbox_demo_desktop/macos/Runner/Base.lproj/MainMenu.xib diff --git a/examples/flutter/objectbox_demo_desktop/macos/Runner/Configs/AppInfo.xcconfig b/example/flutter/objectbox_demo_desktop/macos/Runner/Configs/AppInfo.xcconfig similarity index 100% rename from examples/flutter/objectbox_demo_desktop/macos/Runner/Configs/AppInfo.xcconfig rename to example/flutter/objectbox_demo_desktop/macos/Runner/Configs/AppInfo.xcconfig diff --git a/examples/flutter/objectbox_demo_desktop/macos/Runner/Configs/Debug.xcconfig b/example/flutter/objectbox_demo_desktop/macos/Runner/Configs/Debug.xcconfig similarity index 100% rename from examples/flutter/objectbox_demo_desktop/macos/Runner/Configs/Debug.xcconfig rename to example/flutter/objectbox_demo_desktop/macos/Runner/Configs/Debug.xcconfig diff --git a/examples/flutter/objectbox_demo_desktop/macos/Runner/Configs/Release.xcconfig b/example/flutter/objectbox_demo_desktop/macos/Runner/Configs/Release.xcconfig similarity index 100% rename from examples/flutter/objectbox_demo_desktop/macos/Runner/Configs/Release.xcconfig rename to example/flutter/objectbox_demo_desktop/macos/Runner/Configs/Release.xcconfig diff --git a/examples/flutter/objectbox_demo_desktop/macos/Runner/Configs/Warnings.xcconfig b/example/flutter/objectbox_demo_desktop/macos/Runner/Configs/Warnings.xcconfig similarity index 100% rename from examples/flutter/objectbox_demo_desktop/macos/Runner/Configs/Warnings.xcconfig rename to example/flutter/objectbox_demo_desktop/macos/Runner/Configs/Warnings.xcconfig diff --git a/examples/flutter/objectbox_demo_desktop/macos/Runner/DebugProfile.entitlements b/example/flutter/objectbox_demo_desktop/macos/Runner/DebugProfile.entitlements similarity index 100% rename from examples/flutter/objectbox_demo_desktop/macos/Runner/DebugProfile.entitlements rename to example/flutter/objectbox_demo_desktop/macos/Runner/DebugProfile.entitlements diff --git a/examples/flutter/objectbox_demo_desktop/macos/Runner/Info.plist b/example/flutter/objectbox_demo_desktop/macos/Runner/Info.plist similarity index 100% rename from examples/flutter/objectbox_demo_desktop/macos/Runner/Info.plist rename to example/flutter/objectbox_demo_desktop/macos/Runner/Info.plist diff --git a/examples/flutter/objectbox_demo_desktop/macos/Runner/MainFlutterWindow.swift b/example/flutter/objectbox_demo_desktop/macos/Runner/MainFlutterWindow.swift similarity index 100% rename from examples/flutter/objectbox_demo_desktop/macos/Runner/MainFlutterWindow.swift rename to example/flutter/objectbox_demo_desktop/macos/Runner/MainFlutterWindow.swift diff --git a/examples/flutter/objectbox_demo_desktop/macos/Runner/Release.entitlements b/example/flutter/objectbox_demo_desktop/macos/Runner/Release.entitlements similarity index 100% rename from examples/flutter/objectbox_demo_desktop/macos/Runner/Release.entitlements rename to example/flutter/objectbox_demo_desktop/macos/Runner/Release.entitlements diff --git a/examples/flutter/objectbox_demo_desktop/objectbox-model.json b/example/flutter/objectbox_demo_desktop/objectbox-model.json similarity index 100% rename from examples/flutter/objectbox_demo_desktop/objectbox-model.json rename to example/flutter/objectbox_demo_desktop/objectbox-model.json diff --git a/examples/flutter/objectbox_demo_desktop/pubspec.yaml b/example/flutter/objectbox_demo_desktop/pubspec.yaml similarity index 92% rename from examples/flutter/objectbox_demo_desktop/pubspec.yaml rename to example/flutter/objectbox_demo_desktop/pubspec.yaml index 0c2aaa520..440f0aea2 100644 --- a/examples/flutter/objectbox_demo_desktop/pubspec.yaml +++ b/example/flutter/objectbox_demo_desktop/pubspec.yaml @@ -20,8 +20,8 @@ dev_dependencies: flutter_test: sdk: flutter build_runner: ^1.0.0 - objectbox_model_generator: - path: ../../../bin/objectbox_model_generator + objectbox_generator: + path: ../../../generator flutter: uses-material-design: true diff --git a/examples/flutter/objectbox_demo_desktop/test/widget_test.dart b/example/flutter/objectbox_demo_desktop/test/widget_test.dart similarity index 100% rename from examples/flutter/objectbox_demo_desktop/test/widget_test.dart rename to example/flutter/objectbox_demo_desktop/test/widget_test.dart diff --git a/examples/flutter/objectbox_demo_desktop/windows/.gitignore b/example/flutter/objectbox_demo_desktop/windows/.gitignore similarity index 100% rename from examples/flutter/objectbox_demo_desktop/windows/.gitignore rename to example/flutter/objectbox_demo_desktop/windows/.gitignore diff --git a/examples/flutter/objectbox_demo_desktop/windows/AppConfiguration.props b/example/flutter/objectbox_demo_desktop/windows/AppConfiguration.props similarity index 100% rename from examples/flutter/objectbox_demo_desktop/windows/AppConfiguration.props rename to example/flutter/objectbox_demo_desktop/windows/AppConfiguration.props diff --git a/examples/flutter/objectbox_demo_desktop/windows/FlutterBuild.vcxproj b/example/flutter/objectbox_demo_desktop/windows/FlutterBuild.vcxproj similarity index 100% rename from examples/flutter/objectbox_demo_desktop/windows/FlutterBuild.vcxproj rename to example/flutter/objectbox_demo_desktop/windows/FlutterBuild.vcxproj diff --git a/examples/flutter/objectbox_demo_desktop/windows/Runner.rc b/example/flutter/objectbox_demo_desktop/windows/Runner.rc similarity index 100% rename from examples/flutter/objectbox_demo_desktop/windows/Runner.rc rename to example/flutter/objectbox_demo_desktop/windows/Runner.rc diff --git a/examples/flutter/objectbox_demo_desktop/windows/Runner.sln b/example/flutter/objectbox_demo_desktop/windows/Runner.sln similarity index 100% rename from examples/flutter/objectbox_demo_desktop/windows/Runner.sln rename to example/flutter/objectbox_demo_desktop/windows/Runner.sln diff --git a/examples/flutter/objectbox_demo_desktop/windows/Runner.vcxproj b/example/flutter/objectbox_demo_desktop/windows/Runner.vcxproj similarity index 100% rename from examples/flutter/objectbox_demo_desktop/windows/Runner.vcxproj rename to example/flutter/objectbox_demo_desktop/windows/Runner.vcxproj diff --git a/examples/flutter/objectbox_demo_desktop/windows/Runner.vcxproj.filters b/example/flutter/objectbox_demo_desktop/windows/Runner.vcxproj.filters similarity index 100% rename from examples/flutter/objectbox_demo_desktop/windows/Runner.vcxproj.filters rename to example/flutter/objectbox_demo_desktop/windows/Runner.vcxproj.filters diff --git a/examples/flutter/objectbox_demo_desktop/windows/main.cpp b/example/flutter/objectbox_demo_desktop/windows/main.cpp similarity index 100% rename from examples/flutter/objectbox_demo_desktop/windows/main.cpp rename to example/flutter/objectbox_demo_desktop/windows/main.cpp diff --git a/examples/flutter/objectbox_demo_desktop/windows/plugin_registrant.cpp b/example/flutter/objectbox_demo_desktop/windows/plugin_registrant.cpp similarity index 100% rename from examples/flutter/objectbox_demo_desktop/windows/plugin_registrant.cpp rename to example/flutter/objectbox_demo_desktop/windows/plugin_registrant.cpp diff --git a/examples/flutter/objectbox_demo_desktop/windows/plugin_registrant.h b/example/flutter/objectbox_demo_desktop/windows/plugin_registrant.h similarity index 100% rename from examples/flutter/objectbox_demo_desktop/windows/plugin_registrant.h rename to example/flutter/objectbox_demo_desktop/windows/plugin_registrant.h diff --git a/examples/flutter/objectbox_demo_desktop/windows/resource.h b/example/flutter/objectbox_demo_desktop/windows/resource.h similarity index 100% rename from examples/flutter/objectbox_demo_desktop/windows/resource.h rename to example/flutter/objectbox_demo_desktop/windows/resource.h diff --git a/examples/flutter/objectbox_demo_desktop/windows/resources/app_icon.ico b/example/flutter/objectbox_demo_desktop/windows/resources/app_icon.ico similarity index 100% rename from examples/flutter/objectbox_demo_desktop/windows/resources/app_icon.ico rename to example/flutter/objectbox_demo_desktop/windows/resources/app_icon.ico diff --git a/examples/flutter/objectbox_demo_desktop/windows/runner.exe.manifest b/example/flutter/objectbox_demo_desktop/windows/runner.exe.manifest similarity index 100% rename from examples/flutter/objectbox_demo_desktop/windows/runner.exe.manifest rename to example/flutter/objectbox_demo_desktop/windows/runner.exe.manifest diff --git a/examples/flutter/objectbox_demo_desktop/windows/scripts/bundle_assets_and_deps.bat b/example/flutter/objectbox_demo_desktop/windows/scripts/bundle_assets_and_deps.bat similarity index 100% rename from examples/flutter/objectbox_demo_desktop/windows/scripts/bundle_assets_and_deps.bat rename to example/flutter/objectbox_demo_desktop/windows/scripts/bundle_assets_and_deps.bat diff --git a/examples/flutter/objectbox_demo_desktop/windows/scripts/prepare_dependencies.bat b/example/flutter/objectbox_demo_desktop/windows/scripts/prepare_dependencies.bat similarity index 100% rename from examples/flutter/objectbox_demo_desktop/windows/scripts/prepare_dependencies.bat rename to example/flutter/objectbox_demo_desktop/windows/scripts/prepare_dependencies.bat diff --git a/examples/flutter/objectbox_demo_desktop/windows/win32_window.cc b/example/flutter/objectbox_demo_desktop/windows/win32_window.cc similarity index 100% rename from examples/flutter/objectbox_demo_desktop/windows/win32_window.cc rename to example/flutter/objectbox_demo_desktop/windows/win32_window.cc diff --git a/examples/flutter/objectbox_demo_desktop/windows/win32_window.h b/example/flutter/objectbox_demo_desktop/windows/win32_window.h similarity index 100% rename from examples/flutter/objectbox_demo_desktop/windows/win32_window.h rename to example/flutter/objectbox_demo_desktop/windows/win32_window.h diff --git a/examples/flutter/objectbox_demo_desktop/windows/window_configuration.cpp b/example/flutter/objectbox_demo_desktop/windows/window_configuration.cpp similarity index 100% rename from examples/flutter/objectbox_demo_desktop/windows/window_configuration.cpp rename to example/flutter/objectbox_demo_desktop/windows/window_configuration.cpp diff --git a/examples/flutter/objectbox_demo_desktop/windows/window_configuration.h b/example/flutter/objectbox_demo_desktop/windows/window_configuration.h similarity index 100% rename from examples/flutter/objectbox_demo_desktop/windows/window_configuration.h rename to example/flutter/objectbox_demo_desktop/windows/window_configuration.h diff --git a/generator/CHANGELOG.md b/generator/CHANGELOG.md new file mode 100644 index 000000000..83861ac08 --- /dev/null +++ b/generator/CHANGELOG.md @@ -0,0 +1 @@ +See [ObjectBox changelog](https://pub.dev/packages/objectbox#-changelog-tab-). \ No newline at end of file diff --git a/generator/LICENSE b/generator/LICENSE new file mode 100644 index 000000000..261eeb9e9 --- /dev/null +++ b/generator/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/generator/README.md b/generator/README.md new file mode 100644 index 000000000..0fd766ec3 --- /dev/null +++ b/generator/README.md @@ -0,0 +1 @@ +See package [ObjectBox](https://pub.dev/packages/objectbox). \ No newline at end of file diff --git a/generator/analysis_options.yaml b/generator/analysis_options.yaml new file mode 100644 index 000000000..24add03a7 --- /dev/null +++ b/generator/analysis_options.yaml @@ -0,0 +1,6 @@ +include: package:pedantic/analysis_options.yaml + +linter: + rules: + # additional rules used by pub.dev + - non_constant_identifier_names diff --git a/bin/objectbox_model_generator/build.yaml b/generator/build.yaml similarity index 58% rename from bin/objectbox_model_generator/build.yaml rename to generator/build.yaml index 904ccd5df..346aba44e 100644 --- a/bin/objectbox_model_generator/build.yaml +++ b/generator/build.yaml @@ -1,15 +1,18 @@ targets: $default: builders: - objectbox_model_generator|objectbox: + objectbox_generator|objectbox: enabled: true builders: objectbox: - target: ":objectbox_model_generator" - import: "package:objectbox_model_generator/builder.dart" + target: ":objectbox_generator" + import: "package:objectbox_generator/objectbox_generator.dart" builder_factories: ["objectboxModelFactory"] build_extensions: {".dart": [".objectbox_model.g.part"]} auto_apply: dependents build_to: cache applies_builders: ["source_gen|combining_builder"] + defaults: + generate_for: + exclude: ["example/**"] diff --git a/generator/example/README.md b/generator/example/README.md new file mode 100644 index 000000000..bed7e4331 --- /dev/null +++ b/generator/example/README.md @@ -0,0 +1 @@ +See [ObjectBox example](https://pub.dev/packages/objectbox#-example-tab-). \ No newline at end of file diff --git a/bin/objectbox_model_generator/lib/builder.dart b/generator/lib/objectbox_generator.dart similarity index 75% rename from bin/objectbox_model_generator/lib/builder.dart rename to generator/lib/objectbox_generator.dart index bd484c3a4..1427324ba 100644 --- a/bin/objectbox_model_generator/lib/builder.dart +++ b/generator/lib/objectbox_generator.dart @@ -1,5 +1,5 @@ import "package:build/build.dart"; import "package:source_gen/source_gen.dart"; -import "package:objectbox_model_generator/src/generator.dart"; +import "package:objectbox_generator/src/generator.dart"; Builder objectboxModelFactory(BuilderOptions options) => SharedPartBuilder([EntityGenerator()], "objectbox_model"); diff --git a/generator/lib/src/code_chunks.dart b/generator/lib/src/code_chunks.dart new file mode 100644 index 000000000..fa4e0b320 --- /dev/null +++ b/generator/lib/src/code_chunks.dart @@ -0,0 +1,97 @@ +import "package:objectbox/src/modelinfo/index.dart"; +import "package:objectbox/src/bindings/constants.dart" show OBXPropertyType; +import "package:source_gen/source_gen.dart" show InvalidGenerationSourceError; + +class CodeChunks { + static String modelInfoLoader() => """ + Map _allOBXModelEntities; + + void _loadOBXModelEntities() { + _allOBXModelEntities = {}; + ModelInfo modelInfo = ModelInfo.fromMap(||MODEL-JSON||); + modelInfo.entities.forEach((e) => _allOBXModelEntities[e.id.uid] = e); + } + + ModelEntity _getOBXModelEntity(int entityUid) { + if (_allOBXModelEntities == null) _loadOBXModelEntities(); + if (!_allOBXModelEntities.containsKey(entityUid)) { + throw Exception("entity uid missing in objectbox-model.json: \$entityUid"); + } + return _allOBXModelEntities[entityUid]; + } + """; + + static String instanceBuildersReaders(ModelEntity readEntity) { + String name = readEntity.name; + return """ + ModelEntity _${name}_OBXModelGetter() { + return _getOBXModelEntity(${readEntity.id.uid}); + } + + $name _${name}_OBXBuilder(Map members) { + $name r = $name(); + ${readEntity.properties.map((p) => "r.${p.name} = members[\"${p.name}\"];").join()} + return r; + } + + Map _${name}_OBXReader($name inst) { + Map r = {}; + ${readEntity.properties.map((p) => "r[\"${p.name}\"] = inst.${p.name};").join()} + return r; + } + + const ${name}_OBXDefs = EntityDefinition<${name}>(_${name}_OBXModelGetter, _${name}_OBXReader, _${name}_OBXBuilder); + """; + } + + static String _queryConditionBuilder(ModelEntity readEntity) { + final ret = []; + for (var f in readEntity.properties) { + final name = f.name; + + // see OBXPropertyType + String fieldType; + switch (f.type) { + case OBXPropertyType.Bool: + fieldType = "Boolean"; + break; + case OBXPropertyType.String: + fieldType = "String"; + break; + float: + case OBXPropertyType.Double: + fieldType = "Double"; + break; + case OBXPropertyType.Float: + continue float; + integer: + case OBXPropertyType.Int: + fieldType = "Integer"; + break; + case OBXPropertyType.Byte: + continue integer; + case OBXPropertyType.Short: + continue integer; + case OBXPropertyType.Char: + continue integer; + case OBXPropertyType.Long: + continue integer; + default: + throw InvalidGenerationSourceError("Unsupported property type (${f.type}): ${readEntity.name}.${name}"); + } + + ret.add(""" + static final ${name} = Query${fieldType}Property(entityId:${readEntity.id.id}, propertyId:${f.id.id}, obxType:${f.type}); + """); + } + return ret.join(); + } + + static String queryConditionClasses(ModelEntity readEntity) { + // TODO add entity.id check to throw an error Box if the wrong entity.property is used + return """ + class ${readEntity.name}_ { + ${_queryConditionBuilder(readEntity)} + }"""; + } +} diff --git a/bin/objectbox_model_generator/lib/src/generator.dart b/generator/lib/src/generator.dart similarity index 50% rename from bin/objectbox_model_generator/lib/src/generator.dart rename to generator/lib/src/generator.dart index 6c0a623b3..9412781d4 100644 --- a/bin/objectbox_model_generator/lib/src/generator.dart +++ b/generator/lib/src/generator.dart @@ -2,15 +2,14 @@ import "dart:async"; import "dart:convert"; import "dart:io"; import "package:analyzer/dart/element/element.dart"; +import 'package:build/build.dart'; import "package:build/src/builder/build_step.dart"; import "package:source_gen/source_gen.dart"; - import "package:objectbox/objectbox.dart" as obx; import "package:objectbox/src/bindings/constants.dart"; - +import "package:objectbox/src/modelinfo/index.dart"; import "code_chunks.dart"; import "merge.dart"; -import "package:objectbox/src/modelinfo/index.dart"; class EntityGenerator extends GeneratorForAnnotation { static const ALL_MODELS_JSON = "objectbox-model.json"; @@ -19,17 +18,22 @@ class EntityGenerator extends GeneratorForAnnotation { List entityHeaderDone = []; Future _loadModelInfo() async { - if ((await FileSystemEntity.type(ALL_MODELS_JSON)) == FileSystemEntityType.notFound) + if ((await FileSystemEntity.type(ALL_MODELS_JSON)) == FileSystemEntityType.notFound) { return ModelInfo.createDefault(); - return ModelInfo.fromMap(json.decode(await (new File(ALL_MODELS_JSON).readAsString()))); + } + return ModelInfo.fromMap(json.decode(await (File(ALL_MODELS_JSON).readAsString()))); } + final _propertyChecker = const TypeChecker.fromRuntime(obx.Property); + final _idChecker = const TypeChecker.fromRuntime(obx.Id); + @override Future generateForAnnotatedElement( Element elementBare, ConstantReader annotation, BuildStep buildStep) async { try { - if (elementBare is! ClassElement) + if (elementBare is! ClassElement) { throw InvalidGenerationSourceError("in target ${elementBare.name}: annotated element isn't a class"); + } var element = elementBare as ClassElement; // load existing model from JSON file if possible @@ -38,13 +42,13 @@ class EntityGenerator extends GeneratorForAnnotation { // optionally add header for loading the .g.json file var ret = ""; - if (entityHeaderDone.indexOf(inputFileId) == -1) { + if (!entityHeaderDone.contains(inputFileId)) { ret += CodeChunks.modelInfoLoader(); entityHeaderDone.add(inputFileId); } // process basic entity (note that allModels.createEntity is not used, as the entity will be merged) - ModelEntity readEntity = new ModelEntity(IdUid.empty(), null, element.name, [], allModels); + ModelEntity readEntity = ModelEntity(IdUid.empty(), null, element.name, [], allModels); var entityUid = annotation.read("uid"); if (entityUid != null && !entityUid.isNull) readEntity.id.uid = entityUid.intValue; @@ -54,74 +58,91 @@ class EntityGenerator extends GeneratorForAnnotation { int fieldType, flags = 0; int propUid; - if (f.metadata != null && f.metadata.length == 1) { - var annotElmt = f.metadata[0].element as ConstructorElement; - var annotType = annotElmt.returnType.toString(); - var annotVal = f.metadata[0].computeConstantValue(); - var fieldTypeAnnot = null; // for the future, with custom type sizes allowed: annotVal.getField("type"); - fieldType = fieldTypeAnnot == null ? null : fieldTypeAnnot.toIntValue(); - propUid = annotVal.getField("uid").toIntValue(); - - // find property flags - if (annotType == "Id") { - if (hasIdProperty) - throw InvalidGenerationSourceError( - "in target ${elementBare.name}: has more than one properties annotated with @Id"); - if (fieldType != null) - throw InvalidGenerationSourceError( - "in target ${elementBare.name}: programming error: @Id property may not specify a type"); - if (f.type.toString() != "int") - throw InvalidGenerationSourceError( - "in target ${elementBare.name}: field with @Id property has type '${f.type.toString()}', but it must be 'int'"); - - fieldType = OBXPropertyType.Long; - flags |= OBXPropertyFlag.ID; - hasIdProperty = true; - } else if (annotType == "Property") { - // nothing special - } else { - // skip unknown annotations - print( - "warning: skipping field '${f.name}' in entity '${element.name}', as it has the unknown annotation type '$annotType'"); - continue; + if (_idChecker.hasAnnotationOfExact(f)) { + if (hasIdProperty) { + throw InvalidGenerationSourceError( + "in target ${elementBare.name}: has more than one properties annotated with @Id"); + } + if (f.type.toString() != "int") { + throw InvalidGenerationSourceError( + "in target ${elementBare.name}: field with @Id property has type '${f.type.toString()}', but it must be 'int'"); } + + hasIdProperty = true; + + fieldType = OBXPropertyType.Long; + flags |= OBXPropertyFlag.ID; + + final _idAnnotation = _idChecker.firstAnnotationOfExact(f); + propUid = _idAnnotation.getField('uid').toIntValue(); + } else if (_propertyChecker.hasAnnotationOfExact(f)) { + final _propertyAnnotation = _propertyChecker.firstAnnotationOfExact(f); + propUid = _propertyAnnotation.getField('uid').toIntValue(); + fieldType = _propertyAnnotation.getField('type').toIntValue(); + flags = _propertyAnnotation.getField('flag').toIntValue() ?? 0; + + log.info( + "annotated property found on ${f.name} with parameters: propUid(${propUid}) fieldType(${fieldType}) flags(${flags})"); + } else { + log.info( + "property found on ${f.name} with parameters: propUid(${propUid}) fieldType(${fieldType}) flags(${flags})"); } if (fieldType == null) { var fieldTypeStr = f.type.toString(); - if (fieldTypeStr == "int") - fieldType = OBXPropertyType.Int; - else if (fieldTypeStr == "String") + + if (fieldTypeStr == "int") { + // dart: 8 bytes + // ob: 8 bytes + fieldType = OBXPropertyType.Long; + } else if (fieldTypeStr == "String") { fieldType = OBXPropertyType.String; - else { - print( - "warning: skipping field '${f.name}' in entity '${element.name}', as it has the unsupported type '$fieldTypeStr'"); + } else if (fieldTypeStr == "bool") { + // dart: 1 byte + // ob: 1 byte + fieldType = OBXPropertyType.Bool; + } else if (fieldTypeStr == "double") { + // dart: 8 bytes + // ob: 8 bytes + fieldType = OBXPropertyType.Double; + } else { + log.warning( + "skipping field '${f.name}' in entity '${element.name}', as it has the unsupported type '$fieldTypeStr'"); continue; } } // create property (do not use readEntity.createProperty in order to avoid generating new ids) - ModelProperty prop = new ModelProperty(IdUid.empty(), f.name, fieldType, flags, readEntity); + ModelProperty prop = ModelProperty(IdUid.empty(), f.name, fieldType, flags, readEntity); if (propUid != null) prop.id.uid = propUid; readEntity.properties.add(prop); } // some checks on the entity's integrity - if (!hasIdProperty) + if (!hasIdProperty) { throw InvalidGenerationSourceError("in target ${elementBare.name}: has no properties annotated with @Id"); + } // merge existing model and annotated model that was just read, then write new final model to file mergeEntity(allModels, readEntity); - new File(ALL_MODELS_JSON).writeAsString(new JsonEncoder.withIndent(" ").convert(allModels.toMap())); + final modelJson = JsonEncoder.withIndent(" ").convert(allModels.toMap()); + await File(ALL_MODELS_JSON).writeAsString(modelJson); + + // add model-json to the generated code + ret = ret.replaceFirst('||MODEL-JSON||', modelJson); + readEntity = allModels.findEntityByName(element.name); if (readEntity == null) return ret; // main code for instance builders and readers ret += CodeChunks.instanceBuildersReaders(readEntity); + // for building queries + ret += CodeChunks.queryConditionClasses(readEntity); + return ret; } catch (e, s) { - print(s); + log.warning(s); rethrow; } } diff --git a/bin/objectbox_model_generator/lib/src/merge.dart b/generator/lib/src/merge.dart similarity index 100% rename from bin/objectbox_model_generator/lib/src/merge.dart rename to generator/lib/src/merge.dart diff --git a/generator/pubspec.yaml b/generator/pubspec.yaml new file mode 100644 index 000000000..0fb264ead --- /dev/null +++ b/generator/pubspec.yaml @@ -0,0 +1,21 @@ +name: objectbox_generator +version: 0.4.0 +repository: https://github.com/objectbox/objectbox-dart +homepage: https://objectbox.io +author: objectbox.io +description: ObjectBox binding code generator - finds annotated entities and adds them to the ObjectBox DB model. + +environment: + sdk: ">=2.5.0 <3.0.0" + +dependencies: + objectbox: 0.4.0 + build: ^1.0.0 + source_gen: ^0.9.0 + analyzer: ">=0.35.0 <0.100.0" + +dev_dependencies: + build_runner: ^1.0.0 + test: ^1.0.0 + glob: ^1.1.0 + build_resolvers: ^1.0.0 diff --git a/bin/objectbox_model_generator/test/cases/single_entity/objectbox-model.json_expected b/generator/test/cases/single_entity/objectbox-model.json_expected similarity index 100% rename from bin/objectbox_model_generator/test/cases/single_entity/objectbox-model.json_expected rename to generator/test/cases/single_entity/objectbox-model.json_expected diff --git a/bin/objectbox_model_generator/test/cases/single_entity/single_entity.dart_testcase b/generator/test/cases/single_entity/single_entity.dart_testcase similarity index 100% rename from bin/objectbox_model_generator/test/cases/single_entity/single_entity.dart_testcase rename to generator/test/cases/single_entity/single_entity.dart_testcase diff --git a/generator/test/cases/single_entity/single_entity.g.dart_expected b/generator/test/cases/single_entity/single_entity.g.dart_expected new file mode 100644 index 000000000..39a21dbd3 --- /dev/null +++ b/generator/test/cases/single_entity/single_entity.g.dart_expected @@ -0,0 +1,78 @@ +// ************************************************************************** +// EntityGenerator +// ************************************************************************** + +Map _allOBXModelEntities; + +void _loadOBXModelEntities() { + _allOBXModelEntities = {}; + ModelInfo modelInfo = ModelInfo.fromMap({ + "_note1": + "KEEP THIS FILE! Check it into a version control system (VCS) like git.", + "_note2": + "ObjectBox manages crucial IDs for your object model. See docs for details.", + "_note3": + "If you have VCS merge conflicts, you must resolve them according to ObjectBox docs.", + "entities": [ + { + "id": "1:1234", + "lastPropertyId": "2:4321", + "name": "SingleEntity", + "properties": [ + {"id": "1:5678", "name": "id", "type": 6, "flags": 1}, + {"id": "2:4321", "name": "texta", "type": 9} + ] + } + ], + "lastEntityId": "1:1234", + "lastIndexId": "0:0", + "lastRelationId": "0:0", + "lastSequenceId": "0:0", + "modelVersion": 5, + "modelVersionParserMinimum": 5, + "retiredEntityUids": [], + "retiredIndexUids": [], + "retiredPropertyUids": [], + "retiredRelationUids": [], + "version": 1 + }); + modelInfo.entities.forEach((e) => _allOBXModelEntities[e.id.uid] = e); +} + +ModelEntity _getOBXModelEntity(int entityUid) { + if (_allOBXModelEntities == null) _loadOBXModelEntities(); + if (!_allOBXModelEntities.containsKey(entityUid)) { + throw Exception("entity uid missing in objectbox-model.json: $entityUid"); + } + return _allOBXModelEntities[entityUid]; +} + +ModelEntity _SingleEntity_OBXModelGetter() { + return _getOBXModelEntity(1234); +} + +SingleEntity _SingleEntity_OBXBuilder(Map members) { + SingleEntity r = SingleEntity(); + r.id = members["id"]; + r.texta = members["texta"]; + return r; +} + +Map _SingleEntity_OBXReader(SingleEntity inst) { + Map r = {}; + r["id"] = inst.id; + r["texta"] = inst.texta; + return r; +} + +const SingleEntity_OBXDefs = EntityDefinition( + _SingleEntity_OBXModelGetter, + _SingleEntity_OBXReader, + _SingleEntity_OBXBuilder); + +class SingleEntity_ { + static final id = + QueryIntegerProperty(entityId: 1, propertyId: 1, obxType: 6); + static final texta = + QueryStringProperty(entityId: 1, propertyId: 2, obxType: 9); +} diff --git a/bin/objectbox_model_generator/test/generator_test.dart b/generator/test/generator_test.dart similarity index 78% rename from bin/objectbox_model_generator/test/generator_test.dart rename to generator/test/generator_test.dart index 8834961e7..331314227 100644 --- a/bin/objectbox_model_generator/test/generator_test.dart +++ b/generator/test/generator_test.dart @@ -6,7 +6,7 @@ import "helpers.dart"; void main() async { group("generator", () { tearDown(() { - new File("objectbox-model.json").deleteSync(); + File("objectbox-model.json").deleteSync(); }); testGeneratorOutput("single_entity"); diff --git a/bin/objectbox_model_generator/test/helpers.dart b/generator/test/helpers.dart similarity index 63% rename from bin/objectbox_model_generator/test/helpers.dart rename to generator/test/helpers.dart index 3d93162e4..f500eff9e 100644 --- a/bin/objectbox_model_generator/test/helpers.dart +++ b/generator/test/helpers.dart @@ -5,7 +5,7 @@ import "package:test/test.dart"; import 'package:build/build.dart'; import 'package:glob/glob.dart' show Glob; import "package:build/src/asset/id.dart"; -import "package:objectbox_model_generator/builder.dart"; +import "package:objectbox_generator/objectbox_generator.dart"; import "package:build/src/asset/reader.dart"; import "package:build/src/asset/writer.dart"; import "package:build/src/analyzer/resolver.dart"; @@ -32,29 +32,35 @@ class _SingleFileAssetReader extends AssetReader { AssetId id; _SingleFileAssetReader(this.id) { - if (id.package != "objectbox_model_generator") - throw Exception("asset package needs to be 'objectbox_model_generator', but got '${id.package}'"); + if (id.package != "objectbox_generator") { + throw Exception("asset package needs to be 'objectbox_generator', but got '${id.package}'"); + } } Future canRead(AssetId id) async => true; //this.id == id; - Future> readAsBytes(AssetId id) => throw UnimplementedError(); - Stream findAssets(Glob glob, {String package}) => Stream.fromIterable([id]); // throw UnimplementedError(); + + Stream findAssets(Glob glob, {String package}) => Stream.fromIterable([id]); + + @override + Future> readAsBytes(AssetId id) async => utf8.encode(await readAsString(id)); @override Future readAsString(AssetId id, {Encoding encoding = utf8}) async { - if (id.package != "objectbox" && id.package != "objectbox_model_generator") return ""; + if (id.package != "objectbox" && id.package != "objectbox_generator") return ""; + if (id.path.endsWith(".g.dart")) return ""; String path = id.path; - if (id.package == "objectbox") path = "../../" + path; - if (id.package == "objectbox_model_generator" && id.path.startsWith("test/cases") && id.path.endsWith(".dart")) + if (id.package == "objectbox") path = "../" + path; + if (id.package == "objectbox_generator" && id.path.startsWith("test/cases") && id.path.endsWith(".dart")) { path += "_testcase"; + } if (FileSystemEntity.typeSync(path) == FileSystemEntityType.notFound) throw AssetNotFoundException(id); - return await (new File(path).readAsString()); + return await (File(path).readAsString()); } } Future _buildGeneratorOutput(String caseName) async { - AssetId assetId = AssetId("objectbox_model_generator", "test/cases/$caseName/$caseName.dart"); + AssetId assetId = AssetId("objectbox_generator", "test/cases/$caseName/$caseName.dart"); var writer = _InMemoryAssetWriter(); var reader = _SingleFileAssetReader(assetId); Resolvers resolvers = AnalyzerResolvers(); @@ -66,11 +72,11 @@ Future _buildGeneratorOutput(String caseName) async { void testGeneratorOutput(String caseName) { test(caseName, () async { String built = await _buildGeneratorOutput(caseName); - String expected = await new File("test/cases/$caseName/$caseName.g.dart_expected").readAsString(); + String expected = await File("test/cases/$caseName/$caseName.g.dart_expected").readAsString(); expect(built, equals(expected)); - String jsonBuilt = await new File("objectbox-model.json").readAsString(); - String jsonExpected = await new File("test/cases/$caseName/objectbox-model.json_expected").readAsString(); + String jsonBuilt = await File("objectbox-model.json").readAsString(); + String jsonExpected = await File("test/cases/$caseName/objectbox-model.json_expected").readAsString(); expect(jsonBuilt, equals(jsonExpected)); }); } diff --git a/lib/integration_test.dart b/lib/integration_test.dart new file mode 100644 index 000000000..b4d378edd --- /dev/null +++ b/lib/integration_test.dart @@ -0,0 +1,34 @@ +library integration_test; + +import 'package:objectbox/objectbox.dart'; +import './src/bindings/constants.dart'; +import './src/bindings/helpers.dart'; +import './src/bindings/bindings.dart'; + +// Todo: maybe make this a standalone package + +/// Implements simple integration tests for platform compatibility. +/// It's functions are designed to be callable from flutter apps - to test on the target platform. +class IntegrationTest { + static const int64_max = 9223372036854775807; + + static int64() { + assert("9223372036854775807" == "$int64_max"); + } + + static model() { + // create a model with a single entity and a single property + final modelInfo = ModelInfo.createDefault(); + final property = ModelProperty(IdUid.create(1, int64_max - 1), "id", OBXPropertyType.Long, 0, null); + final entity = ModelEntity(IdUid.create(1, int64_max), null, "entity", [], modelInfo); + property.entity = entity; + entity.properties.add(property); + entity.lastPropertyId = property.id; + modelInfo.entities.add(entity); + modelInfo.lastEntityId = entity.id; + modelInfo.validate(); + + final model = Model(modelInfo.entities); + checkObx(bindings.obx_model_free(model.ptr)); + } +} diff --git a/lib/objectbox.dart b/lib/objectbox.dart index 41efb9b34..2e4837cfc 100644 --- a/lib/objectbox.dart +++ b/lib/objectbox.dart @@ -9,3 +9,4 @@ export "src/model.dart"; export "src/store.dart"; export "src/box.dart"; export "src/modelinfo/index.dart"; +export "src/query/query.dart"; diff --git a/lib/src/annotations.dart b/lib/src/annotations.dart index 2a83294e0..4b0fab408 100644 --- a/lib/src/annotations.dart +++ b/lib/src/annotations.dart @@ -1,14 +1,22 @@ class Entity { final int uid; - const Entity({this.uid = null}); + const Entity({this.uid}); } +/// A dart int value can map to different OBXPropertyTypes, +/// e.g. Short (Int16), Int (Int32), Long (Int64), all signed values. +/// Also a dart double can also map to e.g. Float and Double +/// +/// Property allows the mapping to be specific. The defaults are +/// e.g. Int -> Int64, double -> Float64, bool -> Bool. +/// +/// Use OBXPropertyType and OBXPropertyFlag values, resp. for type and flag. class Property { - final int uid; - const Property({this.uid = null}); + final int uid, type, flag; + const Property({this.type, this.flag, this.uid}); } class Id { final int uid; - const Id({this.uid = null}); + const Id({this.uid}); } diff --git a/lib/src/bindings/bindings.dart b/lib/src/bindings/bindings.dart index c1cbc5f99..a0eb2e37b 100644 --- a/lib/src/bindings/bindings.dart +++ b/lib/src/bindings/bindings.dart @@ -2,6 +2,9 @@ import "dart:ffi"; import "dart:io" show Platform; import "signatures.dart"; +import "structs.dart"; + +// ignore_for_file: non_constant_identifier_names // bundles all C functions to be exposed to Dart class _ObjectBoxBindings { @@ -10,7 +13,15 @@ class _ObjectBoxBindings { // common functions void Function(Pointer major, Pointer minor, Pointer patch) obx_version; Pointer Function() obx_version_string; - void Function(Pointer array) obx_bytes_array_free; + void Function(Pointer structPtr) obx_string_array_free, + obx_int64_array_free, + obx_int32_array_free, + obx_int16_array_free, + obx_int8_array_free, + obx_double_array_free, + obx_float_array_free; + obx_free_t obx_bytes_array_free; + obx_free_t obx_id_array_free; // error info int Function() obx_last_error_code; @@ -21,6 +32,8 @@ class _ObjectBoxBindings { // schema model creation Pointer Function() obx_model; int Function(Pointer model) obx_model_free; + int Function(Pointer model) obx_model_error_code; + Pointer Function(Pointer model) obx_model_error_message; int Function(Pointer model, Pointer name, int entity_id, int entity_uid) obx_model_entity; int Function(Pointer model, Pointer name, int type, int property_id, int property_uid) obx_model_property; @@ -45,19 +58,78 @@ class _ObjectBoxBindings { int Function(Pointer txn) obx_txn_close; int Function(Pointer txn) obx_txn_abort; int Function(Pointer txn) obx_txn_success; + int Function(Pointer txn, int was_successful) obx_txn_mark_success; // box management Pointer Function(Pointer store, int entity_id) obx_box; - int Function(Pointer box, int id, Pointer out_contains) obx_box_contains; - int Function(Pointer box, Pointer ids, Pointer out_contains) obx_box_contains_many; - int Function(Pointer box, int id, Pointer> data, Pointer size) obx_box_get; - Pointer Function(Pointer box, Pointer ids) obx_box_get_many; - Pointer Function(Pointer box) obx_box_get_all; + int Function(Pointer box, int id, Pointer out_contains) obx_box_contains; + int Function(Pointer box, Pointer ids, Pointer out_contains) obx_box_contains_many; + int Function(Pointer box, int id, Pointer> data, Pointer size) obx_box_get; + Pointer Function(Pointer box, Pointer ids) obx_box_get_many; + Pointer Function(Pointer box) obx_box_get_all; int Function(Pointer box, int id_or_zero) obx_box_id_for_put; int Function(Pointer box, int count, Pointer out_first_id) obx_box_ids_for_put; - int Function(Pointer box, int id, Pointer data, int size, int mode) obx_box_put; - int Function(Pointer box, Pointer objects, Pointer ids, int mode) obx_box_put_many; + int Function(Pointer box, int id, Pointer data, int size, int mode) obx_box_put; + int Function(Pointer box, Pointer objects, Pointer ids, int mode) obx_box_put_many; int Function(Pointer box, int id) obx_box_remove; + int Function(Pointer box, Pointer removed) obx_box_remove_all; + int Function(Pointer box, Pointer ids, Pointer removed) obx_box_remove_many; + + // box analytics + int Function(Pointer box, int limit, Pointer count) obx_box_count; + int Function(Pointer box, Pointer is_empty) obx_box_is_empty; + + // query builder + obx_query_builder_dart_t obx_qb_create; + obx_qb_close_dart_t obx_qb_close; + obx_qb_close_dart_t obx_qb_error_code; + obx_qb_error_message_t obx_qb_error_message; + + obx_qb_cond_operator_0_dart_t obx_qb_null, obx_qb_not_null; + + obx_qb_cond_operator_1_dart_t obx_qb_int_equal, obx_qb_int_not_equal, obx_qb_int_greater, obx_qb_int_less; + + obx_qb_cond_operator_2_dart_t obx_qb_int_between; + + obx_qb_cond_operator_in_dart_t obx_qb_int64_in, obx_qb_int64_not_in; + obx_qb_cond_operator_in_dart_t obx_qb_int32_in, obx_qb_int32_not_in; + + obx_qb_cond_string_op_1_dart_t obx_qb_string_equal, + obx_qb_string_not_equal, + obx_qb_string_contains, + obx_qb_string_starts_with, + obx_qb_string_ends_with; + + obx_qb_cond_operator_1_dart_t obx_qb_double_greater, obx_qb_double_less; + obx_qb_cond_operator_2_dart_t obx_qb_double_between; + + obx_qb_string_lt_gt_op_dart_t obx_qb_string_greater, obx_qb_string_less; + obx_qb_string_in_dart_t obx_qb_string_in; + + obx_qb_bytes_eq_dart_t obx_qb_bytes_equal; + obx_qb_bytes_lt_gt_dart_t obx_qb_bytes_greater, obx_qb_bytes_less; + + obx_qb_join_op_dart_t obx_qb_all, obx_qb_any; + + obx_qb_param_alias_dart_t obx_qb_param_alias; + + obx_qb_order_dart_t obx_qb_order; + + // query + obx_query_t obx_query_create; + obx_query_close_dart_t obx_query_close; + obx_query_find_t obx_query_find; + obx_query_find_ids_t obx_query_find_ids; + + obx_query_count_dart_t obx_query_count, obx_query_remove; + + obx_query_describe_t obx_query_describe, obx_query_describe_params; + + obx_query_visit_dart_t obx_query_visit; + + // Utilities + obx_bytes_array_t obx_bytes_array; + obx_bytes_array_set_t obx_bytes_array_set; // TODO return .asFunction() -> requires properly determined static return type Pointer> _fn(String name) { @@ -66,20 +138,31 @@ class _ObjectBoxBindings { _ObjectBoxBindings() { var libName = "objectbox"; - if (Platform.isWindows) + if (Platform.isWindows) { libName += ".dll"; - else if (Platform.isMacOS) + } else if (Platform.isMacOS) { libName = "lib" + libName + ".dylib"; - else if (Platform.isLinux || Platform.isAndroid) + } else if (Platform.isAndroid) { + libName = "lib" + libName + "-jni.so"; + } else if (Platform.isLinux) { libName = "lib" + libName + ".so"; - else + } else { throw Exception("unsupported platform detected"); + } objectbox = DynamicLibrary.open(libName); // common functions obx_version = _fn("obx_version").asFunction(); obx_version_string = _fn("obx_version_string").asFunction(); - obx_bytes_array_free = _fn("obx_bytes_array_free").asFunction(); + obx_bytes_array_free = _fn>("obx_bytes_array_free").asFunction(); + obx_id_array_free = _fn>("obx_id_array_free").asFunction(); + obx_string_array_free = _fn("obx_string_array_free").asFunction(); + obx_int64_array_free = _fn("obx_int64_array_free").asFunction(); + obx_int32_array_free = _fn("obx_int32_array_free").asFunction(); + obx_int16_array_free = _fn("obx_int16_array_free").asFunction(); + obx_int8_array_free = _fn("obx_int8_array_free").asFunction(); + obx_double_array_free = _fn("obx_double_array_free").asFunction(); + obx_float_array_free = _fn("obx_float_array_free").asFunction(); // error info obx_last_error_code = _fn("obx_last_error_code").asFunction(); @@ -90,6 +173,8 @@ class _ObjectBoxBindings { // schema model creation obx_model = _fn("obx_model").asFunction(); obx_model_free = _fn("obx_model_free").asFunction(); + obx_model_error_code = _fn("obx_model_error_code").asFunction(); + obx_model_error_message = _fn("obx_model_error_message").asFunction(); obx_model_entity = _fn("obx_model_entity").asFunction(); obx_model_property = _fn("obx_model_property").asFunction(); obx_model_property_flags = _fn("obx_model_property_flags").asFunction(); @@ -113,6 +198,7 @@ class _ObjectBoxBindings { obx_txn_close = _fn("obx_txn_close").asFunction(); obx_txn_abort = _fn("obx_txn_abort").asFunction(); obx_txn_success = _fn("obx_txn_success").asFunction(); + obx_txn_mark_success = _fn("obx_txn_mark_success").asFunction(); // box management obx_box = _fn("obx_box").asFunction(); @@ -126,6 +212,80 @@ class _ObjectBoxBindings { obx_box_put = _fn("obx_box_put").asFunction(); obx_box_put_many = _fn("obx_box_put_many").asFunction(); obx_box_remove = _fn("obx_box_remove").asFunction(); + obx_box_remove_all = _fn("obx_box_remove_all").asFunction(); + obx_box_remove_many = _fn("obx_box_remove_many").asFunction(); + + // box analytics + obx_box_count = _fn("obx_box_count").asFunction(); + obx_box_is_empty = _fn("obx_box_is_empty").asFunction(); + + // query builder + obx_qb_create = _fn("obx_query_builder").asFunction(); + obx_qb_close = _fn("obx_qb_close").asFunction(); + obx_qb_error_code = _fn("obx_qb_error_code").asFunction(); + obx_qb_error_message = _fn("obx_qb_error_message").asFunction(); + + obx_qb_null = _fn("obx_qb_null").asFunction(); + obx_qb_not_null = _fn("obx_qb_not_null").asFunction(); + + obx_qb_int_equal = _fn>("obx_qb_int_equal").asFunction(); + obx_qb_int_not_equal = _fn>("obx_qb_int_not_equal").asFunction(); + obx_qb_int_greater = _fn>("obx_qb_int_greater").asFunction(); + obx_qb_int_less = _fn>("obx_qb_int_less").asFunction(); + + obx_qb_int_between = _fn>("obx_qb_int_between").asFunction(); + + obx_qb_int64_in = _fn>("obx_qb_int64_in").asFunction(); + obx_qb_int64_not_in = _fn>("obx_qb_int64_not_in").asFunction(); + + obx_qb_int32_in = _fn>("obx_qb_int32_in").asFunction(); + obx_qb_int32_not_in = _fn>("obx_qb_int32_not_in").asFunction(); + + obx_qb_string_equal = _fn("obx_qb_string_equal").asFunction(); + obx_qb_string_not_equal = _fn("obx_qb_string_not_equal").asFunction(); + obx_qb_string_contains = _fn("obx_qb_string_contains").asFunction(); + + obx_qb_string_starts_with = _fn("obx_qb_string_starts_with").asFunction(); + obx_qb_string_ends_with = _fn("obx_qb_string_ends_with").asFunction(); + + obx_qb_string_greater = _fn("obx_qb_string_greater").asFunction(); + obx_qb_string_less = _fn("obx_qb_string_less").asFunction(); + + obx_qb_string_in = _fn("obx_qb_string_in").asFunction(); + + obx_qb_double_greater = _fn>("obx_qb_double_greater").asFunction(); + obx_qb_double_less = _fn>("obx_qb_double_less").asFunction(); + + obx_qb_double_between = _fn>("obx_qb_double_between").asFunction(); + + obx_qb_bytes_equal = _fn("obx_qb_bytes_equal").asFunction(); + obx_qb_bytes_greater = _fn("obx_qb_bytes_greater").asFunction(); + obx_qb_bytes_less = _fn("obx_qb_bytes_less").asFunction(); + + obx_qb_all = _fn("obx_qb_all").asFunction(); + obx_qb_any = _fn("obx_qb_any").asFunction(); + + obx_qb_param_alias = _fn("obx_qb_param_alias").asFunction(); + + obx_qb_order = _fn("obx_qb_order").asFunction(); + + // query + obx_query_create = _fn("obx_query").asFunction(); + obx_query_close = _fn("obx_query_close").asFunction(); + + obx_query_find_ids = _fn>("obx_query_find_ids").asFunction(); + obx_query_find = _fn>("obx_query_find").asFunction(); + + obx_query_count = _fn("obx_query_count").asFunction(); + obx_query_remove = _fn("obx_query_remove").asFunction(); + obx_query_describe = _fn("obx_query_describe").asFunction(); + obx_query_describe_params = _fn("obx_query_describe_params").asFunction(); + + obx_query_visit = _fn("obx_query_visit").asFunction(); + + // Utilities + obx_bytes_array = _fn>("obx_bytes_array").asFunction(); + obx_bytes_array_set = _fn>("obx_bytes_array_set").asFunction(); } } diff --git a/lib/src/bindings/flatbuffers.dart b/lib/src/bindings/flatbuffers.dart index 95b4a402b..322fb3a73 100644 --- a/lib/src/bindings/flatbuffers.dart +++ b/lib/src/bindings/flatbuffers.dart @@ -8,9 +8,11 @@ import "../modelinfo/index.dart"; class _OBXFBEntity { _OBXFBEntity._(this._bc, this._bcOffset); - static const fb.Reader<_OBXFBEntity> reader = const _OBXFBEntityReader(); - factory _OBXFBEntity(Uint8List bytes) { - fb.BufferContext rootRef = new fb.BufferContext.fromBytes(bytes); + + static const fb.Reader<_OBXFBEntity> reader = _OBXFBEntityReader(); + + factory _OBXFBEntity(final Uint8List bytes) { + fb.BufferContext rootRef = fb.BufferContext.fromBytes(bytes); return reader.read(rootRef, 0); } @@ -24,7 +26,7 @@ class _OBXFBEntityReader extends fb.TableReader<_OBXFBEntity> { const _OBXFBEntityReader(); @override - _OBXFBEntity createObject(fb.BufferContext bc, int offset) => new _OBXFBEntity._(bc, offset); + _OBXFBEntity createObject(fb.BufferContext bc, int offset) => _OBXFBEntity._(bc, offset); } class OBXFlatbuffersManager { @@ -33,8 +35,8 @@ class OBXFlatbuffersManager { OBXFlatbuffersManager(this._modelEntity, this._entityBuilder); - ByteBuffer marshal(propVals) { - var builder = new fb.Builder(initialSize: 1024); + Pointer marshal(Map propVals) { + var builder = fb.Builder(initialSize: 1024); // write all strings Map offsets = {}; @@ -73,19 +75,24 @@ class OBXFlatbuffersManager { case OBXPropertyType.String: builder.addOffset(field, offsets[p.name]); break; + case OBXPropertyType.Float: + builder.addFloat32(field, value); + break; + case OBXPropertyType.Double: + builder.addFloat64(field, value); + break; default: throw Exception("unsupported type: ${p.type}"); // TODO: support more types } }); var endOffset = builder.endTable(); - return ByteBuffer.allocate(builder.finish(endOffset)); + return OBX_bytes.managedCopyOf(builder.finish(endOffset)); } - T unmarshal(ByteBuffer buffer) { - if (buffer.size == 0 || buffer.address == 0) return null; + T unmarshal(final Uint8List bytes) { + final entity = _OBXFBEntity(bytes); Map propVals = {}; - var entity = new _OBXFBEntity(buffer.data); _modelEntity.properties.forEach((p) { var propReader; @@ -111,6 +118,12 @@ class OBXFlatbuffersManager { case OBXPropertyType.String: propReader = fb.StringReader(); break; + case OBXPropertyType.Float: + propReader = fb.Float32Reader(); + break; + case OBXPropertyType.Double: + propReader = fb.Float64Reader(); + break; default: throw Exception("unsupported type: ${p.type}"); // TODO: support more types } @@ -122,7 +135,12 @@ class OBXFlatbuffersManager { } // expects pointer to OBX_bytes_array and manually resolves its contents (see objectbox.h) - List unmarshalArray(Pointer bytesArray) { - return ByteBufferArray.fromOBXBytesArray(bytesArray).buffers.map((b) => unmarshal(b)).toList(); + List unmarshalArray(final Pointer bytesArray, {bool allowMissing = false}) { + final OBX_bytes_array array = bytesArray.load(); + var fn = (OBX_bytes b) => unmarshal(b.data); + if (allowMissing) { + fn = (OBX_bytes b) => b.isEmpty ? null : unmarshal(b.data); + } + return array.items().map(fn).toList(); } } diff --git a/lib/src/bindings/helpers.dart b/lib/src/bindings/helpers.dart index 18d27966e..d22c68b1a 100644 --- a/lib/src/bindings/helpers.dart +++ b/lib/src/bindings/helpers.dart @@ -9,16 +9,30 @@ checkObx(errorCode) { if (errorCode != OBXError.OBX_SUCCESS) throw ObjectBoxException(lastObxErrorString(errorCode)); } -checkObxPtr(Pointer ptr, String msg, [bool hasLastError = false]) { - if (ptr == null || ptr.address == 0) throw ObjectBoxException("$msg: ${hasLastError ? lastObxErrorString() : ""}"); +Pointer checkObxPtr(Pointer ptr, String msg) { + if (ptr == null || ptr.address == 0) { + final info = lastObxErrorString(); + throw ObjectBoxException(info.isEmpty ? msg : "$msg: $info"); + } return ptr; } -String lastObxErrorString([err]) { - if (err != null) return "code $err"; +String lastObxErrorString([int err = 0]) { + int code = bindings.obx_last_error_code(); + String text = cString(bindings.obx_last_error_message()); - int last = bindings.obx_last_error_code(); - int last2 = bindings.obx_last_error_secondary(); - String desc = Utf8.fromUtf8(bindings.obx_last_error_message().cast()); - return "code $last, $last2 ($desc)"; + if (code == 0 && text.isEmpty) { + return (err != 0) ? "code $err" : "unknown native error"; + } + + return code == 0 ? text : "$code $text"; +} + +String cString(Pointer charPtr) { + // Utf8.fromUtf8 segfaults when called on nullptr + if (charPtr.address == 0) { + return ""; + } + + return Utf8.fromUtf8(charPtr.cast()); } diff --git a/lib/src/bindings/signatures.dart b/lib/src/bindings/signatures.dart index fba6f3ece..1da20d4d8 100644 --- a/lib/src/bindings/signatures.dart +++ b/lib/src/bindings/signatures.dart @@ -1,9 +1,13 @@ import "dart:ffi"; +import 'structs.dart'; + +// ignore_for_file: non_constant_identifier_names // common functions typedef obx_version_native_t = Void Function(Pointer major, Pointer minor, Pointer patch); typedef obx_version_string_native_t = Pointer Function(); -typedef obx_bytes_array_free_native_t = Void Function(Pointer array); +typedef obx_free_t = Void Function(Pointer ptr); +typedef obx_free_struct_native_t = Void Function(Pointer structPtr); // error info typedef obx_last_error_code_native_t = Int32 Function(); @@ -14,10 +18,12 @@ typedef obx_last_error_clear_native_t = Void Function(); // schema model creation typedef obx_model_native_t = Pointer Function(); typedef obx_model_free_native_t = Int32 Function(Pointer); +typedef obx_model_error_code_native_t = Int32 Function(Pointer); +typedef obx_model_error_message_native_t = Pointer Function(Pointer); typedef obx_model_entity_native_t = Int32 Function( Pointer model, Pointer name, Uint32 entity_id, Uint64 entity_uid); typedef obx_model_property_native_t = Int32 Function( - Pointer model, Pointer name, Uint32 type, Uint64 property_id, Uint64 property_uid); + Pointer model, Pointer name, Uint32 type, Uint32 property_id, Uint64 property_uid); typedef obx_model_property_flags_native_t = Int32 Function(Pointer model, Uint32 flags); typedef obx_model_entity_last_property_id_native_t = Int32 Function( Pointer model, Uint32 property_id, Uint64 property_uid); @@ -39,19 +45,113 @@ typedef obx_txn_read_native_t = Pointer Function(Pointer store); typedef obx_txn_close_native_t = Int32 Function(Pointer txn); typedef obx_txn_abort_native_t = Int32 Function(Pointer txn); typedef obx_txn_success_native_t = Int32 Function(Pointer txn); +typedef obx_txn_mark_success_native_t = Int32 Function(Pointer txn, Uint8 wasSuccessful); // box management typedef obx_box_native_t = Pointer Function(Pointer store, Uint32 entity_id); -typedef obx_box_contains_native_t = Int32 Function(Pointer box, Uint64 id, Pointer out_contains); +typedef obx_box_contains_native_t = Int32 Function(Pointer box, Uint64 id, Pointer out_contains); typedef obx_box_contains_many_native_t = Int32 Function( - Pointer box, Pointer ids, Pointer out_contains); + Pointer box, Pointer ids, Pointer out_contains); typedef obx_box_get_native_t = Int32 Function( - Pointer box, Uint64 id, Pointer> data, Pointer size); -typedef obx_box_get_many_native_t = Pointer Function(Pointer box, Pointer ids); -typedef obx_box_get_all_native_t = Pointer Function(Pointer box); + Pointer box, Uint64 id, Pointer> data, Pointer size); +typedef obx_box_get_many_native_t = Pointer Function(Pointer box, Pointer ids); +typedef obx_box_get_all_native_t = Pointer Function(Pointer box); typedef obx_box_id_for_put_native_t = Uint64 Function(Pointer box, Uint64 id_or_zero); typedef obx_box_ids_for_put_native_t = Int32 Function(Pointer box, Uint64 count, Pointer out_first_id); -typedef obx_box_put_native_t = Int32 Function(Pointer box, Uint64 id, Pointer data, Int32 size, Int32 mode); +typedef obx_box_put_native_t = Int32 Function( + Pointer box, Uint64 id, Pointer data, IntPtr size, Int32 mode); typedef obx_box_put_many_native_t = Int32 Function( - Pointer box, Pointer objects, Pointer ids, Int32 mode); + Pointer box, Pointer objects, Pointer ids, Int32 mode); typedef obx_box_remove_native_t = Int32 Function(Pointer box, Uint64 id); +typedef obx_box_remove_all_native_t = Int32 Function(Pointer box, Pointer removed); +typedef obx_box_remove_many_native_t = Int32 Function( + Pointer box, Pointer ids, Pointer removed); +typedef obx_box_count_native_t = Int32 Function(Pointer box, Uint64 limit, Pointer _count); +typedef obx_box_is_empty_native_t = Int32 Function(Pointer box, Pointer is_empty); + +// no typedef for non-functions yet, see https://github.com/dart-lang/sdk/issues/2626 +// typedef obx_err = Int32 +// typedef Pointer -> char[] +// typedef Pointer -> int (e.g. obx_qb_cond); + +// query builider +typedef obx_query_builder_native_t = Pointer Function(Pointer store, Uint32 entity_id); +typedef obx_query_builder_dart_t = Pointer Function(Pointer store, int entity_id); + +typedef obx_qb_close_native_t = Int32 Function(Pointer builder); +typedef obx_qb_close_dart_t = int Function(Pointer builder); + +typedef obx_qb_error_message_t = Pointer Function(Pointer builder); + +typedef obx_qb_cond_operator_0_native_t = Int32 Function(Pointer builder, Uint32 property_id); +typedef obx_qb_cond_operator_0_dart_t = int Function(Pointer builder, int property_id); + +typedef obx_qb_cond_operator_1_native_t

= Int32 Function(Pointer builder, Uint32 property_id, P value); +typedef obx_qb_cond_operator_1_dart_t

= int Function(Pointer builder, int property_id, P value); + +typedef obx_qb_cond_operator_2_native_t

= Int32 Function(Pointer builder, Uint32 property_id, P v1, P v2); +typedef obx_qb_cond_operator_2_dart_t

= int Function(Pointer builder, int property_id, P v1, P v2); + +typedef obx_qb_cond_operator_in_native_t

= Int32 Function( + Pointer builder, Uint32 property_id, Pointer

values, Uint64 count); +typedef obx_qb_cond_operator_in_dart_t

= int Function( + Pointer builder, int property_id, Pointer

values, int count); + +typedef obx_qb_join_op_native_t = Int32 Function(Pointer builder, Pointer cond_array, Uint64 count); +typedef obx_qb_join_op_dart_t = int Function(Pointer builder, Pointer cond_array, int count); + +typedef obx_qb_cond_string_op_1_native_t = Int32 Function( + Pointer builder, Uint32 property_id, Pointer value, Int8 case_sensitive); +typedef obx_qb_cond_string_op_1_dart_t = int Function( + Pointer builder, int property_id, Pointer value, int case_sensitive); + +typedef obx_qb_string_lt_gt_op_native_t = Int32 Function( + Pointer builder, Uint32 property_id, Pointer value, Int8 case_sensitive, Int8 with_equal); +typedef obx_qb_string_lt_gt_op_dart_t = int Function( + Pointer builder, int property_id, Pointer value, int case_sensitive, int with_equal); + +typedef obx_qb_string_in_native_t = Int32 Function( + Pointer builder, Uint32 property_id, Pointer> value, Uint64 count, Int8 case_sensitive); +typedef obx_qb_string_in_dart_t = int Function( + Pointer builder, int property_id, Pointer> value, int count, int case_sensitive); + +typedef obx_qb_bytes_eq_native_t = Int32 Function( + Pointer builder, Uint32 property_id, Pointer value, Uint64 size); +typedef obx_qb_bytes_eq_dart_t = int Function(Pointer builder, int property_id, Pointer value, int size); + +typedef obx_qb_bytes_lt_gt_native_t = Int32 Function( + Pointer builder, Uint32 property_id, Pointer value, Uint64 size, Int8 with_equal); +typedef obx_qb_bytes_lt_gt_dart_t = int Function( + Pointer builder, int property_id, Pointer value, int size, int with_equal); + +typedef obx_qb_param_alias_native_t = Int32 Function(Pointer builder, Pointer alias); +typedef obx_qb_param_alias_dart_t = int Function(Pointer builder, Pointer alias); + +typedef obx_qb_order_native_t = Int32 Function(Pointer builder, Uint32 property_id, Uint32 flags); +typedef obx_qb_order_dart_t = int Function(Pointer builder, int property_id, int flags); + +// query + +typedef obx_query_t = Pointer Function(Pointer builder); + +typedef obx_query_close_native_t = Int32 Function(Pointer query); +typedef obx_query_close_dart_t = int Function(Pointer query); + +typedef obx_query_find_t = Pointer Function(Pointer query, T offset, T limit); +typedef obx_query_find_ids_t = Pointer Function(Pointer query, T offset, T limit); + +typedef obx_query_count_native_t = Int32 Function(Pointer query, Pointer count); +typedef obx_query_count_dart_t = int Function(Pointer query, Pointer count); + +typedef obx_query_describe_t = Pointer Function(Pointer query); + +typedef obx_query_visit_native_t = Int32 Function( + Pointer query, Pointer visitor, Pointer user_data, Uint64 offset, Uint64 limit); +typedef obx_query_visit_dart_t = int Function( + Pointer query, Pointer visitor, Pointer user_data, int offset, int limit); + +// Utilities + +typedef obx_bytes_array_t = Pointer Function(SizeT count); +typedef obx_bytes_array_set_t = Ret Function( + Pointer array, SizeT index, Pointer data, SizeT size); diff --git a/lib/src/bindings/structs.dart b/lib/src/bindings/structs.dart index 014419696..21a56bb1f 100644 --- a/lib/src/bindings/structs.dart +++ b/lib/src/bindings/structs.dart @@ -1,97 +1,160 @@ -import "dart:ffi"; -import "dart:typed_data" show Uint8List; - -class IDArray { - // wrapper for "struct OBX_id_array" - Pointer _idsPtr, _structPtr; - - IDArray(List ids) { - _idsPtr = Pointer.allocate(count: ids.length); - for (int i = 0; i < ids.length; ++i) _idsPtr.elementAt(i).store(ids[i]); - _structPtr = Pointer.allocate(count: 2); - _structPtr.store(_idsPtr.address); - _structPtr.elementAt(1).store(ids.length); - } - - get ptr => _structPtr; +import 'dart:ffi'; +import "dart:typed_data" show Uint8List, Uint64List; + +import '../common.dart'; + +// Note: IntPtr seems to be the the correct representation for size_t: "Represents a native pointer-sized integer in C." + +/// Represents the following C struct: +/// struct OBX_id_array { +/// obx_id* ids; +/// size_t count; +/// }; +class OBX_id_array extends Struct { + Pointer _itemsPtr; + + @IntPtr() // size_t + int length; + + /// Get a copy of the list + List items() => Uint64List.view(_itemsPtr.asExternalTypedData(count: length).buffer).toList(); + + /// Execute the given function, managing the resources consistently + static R executeWith(List items, R Function(Pointer) fn) { + // allocate a temporary structure + final ptr = Pointer.allocate(); + + // fill it with data + OBX_id_array array = ptr.load(); + array.length = items.length; + array._itemsPtr = Pointer.allocate(count: array.length); + for (int i = 0; i < items.length; ++i) { + array._itemsPtr.elementAt(i).store(items[i]); + } - free() { - _idsPtr.free(); - _structPtr.free(); + // call the function with the structure and free afterwards + try { + return fn(ptr); + } finally { + array._itemsPtr.free(); + ptr.free(); + } } } -class ByteBuffer { - Pointer _ptr; - int _size; - - ByteBuffer(this._ptr, this._size); - - ByteBuffer.allocate(Uint8List dartData, [bool align = true]) { - _ptr = Pointer.allocate(count: align ? ((dartData.length + 3.0) ~/ 4.0) * 4 : dartData.length); - for (int i = 0; i < dartData.length; ++i) _ptr.elementAt(i).store(dartData[i]); - _size = dartData.length; - } - - ByteBuffer.fromOBXBytes(Pointer obxPtr) { - // extract fields from "struct OBX_bytes" - _ptr = Pointer.fromAddress(obxPtr.load()); - _size = obxPtr.elementAt(1).load(); - } - - get ptr => _ptr; - get voidPtr => Pointer.fromAddress(_ptr.address); - get address => _ptr.address; - get size => _size; +/// Represents the following C struct: +/// struct OBX_bytes { +/// const void* data; +/// size_t size; +/// }; +class OBX_bytes extends Struct { + Pointer _dataPtr; + + @IntPtr() // size_t + int length; + + /// Get access to the data (no-copy) + Uint8List get data => isEmpty + ? throw ObjectBoxException("can't access data of empty OBX_bytes") + : Uint8List.view(_dataPtr.asExternalTypedData(count: length).buffer); + + bool get isEmpty => length == 0 || _dataPtr.address == 0; + + Pointer get ptr => _dataPtr; + + /// Returns a pointer to OBX_bytes with copy of the passed data. + /// Warning: this creates an two unmanaged pointers which must be freed manually: OBX_bytes.freeManaged(result). + static Pointer managedCopyOf(Uint8List data) { + final ptr = Pointer.allocate(); + final OBX_bytes bytes = ptr.load(); + + const align = true; // ObjectBox requires data to be aligned to the length of 4 + bytes.length = align ? ((data.length + 3.0) ~/ 4.0) * 4 : data.length; + + // TODO (perf) find a way to get access to the underlying memory of Uint8List to avoid a copy + // In that case, don't forget to change the caller (FlatbuffersManager) which expect to get a copy + // if (data.length == bytes.length) { + // bytes._dataPtr = data.some-way-to-get-the-underlying-memory-pointer + // return ptr; + // } + + // create a copy of the data + bytes._dataPtr = Pointer.allocate(count: bytes.length); + for (int i = 0; i < data.length; ++i) { + bytes._dataPtr.elementAt(i).store(data[i]); + } - Uint8List get data { - var buffer = new Uint8List(size); - for (int i = 0; i < size; ++i) buffer[i] = _ptr.elementAt(i).load(); - return buffer; + return ptr; } - free() => _ptr.free(); -} - -class _SerializedByteBufferArray { - Pointer _outerPtr, - _innerPtr; // outerPtr points to the instance itself, innerPtr points to the respective OBX_bytes_array.bytes - - _SerializedByteBufferArray(this._outerPtr, this._innerPtr); - get ptr => _outerPtr; - - free() { - _innerPtr.free(); - _outerPtr.free(); + /// Free a dart-created OBX_bytes pointer. + static void freeManaged(Pointer ptr) { + final OBX_bytes bytes = ptr.load(); + bytes._dataPtr.free(); + ptr.free(); } } -class ByteBufferArray { - List _buffers; - - ByteBufferArray(this._buffers); - - ByteBufferArray.fromOBXBytesArray(Pointer bytesArray) { - _buffers = []; - Pointer bufferPtrs = Pointer.fromAddress(bytesArray.load()); // bytesArray.bytes - int numBuffers = bytesArray.elementAt(1).load(); // bytesArray.count - for (int i = 0; i < numBuffers; ++i) // loop through instances of "struct OBX_bytes" - _buffers.add(ByteBuffer.fromOBXBytes( - bufferPtrs.elementAt(2 * i))); // 2 * i, because each instance of "struct OBX_bytes" has .data and .size - } - - _SerializedByteBufferArray toOBXBytesArray() { - Pointer bufferPtrs = Pointer.allocate(count: _buffers.length * 2); - for (int i = 0; i < _buffers.length; ++i) { - bufferPtrs.elementAt(2 * i).store(_buffers[i].ptr.address as int); - bufferPtrs.elementAt(2 * i + 1).store(_buffers[i].size as int); +/// Represents the following C struct: +/// struct OBX_bytes_array { +/// OBX_bytes* bytes; +/// size_t count; +/// }; +class OBX_bytes_array extends Struct { + Pointer _items; + + @IntPtr() // size_t + int length; + + /// Get a list of the underlying OBX_bytes (a shallow copy). + List items() { + final result = List(); + for (int i = 0; i < length; i++) { + result.add(_items.elementAt(i).load()); } - - Pointer outerPtr = Pointer.allocate(count: 2); - outerPtr.store(bufferPtrs.address); - outerPtr.elementAt(1).store(_buffers.length); - return _SerializedByteBufferArray(outerPtr, bufferPtrs); + return result; } - get buffers => _buffers; + /// TODO: try this with new Dart 2.6 FFI... with the previous versions it was causing memory corruption issues. + /// It's supposed to be used by PutMany() +// /// Create a dart-managed OBX_bytes_array. +// static Pointer createManaged(int count) { +// final ptr = Pointer.allocate(); +// final OBX_bytes_array array = ptr.load(); +// array.length = count; +// array._items = Pointer.allocate(count: count); +// return ptr; +// } +// +// /// Replace the data at the given index with the passed pointer. +// void setAndFree(int i, Pointer src) { +// assert(i >= 0 && i < length); +// +// final OBX_bytes srcBytes = src.load(); +// final OBX_bytes tarBytes = _items.elementAt(i).load(); +// +// assert(!srcBytes.isEmpty); +// assert(tarBytes.isEmpty); +// +// tarBytes._dataPtr = srcBytes._dataPtr; +// tarBytes.length = srcBytes.length; +// +// srcBytes._dataPtr.store(nullptr.address); +// srcBytes.length = 0; +// src.free(); +// } +// +// /// Free a dart-created OBX_bytes pointer. +// static void freeManaged(Pointer ptr, bool freeIncludedBytes) { +// final OBX_bytes_array array = ptr.load(); +// if (freeIncludedBytes) { +// for (int i = 0; i < array.length; i++) { +// // Calling OBX_bytes.freeManaged() would cause double free +// final OBX_bytes bytes = array._items.elementAt(i).load(); +// bytes._dataPtr.free(); +// } +// } +// array._items.free(); +// ptr.free(); +// } } diff --git a/lib/src/box.dart b/lib/src/box.dart index dfd640241..3b77e2fde 100644 --- a/lib/src/box.dart +++ b/lib/src/box.dart @@ -1,5 +1,7 @@ import "dart:ffi"; +import 'dart:typed_data'; +import 'common.dart'; import "store.dart"; import "bindings/bindings.dart"; import "bindings/constants.dart"; @@ -7,6 +9,7 @@ import "bindings/flatbuffers.dart"; import "bindings/helpers.dart"; import "bindings/structs.dart"; import "modelinfo/index.dart"; +import "query/query.dart"; enum _PutMode { Put, @@ -22,10 +25,10 @@ class Box { OBXFlatbuffersManager _fbManager; Box(this._store) { - EntityDefinition entityDefs = _store.entityDef(T); + EntityDefinition entityDefs = _store.entityDef(); _modelEntity = entityDefs.getModel(); _entityReader = entityDefs.reader; - _fbManager = new OBXFlatbuffersManager(_modelEntity, entityDefs.writer); + _fbManager = OBXFlatbuffersManager(_modelEntity, entityDefs.writer); _cBox = bindings.obx_box(_store.ptr, _modelEntity.id.id); checkObxPtr(_cBox, "failed to create box"); @@ -51,19 +54,21 @@ class Box { } // put object into box and free the buffer - ByteBuffer buffer = _fbManager.marshal(propVals); + final Pointer bytesPtr = _fbManager.marshal(propVals); try { + final OBX_bytes bytes = bytesPtr.load(); checkObx(bindings.obx_box_put( - _cBox, propVals[_modelEntity.idPropName], buffer.voidPtr, buffer.size, _getOBXPutMode(mode))); + _cBox, propVals[_modelEntity.idPropName], bytes.ptr, bytes.length, _getOBXPutMode(mode))); } finally { - buffer.free(); + // because fbManager.marshal() allocates the inner bytes, we need to clean those as well + OBX_bytes.freeManaged(bytesPtr); } return propVals[_modelEntity.idPropName]; } // only instances whose ID property ot null or 0 will be given a new, valid number for that. A list of the final IDs is returned List putMany(List objects, {_PutMode mode = _PutMode.Put}) { - if (objects.length == 0) return []; + if (objects.isEmpty) return []; // read all property values and find number of instances where ID is missing var allPropVals = objects.map(_entityReader).toList(); @@ -95,52 +100,66 @@ class Box { // generate this list now (only needed if not all IDs have been generated) Pointer allIdsMemory = Pointer.allocate(count: objects.length); try { - for (int i = 0; i < allPropVals.length; ++i) + for (int i = 0; i < allPropVals.length; ++i) { allIdsMemory.elementAt(i).store(allPropVals[i][_modelEntity.idPropName] as int); + } // marshal all objects to be put into the box - var putObjects = ByteBufferArray(allPropVals.map(_fbManager.marshal).toList()).toOBXBytesArray(); + // final bytesArrayPtr = OBX_bytes_array.createManaged(allPropVals.length); + final bytesArrayPtr = + checkObxPtr(bindings.obx_bytes_array(allPropVals.length), "could not create OBX_bytes_array"); + final listToFree = List>(); try { - checkObx(bindings.obx_box_put_many(_cBox, putObjects.ptr, allIdsMemory, _getOBXPutMode(mode))); + // final OBX_bytes_array bytesArray = bytesArrayPtr.load(); + for (int i = 0; i < allPropVals.length; i++) { + // bytesArray.setAndFree(i, _fbManager.marshal(allPropVals[i])); + final bytesPtr = _fbManager.marshal(allPropVals[i]); + listToFree.add(bytesPtr); + final OBX_bytes bytes = bytesPtr.load(); + bindings.obx_bytes_array_set(bytesArrayPtr, i, bytes.ptr, bytes.length); + } + + checkObx(bindings.obx_box_put_many(_cBox, bytesArrayPtr, allIdsMemory, _getOBXPutMode(mode))); } finally { - putObjects.free(); + // OBX_bytes_array.freeManaged(bytesArrayPtr, true); + bindings.obx_bytes_array_free(bytesArrayPtr); + listToFree.forEach(OBX_bytes.freeManaged); } } finally { allIdsMemory.free(); } + return allPropVals.map((p) => p[_modelEntity.idPropName] as int).toList(); } get(int id) { - Pointer> dataPtr = Pointer>.allocate(); - Pointer sizePtr = Pointer.allocate(); + final dataPtrPtr = Pointer>.allocate(); + final sizePtr = Pointer.allocate(); - // get element with specified id from database - return _store.runInTransaction(TxMode.Read, () { - ByteBuffer buffer; - try { - checkObx(bindings.obx_box_get(_cBox, id, dataPtr, sizePtr)); + try { + // get element with specified id from database + return _store.runInTransaction(TxMode.Read, () { + checkObx(bindings.obx_box_get(_cBox, id, dataPtrPtr, sizePtr)); - Pointer data = Pointer.fromAddress(dataPtr.load>().address); - var size = sizePtr.load(); + Pointer dataPtr = dataPtrPtr.load(); + final size = sizePtr.load(); - // transform bytes from memory to Dart byte list - buffer = ByteBuffer(data, size); - } finally { - dataPtr.free(); - sizePtr.free(); - } + // create a no-copy view + final bytes = Uint8List.view(dataPtr.asExternalTypedData(count: size).buffer); - return _fbManager.unmarshal(buffer); - }); + return _fbManager.unmarshal(bytes); + }); + } finally { + dataPtrPtr.free(); + sizePtr.free(); + } } - List _getMany(Pointer Function() cCall) { + List _getMany(bool allowMissing, Pointer Function() cCall) { return _store.runInTransaction(TxMode.Read, () { - // OBX_bytes_array*, has two Uint64 members (data and size) - Pointer bytesArray = cCall(); + final bytesArray = cCall(); try { - return _fbManager.unmarshalArray(bytesArray); + return _fbManager.unmarshalArray(bytesArray, allowMissing: allowMissing); } finally { bindings.obx_bytes_array_free(bytesArray); } @@ -149,21 +168,92 @@ class Box { // returns list of ids.length objects of type T, each corresponding to the location of its ID in the ids array. Non-existant IDs become null List getMany(List ids) { - if (ids.length == 0) return []; + if (ids.isEmpty) return []; + + const bool allowMissing = true; // returns null if null is encountered in the data found + return OBX_id_array.executeWith( + ids, + (ptr) => _getMany(allowMissing, + () => checkObxPtr(bindings.obx_box_get_many(_cBox, ptr), "failed to get many objects from box"))); + } + + List getAll() { + const bool allowMissing = false; // throw if null is encountered in the data found + return _getMany( + allowMissing, () => checkObxPtr(bindings.obx_box_get_all(_cBox), "failed to get all objects from box")); + } - // write ids in buffer for FFI call - var idArray = new IDArray(ids); + QueryBuilder query(Condition qc) => QueryBuilder(_store, _fbManager, _modelEntity.id.id, qc); + int count({int limit = 0}) { + Pointer count = Pointer.allocate(); try { - return _getMany(() => - checkObxPtr(bindings.obx_box_get_many(_cBox, idArray.ptr), "failed to get many objects from box", true)); + checkObx(bindings.obx_box_count(_cBox, limit, count)); + return count.load(); } finally { - idArray.free(); + count.free(); } } - List getAll() { - return _getMany(() => checkObxPtr(bindings.obx_box_get_all(_cBox), "failed to get all objects from box", true)); + bool isEmpty() { + Pointer isEmpty = Pointer.allocate(); + try { + checkObx(bindings.obx_box_is_empty(_cBox, isEmpty)); + return isEmpty.load() > 0 ? true : false; + } finally { + isEmpty.free(); + } + } + + bool contains(int id) { + Pointer contains = Pointer.allocate(); + try { + checkObx(bindings.obx_box_contains(_cBox, id, contains)); + return contains.load() > 0 ? true : false; + } finally { + contains.free(); + } + } + + bool containsMany(List ids) { + Pointer contains = Pointer.allocate(); + try { + return OBX_id_array.executeWith(ids, (ptr) { + checkObx(bindings.obx_box_contains_many(_cBox, ptr, contains)); + return contains.load() > 0 ? true : false; + }); + } finally { + contains.free(); + } + } + + bool remove(int id) { + final err = bindings.obx_box_remove(_cBox, id); + if (err == OBXError.OBX_NOT_FOUND) return false; + checkObx(err); // throws on other errors + return true; + } + + int removeMany(List ids) { + Pointer removedIds = Pointer.allocate(); + try { + return OBX_id_array.executeWith(ids, (ptr) { + checkObx(bindings.obx_box_remove_many(_cBox, ptr, removedIds)); + return removedIds.load(); + }); + } finally { + removedIds.free(); + } + } + + int removeAll() { + Pointer removedItems = Pointer.allocate(); + try { + checkObx(bindings.obx_box_remove_all(_cBox, removedItems)); + return removedItems.load(); + } finally { + removedItems.free(); + } } get ptr => _cBox; diff --git a/lib/src/common.dart b/lib/src/common.dart index 45e54b003..84bec8479 100644 --- a/lib/src/common.dart +++ b/lib/src/common.dart @@ -1,7 +1,6 @@ import "dart:ffi"; import "bindings/bindings.dart"; -import "package:ffi/ffi.dart"; class Version { final int major; @@ -27,10 +26,13 @@ Version versionLib() { } } -class ObjectBoxException { +class ObjectBoxException implements Exception { final String message; + final String msg; - ObjectBoxException(msg) : message = "ObjectBoxException: " + msg; + ObjectBoxException(msg) + : message = "ObjectBoxException: " + msg, + msg = msg; String toString() => message; } diff --git a/lib/src/model.dart b/lib/src/model.dart index e7a1fe2f1..83e3a4794 100644 --- a/lib/src/model.dart +++ b/lib/src/model.dart @@ -1,52 +1,27 @@ import "dart:ffi"; +import "package:ffi/ffi.dart"; +import 'bindings/constants.dart'; import "bindings/bindings.dart"; import "bindings/helpers.dart"; +import 'common.dart'; import "modelinfo/index.dart"; -import "package:ffi/ffi.dart"; - class Model { Pointer _cModel; + get ptr => _cModel; + Model(List modelEntities) { - _cModel = bindings.obx_model(); - checkObxPtr(_cModel, "failed to create model"); + _cModel = checkObxPtr(bindings.obx_model(), "failed to create model"); try { // transform classes into model descriptions and loop through them - modelEntities.forEach((currentEntity) { - // start entity - var entityUtf8 = Utf8.toUtf8(currentEntity.name); - try { - var entityNamePointer = entityUtf8.cast(); - checkObx( - bindings.obx_model_entity(_cModel, entityNamePointer, currentEntity.id.id, currentEntity.id.uid)); - } finally { - entityUtf8.free(); - } - - // add all properties - currentEntity.properties.forEach((p) { - var propertyUtf8 = Utf8.toUtf8(p.name); - try { - var propertyNamePointer = propertyUtf8.cast(); - checkObx(bindings.obx_model_property(_cModel, propertyNamePointer, p.type, p.id.id, p.id.uid)); - checkObx(bindings.obx_model_property_flags(_cModel, p.flags)); - } finally { - propertyUtf8.free(); - } - }); - - // set last property id - if (currentEntity.properties.length > 0) { - ModelProperty lastProp = currentEntity.properties[currentEntity.properties.length - 1]; - checkObx(bindings.obx_model_entity_last_property_id(_cModel, lastProp.id.id, lastProp.id.uid)); - } - }); + modelEntities.forEach(addEntity); // set last entity id - if (modelEntities.length > 0) { + // TODO read last entity ID from the model + if (modelEntities.isNotEmpty) { ModelEntity lastEntity = modelEntities[modelEntities.length - 1]; bindings.obx_model_last_entity_id(_cModel, lastEntity.id.id, lastEntity.id.uid); } @@ -57,5 +32,41 @@ class Model { } } - get ptr => _cModel; + void _check(int errorCode) { + if (errorCode == OBXError.OBX_SUCCESS) return; + + int code = bindings.obx_model_error_code(_cModel); + String text = cString(bindings.obx_model_error_message(_cModel)); + + throw ObjectBoxException("$code $text"); + } + + void addEntity(ModelEntity entity) { + // start entity + var name = Utf8.toUtf8(entity.name); + try { + _check(bindings.obx_model_entity(_cModel, name.cast(), entity.id.id, entity.id.uid)); + } finally { + name.free(); + } + + // add all properties + entity.properties.forEach(addProperty); + + // set last property id + _check(bindings.obx_model_entity_last_property_id(_cModel, entity.lastPropertyId.id, entity.lastPropertyId.uid)); + } + + void addProperty(ModelProperty prop) { + var name = Utf8.toUtf8(prop.name); + try { + _check(bindings.obx_model_property(_cModel, name.cast(), prop.type, prop.id.id, prop.id.uid)); + } finally { + name.free(); + } + + if (prop.flags != 0) { + _check(bindings.obx_model_property_flags(_cModel, prop.flags)); + } + } } diff --git a/lib/src/modelinfo/modelentity.dart b/lib/src/modelinfo/modelentity.dart index 2ef320259..426e095eb 100644 --- a/lib/src/modelinfo/modelentity.dart +++ b/lib/src/modelinfo/modelentity.dart @@ -16,40 +16,43 @@ class ModelEntity { ModelEntity.fromMap(Map data, this.model) { id = IdUid(data["id"]); - if (data.containsKey("lastPropertyId") && data["lastPropertyId"] != null) - lastPropertyId = IdUid(data["lastPropertyId"]); + lastPropertyId = IdUid(data["lastPropertyId"]); name = data["name"]; properties = data["properties"].map((p) => ModelProperty.fromMap(p, this)).toList(); validate(); } void validate() { - if (name == null || name.length == 0) throw Exception("name is not defined"); + if (name == null || name.isEmpty) throw Exception("name is not defined"); if (properties == null) throw Exception("properties is null"); if (model == null) throw Exception("model is null"); - if (properties.length == 0) { + if (properties.isEmpty) { if (lastPropertyId != null) throw Exception("lastPropertyId is not null although there are no properties"); } else { var entity = this; bool lastPropertyIdFound = false; properties.forEach((p) { - if (p.entity != entity) + if (p.entity != entity) { throw Exception("property '${p.name}' with id ${p.id.toString()} has incorrect parent entity reference"); - if (lastPropertyId.id < p.id.id) + } + if (lastPropertyId.id < p.id.id) { throw Exception( "lastPropertyId ${lastPropertyId.toString()} is lower than the one of property '${p.name}' with id ${p.id.toString()}"); + } if (lastPropertyId.id == p.id.id) { - if (lastPropertyId.uid != p.id.uid) + if (lastPropertyId.uid != p.id.uid) { throw Exception( "lastPropertyId ${lastPropertyId.toString()} does not match property '${p.name}' with id ${p.id.toString()}"); + } lastPropertyIdFound = true; } }); - if (properties.length > 0 && !lastPropertyIdFound) + if (properties.isNotEmpty && !lastPropertyIdFound) { throw Exception("lastPropertyId ${lastPropertyId.toString()} does not match any property"); + } } for (int i = 0; i < properties.length; ++i) { @@ -77,7 +80,7 @@ class ModelEntity { ModelProperty findPropertyByName(String name) { final found = properties.where((p) => p.name.toLowerCase() == name.toLowerCase()).toList(); - if (found.length == 0) return null; + if (found.isEmpty) return null; if (found.length >= 2) throw Exception("ambiguous property name: $name; please specify a UID in its annotation"); return found[0]; } @@ -91,7 +94,7 @@ class ModelEntity { ModelProperty createProperty(String name, [int uid = 0]) { int id = 1; - if (properties.length > 0) id = lastPropertyId.id + 1; + if (properties.isNotEmpty) id = lastPropertyId.id + 1; if (uid != 0 && model.containsUid(uid)) throw Exception("uid already exists: $uid"); int uniqueUid = uid == 0 ? model.generateUid() : uid; @@ -119,8 +122,9 @@ class ModelEntity { void removeProperty(ModelProperty prop) { if (prop == null) return; ModelProperty foundProp = findSameProperty(prop); - if (foundProp == null) + if (foundProp == null) { throw Exception("cannot remove property '${prop.name}' with id ${prop.id.toString()}: not found"); + } properties = properties.where((p) => p != foundProp).toList(); model.retiredPropertyUids.add(prop.id.uid); _recalculateLastPropertyId(); diff --git a/lib/src/modelinfo/modelinfo.dart b/lib/src/modelinfo/modelinfo.dart index b388d5426..86befd60f 100644 --- a/lib/src/modelinfo/modelinfo.dart +++ b/lib/src/modelinfo/modelinfo.dart @@ -49,12 +49,14 @@ class ModelInfo { } void validate() { - if (modelVersion < _minModelVersion) + if (modelVersion < _minModelVersion) { throw Exception( "the loaded model is too old: version $modelVersion while the minimum supported is $_minModelVersion, consider upgrading with an older generator or manually"); - if (modelVersion > _maxModelVersion) + } + if (modelVersion > _maxModelVersion) { throw Exception( "the loaded model has been created with a newer generator version $modelVersion, while the maximimum supported version is $_maxModelVersion. Please upgrade your toolchain/generator"); + } if (entities == null) throw Exception("entities is null"); if (retiredEntityUids == null) throw Exception("retiredEntityUids is null"); @@ -65,21 +67,25 @@ class ModelInfo { var model = this; bool lastEntityIdFound = false; entities.forEach((e) { - if (e.model != model) + if (e.model != model) { throw Exception("entity '${e.name}' with id ${e.id.toString()} has incorrect parent model reference"); - if (lastEntityId.id < e.id.id) + } + if (lastEntityId.id < e.id.id) { throw Exception( "lastEntityId ${lastEntityId.toString()} is lower than the one of entity '${e.name}' with id ${e.id.toString()}"); + } if (lastEntityId.id == e.id.id) { - if (lastEntityId.uid != e.id.uid) + if (lastEntityId.uid != e.id.uid) { throw Exception( "lastEntityId ${lastEntityId.toString()} does not match entity '${e.name}' with id ${e.id.toString()}"); + } lastEntityIdFound = true; } }); - if (entities.length > 0 && !lastEntityIdFound) + if (entities.isNotEmpty && !lastEntityIdFound) { throw Exception("lastEntityId ${lastEntityId.toString()} does not match any entity"); + } } Map toMap() { @@ -109,7 +115,7 @@ class ModelInfo { ModelEntity findEntityByName(String name) { final found = entities.where((e) => e.name.toLowerCase() == name.toLowerCase()).toList(); - if (found.length == 0) return null; + if (found.isEmpty) return null; if (found.length >= 2) throw Exception("ambiguous entity name: $name; please specify a UID in its annotation"); return found[0]; } @@ -129,18 +135,18 @@ class ModelInfo { ModelEntity createEntity(String name, [int uid = 0]) { int id = 1; - if (entities.length > 0) id = lastEntityId.id + 1; + if (entities.isNotEmpty) id = lastEntityId.id + 1; if (uid != 0 && containsUid(uid)) throw Exception("uid already exists: $uid"); int uniqueUid = uid == 0 ? generateUid() : uid; - var entity = new ModelEntity(IdUid.create(id, uniqueUid), null, name, [], this); + var entity = ModelEntity(IdUid.create(id, uniqueUid), null, name, [], this); entities.add(entity); lastEntityId = entity.id; return entity; } int generateUid() { - var rng = new Random(); + var rng = Random(); for (int i = 0; i < 1000; ++i) { // Dart can only generate random numbers up to 1 << 32, so concat two of them and remove the upper bit to make the number non-negative int uid = rng.nextInt(1 << 32); diff --git a/lib/src/query/builder.dart b/lib/src/query/builder.dart new file mode 100644 index 000000000..a05048acc --- /dev/null +++ b/lib/src/query/builder.dart @@ -0,0 +1,75 @@ +part of query; + +// Construct a tree from the first condition object +class QueryBuilder { + Store _store; + int _entityId; // aka model id, entity id + Condition _queryCondition; + Pointer _cBuilder; + OBXFlatbuffersManager _fbManager; + + QueryBuilder(this._store, this._fbManager, this._entityId, this._queryCondition); + + void _throwExceptionIfNecessary() { + if (bindings.obx_qb_error_code(_cBuilder) != OBXError.OBX_SUCCESS) { + final msg = cString(bindings.obx_qb_error_message(_cBuilder)); + throw ObjectBoxException("$msg"); + } + } + + _createBuilder() => _cBuilder ??= bindings.obx_qb_create(_store.ptr, _entityId); + + Query build() { + _createBuilder(); + + if (0 == _queryCondition.apply(this, true)) { + _throwExceptionIfNecessary(); + } + + try { + return Query._(_store, _fbManager, _cBuilder); + } finally { + checkObx(bindings.obx_qb_close(_cBuilder)); + } + } + + QueryBuilder order(QueryProperty p, {int flags = 0}) { + _createBuilder(); + checkObx(bindings.obx_qb_order(_cBuilder, p._propertyId, flags)); + return this; + } +} + +/* // Not done yet + obx_qb_bytes_eq_dart_t obx_qb_bytes_equal; + obx_qb_bytes_lt_gt_dart_t obx_qb_bytes_greater, obx_qb_bytes_less; + + obx_qb_param_alias_dart_t obx_qb_param_alias; +*/ + +////// +////// + +/** Inspiration + Modifier and Type Method Description + QueryBuilder backlink​(RelationInfo relationInfo) + Creates a backlink (reversed link) to another entity, for which you also can describe conditions using the returned builder. + void close() + ** QueryBuilder eager​(int limit, RelationInfo relationInfo, RelationInfo... more) + Like eager(RelationInfo, RelationInfo[]), but limits eager loading to the given count. + ** QueryBuilder eager​(RelationInfo relationInfo, RelationInfo... more) + Specifies relations that should be resolved eagerly. + ** QueryBuilder filter​(QueryFilter filter) // dart has built-in higher order functions + Sets a filter that executes on primary query results (returned from the db core) on a Java level. + QueryBuilder link​(RelationInfo relationInfo) + Creates a link to another entity, for which you also can describe conditions using the returned builder. + ** QueryBuilder order​(Property property) + Specifies given property to be used for sorting. + ** QueryBuilder order​(Property property, int flags) + Defines the order with which the results are ordered (default: none). + ** QueryBuilder orderDesc​(Property property) + Specifies given property in descending order to be used for sorting. + ** QueryBuilder parameterAlias​(java.lang.String alias) + Assigns the given alias to the previous condition. + ** QueryBuilder sort​(java.util.Comparator comparator) + */ diff --git a/lib/src/query/query.dart b/lib/src/query/query.dart new file mode 100644 index 000000000..2a7e1d7be --- /dev/null +++ b/lib/src/query/query.dart @@ -0,0 +1,597 @@ +library query; + +import "dart:ffi"; + +import "../store.dart"; +import "../common.dart"; +import "../bindings/bindings.dart"; +import "../bindings/constants.dart"; +import "../bindings/flatbuffers.dart"; +import "../bindings/helpers.dart"; +import "../bindings/structs.dart"; +import "../bindings/signatures.dart"; +import "package:ffi/ffi.dart"; + +part "builder.dart"; + +class Order { + /// Reverts the order from ascending (default) to descending. + static final descending = 1; + + /// Makes upper case letters (e.g. "Z") be sorted before lower case letters (e.g. "a"). + /// If not specified, the default is case insensitive for ASCII characters. + static final caseSensitive = 2; + + /// For scalars only: changes the comparison to unsigned (default is signed). + static final unsigned = 4; + + /// null values will be put last. + /// If not specified, by default null values will be put first. + static final nullsLast = 8; + + /// null values should be treated equal to zero (scalars only). + static final nullsAsZero = 16; +} + + +/// The QueryProperty types are responsible for the operator overloading. +/// A QueryBuilder will be constructed, based on the any / all operations applied. +/// When build() is called on the QueryBuilder a Query object will be created. +class QueryProperty { + int _propertyId, _entityId, _type; + + QueryProperty(this._entityId, this._propertyId, this._type); + + Condition isNull() { + // the integer serves as a dummy type, to initialize the base type + return IntegerCondition(ConditionOp.isNull, this, null, null); + } + + Condition notNull() { + return IntegerCondition(ConditionOp.notNull, this, null, null); + } +} + +class QueryStringProperty extends QueryProperty { + QueryStringProperty({int entityId, int propertyId, int obxType}) : super(entityId, propertyId, obxType); + + Condition _op(String p, ConditionOp cop, [bool caseSensitive = false, bool descending = false]) { + return StringCondition(cop, this, p, null, caseSensitive, descending); + } + + Condition _opWithEqual(String p, ConditionOp cop, [bool caseSensitive = false, bool withEqual = false]) { + return StringCondition._withEqual(cop, this, p, caseSensitive, withEqual); + } + + Condition _opList(List list, ConditionOp cop, [bool caseSensitive = false]) { + return StringCondition._fromList(cop, this, list, caseSensitive); + } + + Condition equals(String p, {bool caseSensitive = false}) { + return _op(p, ConditionOp.eq, caseSensitive, false); + } + + Condition notEquals(String p, {bool caseSensitive = false}) { + return _op(p, ConditionOp.notEq, caseSensitive, false); + } + + Condition endsWith(String p, {bool descending = false}) { + return _op(p, ConditionOp.stringEnds, false, descending); + } + + Condition startsWith(String p, {bool descending = false}) { + return _op(p, ConditionOp.stringStarts, false, descending); + } + + Condition contains(String p, {bool caseSensitive = false}) { + return _op(p, ConditionOp.stringContains, caseSensitive, false); + } + + Condition inside(List list, {bool caseSensitive = false}) { + return _opList(list, ConditionOp.inside, caseSensitive); + } + + Condition notIn(List list, {bool caseSensitive = false}) { + return _opList(list, ConditionOp.notIn, caseSensitive); + } + + Condition greaterThan(String p, {bool caseSensitive = false, bool withEqual = false}) { + return _opWithEqual(p, ConditionOp.gt, caseSensitive, withEqual); + } + + Condition lessThan(String p, {bool caseSensitive = false, bool withEqual = false}) { + return _opWithEqual(p, ConditionOp.lt, caseSensitive, withEqual); + } + +// Condition operator ==(String p) => equals(p); // see issue #43 +// Condition operator != (String p) => notEqual(p); // not overloadable +} + +class QueryIntegerProperty extends QueryProperty { + QueryIntegerProperty({int entityId, int propertyId, int obxType}) : super(entityId, propertyId, obxType); + + Condition _op(int p, ConditionOp cop) { + return IntegerCondition(cop, this, p, 0); + } + + Condition _opList(List list, ConditionOp cop) { + return IntegerCondition.fromList(cop, this, list); + } + + Condition equals(int p) { + return _op(p, ConditionOp.eq); + } + + Condition notEquals(int p) { + return _op(p, ConditionOp.notEq); + } + + Condition greaterThan(int p) { + return _op(p, ConditionOp.gt); + } + + Condition lessThan(int p) { + return _op(p, ConditionOp.lt); + } + + Condition operator <(int p) => lessThan(p); + + Condition operator >(int p) => greaterThan(p); + + Condition inside(List list) { + return _opList(list, ConditionOp.inside); + } + + Condition notInList(List list) { + return _opList(list, ConditionOp.notIn); + } + + Condition notIn(List list) { + return notInList(list); + } + +// Condition operator != (int p) => notEqual(p); // not overloadable +// Condition operator ==(int p) => equals(p); // see issue #43 +} + +class QueryDoubleProperty extends QueryProperty { + QueryDoubleProperty({int entityId, int propertyId, int obxType}) : super(entityId, propertyId, obxType); + + Condition _op(ConditionOp op, double p1, double p2) { + return DoubleCondition(op, this, p1, p2); + } + + Condition between(double p1, double p2) { + return _op(ConditionOp.between, p1, p2); + } + + // NOTE: objectbox-c doesn't support double/float equality (because it's a rather peculiar thing). + // Therefore, we're currently not providing this in Dart either, not even with some `between()` workarounds. + // Condition equals(double p) { + // _op(ConditionOp.eq, p); + // } + + Condition greaterThan(double p) { + return _op(ConditionOp.gt, p, null); + } + + Condition lessThan(double p) { + return _op(ConditionOp.lt, p, null); + } + + Condition operator <(double p) => lessThan(p); + + Condition operator >(double p) => greaterThan(p); + +// Note: currently not supported - override the operator and throw explicitly to prevent the default comparison. +// void operator ==(double p) => DoubleCondition(ConditionOp.eq, this, null, null); // see issue #43 +} + +class QueryBooleanProperty extends QueryProperty { + QueryBooleanProperty({int entityId, int propertyId, int obxType}) : super(entityId, propertyId, obxType); + + Condition equals(bool p) { + return IntegerCondition(ConditionOp.eq, this, (p ? 1 : 0)); + } + + Condition notEquals(bool p) { + return IntegerCondition(ConditionOp.notEq, this, (p ? 1 : 0)); + } + +// Condition operator ==(bool p) => equals(p); // see issue #43 +} + +enum ConditionOp { + isNull, + notNull, + eq, + notEq, + stringContains, + stringStarts, + stringEnds, + gt, + lt, + inside, + notIn, + between, +} + +abstract class Condition { + // using & because && is not overridable + Condition operator &(Condition rh) => and(rh); + + Condition and(Condition rh) { + if (this is ConditionGroupAll) { + // no need for brackets + return ConditionGroupAll([...(this as ConditionGroupAll)._conditions, rh]); + } + return ConditionGroupAll([this, rh]); + } + + Condition andAll(List rh) { + return ConditionGroupAll([this, ...rh]); + } + + // using | because || is not overridable + Condition operator |(Condition rh) => or(rh); + + Condition or(Condition rh) { + if (this is ConditionGroupAny) { + // no need for brackets + return ConditionGroupAny([...(this as ConditionGroupAny)._conditions, rh]); + } + return ConditionGroupAny([this, rh]); + } + + Condition orAny(List rh) { + return ConditionGroupAny([this, ...rh]); + } + + int apply(QueryBuilder builder, bool isRoot); +} + +abstract class PropertyCondition extends Condition { + QueryProperty _property; + DartType _value, _value2; + List _list; + + ConditionOp _op; + + PropertyCondition(this._op, this._property, this._value, [this._value2]); + + PropertyCondition.fromList(this._op, this._property, this._list); + + int tryApply(QueryBuilder builder) { + switch (_op) { + case ConditionOp.isNull: + return bindings.obx_qb_null(builder._cBuilder, _property._propertyId); + case ConditionOp.notNull: + return bindings.obx_qb_not_null(builder._cBuilder, _property._propertyId); + default: + return 0; + } + } +} + +class StringCondition extends PropertyCondition { + bool _caseSensitive, _withEqual; + + StringCondition(ConditionOp op, QueryProperty prop, String value, + [String value2, bool caseSensitive, bool descending]) + : super(op, prop, value, value2) { + _caseSensitive = caseSensitive; + } + + StringCondition._fromList(ConditionOp op, QueryProperty prop, List list, bool caseSensitive) + : super.fromList(op, prop, list) { + _caseSensitive = caseSensitive; + } + + StringCondition._withEqual(ConditionOp op, QueryProperty prop, String value, bool caseSensitive, bool withEqual) + : super(op, prop, value) { + _caseSensitive = caseSensitive; + _withEqual = withEqual; + } + + int _op1(QueryBuilder builder, obx_qb_cond_string_op_1_dart_t func) { + final utf8Str = Utf8.toUtf8(_value); + try { + var uint8Str = utf8Str.cast(); + return func(builder._cBuilder, _property._propertyId, uint8Str, _caseSensitive ? 1 : 0); + } finally { + utf8Str.free(); + } + } + + int _inside(QueryBuilder builder) { + final func = bindings.obx_qb_string_in; + final listLength = _list.length; + final arrayOfUint8Ptrs = Pointer>.allocate(count: listLength); + try { + for (int i = 0; i < _list.length; i++) { + var uint8Str = Utf8.toUtf8(_list[i]).cast(); + arrayOfUint8Ptrs.elementAt(i).store(uint8Str); + } + return func(builder._cBuilder, _property._propertyId, arrayOfUint8Ptrs, listLength, _caseSensitive ? 1 : 0); + } finally { + for (int i = 0; i < _list.length; i++) { + var uint8Str = arrayOfUint8Ptrs.elementAt(i).load(); + uint8Str.free(); // I assume the casted Uint8 retains the same Utf8 address + } + arrayOfUint8Ptrs.free(); // It probably doesn't release recursively + } + } + + int _opWithEqual(QueryBuilder builder, obx_qb_string_lt_gt_op_dart_t func) { + final utf8Str = Utf8.toUtf8(_value); + try { + var uint8Str = utf8Str.cast(); + return func(builder._cBuilder, _property._propertyId, uint8Str, _caseSensitive ? 1 : 0, _withEqual ? 1 : 0); + } finally { + utf8Str.free(); + } + } + + int apply(QueryBuilder builder, bool isRoot) { + final c = tryApply(builder); + if (c != 0) { + return c; + } + + switch (_op) { + case ConditionOp.eq: + return _op1(builder, bindings.obx_qb_string_equal); + case ConditionOp.notEq: + return _op1(builder, bindings.obx_qb_string_not_equal); + case ConditionOp.stringContains: + return _op1(builder, bindings.obx_qb_string_contains); + case ConditionOp.stringStarts: + return _op1(builder, bindings.obx_qb_string_starts_with); + case ConditionOp.stringEnds: + return _op1(builder, bindings.obx_qb_string_ends_with); + case ConditionOp.lt: + return _opWithEqual(builder, bindings.obx_qb_string_less); + case ConditionOp.gt: + return _opWithEqual(builder, bindings.obx_qb_string_greater); + case ConditionOp.inside: + return _inside(builder); // bindings.obx_qb_string_in + default: + throw Exception("Unsupported operation ${_op.toString()}"); + } + } +} + +class IntegerCondition extends PropertyCondition { + IntegerCondition(ConditionOp op, QueryProperty prop, int value, [int value2]) : super(op, prop, value, value2); + + IntegerCondition.fromList(ConditionOp op, QueryProperty prop, List list) : super.fromList(op, prop, list); + + int _op1(QueryBuilder builder, obx_qb_cond_operator_1_dart_t func) { + return func(builder._cBuilder, _property._propertyId, _value); + } + + // ideally it should be implemented like this, but this doesn't work, TODO report to google + /* + int _opList

(QueryBuilder builder, obx_qb_cond_operator_in_dart_t

func) { + + int length = _list.length; + final listPtr = Pointer

.allocate(count: length); + try { + for (int i=0; i func) { + int length = _list.length; + final listPtr = Pointer.allocate(count: length); + try { + for (int i = 0; i < length; i++) { + listPtr.elementAt(i).store(_list[i]); + } + return func(builder._cBuilder, _property._propertyId, listPtr, length); + } finally { + listPtr.free(); + } + } + + // TODO replace duplication with implementation above, when fix is in + int _opList64(QueryBuilder builder, obx_qb_cond_operator_in_dart_t func) { + int length = _list.length; + final listPtr = Pointer.allocate(count: length); + try { + for (int i = 0; i < length; i++) { + listPtr.elementAt(i).store(_list[i]); + } + return func(builder._cBuilder, _property._propertyId, listPtr, length); + } finally { + listPtr.free(); + } + } + + int apply(QueryBuilder builder, bool isRoot) { + final c = tryApply(builder); + if (c != 0) { + return c; + } + + switch (_op) { + case ConditionOp.eq: + return _op1(builder, bindings.obx_qb_int_equal); + case ConditionOp.notEq: + return _op1(builder, bindings.obx_qb_int_not_equal); + case ConditionOp.gt: + return _op1(builder, bindings.obx_qb_int_greater); + case ConditionOp.lt: + return _op1(builder, bindings.obx_qb_int_less); + case ConditionOp.between: + return bindings.obx_qb_int_between(builder._cBuilder, _property._propertyId, _value, _value2); + case ConditionOp.inside: + switch (_property._type) { + case OBXPropertyType.Int: + return _opList32(builder, bindings.obx_qb_int32_in); + case OBXPropertyType.Long: + return _opList64(builder, bindings.obx_qb_int64_in); + default: + throw Exception("Unsupported type for IN: ${_property._type}"); + } + break; + case ConditionOp.notIn: + switch (_property._type) { + case OBXPropertyType.Int: + return _opList32(builder, bindings.obx_qb_int32_not_in); + case OBXPropertyType.Long: + return _opList64(builder, bindings.obx_qb_int64_not_in); + default: + throw Exception("Unsupported type for IN: ${_property._type}"); + } + break; + default: + throw Exception("Unsupported operation ${_op.toString()}"); + } + } +} + +class DoubleCondition extends PropertyCondition { + DoubleCondition(ConditionOp op, QueryProperty prop, double value, double value2) : super(op, prop, value, value2) { + assert( + op != ConditionOp.eq, "Equality operator is not supported on floating point numbers - use between() instead."); + } + + int _op1(QueryBuilder builder, obx_qb_cond_operator_1_dart_t func) { + return func(builder._cBuilder, _property._propertyId, _value); + } + + int apply(QueryBuilder builder, bool isRoot) { + final c = tryApply(builder); + if (c != 0) { + return c; + } + + switch (_op) { + case ConditionOp.gt: + return _op1(builder, bindings.obx_qb_double_greater); + case ConditionOp.lt: + return _op1(builder, bindings.obx_qb_double_less); + case ConditionOp.between: + return bindings.obx_qb_double_between(builder._cBuilder, _property._propertyId, _value, _value2); + default: + throw Exception("Unsupported operation ${_op.toString()}"); + } + } +} + +class ConditionGroup extends Condition { + List _conditions; + obx_qb_join_op_dart_t _func; + + ConditionGroup(this._conditions, this._func); + + int apply(QueryBuilder builder, bool isRoot) { + final size = _conditions.length; + + if (size == 0) { + return -1; // -1 instead of 0 which indicates an error + } else if (size == 1) { + return _conditions[0].apply(builder, isRoot); + } + + final intArrayPtr = Pointer.allocate(count: size); + try { + for (int i = 0; i < size; ++i) { + final cid = _conditions[i].apply(builder, false); + if (cid == 0) { + builder._throwExceptionIfNecessary(); + throw Exception("Failed to create condition " + _conditions[i].toString()); + } + + intArrayPtr.elementAt(i).store(cid); + } + + // root All (AND) is implicit so no need to actually combine the conditions + if (isRoot && this is ConditionGroupAll) { + return -1; // no error but no condition ID either + } + + return _func(builder._cBuilder, intArrayPtr, size); + } finally { + intArrayPtr.free(); + } + } +} + +class ConditionGroupAny extends ConditionGroup { + ConditionGroupAny(conditions) : super(conditions, bindings.obx_qb_any); +} + +class ConditionGroupAll extends ConditionGroup { + ConditionGroupAll(conditions) : super(conditions, bindings.obx_qb_all); +} + +class Query { + Pointer _cQuery; + Store _store; + OBXFlatbuffersManager _fbManager; + + // package private ctor + Query._(this._store, this._fbManager, Pointer cBuilder) { + _cQuery = checkObxPtr(bindings.obx_query_create(cBuilder), "create query"); + } + + int count() { + final ptr = Pointer.allocate(count: 1); + try { + checkObx(bindings.obx_query_count(_cQuery, ptr)); + return ptr.load(); + } finally { + ptr.free(); + } + } + + // TODO Document wrap with closure to fake auto close + void close() { + checkObx(bindings.obx_query_close(_cQuery)); + } + + T findFirst() { + final list = find(offset: 0, limit: 1); + return (list.isEmpty ? null : list[0]); + } + + List findIds({int offset = 0, int limit = 0}) { + final idArrayPtr = checkObxPtr(bindings.obx_query_find_ids(_cQuery, offset, limit), "find ids"); + try { + OBX_id_array idArray = idArrayPtr.load(); + return idArray.length == 0 ? List() : idArray.items(); + } finally { + bindings.obx_id_array_free(idArrayPtr); + } + } + + List find({int offset = 0, int limit = 0}) { + return _store.runInTransaction(TxMode.Read, () { + final bytesArray = checkObxPtr(bindings.obx_query_find(_cQuery, offset, limit), "find"); + try { + return _fbManager.unmarshalArray(bytesArray); + } finally { + bindings.obx_bytes_array_free(bytesArray); + } + }); + } + + // For testing purposes + String describe() { + return cString(bindings.obx_query_describe(_cQuery)); + } + + // For testing purposes + String describeParameters() { + return cString(bindings.obx_query_describe_params(_cQuery)); + } +} diff --git a/lib/src/store.dart b/lib/src/store.dart index 9b246291f..46ba3d5da 100644 --- a/lib/src/store.dart +++ b/lib/src/store.dart @@ -26,7 +26,7 @@ class Store { try { checkObx(bindings.obx_opt_model(opt, model.ptr)); - if (directory != null && directory.length != 0) { + if (directory != null && directory.isNotEmpty) { var cStr = Utf8.toUtf8(directory).cast(); try { checkObx(bindings.obx_opt_directory(opt, cStr)); @@ -49,18 +49,27 @@ class Store { checkObx(bindings.obx_store_close(_cStore)); } - EntityDefinition entityDef(T) { + EntityDefinition entityDef() { return _entityDefinitions[T]; } /// Executes a given function inside a transaction + /// + /// Returns type of [fn] if [return] is called in [fn] R runInTransaction(TxMode mode, R Function() fn) { - assert(mode == TxMode.Read, "write transactions are currently not supported"); // TODO implement - - Pointer txn = bindings.obx_txn_read(_cStore); - checkObxPtr(txn, "failed to created transaction"); + bool write = mode == TxMode.Write; + Pointer txn = write ? bindings.obx_txn_write(_cStore) : bindings.obx_txn_read(_cStore); + checkObxPtr(txn, "failed to create transaction"); try { + if (write) { + checkObx(bindings.obx_txn_mark_success(txn, 1)); + } return fn(); + } catch (ex) { + if (write) { + checkObx(bindings.obx_txn_mark_success(txn, 0)); + } + rethrow; } finally { checkObx(bindings.obx_txn_close(txn)); } diff --git a/objectbox-model.json b/objectbox-model.json index 02ef88a4f..759f37bfe 100644 --- a/objectbox-model.json +++ b/objectbox-model.json @@ -5,7 +5,7 @@ "entities": [ { "id": "1:4630700155272683157", - "lastPropertyId": "2:6119953496456269132", + "lastPropertyId": "5:1874578842943403707", "name": "TestEntity", "properties": [ { @@ -18,11 +18,84 @@ "id": "2:6119953496456269132", "name": "text", "type": 9 + }, + { + "id": "3:1043358010384181585", + "name": "number", + "type": 6 + }, + { + "id": "4:8886007565639578393", + "name": "d", + "type": 8 + }, + { + "id": "5:1874578842943403707", + "name": "b", + "type": 1 + } + ] + }, + { + "id": "2:2679953000475642792", + "lastPropertyId": "11:2900967122054840440", + "name": "TestEntityProperty", + "properties": [ + { + "id": "1:118121232448890483", + "name": "id", + "type": 6, + "flags": 1 + }, + { + "id": "2:8913396295192638938", + "name": "tBool", + "type": 1 + }, + { + "id": "3:7399685737415196420", + "name": "tLong", + "type": 6 + }, + { + "id": "4:5773372525238577573", + "name": "tDouble", + "type": 8 + }, + { + "id": "5:1652533532846680965", + "name": "tString", + "type": 9 + }, + { + "id": "7:2032187304851276864", + "name": "tShort", + "type": 3 + }, + { + "id": "8:263290714597678490", + "name": "tChar", + "type": 4 + }, + { + "id": "9:2191004797635629014", + "name": "tInt", + "type": 5 + }, + { + "id": "10:6174275661850707374", + "name": "tFloat", + "type": 7 + }, + { + "id": "11:2900967122054840440", + "name": "tByte", + "type": 2 } ] } ], - "lastEntityId": "1:4630700155272683157", + "lastEntityId": "2:2679953000475642792", "lastIndexId": "0:0", "lastRelationId": "0:0", "lastSequenceId": "0:0", @@ -30,7 +103,9 @@ "modelVersionParserMinimum": 5, "retiredEntityUids": [], "retiredIndexUids": [], - "retiredPropertyUids": [], + "retiredPropertyUids": [ + 5849170199816666167 + ], "retiredRelationUids": [], "version": 1 } \ No newline at end of file diff --git a/pubspec.yaml b/pubspec.yaml index 43569b13f..80a90924c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,15 +1,20 @@ name: objectbox -version: 0.3.0 -description: >- - ObjectBox binding for Dart. +version: 0.4.0 +repository: https://github.com/objectbox/objectbox-dart +homepage: https://objectbox.io +author: objectbox.io +description: ObjectBox is a super-fast NoSQL ACID compliant object database. + environment: - sdk: '>=2.5.0 <3.0.0' + sdk: '>=2.5.0 <2.6.0' + dependencies: - # take care updating flatbuffers - keep aligned with other bindings - flat_buffers: 1.11.0 + flat_buffers: 1.11.0 # take care updating flatbuffers - keep aligned with other bindings ffi: 0.1.2 + dev_dependencies: build_runner: ^1.0.0 - objectbox_model_generator: - path: bin/objectbox_model_generator + objectbox_generator: + path: generator + pedantic: ^1.8.0+1 test: ^1.0.0 diff --git a/test/box_test.dart b/test/box_test.dart index 424c402e4..25edcda61 100644 --- a/test/box_test.dart +++ b/test/box_test.dart @@ -1,103 +1,277 @@ -import "dart:io"; import "package:test/test.dart"; import "package:objectbox/objectbox.dart"; -part "box_test.g.dart"; +import "entity.dart"; +import 'test_env.dart'; -@Entity() -class TestEntity { - @Id() - int id; +void main() { + TestEnv env; + Box box; + Store store; - String text; + final List simpleItems = + ["One", "Two", "Three", "Four", "Five", "Six"].map((s) => TestEntity.initText(s)).toList(); - TestEntity(); - TestEntity.constructWithId(this.id, this.text); - TestEntity.construct(this.text); -} + setUp(() { + env = TestEnv("box"); + box = env.box; + store = env.store; + }); -void main() { - Store store; - Box box; + test(".put() returns a valid id", () { + int putId = box.put(TestEntity.initText("Hello")); + expect(putId, greaterThan(0)); + }); - group("box", () { - setUp(() { - store = Store([TestEntity_OBXDefs]); - box = Box(store); - }); + test(".get() returns the correct item", () { + final int putId = box.put(TestEntity.initText("Hello")); + final TestEntity item = box.get(putId); + expect(item.id, equals(putId)); + expect(item.text, equals("Hello")); + }); - test(".put() returns a valid id", () { - int putId = box.put(TestEntity.construct("Hello")); - expect(putId, greaterThan(0)); - }); + test(".put() and box.get() keep Unicode characters", () { + final String text = "😄你好"; + final TestEntity inst = box.get(box.put(TestEntity.initText(text))); + expect(inst.text, equals(text)); + }); - test(".get() returns the correct item", () { - final int putId = box.put(TestEntity.construct("Hello")); - final TestEntity item = box.get(putId); - expect(item.id, equals(putId)); - expect(item.text, equals("Hello")); - }); + test(".put() can update an item", () { + final int putId1 = box.put(TestEntity.initText("One")); + final int putId2 = box.put(TestEntity.initId(putId1, "Two")); + expect(putId2, equals(putId1)); + final TestEntity item = box.get(putId2); + expect(item.text, equals("Two")); + }); - test(".put() and box.get() keep Unicode characters", () { - final String text = "😄你好"; - final TestEntity inst = box.get(box.put(TestEntity.construct(text))); - expect(inst.text, equals(text)); - }); + test(".getAll retrieves all items", () { + final int id1 = box.put(TestEntity.initText("One")); + final int id2 = box.put(TestEntity.initText("Two")); + final int id3 = box.put(TestEntity.initText("Three")); + final List items = box.getAll(); + expect(items.length, equals(3)); + expect(items.where((i) => i.id == id1).single.text, equals("One")); + expect(items.where((i) => i.id == id2).single.text, equals("Two")); + expect(items.where((i) => i.id == id3).single.text, equals("Three")); + }); - test(".put() can update an item", () { - final int putId1 = box.put(TestEntity.construct("One")); - final int putId2 = box.put(TestEntity.constructWithId(putId1, "Two")); - expect(putId2, equals(putId1)); - final TestEntity item = box.get(putId2); - expect(item.text, equals("Two")); - }); + test(".putMany inserts multiple items", () { + final List items = [ + TestEntity.initText("One"), + TestEntity.initText("Two"), + TestEntity.initText("Three") + ]; + box.putMany(items); + final List itemsFetched = box.getAll(); + expect(itemsFetched.length, equals(items.length)); + expect(itemsFetched[0].text, equals(items[0].text)); + expect(itemsFetched[1].text, equals(items[1].text)); + expect(itemsFetched[2].text, equals(items[2].text)); + }); - test(".getAll retrieves all items", () { - final int id1 = box.put(TestEntity.construct("One")); - final int id2 = box.put(TestEntity.construct("Two")); - final int id3 = box.put(TestEntity.construct("Three")); - final List items = box.getAll(); - expect(items.length, equals(3)); - expect(items.where((i) => i.id == id1).single.text, equals("One")); - expect(items.where((i) => i.id == id2).single.text, equals("Two")); - expect(items.where((i) => i.id == id3).single.text, equals("Three")); - }); + test(".putMany returns the new item IDs", () { + final List items = + ["One", "Two", "Three", "Four", "Five", "Six", "Seven"].map((s) => TestEntity.initText(s)).toList(); + final List ids = box.putMany(items); + expect(ids.length, equals(items.length)); + for (int i = 0; i < items.length; ++i) { + expect(box.get(ids[i]).text, equals(items[i].text)); + } + }); - test(".putMany inserts multiple items", () { - final List items = [ - TestEntity.construct("One"), - TestEntity.construct("Two"), - TestEntity.construct("Three") - ]; - box.putMany(items); - final List itemsFetched = box.getAll(); - expect(itemsFetched.length, equals(items.length)); - }); + test(".getMany correctly handles non-existant items", () { + final List items = ["One", "Two"].map((s) => TestEntity.initText(s)).toList(); + final List ids = box.putMany(items); + int otherId = 1; + while (ids.indexWhere((id) => id == otherId) != -1) { + ++otherId; + } + final List fetchedItems = box.getMany([ids[0], otherId, ids[1]]); + expect(fetchedItems.length, equals(3)); + expect(fetchedItems[0].text, equals("One")); + expect(fetchedItems[1], equals(null)); + expect(fetchedItems[2].text, equals("Two")); + }); + + test("limit integers are stored correctly", () { + final int64Min = -9223372036854775808; + final int64Max = 9223372036854775807; + final List items = [int64Min, int64Max].map((n) => TestEntity.initInteger(n)).toList(); + expect("${items[0].number}", equals("$int64Min")); + expect("${items[1].number}", equals("$int64Max")); + final List fetchedItems = box.getMany(box.putMany(items)); + expect(fetchedItems[0].number, equals(int64Min)); + expect(fetchedItems[1].number, equals(int64Max)); + }); + + test("null properties are handled correctly", () { + final List items = [TestEntity(), TestEntity.initInteger(10), TestEntity.initText("Hello")]; + final List fetchedItems = box.getMany(box.putMany(items)); + expect(fetchedItems[0].id, isNot(equals(null))); + expect(fetchedItems[0].number, equals(null)); + expect(fetchedItems[0].text, equals(null)); + expect(fetchedItems[0].b, equals(null)); + expect(fetchedItems[0].d, equals(null)); + expect(fetchedItems[1].id, isNot(equals(null))); + expect(fetchedItems[1].number, isNot(equals(null))); + expect(fetchedItems[1].text, equals(null)); + expect(fetchedItems[1].b, equals(null)); + expect(fetchedItems[1].d, equals(null)); + expect(fetchedItems[2].id, isNot(equals(null))); + expect(fetchedItems[2].number, equals(null)); + expect(fetchedItems[2].text, isNot(equals(null))); + expect(fetchedItems[2].b, equals(null)); + expect(fetchedItems[2].d, equals(null)); + }); - test(".putMany returns the new item IDs", () { - final List items = - ["One", "Two", "Three", "Four", "Five", "Six", "Seven"].map((s) => TestEntity.construct(s)).toList(); - final List ids = box.putMany(items); - expect(ids.length, equals(items.length)); - for (int i = 0; i < items.length; ++i) expect(box.get(ids[i]).text, equals(items[i].text)); + test(".count() works", () { + expect(box.count(), equals(0)); + List ids = box.putMany(simpleItems); + expect(box.count(), equals(6)); + expect(box.count(limit: 2), equals(2)); + expect(box.count(limit: 10), equals(6)); + //add more + ids.addAll(box.putMany(simpleItems)); + expect(box.count(), equals(12)); + }); + + test(".isEmpty() works", () { + bool isEmpty = box.isEmpty(); + expect(isEmpty, equals(true)); + //check complementary + box.putMany(simpleItems); + isEmpty = box.isEmpty(); + expect(isEmpty, equals(false)); + }); + + test(".contains() works", () { + int id = box.put(TestEntity.initText("container")); + bool contains = box.contains(id); + expect(contains, equals(true)); + //check complementary + box.remove(id); + contains = box.contains(id); + expect(contains, equals(false)); + }); + + test(".containsMany() works", () { + List ids = box.putMany(simpleItems); + bool contains = box.containsMany(ids); + expect(contains, equals(true)); + //check with one missing id + box.remove(ids[1]); + contains = box.containsMany(ids); + expect(contains, equals(false)); + //check complementary + box.removeAll(); + contains = box.containsMany(ids); + expect(contains, equals(false)); + }); + + test(".remove(id) works", () { + final List ids = box.putMany(simpleItems); + //check if single id remove works + expect(box.remove(ids[1]), equals(true)); + expect(box.count(), equals(5)); + //check what happens if id already deleted -> throws OBJBOXEX 404 + bool success = box.remove(ids[1]); + expect(box.count(), equals(5)); + expect(success, equals(false)); + }); + + test(".removeMany(ids) works", () { + final List ids = box.putMany(simpleItems); + expect(box.count(), equals(6)); + box.removeMany(ids.sublist(4)); + expect(box.count(), equals(4)); + //again test what happens if ids already deleted + box.removeMany(ids.sublist(4)); + expect(box.count(), equals(4)); + + // verify the right items were removed + final List remainingIds = box.getAll().map((o) => (o as TestEntity).id).toList(); + expect(remainingIds, unorderedEquals(ids.sublist(0, 4))); + }); + + test(".removeAll() works", () { + List ids = box.putMany(simpleItems); + int removed = box.removeAll(); + expect(removed, equals(6)); + expect(box.count(), equals(0)); + //try with different number of items + List items = ["one", "two", "three"].map((s) => TestEntity.initText(s)).toList(); + ids.addAll(box.putMany(items)); + removed = box.removeAll(); + expect(removed, equals(3)); + }); + + test("simple write in txn works", () { + int count; + fn() { + box.putMany(simpleItems); + } + + store.runInTransaction(TxMode.Write, fn); + count = box.count(); + expect(count, equals(6)); + }); + + test("failing transactions", () { + try { + store.runInTransaction(TxMode.Write, () { + box.putMany(simpleItems); + throw Exception("Test exception"); + }); + } on Exception { + ; //otherwise test fails due to not handling exceptions + } finally { + expect(box.count(), equals(0)); + } + }); + + test("recursive write in write transaction", () { + store.runInTransaction(TxMode.Write, () { + box.putMany(simpleItems); + store.runInTransaction(TxMode.Write, () { + box.putMany(simpleItems); + }); }); + expect(box.count(), equals(12)); + }); - test(".getMany correctly handles non-existant items", () { - final List items = ["One", "Two"].map((s) => TestEntity.construct(s)).toList(); - final List ids = box.putMany(items); - int otherId = 1; - while (ids.indexWhere((id) => id == otherId) != -1) ++otherId; - final List fetchedItems = box.getMany([ids[0], otherId, ids[1]]); - expect(fetchedItems.length, equals(3)); - expect(fetchedItems[0].text, equals("One")); - expect(fetchedItems[1], equals(null)); - expect(fetchedItems[2].text, equals("Two")); + test("recursive read in write transaction", () { + int count = store.runInTransaction(TxMode.Write, () { + box.putMany(simpleItems); + return store.runInTransaction(TxMode.Read, () { + return box.count(); + }); }); + expect(count, equals(6)); + }); + + test("recursive write in read -> fails during creation", () { + try { + store.runInTransaction(TxMode.Read, () { + box.count(); + return store.runInTransaction(TxMode.Write, () { + return box.putMany(simpleItems); + }); + }); + } on ObjectBoxException catch (ex) { + expect(ex.toString(), startsWith("ObjectBoxException: failed to create transaction")); + } + }); - tearDown(() { - if (store != null) store.close(); - store = null; - var dir = new Directory("objectbox"); - if (dir.existsSync()) dir.deleteSync(recursive: true); + test("failing in recursive txn", () { + store.runInTransaction(TxMode.Write, () { + //should throw code10001 -> valid until fix + List ids = store.runInTransaction(TxMode.Read, () { + return box.putMany(simpleItems); + }); + expect(ids.length, equals(6)); }); }); + + tearDown(() { + env.close(); + }); } diff --git a/test/entity.dart b/test/entity.dart new file mode 100644 index 000000000..53896c169 --- /dev/null +++ b/test/entity.dart @@ -0,0 +1,48 @@ +import "package:objectbox/objectbox.dart"; +part 'entity.g.dart'; + +@Entity() +class TestEntity { + @Id() + int id; + + String text; + int number; + double d; + bool b; + + TestEntity(); + + TestEntity.initId(this.id, this.text); + TestEntity.initInteger(this.number); + TestEntity.initIntegerAndText(this.number, this.text); + TestEntity.initText(this.text); + TestEntity.initDoubleAndBoolean(this.d, this.b); +} + +@Entity() +class TestEntityProperty { + @Id() + int id; + + // See OB-C, objectbox.h + bool tBool; // 1 byte + int tLong; // ob: 8 bytes, dart: 8 bytes + double tDouble; // ob: 8 bytes, dart: 8 bytes + String tString; + + @Property(type: 2 /*OBXPropertyType.Byte*/) + int tByte; // 1 byte + + @Property(type: 3 /*OBXPropertyType.Short*/) + int tShort; // 2 byte + + @Property(type: 4 /*OBXPropertyType.Char*/) + int tChar; // 1 byte + + @Property(type: 5 /*OBXPropertyType.Int*/) + int tInt; // ob: 4 bytes, dart: 8 bytes + + @Property(type: 7 /*OBXPropertyType.Float*/) + double tFloat; // 4 bytes +} diff --git a/test/index.dart b/test/index.dart deleted file mode 100644 index e89ad4ff4..000000000 --- a/test/index.dart +++ /dev/null @@ -1,5 +0,0 @@ -import "box_test.dart" as box_test; - -void main() { - box_test.main(); -} diff --git a/test/integration_test.dart b/test/integration_test.dart new file mode 100644 index 000000000..48db9917b --- /dev/null +++ b/test/integration_test.dart @@ -0,0 +1,7 @@ +import "package:test/test.dart"; +import "package:objectbox/integration_test.dart"; + +void main() { + test("int64", IntegrationTest.int64); + test("model", IntegrationTest.model); +} diff --git a/test/query_test.dart b/test/query_test.dart new file mode 100644 index 000000000..60a4149d3 --- /dev/null +++ b/test/query_test.dart @@ -0,0 +1,397 @@ +import "package:test/test.dart"; +import "package:objectbox/objectbox.dart"; +import "entity.dart"; +import 'test_env.dart'; + +void main() { + TestEnv env; + Box box; + + setUp(() { + env = TestEnv("query"); + box = env.box; + }); + + test(".null and .notNull", () { + box.putMany([ + TestEntity.initDoubleAndBoolean(0.1, true), + TestEntity.initDoubleAndBoolean(0.3, false), + TestEntity.initText("one"), + TestEntity.initText("two"), + ] as List); + + final b = TestEntity_.b; + final t = TestEntity_.text; + + final qbNull = box.query(b.isNull()).build(); + final qbNotNull = box.query(b.notNull()).build(); + final qtNull = box.query(t.isNull()).build(); + final qtNotNull = box.query(t.notNull()).build(); + final qdNull = box.query(t.isNull()).build(); + final qdNotNull = box.query(t.notNull()).build(); + + [qbNull, qbNotNull, qtNull, qtNotNull, qdNull, qdNotNull].forEach((q) { + expect(q.count(), 2); + q.close(); + }); + }); + + test(".count doubles and booleans", () { + box.putMany([ + TestEntity.initDoubleAndBoolean(0.1, true), + TestEntity.initDoubleAndBoolean(0.3, false), + TestEntity.initDoubleAndBoolean(0.5, true), + TestEntity.initDoubleAndBoolean(0.7, false), + TestEntity.initDoubleAndBoolean(0.9, true) + ] as List); + + final d = TestEntity_.d; + final b = TestEntity_.b; + + // #43 final anyQuery0 = (d.between(0.79, 0.81) & ((b == false) as Condition)) | (d.between(0.69, 0.71) & ((b == false) as Condition)); + final anyQuery0 = (d.between(0.79, 0.81) & b.equals(false) | (d.between(0.69, 0.71) & b.equals(false))); + final anyQuery1 = (d.between(0.79, 0.81).and(b.equals(false))).or(d.between(0.69, 0.71).and(b.equals(false))); + final anyQuery2 = d.between(0.79, 0.81).and(b.equals(false)).or(d.between(0.69, 0.71).and(b.equals(false))); + final anyQuery3 = d.between(0.79, 0.81).and(b.equals(false)).or(d.between(0.69, 0.71)).and(b.equals(false)); + + // #43 final allQuery0 = d.between(0.09, 0.11) & ((b == true) as Condition); + final allQuery0 = d.between(0.09, 0.11) & b.equals(true); + + final q0 = box.query(b.equals(false)).build(); + final qany0 = box.query(anyQuery0).build(); + final qany1 = box.query(anyQuery1).build(); + final qany2 = box.query(anyQuery2).build(); + final qany3 = box.query(anyQuery3).build(); + + final qall0 = box.query(allQuery0).build(); + + expect(q0.count(), 2); + expect(qany0.count(), 1); + expect(qany1.count(), 1); + expect(qany2.count(), 1); + expect(qany3.count(), 1); + expect(qall0.count(), 1); + + [q0, qany0, qany1, qany2, qany3, qall0].forEach((q) => q.close()); + }); + + test(".count matches of `greater` and `less`", () { + box.putMany([ + TestEntity.initIntegerAndText(1336, "mord"), + TestEntity.initIntegerAndText(1337, "more"), + TestEntity.initIntegerAndText(1338, "morf"), + TestEntity.initDoubleAndBoolean(0.0, false), + TestEntity.initDoubleAndBoolean(0.1, true), + TestEntity.initDoubleAndBoolean(0.2, true), + TestEntity.initDoubleAndBoolean(0.3, false), + ] as List); + + final d = TestEntity_.d; + final b = TestEntity_.b; + final t = TestEntity_.text; + final n = TestEntity_.number; + + final q0 = box.query(d.greaterThan(0.1)).build(); + final q1 = box.query(b.equals(false)).build(); + final q2 = box.query(t.greaterThan("more")).build(); + final q3 = box.query(t.lessThan("more")).build(); + final q4 = box.query(d.lessThan(0.3)).build(); + final q5 = box.query(n.lessThan(1337)).build(); + final q6 = box.query(n.greaterThan(1337)).build(); + + expect(q0.count(), 2); + expect(q1.count(), 2); + expect(q2.count(), 1); + expect(q3.count(), 1); + expect(q4.count(), 3); + expect(q5.count(), 1); + expect(q6.count(), 1); + + [q0, q1, q2, q3, q4, q5, q6].forEach((q) => q.close()); + }); + + test(".count matches of `in`, `contains`", () { + box.put(TestEntity.initIntegerAndText(1337, "meh")); + box.put(TestEntity.initIntegerAndText(1, "bleh")); + box.put(TestEntity.initIntegerAndText(1337, "bleh")); + box.put(TestEntity.initIntegerAndText(1337, "blh")); + + final text = TestEntity_.text; + final number = TestEntity_.number; + + final qs0 = box.query(text.inside(["meh"])).build(); + final qs1 = box.query(text.inside(["bleh"])).build(); + final qs2 = box.query(text.inside(["meh", "bleh"])).build(); + final qs3 = box.query(text.contains("eh")).build(); + + final qn0 = box.query(number.inside([1])).build(); + final qn1 = box.query(number.inside([1337])).build(); + final qn2 = box.query(number.inside([1, 1337])).build(); + + expect(qs0.count(), 1); + expect(qs1.count(), 2); + expect(qs2.count(), 3); + expect(qs3.count(), 3); + expect(qn0.count(), 1); + expect(qn1.count(), 3); + expect(qn2.count(), 4); + + [qs0, qs1, qs2, qs3, qn0, qn1, qn2].forEach((q) => q.close()); + }); + + test(".findIds returns List", () { + box.put(TestEntity.initId(0, "meh")); + box.put(TestEntity.initId(0, "bleh")); + box.put(TestEntity.initId(0, "bleh")); + box.put(TestEntity.initId(0, "helb")); + box.put(TestEntity.initId(0, "helb")); + box.put(TestEntity.initId(0, "bleh")); + box.put(TestEntity.initId(0, "blh")); + + final text = TestEntity_.text; + + final q0 = box.query(text.notNull()).build(); + final result0 = q0.findIds(); + + final q2 = box.query(text.equals("blh")).build(); + final result2 = q2.findIds(); + + final q3 = box.query(text.equals("can't find this")).build(); + final result3 = q3.findIds(); + + expect(result0.length, 7); + expect(result2.length, 1); + expect(result3.length, 0); + + q0.close(); + q2.close(); + q3.close(); + }); + + test(".find returns List", () { + box.put(TestEntity.initInteger(0)); + box.put(TestEntity.initText("test")); + box.put(TestEntity.initText("test")); + + final text = TestEntity_.text; + + var q = box.query(text.notNull()).build(); + expect(q.find().length, 2); + q.close(); + + q = box.query(text.isNull()).build(); + expect(q.find(offset: 0, limit: 1).length, 1); + q.close(); + }); + + test(".findFirst returns TestEntity", () { + box.put(TestEntity.initInteger(0)); + box.put(TestEntity.initText("test1t")); + box.put(TestEntity.initText("test")); + + final text = TestEntity_.text; + final number = TestEntity_.number; + + final c = text.startsWith("t") & text.endsWith("t"); + + var q = box.query(c).build(); + + expect(q.findFirst().text, "test1t"); + q.close(); + + q = box.query(number.notNull()).build(); + expect(q.findFirst().number, 0); + q.close(); + }); + + test(".count items after grouping with and/or", () { + box.put(TestEntity.initText("Hello")); + box.put(TestEntity.initText("Goodbye")); + box.put(TestEntity.initText("World")); + + box.put(TestEntity.initInteger(1337)); + box.put(TestEntity.initInteger(80085)); + + box.put(TestEntity.initIntegerAndText(-1337, "meh")); + box.put(TestEntity.initIntegerAndText(-1332 + -5, "bleh")); + box.put(TestEntity.initIntegerAndText(1337, "Goodbye")); + + final text = TestEntity_.text; + final number = TestEntity_.number; + + // #43 Condition cond1 = ((text == "Hello") as Condition) | ((number == 1337) as Condition); + Condition cond1 = text.equals("Hello") | number.equals(1337); + Condition cond2 = text.equals("Hello") | number.equals(1337); + Condition cond3 = text.equals("What?").and(text.equals("Hello")).or(text.equals("World")); + Condition cond4 = text + .equals("Goodbye") + .and(number.equals(1337)) + .or(number.equals(1337)) + .or(text.equals("Cruel")) + .or(text.equals("World")); + Condition cond5 = text.equals("bleh") & number.equals(-1337); + // #43 Condition cond6 = ((text == "Hello") as Condition) & ((number == 1337) as Condition); + Condition cond6 = text.equals("Hello") & number.equals(1337); + + // #43 final selfInference1 = (text == "Hello") & (number == 1337); + // #43 final selfInference2 = (text == "Hello") | (number == 1337); + + final q1 = box.query(cond1).build(); + final q2 = box.query(cond2).build(); + final q3 = box.query(cond3).build(); + final q4 = box.query(cond4).build(); + final q5 = box.query(cond5).build(); + final q6 = box.query(cond6).build(); + // #43 final q7 = box.query(selfInference1 as Condition).build(); + // #43 final q8 = box.query(selfInference2 as Condition).build(); + + expect(q1.count(), 3); + expect(q2.count(), 3); + expect(q3.count(), 1); + expect(q4.count(), 3); + expect(q5.count(), 1); + expect(q6.count(), 0); + // #43 expect(q7.count(), 0); + // #43 expect(q8.count(), 3); + + // #43 [q1, q2, q3, q4, q5, q6, q7, q8].forEach((q) => q.close()); + [q1, q2, q3, q4, q5, q6].forEach((q) => q.close()); + }); + + test(".describe query", () { + final text = TestEntity_.text; + final number = TestEntity_.number; + Condition c = text + .equals("Goodbye") + .and(number.equals(1337)) + .or(number.equals(1337)) + .or(text.equals("Cruel")) + .or(text.equals("World")); + final q = box.query(c).build(); + // 5 partial conditions, + 1 'and' + 1 'any' = 7 conditions + expect(q.describe(), "Query for entity TestEntity with 7 conditions with properties number, text"); + q.close(); + + for (int j = 1; j < 20; j++) { + var tc = text.equals("Hello"); + for (int i = 0; i < j; i++) { + tc = tc.or(text.endsWith("lo")); + } + final q = box.query(tc).build(); + expect(q.describe(), '''Query for entity TestEntity with ${j + 2} conditions with properties text'''); + q.close(); + } + + for (int j = 1; j < 20; j++) { + var tc = text.equals("Hello"); + for (int i = 0; i < j; i++) { + tc = tc.and(text.startsWith("lo")); + } + final q = box.query(tc).build(); + expect(q.describe(), '''Query for entity TestEntity with ${j + 2} conditions with properties text'''); + q.close(); + } + }); + + test("query condition grouping", () { + final n = TestEntity_.id; + final b = TestEntity_.b; + + final check = (Condition condition, String text) { + final q = box.query(condition).build(); + expect(q.describeParameters(), text); + q.close(); + }; + + check((n.equals(0) & b.equals(false)) | (n.equals(1) & b.equals(true)), + '((id == 0\n AND b == 0)\n OR (id == 1\n AND b == 1))'); + check(n.equals(0) & b.equals(false) | n.equals(1) & b.equals(true), + '((id == 0\n AND b == 0)\n OR (id == 1\n AND b == 1))'); + check((n.equals(0) & b.equals(false)) | (n.equals(1) | b.equals(true)), + '((id == 0\n AND b == 0)\n OR (id == 1\n OR b == 1))'); + check((n.equals(0) & b.equals(false)) | n.equals(1) | b.equals(true), + '((id == 0\n AND b == 0)\n OR id == 1\n OR b == 1)'); + check(n.equals(0) | b.equals(false) & n.equals(1) | b.equals(true), + '(id == 0\n OR (b == 0\n AND id == 1)\n OR b == 1)'); + }); + + test(".describeParameters query", () { + final text = TestEntity_.text; + final number = TestEntity_.number; + Condition c = text + .equals("Goodbye") + .and(number.equals(1337)) + .or(number.equals(1337)) + .or(text.equals("Cruel")) + .or(text.equals("World")); + final q = box.query(c).build(); + final expectedString = [ + '''((text ==(i) "Goodbye"''', + ''' AND number == 1337)''', + ''' OR number == 1337''', + ''' OR text ==(i) "Cruel"''', + ''' OR text ==(i) "World")''' + ].join("\n"); + expect(q.describeParameters(), expectedString); + q.close(); + + for (int j = 1; j < 20; j++) { + var tc = text.equals("Goodbye"); + var expected = ['''text ==(i) "Goodbye"''']; + for (int i = 0; i < j; i++) { + tc = tc.and(text.endsWith("ye")); + expected.add(''' AND text ends with(i) "ye"'''); + } + final q = box.query(tc).build(); + expect(q.describeParameters(), '''(${expected.join("\n")})'''); + q.close(); + } + + for (int j = 1; j < 20; j++) { + var tc = text.equals("Goodbye"); + var expected = ['''text ==(i) "Goodbye"''']; + for (int i = 0; i < j; i++) { + tc = tc.or(text.startsWith("Good")); + expected.add(''' OR text starts with(i) "Good"'''); + } + final q = box.query(tc).build(); + expect(q.describeParameters(), '''(${expected.join("\n")})'''); + q.close(); + } + }); + + test(".order queryBuilder", () { + box.put(TestEntity.initText("World")); + box.put(TestEntity.initText("Hello")); + box.put(TestEntity.initText("HELLO")); + box.put(TestEntity.initText("World")); + box.put(TestEntity.initText("Goodbye")); + box.put(TestEntity.initText("Cruel")); + box.put(TestEntity.initInteger(1337)); + + final text = TestEntity_.text; + + final condition = text.notNull(); + + final query = box.query(condition).order(text).build(); + final result1 = query.find().map((e) => e.text).toList(); + + expect("Cruel", result1[0]); + expect("Hello", result1[2]); + expect("HELLO", result1[3]); + + final queryReverseOrder = box.query(condition).order(text, flags: Order.descending | Order.caseSensitive).build(); + final result2 = queryReverseOrder.find().map((e) => e.text).toList(); + + expect("World", result2[0]); + expect("Hello", result2[2]); + expect("HELLO", result2[3]); + + query.close(); + queryReverseOrder.close(); + }); + + tearDown(() { + env.close(); + }); +} diff --git a/test/test_env.dart b/test/test_env.dart new file mode 100644 index 000000000..64b7b10e5 --- /dev/null +++ b/test/test_env.dart @@ -0,0 +1,20 @@ +import "package:objectbox/objectbox.dart"; +import "entity.dart"; + +class TestEnv { + final Directory dir; + Store store; + Box box; + + TestEnv(String name) : dir = Directory("testdata-" + name) { + if (dir.existsSync()) dir.deleteSync(recursive: true); + + store = Store([TestEntity_OBXDefs], directory: dir.path); + box = Box(store); + } + + close() { + store.close(); + if (dir.existsSync()) dir.deleteSync(recursive: true); + } +}