├── .github ├── ISSUE_TEMPLATE.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── LICENSE ├── README.md ├── all_lint_rules.yaml ├── analysis_options.yaml ├── docs ├── code-generation.md ├── code-of-conduct.md ├── contributing.md ├── defining-models.md ├── references.md └── subcollections.md └── packages ├── cloud_firestore_odm ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── example │ ├── .gitignore │ ├── .metadata │ ├── build.yaml │ ├── integration_test │ │ ├── cloud_firestore_odm_e2e_test.dart │ │ ├── collection_reference_test.dart │ │ ├── common.dart │ │ ├── document_reference_test.dart │ │ ├── firebase_options.dart │ │ ├── firestore_builder_test.dart │ │ ├── freezed_test.dart │ │ ├── path_test.dart │ │ └── query_reference_test.dart │ ├── lib │ │ ├── integration.dart │ │ ├── integration.g.dart │ │ ├── integration │ │ │ ├── enums.dart │ │ │ ├── enums.g.dart │ │ │ ├── freezed.dart │ │ │ ├── freezed.freezed.dart │ │ │ ├── freezed.g.dart │ │ │ ├── named_query.dart │ │ │ ├── named_query.g.dart │ │ │ ├── query.dart │ │ │ └── query.g.dart │ │ ├── main.dart │ │ ├── movie.dart │ │ ├── movie.g.dart │ │ ├── movie_detail_screen.dart │ │ ├── movie_item.dart │ │ └── movies_screen.dart │ ├── pubspec.yaml │ ├── test_driver │ │ └── integration_test.dart │ └── web │ │ ├── favicon.png │ │ ├── icons │ │ ├── Icon-192.png │ │ ├── Icon-512.png │ │ ├── Icon-maskable-192.png │ │ └── Icon-maskable-512.png │ │ ├── index.html │ │ └── manifest.json ├── lib │ ├── annotation.dart │ ├── cloud_firestore_odm.dart │ └── src │ │ ├── firestore_builder.dart │ │ ├── firestore_reference.dart │ │ └── validator.dart ├── pubspec.yaml └── test │ ├── common.dart │ └── firestore_builder_test.dart └── cloud_firestore_odm_generator ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── build.yaml ├── cloud_firestore_odm_generator_integration_test ├── .gitignore ├── .metadata ├── build.yaml ├── lib │ ├── freezed.dart │ ├── freezed.freezed.dart │ ├── freezed.g.dart │ ├── simple.dart │ └── simple.g.dart ├── pubspec.yaml └── test │ ├── collection_test.dart │ └── setup_firestore_mock.dart ├── lib ├── cloud_firestore_odm_generator.dart └── src │ ├── collection_data.dart │ ├── collection_generator.dart │ ├── named_query_data.dart │ ├── names.dart │ ├── parse_generator.dart │ ├── templates │ ├── collection_reference.dart │ ├── document_reference.dart │ ├── document_snapshot.dart │ ├── named_query.dart │ ├── query_document_snapshot.dart │ ├── query_reference.dart │ └── query_snapshot.dart │ └── validator_generator.dart ├── pubspec.yaml └── test ├── collection_reference_test.dart ├── document_reference_test.dart └── query_reference_test.dart /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Expected Behavior 2 | 3 | 4 | ## Actual Behavior 5 | 6 | 7 | ## Steps to Reproduce the Problem 8 | 9 | 1. 10 | 1. 11 | 1. 12 | 13 | ## Specifications 14 | 15 | - Version: 16 | - Platform: -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Fixes # 2 | 3 | > It's a good idea to open an issue first for discussion. 4 | 5 | - [ ] Tests pass 6 | - [ ] Appropriate changes to README are included in PR -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .atom/ 3 | .idea/ 4 | .vscode/* 5 | .melos_tool/* 6 | !.vscode/tasks.json 7 | !.vscode/settings.json 8 | 9 | .packages 10 | .pub/ 11 | .dart_tool/ 12 | pubspec.lock 13 | pubspec_overrides.yaml 14 | flutter_export_environment.sh 15 | 16 | examples/all_plugins/pubspec.yaml 17 | 18 | Podfile.lock 19 | Pods/ 20 | .symlinks/ 21 | **/Flutter/ephemeral/ 22 | **/Flutter/Flutter.podspec 23 | **/Flutter/App.framework/ 24 | **/Flutter/Flutter.framework/ 25 | **/Flutter/Generated.xcconfig 26 | **/Flutter/flutter_assets/ 27 | ServiceDefinitions.json 28 | xcuserdata/ 29 | 30 | local.properties 31 | keystore.properties 32 | .gradle/ 33 | gradlew 34 | gradlew.bat 35 | gradle-wrapper.jar 36 | *.iml 37 | 38 | GeneratedPluginRegistrant.h 39 | GeneratedPluginRegistrant.m 40 | GeneratedPluginRegistrant.java 41 | GeneratedPluginRegistrant.swift 42 | generated_plugin_registrant.dart 43 | 44 | build/ 45 | .flutter-plugins 46 | .flutter-plugins-dependencies 47 | 48 | .project 49 | .classpath 50 | .settings 51 | .last_build_id 52 | 53 | # Docs 54 | 55 | # Dependencies 56 | node_modules 57 | 58 | # Production 59 | website/build 60 | 61 | # Generated files 62 | .docusaurus 63 | .cache-loader 64 | 65 | # Misc 66 | .env.local 67 | .env.development.local 68 | .env.test.local 69 | .env.production.local 70 | 71 | npm-debug.log* 72 | yarn-debug.log* 73 | yarn-error.log* 74 | 75 | firebase-debug.log 76 | firestore-debug.log 77 | database-debug.log 78 | ui-debug.log 79 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2017 The Chromium Authors. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Google Inc. nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cloud Firestore ODM Package for Flutter 2 | 3 | [![pub package](https://img.shields.io/pub/v/cloud_firestore_odm.svg)](https://pub.dev/packages/cloud_firestore_odm) 4 | 5 | The Cloud Firestore ODM package enables developers to build fully type-safe applications for Flutter using the FlutterFire Cloud Firestore plugin. 6 | 7 | > The Cloud Firestore ODM is currently in **alpha**. Expect breaking changes, API changes and more. The documentation is still a work in progress. See the [discussion](https://github.com/firebase/flutterfire/discussions/7475) for more details. 8 | 9 | ## What is an ODM? 10 | 11 | The Object Document Mapper or ODM is an interface that treats a document as a tree structure wherein each node is an object representing a part of the document. 12 | ODM methods allow programmatic access to the tree making it possible to change the structure, style or content of a document. 13 | 14 | ## What does it do? 15 | 16 | The Cloud Firestore ODM package enables developers to build fully type-safe applications for Flutter using the FlutterFire Cloud Firestore plugin. 17 | 18 | The ODM allows data schemas to be defined which represent the data structure of your Cloud Firestore database. By defining schemas the ODM provides the following benefits: 19 | 20 | - ↔️ Bi-directional data validation. 21 | - 🔒 Type-safe data querying, supporting all Firestore query operations. 22 | - 🔄 Flutter Widgets for simple data binding with your data. 23 | - 🎯 Data selectors to help avoid unnecessary Widget rebuilds. 24 | - 💻 Full API code completion. 25 | 26 | ## Installation 27 | 28 | ### 1. Use a recent Dart version 29 | 30 | The ODM relies on a recent Dart feature: generic annotations. 31 | As such, to use the ODM you will need to upgrade your Dart SDK to enable this feature. 32 | 33 | That is done by adding the following to your `pubspec.yaml`: 34 | 35 | ```yaml 36 | environment: 37 | sdk: ">=2.18.0 <4.0.0" 38 | ``` 39 | 40 | ### 2. FlutterFire Initialization 41 | 42 | Before getting started, [Add Firebase to your Flutter app](https://firebase.google.com/docs/flutter/setup). 43 | 44 | ```dart 45 | void main() async { 46 | WidgetsFlutterBinding.ensureInitialized(); 47 | await Firebase.initializeApp( 48 | options: DefaultFirebaseOptions.currentPlatform, 49 | ); 50 | runApp(MyApp()); 51 | } 52 | ``` 53 | 54 | ### 3. Install `cloud_firestore` plugin 55 | 56 | The ODM depends on the `cloud_firestore` plugin, so first ensure you have it 57 | [installed on your project](https://firebase.google.com/docs/firestore/quickstart#dart). 58 | 59 | ### 4. Install dependency 60 | 61 | To install the ODM, you'll need to install both the [`cloud_firestore_odm`](https://pub.dev/packages/cloud_firestore_odm) and [`json_annotation`](https://pub.dev/packages/json_annotation) packages 62 | from pub: 63 | 64 | ```bash 65 | flutter pub add cloud_firestore_odm 66 | flutter pub add json_annotation 67 | ``` 68 | 69 | Next, install the `build_runner`, `cloud_firestore_odm_generator` & `json_serializable` packages as development dependencies: 70 | 71 | ```bash 72 | flutter pub add --dev build_runner 73 | flutter pub add --dev cloud_firestore_odm_generator 74 | flutter pub add --dev json_serializable 75 | ``` 76 | 77 | ## 5. Enable `create_field_map` and `create_per_field_to_json` of `json_serializable` 78 | 79 | For the ODM to work, it is necessary to enable the `create_field_map` and `create_per_field_to_json` of `json_serializable`. 80 | This can be done by creating a `build.yaml` file next to your `pubspec.yaml` and 81 | paste the following: 82 | 83 | ```yaml 84 | targets: 85 | $default: 86 | builders: 87 | json_serializable: 88 | options: 89 | create_field_map: true 90 | create_per_field_to_json: true 91 | ``` 92 | 93 | This will enable `create_field_map` and `create_per_field_to_json` for the entire project. 94 | 95 | Alternatively, you can enable the option on a per-model basis using `json_annotation`'s `@JsonSerializable` object: 96 | 97 | ```dart 98 | @JsonSerializable(createFieldMap: true, createPerFieldToJson: true) 99 | @Collection(...) 100 | class MyModel {...} 101 | ``` 102 | 103 | ## Next Steps 104 | 105 | Once installed, read the documentation on [defining models](./docs/defining-models.md). 106 | 107 | ## Issues and feedback 108 | 109 | Please file FlutterFire specific issues, bugs, or feature requests in our [issue tracker](https://github.com/firebaseextended/firestoreodm-flutter/issues/new). 110 | 111 | Plugin issues that are not specific to FlutterFire can be filed in the [Flutter issue tracker](https://github.com/flutter/flutter/issues/new). 112 | 113 | To contribute a change to this plugin, 114 | please review our [contribution guide](https://github.com/firebaseextended/firestoreodm-flutter/blob/main/docs/contributing.md) 115 | and open a [pull request](https://github.com/firebaseextended/firestoreodm-flutter/pulls). 116 | -------------------------------------------------------------------------------- /all_lint_rules.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2016-present Invertase Limited & Contributors 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this library except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | linter: 18 | rules: 19 | - always_declare_return_types 20 | - always_put_control_body_on_new_line 21 | - always_put_required_named_parameters_first 22 | - always_require_non_null_named_parameters 23 | - always_specify_types 24 | - always_use_package_imports 25 | - annotate_overrides 26 | - avoid_annotating_with_dynamic 27 | - avoid_bool_literals_in_conditional_expressions 28 | - avoid_catches_without_on_clauses 29 | - avoid_catching_errors 30 | - avoid_classes_with_only_static_members 31 | - avoid_double_and_int_checks 32 | - avoid_dynamic_calls 33 | - avoid_empty_else 34 | - avoid_equals_and_hash_code_on_mutable_classes 35 | - avoid_escaping_inner_quotes 36 | - avoid_field_initializers_in_const_classes 37 | - avoid_function_literals_in_foreach_calls 38 | - avoid_implementing_value_types 39 | - avoid_init_to_null 40 | - avoid_js_rounded_ints 41 | - avoid_multiple_declarations_per_line 42 | - avoid_null_checks_in_equality_operators 43 | - avoid_positional_boolean_parameters 44 | - avoid_print 45 | - avoid_private_typedef_functions 46 | - avoid_redundant_argument_values 47 | - avoid_relative_lib_imports 48 | - avoid_renaming_method_parameters 49 | - avoid_return_types_on_setters 50 | - avoid_returning_null 51 | - avoid_returning_null_for_future 52 | - avoid_returning_null_for_void 53 | - avoid_returning_this 54 | - avoid_setters_without_getters 55 | - avoid_shadowing_type_parameters 56 | - avoid_single_cascade_in_expression_statements 57 | - avoid_slow_async_io 58 | - avoid_type_to_string 59 | - avoid_types_as_parameter_names 60 | - avoid_types_on_closure_parameters 61 | - avoid_unnecessary_containers 62 | - avoid_unused_constructor_parameters 63 | - avoid_void_async 64 | - avoid_web_libraries_in_flutter 65 | - await_only_futures 66 | - camel_case_extensions 67 | - camel_case_types 68 | - cancel_subscriptions 69 | - cascade_invocations 70 | - cast_nullable_to_non_nullable 71 | - close_sinks 72 | - comment_references 73 | - constant_identifier_names 74 | - control_flow_in_finally 75 | - curly_braces_in_flow_control_structures 76 | - deprecated_consistency 77 | - diagnostic_describe_all_properties 78 | - directives_ordering 79 | - do_not_use_environment 80 | - empty_catches 81 | - empty_constructor_bodies 82 | - empty_statements 83 | - exhaustive_cases 84 | - file_names 85 | - flutter_style_todos 86 | - hash_and_equals 87 | - implementation_imports 88 | - invariant_booleans 89 | - iterable_contains_unrelated_type 90 | - join_return_with_assignment 91 | - leading_newlines_in_multiline_strings 92 | - library_names 93 | - library_prefixes 94 | - library_private_types_in_public_api 95 | - lines_longer_than_80_chars 96 | - list_remove_unrelated_type 97 | - literal_only_boolean_expressions 98 | - missing_whitespace_between_adjacent_strings 99 | - no_adjacent_strings_in_list 100 | - no_default_cases 101 | - no_duplicate_case_values 102 | - no_logic_in_create_state 103 | - no_runtimeType_toString 104 | - non_constant_identifier_names 105 | - null_check_on_nullable_type_parameter 106 | - null_closures 107 | - omit_local_variable_types 108 | - one_member_abstracts 109 | - only_throw_errors 110 | - overridden_fields 111 | - package_api_docs 112 | - package_names 113 | - package_prefixed_library_names 114 | - parameter_assignments 115 | - prefer_adjacent_string_concatenation 116 | - prefer_asserts_in_initializer_lists 117 | - prefer_asserts_with_message 118 | - prefer_collection_literals 119 | - prefer_conditional_assignment 120 | - prefer_const_constructors 121 | - prefer_const_constructors_in_immutables 122 | - prefer_const_declarations 123 | - prefer_const_literals_to_create_immutables 124 | - prefer_constructors_over_static_methods 125 | - prefer_contains 126 | - prefer_double_quotes 127 | - prefer_equal_for_default_values 128 | - prefer_expression_function_bodies 129 | - prefer_final_fields 130 | - prefer_final_in_for_each 131 | - prefer_final_locals 132 | - prefer_for_elements_to_map_fromIterable 133 | - prefer_foreach 134 | - prefer_function_declarations_over_variables 135 | - prefer_generic_function_type_aliases 136 | - prefer_if_elements_to_conditional_expressions 137 | - prefer_if_null_operators 138 | - prefer_initializing_formals 139 | - prefer_inlined_adds 140 | - prefer_int_literals 141 | - prefer_interpolation_to_compose_strings 142 | - prefer_is_empty 143 | - prefer_is_not_empty 144 | - prefer_is_not_operator 145 | - prefer_iterable_whereType 146 | - prefer_mixin 147 | - prefer_null_aware_method_calls 148 | - prefer_null_aware_operators 149 | - prefer_relative_imports 150 | - prefer_single_quotes 151 | - prefer_spread_collections 152 | - prefer_typing_uninitialized_variables 153 | - prefer_void_to_null 154 | - provide_deprecation_message 155 | - public_member_api_docs 156 | - recursive_getters 157 | - require_trailing_commas 158 | - sized_box_for_whitespace 159 | - slash_for_doc_comments 160 | - sort_child_properties_last 161 | - sort_constructors_first 162 | - sort_pub_dependencies 163 | - sort_unnamed_constructors_first 164 | - test_types_in_equals 165 | - throw_in_finally 166 | - tighten_type_of_initializing_formals 167 | - type_annotate_public_apis 168 | - type_init_formals 169 | - unawaited_futures 170 | - unnecessary_await_in_return 171 | - unnecessary_brace_in_string_interps 172 | - unnecessary_const 173 | - unnecessary_final 174 | - unnecessary_getters_setters 175 | - unnecessary_lambdas 176 | - unnecessary_new 177 | - unnecessary_null_aware_assignments 178 | - unnecessary_null_checks 179 | - unnecessary_null_in_if_null_operators 180 | - unnecessary_nullable_for_final_variable_declarations 181 | - unnecessary_overrides 182 | - unnecessary_parenthesis 183 | - unnecessary_raw_strings 184 | - unnecessary_statements 185 | - unnecessary_string_escapes 186 | - unnecessary_string_interpolations 187 | - unnecessary_this 188 | - unrelated_type_equality_checks 189 | - unsafe_html 190 | - use_build_context_synchronously 191 | - use_full_hex_values_for_flutter_colors 192 | - use_function_type_syntax_for_parameters 193 | - use_if_null_to_convert_nulls_to_bools 194 | - use_is_even_rather_than_modulo 195 | - use_key_in_widget_constructors 196 | - use_late_for_private_fields_and_variables 197 | - use_named_constants 198 | - use_raw_strings 199 | - use_rethrow_when_possible 200 | - use_setters_to_change_properties 201 | - use_string_buffers 202 | - use_to_and_as_if_applicable 203 | - valid_regexps 204 | - void_checks 205 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2016-present Invertase Limited & Contributors 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this library except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | include: all_lint_rules.yaml 18 | analyzer: 19 | language: 20 | strict-casts: true 21 | strict-inference: true 22 | strict-raw-types: true 23 | errors: 24 | # So we can run ODM dart tests in the melos workspace - otherwise dart test will 25 | # source the ODM package from pub - which won't have the local changes 26 | # (or is not yet published). 27 | # See cloud_firestore_odm_generator/pubspec.yaml 28 | unnecessary_dev_dependency: ignore 29 | # Otherwise cause the import of all_lint_rules to warn because of some rules conflicts. 30 | # We explicitly enabled even conflicting rules and are fixing the conflict 31 | # in this file 32 | included_file_warning: ignore 33 | 34 | linter: 35 | rules: 36 | # TODO(Salakar) re-enable documentation rule and fix issues. 37 | public_member_api_docs: false 38 | 39 | always_put_control_body_on_new_line: false 40 | comment_references: true 41 | prefer_constructors_over_static_methods: true 42 | prefer_final_fields: true 43 | omit_local_variable_types: true 44 | avoid_equals_and_hash_code_on_mutable_classes: false 45 | 46 | ############# 47 | # Conflicts with 'unecessary_final' 48 | prefer_final_locals: true 49 | 50 | cascade_invocations: false 51 | 52 | # Conflicts with `prefer_single_quotes` 53 | # Single quotes are easier to type and don't compromise on readability. 54 | prefer_double_quotes: false 55 | 56 | # Conflicts with `omit_local_variable_types` and other rules. 57 | # As per Dart guidelines, we want to avoid unnecessary types to make the code 58 | # more readable. 59 | # See https://dart.dev/guides/language/effective-dart/design#avoid-type-annotating-initialized-local-variables 60 | always_specify_types: false 61 | 62 | # Incompatible with `prefer_final_locals` 63 | # Having immutable local variables makes larger functions more predictible 64 | # so we will use `prefer_final_locals` instead. 65 | unnecessary_final: false 66 | 67 | # Not quite suitable for Flutter, which may have a `build` method with a single 68 | # return, but that return is still complex enough that a "body" is worth it. 69 | prefer_expression_function_bodies: false 70 | 71 | # Conflicts with the convention used by flutter, which puts `Key key` 72 | # and `@required Widget child` last. 73 | always_put_required_named_parameters_first: false 74 | 75 | # This project doesn't use Flutter-style todos 76 | flutter_style_todos: false 77 | 78 | # There are situations where we voluntarily want to catch everything, 79 | # especially as a library. 80 | avoid_catches_without_on_clauses: false 81 | 82 | # Boring as it sometimes force a line of 81 characters to be split in two. 83 | # As long as we try to respect that 80 characters limit, going slightly 84 | # above is fine. 85 | lines_longer_than_80_chars: false 86 | 87 | # Conflicts with disabling `implicit-dynamic` 88 | avoid_annotating_with_dynamic: false 89 | 90 | # conflicts with `prefer_relative_imports` 91 | always_use_package_imports: false 92 | 93 | # Disabled for now until NNBD as it otherwise conflicts with `missing_return` 94 | no_default_cases: false 95 | 96 | # False positive, null checks don't need a message 97 | prefer_asserts_with_message: false 98 | 99 | # Cumbersome with `context.select` 100 | avoid_types_on_closure_parameters: false 101 | 102 | # Too many false positive (builders) 103 | diagnostic_describe_all_properties: false 104 | 105 | # false positives (setter-like functions) 106 | avoid_positional_boolean_parameters: false 107 | 108 | # Does not apply to providers 109 | prefer_const_constructors_in_immutables: false 110 | -------------------------------------------------------------------------------- /docs/code-generation.md: -------------------------------------------------------------------------------- 1 | # Code Generation 2 | 3 | > The Cloud Firestore ODM is currently in **alpha**. Expect breaking changes, API changes and more. The documentation is still a work in progress. See the [discussion](https://github.com/firebase/flutterfire/discussions/7475) for more details. 4 | 5 | The power of the ODM comes from code generation. The models created need to be run through 6 | code generation to allow for the full type safety which the ODM offers. 7 | 8 | When [defining models](./defining-models.md) we include a `part` declaration - 9 | this declaration references a file which have not yet generated. 10 | 11 | Within the root of your project, run the following command to run code 12 | generation against the defined models: 13 | 14 | ```bash 15 | dart run build_runner build --delete-conflicting-outputs 16 | ``` 17 | 18 | During development, you can watch for changes and automatically update the 19 | generated code: 20 | 21 | ```bash 22 | dart run build_runner watch --delete-conflicting-outputs 23 | ``` 24 | 25 | This will generate files with the postfix of `.g.dart`, corresponding to the 26 | file name of your model. For example: 27 | 28 | - A model is defined within a file named `product.dart`. 29 | - Code generation will create a file in the same directory named `product.g.dart`. 30 | - Within the `product.dart` file, apply the part declaration (`part 'product.g.dart'`). 31 | 32 | ## Next steps 33 | 34 | With our code generated, we can now start to [use the references](./references.md). 35 | -------------------------------------------------------------------------------- /docs/code-of-conduct.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of 9 | experience, education, socio-economic status, nationality, personal appearance, 10 | race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or reject 41 | comments, commits, code, wiki edits, issues, and other contributions that are 42 | not aligned to this Code of Conduct, or to ban temporarily or permanently any 43 | contributor for other behaviors that they deem inappropriate, threatening, 44 | offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | This Code of Conduct also applies outside the project spaces when the Project 56 | Steward has a reasonable belief that an individual's behavior may have a 57 | negative impact on the project or its community. 58 | 59 | ## Conflict Resolution 60 | 61 | We do not believe that all conflict is bad; healthy debate and disagreement 62 | often yield positive results. However, it is never okay to be disrespectful or 63 | to engage in behavior that violates the project’s code of conduct. 64 | 65 | If you see someone violating the code of conduct, you are encouraged to address 66 | the behavior directly with those involved. Many issues can be resolved quickly 67 | and easily, and this gives people more control over the outcome of their 68 | dispute. If you are unable to resolve the matter for any reason, or if the 69 | behavior is threatening or harassing, report it. We are dedicated to providing 70 | an environment where participants feel welcome and safe. 71 | 72 | Reports should be directed to *[PROJECT STEWARD NAME(s) AND EMAIL(s)]*, the 73 | Project Steward(s) for *[PROJECT NAME]*. It is the Project Steward’s duty to 74 | receive and address reported violations of the code of conduct. They will then 75 | work with a committee consisting of representatives from the Open Source 76 | Programs Office and the Google Open Source Strategy team. If for any reason you 77 | are uncomfortable reaching out to the Project Steward, please email 78 | opensource@google.com. 79 | 80 | We will investigate every complaint, but you may not receive a direct response. 81 | We will use our discretion in determining when and how to follow up on reported 82 | incidents, which may range from not taking action to permanent expulsion from 83 | the project and project-sponsored spaces. We will notify the accused of the 84 | report and provide them an opportunity to discuss it before any action is taken. 85 | The identity of the reporter will be omitted from the details of the report 86 | supplied to the accused. In potentially harmful situations, such as ongoing 87 | harassment or threats to anyone's safety, we may take action without notice. 88 | 89 | ## Attribution 90 | 91 | This Code of Conduct is adapted from the Contributor Covenant, version 1.4, 92 | available at 93 | https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 94 | -------------------------------------------------------------------------------- /docs/contributing.md: -------------------------------------------------------------------------------- 1 | # How to Contribute 2 | 3 | We'd love to accept your patches and contributions to this project. 4 | 5 | ## Before you begin 6 | 7 | ### Sign our Contributor License Agreement 8 | 9 | Contributions to this project must be accompanied by a 10 | [Contributor License Agreement](https://cla.developers.google.com/about) (CLA). 11 | You (or your employer) retain the copyright to your contribution; this simply 12 | gives us permission to use and redistribute your contributions as part of the 13 | project. 14 | 15 | If you or your current employer have already signed the Google CLA (even if it 16 | was for a different project), you probably don't need to do it again. 17 | 18 | Visit to see your current agreements or to 19 | sign a new one. 20 | 21 | ### Review our Community Guidelines 22 | 23 | This project follows [Google's Open Source Community 24 | Guidelines](https://opensource.google/conduct/). 25 | 26 | ## Contribution process 27 | 28 | ### Code Reviews 29 | 30 | All submissions, including submissions by project members, require review. We 31 | use GitHub pull requests for this purpose. Consult 32 | [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more 33 | information on using pull requests. 34 | -------------------------------------------------------------------------------- /docs/defining-models.md: -------------------------------------------------------------------------------- 1 | # Defining models 2 | 3 | > The Cloud Firestore ODM is currently in **alpha**. Expect breaking changes, API changes and more. The documentation is still a work in progress. See the [discussion](https://github.com/firebase/flutterfire/discussions/7475) for more details. 4 | 5 | - [Creating models](#creating-models) 6 | - [Creating references](#creating-references) 7 | - [Injecting the document ID in the model](#injecting-the-document-id-in-the-model) 8 | - [Model validation](#model-validation) 9 | - [Available validators](#available-validators) 10 | - [`int`](#int) 11 | - [Custom validators](#custom-validators) 12 | - [Next steps](#next-steps) 13 | 14 | ## Creating models 15 | 16 | A model represents exactly what data we expect to both receive and mutate on Firestore. The ODM 17 | ensures that all data is validated against a model, and if the model is not valid an error will be 18 | thrown. 19 | 20 | To get started, assume we have a collection on our Firestore database called "Users". The collection 21 | contains many documents containing user information such as a name, age, email (and so on!). To 22 | define a model for this data, create a class: 23 | 24 | ```dart 25 | import 'package:json_annotation/json_annotation.dart'; 26 | import 'package:cloud_firestore/cloud_firestore.dart'; 27 | import 'package:cloud_firestore_odm/cloud_firestore_odm.dart'; 28 | 29 | // This doesn't exist yet...! See "Next Steps" 30 | part 'user.g.dart'; 31 | 32 | /// A custom JsonSerializable annotation that supports decoding objects such 33 | /// as Timestamps and DateTimes. 34 | /// This variable can be reused between different models 35 | const firestoreSerializable = JsonSerializable( 36 | converters: firestoreJsonConverters, 37 | // The following values could alternatively be set inside your `build.yaml` 38 | explicitToJson: true, 39 | createFieldMap: true, 40 | createPerFieldToJson: true, 41 | ); 42 | 43 | @firestoreSerializable 44 | class User { 45 | User({ 46 | required this.name, 47 | required this.age, 48 | required this.email, 49 | }); 50 | 51 | final String name; 52 | final int age; 53 | final String email; 54 | } 55 | ``` 56 | 57 | The `User` model defines that a user must have a name and email as a `String` and age as an `int`. 58 | 59 | Supported data types are those of Firestore (string, boolean, number, geo point, timestamp, list), 60 | plus `DateTime` and `DocumentReference>`. Nested object and custom types are 61 | supported with `@JsonKey` (see [json_serializable](https://pub.dev/packages/json_serializable)). 62 | In addition to `toJson` and `fromJson`, `@JsonKey` also offer `ignore` and `name` parameters. 63 | 64 | A current limitation, typesafe query method and update parameters are not generated for nested object 65 | and custom types like enum. 66 | 67 | :::caution 68 | If your model class is defined in a separate file than the Firestore reference, 69 | you will need to explicitly specify `fromJson`/`toJson` functions as followed: 70 | 71 | ```dart 72 | @firestoreSerializable 73 | class User { 74 | User({ 75 | required this.name, 76 | required this.age, 77 | required this.email, 78 | }); 79 | 80 | factory User.fromJson(Map json) => _$UserFromJson(json); 81 | 82 | final String name; 83 | final int age; 84 | final String email; 85 | 86 | Map toJson() => _$UserToJson(this); 87 | } 88 | ``` 89 | 90 | ::: 91 | 92 | ## Creating references 93 | 94 | On their own, a model does not do anything. Instead we create a "reference" using a model. 95 | A reference enables the ODM to interact with Firestore using the model. 96 | 97 | To create a reference, we use the `Collection` annotation which is used as a pointer to a collection 98 | within the Firestore database. For example, the `users` collection in the root of the database 99 | corresponds to the `Users` model we defined previously: 100 | 101 | ```dart 102 | @firestoreSerializable 103 | class User { 104 | // ... 105 | } 106 | 107 | @Collection('users') 108 | final usersRef = UserCollectionReference(); 109 | ``` 110 | 111 | If you are looking to define a model as a reference on a Subcollection, read the [Working with Subcollections](./subcollections.md) documentation. 112 | 113 | ## Injecting the document ID in the model 114 | 115 | By default, the document ID is not present in the firestore object once decoded. 116 | 117 | While you can acccess it using the `DocumentSnapshot`, it isn't always convenient. 118 | A solution to that is to use the `@Id` annotation, to tell Firestore that a 119 | given property in a class would be the document ID: 120 | 121 | ```dart 122 | @Collection('users') 123 | @firestoreSerializable 124 | class Person { 125 | Person({ 126 | required this.name, 127 | required this.age, 128 | required this.id, 129 | }); 130 | 131 | // By adding this annotation, this property will not be considered as part 132 | // of the Firestore document, but instead represent the document ID. 133 | @Id() 134 | final String id; 135 | 136 | final String name; 137 | final int age; 138 | } 139 | ``` 140 | 141 | There are a few restrictions when using this annotation: 142 | 143 | - It can be used only once within an object 144 | - The annotated property must be of type `String`. 145 | 146 | ## Model validation 147 | 148 | Defining a model with standard Dart types (e.g. `String`, `int` etc) works for many applications, 149 | but what about more bespoke validation? 150 | 151 | For example, a users age cannot be a negative value, so how do we validate against this? 152 | 153 | The ODM provides some basic annotation validators which can be used on model properties. In this 154 | example, we can take advantage of the `Min` validator: 155 | 156 | ```dart 157 | @firestoreSerializable 158 | class User { 159 | User({ 160 | required this.name, 161 | required this.age, 162 | required this.email, 163 | }) { 164 | // Apply the validator 165 | _$assertUser(this); 166 | } 167 | 168 | final String name; 169 | final String email; 170 | 171 | // Apply the `Min` validator 172 | @Min(0) 173 | final int age; 174 | } 175 | ``` 176 | 177 | The `Min` annotation ensures that any value for the `age` property is always positive, otherwise an 178 | error will be thrown. 179 | 180 | To ensure validators are applied, the model instance is provided to the generated `$assertUser` 181 | method. Note the name of this class is generated based on the model name (for example a model named 182 | `Product` with validators would generate a `$assertProduct` method). 183 | 184 | ### Available validators 185 | 186 | #### `int` 187 | 188 | The following annotations are available for `int` properties: 189 | 190 | | Annotation | Description | 191 | | ---------- | -------------------------------------------------- | 192 | | `Min` | Validates a number is not less than this value. | 193 | | `Max` | Validates a number is not greater than this value. | 194 | 195 | ### Custom validators 196 | 197 | In some cases, you may wish to validate data against custom validation. For example, we may want to 198 | ensure the string value provided to `email` is in-fact a valid email address. 199 | 200 | To define a custom validator, create a class which implements `Validator`: 201 | 202 | ```dart 203 | class EmailAddressValidator implements Validator { 204 | const EmailAddressValidator(); 205 | 206 | @override 207 | void validate(covariant String value, String propertyName) { 208 | if (!value.endsWith("@google.com")) { 209 | throw ArgumentError.value(value, propertyName); 210 | } 211 | } 212 | } 213 | ``` 214 | 215 | Within the model, you can then apply the validator to the property: 216 | 217 | ```dart 218 | @firestoreSerializable 219 | class User { 220 | User({ 221 | required this.name, 222 | required this.age, 223 | required this.email, 224 | }) { 225 | // Apply the validator 226 | _$assertUser(this); 227 | } 228 | 229 | final String name; 230 | final int age; 231 | 232 | @EmailAddressValidator() 233 | final String email; 234 | } 235 | ``` 236 | 237 | ## Next steps 238 | 239 | Some of the code on this page is created via code generation 240 | (e.g. `_$assertUser`, `UserCollectionReference`) - you can learn more about 241 | how to generate this code via the [Code Generation](./code-generation.md) documentation! 242 | -------------------------------------------------------------------------------- /docs/references.md: -------------------------------------------------------------------------------- 1 | # Using References 2 | 3 | > The Cloud Firestore ODM is currently in **alpha**. Expect breaking changes, API changes and more. The documentation is still a work in progress. See the [discussion](https://github.com/firebase/flutterfire/discussions/7475) for more details. 4 | 5 | A [reference](./defining-models.md#creating-references) provides full type-safe access to a Firestore 6 | Collection and Documents. 7 | 8 | The ODM provides a useful `FirestoreBuilder` widget which allows you to access your Firestore data 9 | via the ODM. 10 | 11 | ## Reading Collections 12 | 13 | Provide a collection reference instance to the `FirestoreBuilder`, returning a builder: 14 | 15 | ```dart 16 | 17 | @Collection('users') 18 | final usersRef = UserCollectionReference(); 19 | 20 | // ... 21 | 22 | class UsersList extends StatelessWidget { 23 | @override 24 | Widget build(BuildContext context) { 25 | return FirestoreBuilder( 26 | ref: usersRef, 27 | builder: (context, AsyncSnapshot snapshot, Widget? child) { 28 | if (snapshot.hasError) return Text('Something went wrong!'); 29 | if (!snapshot.hasData) return Text('Loading users...'); 30 | 31 | // Access the QuerySnapshot 32 | UserQuerySnapshot querySnapshot = snapshot.requireData; 33 | 34 | return ListView.builder( 35 | itemCount: querySnapshot.docs.length, 36 | itemBuilder: (context, index) { 37 | // Access the User instance 38 | User user = querySnapshot.docs[index].data; 39 | 40 | return Text('User name: ${user.name}, age ${user.age}'); 41 | }, 42 | ); 43 | } 44 | ); 45 | } 46 | } 47 | ``` 48 | 49 | In the above example, a realtime subscription is created by the `FirestoreBuilder` widget and 50 | returns all of the documents within the `users` collection as a `UserDocumentSnapshot`. This usage 51 | of the ODM guarantees the following: 52 | 53 | - Each snapshot document is a `User` model instance. 54 | - The data of the model instance is fully validated. If any remote data does not pass model 55 | validation an error will be thrown. 56 | 57 | The `UserDocumentSnapshot` provides access to the `User` model and the `UserDocumentReference` 58 | instance. 59 | 60 | ## Reading Documents 61 | 62 | Similar to collections, you can provide the `FirestoreBuilder` widget a document reference to a 63 | specific document instead by calling the `doc()` method: 64 | 65 | ```dart 66 | 67 | @Collection('users') 68 | final usersRef = UserCollectionReference(); 69 | 70 | // ... 71 | 72 | class User extends StatelessWidget { 73 | User(this.id); 74 | 75 | final String id; 76 | 77 | @override 78 | Widget build(BuildContext context) { 79 | return FirestoreBuilder( 80 | // Access a specific document 81 | ref: usersRef.doc(id), 82 | builder: (context, AsyncSnapshot snapshot, Widget? child) { 83 | if (snapshot.hasError) return Text('Something went wrong!'); 84 | if (!snapshot.hasData) return Text('Loading user...'); 85 | 86 | // Access the UserDocumentSnapshot 87 | UserDocumentSnapshot documentSnapshot = snapshot.requireData; 88 | 89 | if (!documentSnapshot.exists) { 90 | return Text('User does not exist.'); 91 | } 92 | 93 | User user = documentSnapshot.data!; 94 | 95 | return Text('User name: ${user.name}, age ${user.age}'); 96 | } 97 | ); 98 | } 99 | } 100 | ``` 101 | 102 | Much like regular Firestore SDK usage, if the document does not exist a snapshot will still 103 | be returned. In this example, we first check for existence before accessing the `User` instance. 104 | 105 | ## Performing Queries 106 | 107 | Another powerful use-case of the ODM is the generation of type-safe querying. 108 | 109 | Models define exactly what our data schema is, therefore this allows the ODM to generate 110 | useful type-safe methods for querying. 111 | 112 | ```dart 113 | @@firestoreSerializable 114 | class User { 115 | User({ 116 | required this.name, 117 | required this.age, 118 | }); 119 | 120 | final String name; 121 | final int age; 122 | } 123 | 124 | @Collection('users') 125 | final usersRef = UserCollectionReference(); 126 | ``` 127 | 128 | The above `User` model when generated generates some powerful query capabilities: 129 | 130 | ```dart 131 | usersRef.whereName(isEqualTo: 'John'); 132 | usersRef.whereAge(isGreaterThan: 18); 133 | usersRef.orderByAge(); 134 | // ..etc! 135 | ``` 136 | 137 | If a value is passed which does not satisfy a validator (e.g. minimum age) an error will be 138 | thrown. 139 | 140 | Similar to querying collections, provide a query to the `FirestoreBuilder`: 141 | 142 | ```dart 143 | FirestoreBuilder( 144 | ref: usersRef.whereAge(isGreaterThan: 18).orderByAge(), 145 | builder: (context, AsyncSnapshot snapshot, Widget? child) { 146 | // ... 147 | } 148 | ); 149 | ``` 150 | 151 | If any of the query constraints are modified, the state of the builder will be reset. 152 | 153 | ## Optimizing rebuilds 154 | 155 | Through FirestoreBuilder, it is possible to optionally optimize widget rebuilds, using the select method on a reference. The basic idea is; rather than listening to the entire snapshot, select allows us to listen to only a part of the snapshot. 156 | 157 | For example, if we have a document that returns a Person instance, we could voluntarily only listen to that person's name by doing: 158 | 159 | ```dart 160 | FirestoreBuilder( 161 | ref: usersRef.doc('id').select((UserDocumentSnapshot snapshot) => snapshot.data!.name)), 162 | builder: (context, AsyncSnapshot snapshot, Widget? child) { 163 | return Text('Hello ${snapshot.data}'); 164 | } 165 | ); 166 | ``` 167 | 168 | By doing so, now Text will rebuild only when the person's name changes. If that person's age changes, this won't rebuild our Text. 169 | 170 | > Note: This is a client only optimization. The underlying Firestore SDKs will still continue to subscribe to and receive updates for the entire snapshot and therefore this will not reduce any associated billing costs. 171 | -------------------------------------------------------------------------------- /docs/subcollections.md: -------------------------------------------------------------------------------- 1 | # Working with Subcollections 2 | 3 | > The Cloud Firestore ODM is currently in **alpha**. Expect breaking changes, API changes and more. The documentation is still a work in progress. See the [discussion](https://github.com/firebase/flutterfire/discussions/7475) for more details. 4 | 5 | The ODM provides support for subcollections via the `Collection` annotation. For example, first define 6 | the root collection as normal: 7 | 8 | ```dart 9 | @firestoreSerializable 10 | class User { 11 | // ... 12 | } 13 | 14 | @Collection('users') 15 | final usersRef = UserCollectionReference(); 16 | ``` 17 | 18 | Let's assume each user document contains a subcollection containing user addresses. Firstly define 19 | the model for an address: 20 | 21 | ```dart 22 | @firestoreSerializable 23 | class Address { 24 | // ... 25 | } 26 | ``` 27 | 28 | Next, define the path to the subcollection in a new `Collection` annotation: 29 | 30 | ```dart 31 | @Collection('users') 32 | @Collection
('users/*/addresses') 33 | final usersRef = UserCollectionReference(); 34 | ``` 35 | 36 | After [code generation](./code-generation.md), we can now access the sub-collection via the `usersRef` 37 | reference: 38 | 39 | ```dart 40 | AddressCollectionReference addressesRef = usersRef.doc('myDocumentID').addresses; 41 | ``` 42 | 43 | The subcollection reference has full access to the same functionality as root collections. To learn 44 | more about usage of references, see the [using references](./references.md) documentation. 45 | -------------------------------------------------------------------------------- /packages/cloud_firestore_odm/.gitignore: -------------------------------------------------------------------------------- 1 | # flutter folders & files 2 | .dart_tool/ 3 | .flutter-plugins 4 | .flutter-plugins-dependencies 5 | .packages 6 | .pub-cache/ 7 | .pub/ -------------------------------------------------------------------------------- /packages/cloud_firestore_odm/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2021, the Chromium project authors. All rights reserved. 2 | Redistribution and use in source and binary forms, with or without 3 | modification, are permitted provided that the following conditions are 4 | met: 5 | 6 | * Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above 9 | copyright notice, this list of conditions and the following 10 | disclaimer in the documentation and/or other materials provided 11 | with the distribution. 12 | * Neither the name of Google Inc. nor the names of its 13 | contributors may be used to endorse or promote products derived 14 | from this software without specific prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 20 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 21 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 22 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 26 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | -------------------------------------------------------------------------------- /packages/cloud_firestore_odm/README.md: -------------------------------------------------------------------------------- 1 | # Cloud Firestore ODM for Flutter 2 | 3 | A package that provides generated Cloud Firestore bindings for Dart classes, allowing type-safe 4 | queries and updates when using the [Cloud Firestore API](https://firebase.google.com/docs/firestore/). 5 | 6 | To learn more about Firebase Cloud Firestore, please visit the [Firebase website](https://firebase.google.com/products/firestore) 7 | 8 | [![pub package](https://img.shields.io/pub/v/cloud_firestore_odm.svg)](https://pub.dev/packages/cloud_firestore_odm) 9 | 10 | ## Getting Started 11 | 12 | To get started with Cloud Firestore for Flutter, please [see the documentation](https://github.com/firebaseextended/firestoreodm-flutter/tree/main/docs). 13 | 14 | ## Issues and feedback 15 | 16 | Please file FlutterFire specific issues, bugs, or feature requests in our [issue tracker](https://github.com/firebaseextended/firestoreodm-flutter/issues/new). 17 | 18 | Plugin issues that are not specific to FlutterFire can be filed in the [Flutter issue tracker](https://github.com/flutter/flutter/issues/new). 19 | 20 | To contribute a change to this plugin, 21 | please review our [contribution guide](https://github.com/firebaseextended/firestoreodm-flutter/blob/main/docs/contributing.md) 22 | and open a [pull request](https://github.com/firebaseextended/firestoreodm-flutter/pulls). 23 | -------------------------------------------------------------------------------- /packages/cloud_firestore_odm/example/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | **/ios/Flutter/.last_build_id 26 | .dart_tool/ 27 | .flutter-plugins 28 | .flutter-plugins-dependencies 29 | .packages 30 | .pub-cache/ 31 | .pub/ 32 | /build/ 33 | 34 | # Web related 35 | lib/generated_plugin_registrant.dart 36 | 37 | # Symbolication related 38 | app.*.symbols 39 | 40 | # Obfuscation related 41 | app.*.map.json 42 | 43 | # Android Studio will place build artifacts here 44 | /android -------------------------------------------------------------------------------- /packages/cloud_firestore_odm/example/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled. 5 | 6 | version: 7 | revision: 8662e22bac54c71bc4fa5a4f37e9ee80bfd08a4e 8 | channel: dev 9 | 10 | project_type: app 11 | 12 | # Tracks metadata for the flutter migrate command 13 | migration: 14 | platforms: 15 | - platform: root 16 | create_revision: 8662e22bac54c71bc4fa5a4f37e9ee80bfd08a4e 17 | base_revision: 8662e22bac54c71bc4fa5a4f37e9ee80bfd08a4e 18 | - platform: android 19 | create_revision: 8662e22bac54c71bc4fa5a4f37e9ee80bfd08a4e 20 | base_revision: 8662e22bac54c71bc4fa5a4f37e9ee80bfd08a4e 21 | - platform: ios 22 | create_revision: 8662e22bac54c71bc4fa5a4f37e9ee80bfd08a4e 23 | base_revision: 8662e22bac54c71bc4fa5a4f37e9ee80bfd08a4e 24 | - platform: macos 25 | create_revision: 8662e22bac54c71bc4fa5a4f37e9ee80bfd08a4e 26 | base_revision: 8662e22bac54c71bc4fa5a4f37e9ee80bfd08a4e 27 | - platform: web 28 | create_revision: 8662e22bac54c71bc4fa5a4f37e9ee80bfd08a4e 29 | base_revision: 8662e22bac54c71bc4fa5a4f37e9ee80bfd08a4e 30 | - platform: windows 31 | create_revision: 8662e22bac54c71bc4fa5a4f37e9ee80bfd08a4e 32 | base_revision: 8662e22bac54c71bc4fa5a4f37e9ee80bfd08a4e 33 | 34 | # User provided section 35 | 36 | # List of Local paths (relative to this file) that should be 37 | # ignored by the migrate tool. 38 | # 39 | # Files that are not part of the templates will be ignored by default. 40 | unmanaged_files: 41 | - 'lib/main.dart' 42 | - 'ios/Runner.xcodeproj/project.pbxproj' 43 | -------------------------------------------------------------------------------- /packages/cloud_firestore_odm/example/build.yaml: -------------------------------------------------------------------------------- 1 | targets: 2 | $default: 3 | builders: 4 | cloud_firestore_odm_generator: 5 | enabled: true 6 | generate_for: 7 | include: 8 | - lib/* 9 | - lib/** 10 | json_serializable: 11 | enabled: true 12 | generate_for: 13 | include: 14 | - lib/* 15 | - lib/** 16 | options: 17 | create_field_map: true 18 | create_per_field_to_json: true 19 | source_gen|combining_builder: 20 | options: 21 | ignore_for_file: 22 | - 'type=lint' 23 | -------------------------------------------------------------------------------- /packages/cloud_firestore_odm/example/integration_test/cloud_firestore_odm_e2e_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021, the Chromium project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:cloud_firestore/cloud_firestore.dart'; 6 | import 'package:firebase_core/firebase_core.dart'; 7 | import 'package:flutter_test/flutter_test.dart'; 8 | import 'package:integration_test/integration_test.dart'; 9 | 10 | import 'collection_reference_test.dart' as collection_reference_test; 11 | import 'document_reference_test.dart' as document_reference_test; 12 | 13 | import 'firebase_options.dart'; 14 | 15 | import 'path_test.dart' as path_test; 16 | import 'query_reference_test.dart' as query_reference_test; 17 | 18 | void main() { 19 | IntegrationTestWidgetsFlutterBinding.ensureInitialized(); 20 | 21 | group('cloud_firestore_odm', () { 22 | setUpAll(() async { 23 | await Firebase.initializeApp( 24 | options: DefaultFirebaseOptions.currentPlatform, 25 | ); 26 | FirebaseFirestore.instance.useFirestoreEmulator('localhost', 8080); 27 | }); 28 | 29 | collection_reference_test.main(); 30 | document_reference_test.main(); 31 | // TODO CI tests configuration currently not compatible with widget testing 32 | // firestore_builder_test.main(); 33 | query_reference_test.main(); 34 | path_test.main(); 35 | }); 36 | } 37 | -------------------------------------------------------------------------------- /packages/cloud_firestore_odm/example/integration_test/common.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021, the Chromium project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | 7 | import 'package:cloud_firestore_odm/cloud_firestore_odm.dart'; 8 | import 'package:cloud_firestore_odm_example/movie.dart'; 9 | import 'package:firebase_core/firebase_core.dart'; 10 | import 'package:mockito/mockito.dart'; 11 | 12 | Future 13 | initializeTest>( 14 | T ref, 15 | ) async { 16 | final snapshot = await ref.reference.get(); 17 | 18 | await Future.wait( 19 | snapshot.docs.map((e) => e.reference.delete()), 20 | ); 21 | 22 | return ref; 23 | } 24 | 25 | Movie createMovie({ 26 | List genre = const [], 27 | Set tags = const {}, 28 | int likes = 0, 29 | String poster = '', 30 | String rated = '', 31 | String runtime = '', 32 | String title = '', 33 | int year = 1990, 34 | String id = '', 35 | }) { 36 | return Movie( 37 | genre: genre, 38 | tags: tags, 39 | likes: likes, 40 | poster: poster, 41 | rated: rated, 42 | runtime: runtime, 43 | title: title, 44 | year: year, 45 | id: id, 46 | ); 47 | } 48 | 49 | // ignore: avoid_implementing_value_types 50 | class FakeFirebaseApp extends Mock implements FirebaseApp {} 51 | -------------------------------------------------------------------------------- /packages/cloud_firestore_odm/example/integration_test/firebase_options.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Chromium Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | // File generated by FlutterFire CLI. 6 | // ignore_for_file: lines_longer_than_80_chars, avoid_classes_with_only_static_members 7 | import 'package:firebase_core/firebase_core.dart' show FirebaseOptions; 8 | import 'package:flutter/foundation.dart' 9 | show defaultTargetPlatform, kIsWeb, TargetPlatform; 10 | 11 | /// Default [FirebaseOptions] for use with your Firebase apps. 12 | /// 13 | /// Example: 14 | /// ```dart 15 | /// import 'firebase_options.dart'; 16 | /// // ... 17 | /// await Firebase.initializeApp( 18 | /// options: DefaultFirebaseOptions.currentPlatform, 19 | /// ); 20 | /// ``` 21 | class DefaultFirebaseOptions { 22 | static FirebaseOptions get currentPlatform { 23 | if (kIsWeb) { 24 | return web; 25 | } 26 | switch (defaultTargetPlatform) { 27 | case TargetPlatform.android: 28 | return android; 29 | case TargetPlatform.iOS: 30 | return ios; 31 | case TargetPlatform.macOS: 32 | return macos; 33 | case TargetPlatform.windows: 34 | throw UnsupportedError( 35 | 'DefaultFirebaseOptions have not been configured for windows - ' 36 | 'you can reconfigure this by running the FlutterFire CLI again.', 37 | ); 38 | case TargetPlatform.linux: 39 | throw UnsupportedError( 40 | 'DefaultFirebaseOptions have not been configured for linux - ' 41 | 'you can reconfigure this by running the FlutterFire CLI again.', 42 | ); 43 | default: 44 | throw UnsupportedError( 45 | 'DefaultFirebaseOptions are not supported for this platform.', 46 | ); 47 | } 48 | } 49 | 50 | static const FirebaseOptions web = FirebaseOptions( 51 | apiKey: 'AIzaSyB7wZb2tO1-Fs6GbDADUSTs2Qs3w08Hovw', 52 | appId: '1:406099696497:web:87e25e51afe982cd3574d0', 53 | messagingSenderId: '406099696497', 54 | projectId: 'flutterfire-e2e-tests', 55 | authDomain: 'flutterfire-e2e-tests.firebaseapp.com', 56 | databaseURL: 57 | 'https://flutterfire-e2e-tests-default-rtdb.europe-west1.firebasedatabase.app', 58 | storageBucket: 'flutterfire-e2e-tests.appspot.com', 59 | measurementId: 'G-JN95N1JV2E', 60 | ); 61 | 62 | static const FirebaseOptions android = FirebaseOptions( 63 | apiKey: 'AIzaSyCdRjCVZlhrq72RuEklEyyxYlBRCYhI2Sw', 64 | appId: '1:406099696497:android:175ea7a64b2faf5e3574d0', 65 | messagingSenderId: '406099696497', 66 | projectId: 'flutterfire-e2e-tests', 67 | databaseURL: 68 | 'https://flutterfire-e2e-tests-default-rtdb.europe-west1.firebasedatabase.app', 69 | storageBucket: 'flutterfire-e2e-tests.appspot.com', 70 | ); 71 | 72 | static const FirebaseOptions ios = FirebaseOptions( 73 | apiKey: 'AIzaSyDooSUGSf63Ghq02_iIhtnmwMDs4HlWS6c', 74 | appId: '1:406099696497:ios:0670bc5fe8574a9c3574d0', 75 | messagingSenderId: '406099696497', 76 | projectId: 'flutterfire-e2e-tests', 77 | databaseURL: 78 | 'https://flutterfire-e2e-tests-default-rtdb.europe-west1.firebasedatabase.app', 79 | storageBucket: 'flutterfire-e2e-tests.appspot.com', 80 | androidClientId: 81 | '406099696497-17qn06u8a0dc717u8ul7s49ampk13lul.apps.googleusercontent.com', 82 | iosClientId: 83 | '406099696497-l9gojfp6b3h1cgie1se28a9ol9fmsvvk.apps.googleusercontent.com', 84 | iosBundleId: 'io.flutter.plugins.firebase.firestore.example', 85 | ); 86 | 87 | static const FirebaseOptions macos = FirebaseOptions( 88 | apiKey: 'AIzaSyDooSUGSf63Ghq02_iIhtnmwMDs4HlWS6c', 89 | appId: '1:406099696497:ios:0670bc5fe8574a9c3574d0', 90 | messagingSenderId: '406099696497', 91 | projectId: 'flutterfire-e2e-tests', 92 | databaseURL: 93 | 'https://flutterfire-e2e-tests-default-rtdb.europe-west1.firebasedatabase.app', 94 | storageBucket: 'flutterfire-e2e-tests.appspot.com', 95 | androidClientId: 96 | '406099696497-17qn06u8a0dc717u8ul7s49ampk13lul.apps.googleusercontent.com', 97 | iosClientId: 98 | '406099696497-l9gojfp6b3h1cgie1se28a9ol9fmsvvk.apps.googleusercontent.com', 99 | iosBundleId: 'io.flutter.plugins.firebase.firestore.example', 100 | ); 101 | } 102 | -------------------------------------------------------------------------------- /packages/cloud_firestore_odm/example/integration_test/firestore_builder_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021, the Chromium project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:cloud_firestore/cloud_firestore.dart'; 6 | import 'package:cloud_firestore_odm/cloud_firestore_odm.dart'; 7 | import 'package:cloud_firestore_odm_example/movie.dart'; 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_test/flutter_test.dart'; 10 | 11 | import 'common.dart'; 12 | 13 | void main() { 14 | group('FirestoreBuilder', () { 15 | Widget buildMovieList(MovieQuery query) { 16 | return MaterialApp( 17 | home: Scaffold( 18 | body: FirestoreBuilder( 19 | ref: query, 20 | builder: (context, snapshot, _) { 21 | if (snapshot.hasError) return const Text('error'); 22 | if (!snapshot.hasData) return const Text('loading'); 23 | 24 | return ListView( 25 | children: [ 26 | for (final doc in snapshot.requireData.docs) 27 | Text(doc.data.title), 28 | ], 29 | ); 30 | }, 31 | ), 32 | ), 33 | ); 34 | } 35 | 36 | Widget buildMovie(MovieDocumentReference doc) { 37 | return MaterialApp( 38 | home: Scaffold( 39 | body: FirestoreBuilder( 40 | ref: doc, 41 | builder: (context, snapshot, _) { 42 | if (snapshot.hasError) return const Text('error'); 43 | if (!snapshot.hasData) return const Text('loading'); 44 | 45 | return Text(snapshot.requireData.data?.title ?? ''); 46 | }, 47 | ), 48 | ), 49 | ); 50 | } 51 | 52 | group('$FirestoreBuilder', () { 53 | testWidgets('listens to documents', (tester) async { 54 | final collection = await initializeTest(MovieCollectionReference()); 55 | 56 | final doc = collection.doc('123'); 57 | 58 | await tester.pumpWidget(buildMovie(doc)); 59 | 60 | expect(find.text('loading'), findsOneWidget); 61 | 62 | await tester.pumpAndSettle(); 63 | 64 | expect(find.text(''), findsOneWidget); 65 | 66 | await doc.set(createMovie(title: 'Foo')); 67 | await tester.pumpAndSettle(); 68 | 69 | expect(find.text('Foo'), findsOneWidget); 70 | }); 71 | 72 | testWidgets('emits errored snapshot when failed to decode a value', 73 | (tester) async { 74 | final collection = await initializeTest(MovieCollectionReference()); 75 | 76 | await FirebaseFirestore.instance 77 | .collection(collection.path) 78 | .doc('123') 79 | .set({'value': 42}); 80 | 81 | await tester.pumpWidget(buildMovie(collection.doc('123'))); 82 | 83 | expect(find.text('loading'), findsOneWidget); 84 | 85 | await tester.pumpAndSettle(); 86 | 87 | expect(find.text('error'), findsOneWidget); 88 | 89 | await collection.doc('123').set(createMovie(title: 'title')); 90 | 91 | await tester.pumpAndSettle(); 92 | 93 | expect(find.text('title'), findsOneWidget); 94 | }); 95 | 96 | testWidgets('listens to queries', (tester) async { 97 | final collection = await initializeTest(MovieCollectionReference()); 98 | 99 | await collection.add(createMovie(title: 'A')); 100 | final bar = await collection.add(createMovie(title: 'B')); 101 | final barSnapshot = await bar.get(); 102 | 103 | await tester.pumpWidget( 104 | buildMovieList( 105 | collection.orderByTitle(startAtDocument: barSnapshot), 106 | ), 107 | ); 108 | 109 | await tester.pumpAndSettle(); 110 | 111 | expect(find.text('loading'), findsNothing); 112 | expect(find.text('error'), findsNothing); 113 | expect(find.text('A'), findsNothing); 114 | expect(find.text('B'), findsOneWidget); 115 | expect(find.text('C'), findsNothing); 116 | 117 | await collection.add(createMovie(title: 'C')); 118 | 119 | await tester.pumpAndSettle(); 120 | 121 | expect(find.text('A'), findsNothing); 122 | expect(find.text('B'), findsOneWidget); 123 | expect(find.text('C'), findsOneWidget); 124 | }); 125 | 126 | testWidgets('listens to a collection', (tester) async { 127 | final collection = await initializeTest(MovieCollectionReference()); 128 | 129 | await collection.add(createMovie(title: 'A')); 130 | 131 | await tester.pumpWidget( 132 | buildMovieList(collection), 133 | ); 134 | 135 | expect(find.text('loading'), findsOneWidget); 136 | 137 | await tester.pumpAndSettle(); 138 | 139 | expect(find.text('A'), findsOneWidget); 140 | expect(find.text('B'), findsNothing); 141 | 142 | await collection.add(createMovie(title: 'B')); 143 | 144 | await tester.pump(); 145 | 146 | expect(find.text('A'), findsOneWidget); 147 | expect(find.text('B'), findsOneWidget); 148 | }); 149 | testWidgets( 150 | 'does not go back to loading if rebuilding the widget with the same query', 151 | (tester) async { 152 | final collection = await initializeTest(MovieCollectionReference()); 153 | 154 | await collection.add(createMovie(title: 'A')); 155 | await collection.add(createMovie(title: 'B')); 156 | 157 | await tester.pumpWidget( 158 | buildMovieList(collection.orderByTitle().limit(1)), 159 | ); 160 | 161 | expect(find.text('loading'), findsOneWidget); 162 | 163 | await tester.pumpAndSettle(); 164 | 165 | expect(find.text('A'), findsOneWidget); 166 | expect(find.text('B'), findsNothing); 167 | 168 | await tester.pumpWidget( 169 | buildMovieList(collection.orderByTitle().limit(1)), 170 | ); 171 | 172 | expect(find.text('A'), findsOneWidget); 173 | expect(find.text('B'), findsNothing); 174 | }); 175 | }); 176 | }); 177 | } 178 | -------------------------------------------------------------------------------- /packages/cloud_firestore_odm/example/integration_test/freezed_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2022, the Chromium project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:cloud_firestore_odm_example/integration/freezed.dart'; 6 | import 'package:flutter_test/flutter_test.dart'; 7 | 8 | import 'common.dart'; 9 | 10 | void main() { 11 | test('supports field renaming', () async { 12 | final collection = await initializeTest(PersonCollectionReference()); 13 | 14 | final aRef = await collection.add(Person(firstName: 'A', lastName: 'A')); 15 | await collection.add(Person(firstName: 'John', lastName: 'Doe')); 16 | await collection.add(Person(firstName: 'John', lastName: 'Smith')); 17 | await collection.add(Person(firstName: 'Mike', lastName: 'Doe')); 18 | 19 | expect( 20 | await collection.reference 21 | .orderBy('first_name') 22 | .orderBy('LAST_NAME') 23 | .get() 24 | .then((value) => value.docs.map((e) => e.data().toJson())), 25 | [ 26 | {'first_name': 'A', 'LAST_NAME': 'A'}, 27 | {'first_name': 'John', 'LAST_NAME': 'Doe'}, 28 | {'first_name': 'John', 'LAST_NAME': 'Smith'}, 29 | {'first_name': 'Mike', 'LAST_NAME': 'Doe'}, 30 | ], 31 | ); 32 | 33 | expect( 34 | await collection 35 | .orderByFirstName(startAt: 'B') 36 | .get() 37 | .then((value) => value.docs.map((e) => e.data.firstName)), 38 | ['John', 'John', 'Mike'], 39 | ); 40 | expect( 41 | await collection 42 | .orderByLastName(startAt: 'B') 43 | .get() 44 | .then((value) => value.docs.map((e) => e.data.lastName)), 45 | ['Doe', 'Doe', 'Smith'], 46 | ); 47 | expect( 48 | await collection 49 | .whereLastName(isEqualTo: 'Doe') 50 | .get() 51 | .then((value) => value.docs.map((e) => e.data.firstName)), 52 | unorderedEquals(['John', 'Mike']), 53 | ); 54 | 55 | await aRef.update(firstName: 'A2', lastName: 'B2'); 56 | 57 | expect( 58 | await aRef.reference 59 | .withConverter>( 60 | fromFirestore: (value, _) => value.data()!, 61 | toFirestore: (value, _) => value, 62 | ) 63 | .get(), 64 | { 65 | 'first_name': 'A2', 66 | 'LAST_NAME': 'B2', 67 | }, 68 | ); 69 | }); 70 | } 71 | -------------------------------------------------------------------------------- /packages/cloud_firestore_odm/example/integration_test/path_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021, the Chromium project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:cloud_firestore/cloud_firestore.dart'; 6 | import 'package:cloud_firestore_odm_generator_integration_test/simple.dart'; 7 | import 'package:flutter_test/flutter_test.dart'; 8 | 9 | import 'common.dart'; 10 | 11 | void main() { 12 | group('@Collection(path)', () { 13 | group('root collection paths', () { 14 | test('supports direct path to sub-collection', () async { 15 | final collection = await initializeTest(explicitRef); 16 | 17 | await FirebaseFirestore.instance 18 | .collection('root/doc/path') 19 | .add({'value': 42}); 20 | 21 | final snapshot = await collection.get(); 22 | 23 | expect(snapshot.docs, [ 24 | isA() 25 | .having((e) => e.data.value, 'data.value', 42), 26 | ]); 27 | }); 28 | 29 | test('explicit sub-collections can have sub-collections', () async { 30 | final collection = await initializeTest(explicitRef.doc('123').sub); 31 | 32 | await FirebaseFirestore.instance 33 | .collection('root/doc/path/123/sub') 34 | .add({'value': 42}); 35 | 36 | final snapshot = await collection.get(); 37 | 38 | expect(snapshot.docs, [ 39 | isA() 40 | .having((e) => e.data.value, 'data.value', 42), 41 | ]); 42 | }); 43 | }); 44 | 45 | group('sub collection name', () { 46 | test('renames collection name as camelCase by default', () async { 47 | final collection = await initializeTest(rootRef.doc('123').asCamelCase); 48 | 49 | await FirebaseFirestore.instance 50 | .collection('root/123/as-camel-case') 51 | .add({'value': 42}); 52 | 53 | final snapshot = await collection.get(); 54 | 55 | expect(snapshot.docs, [ 56 | isA() 57 | .having((e) => e.data.value, 'data.value', 42), 58 | ]); 59 | }); 60 | 61 | test('can be manually specified through the Collection annotation', 62 | () async { 63 | final collection = 64 | await initializeTest(rootRef.doc('123').thisIsACustomName); 65 | 66 | await FirebaseFirestore.instance 67 | .collection('root/123/custom-sub-name') 68 | .add({'value': 42}); 69 | 70 | final snapshot = await collection.get(); 71 | 72 | expect(snapshot.docs, [ 73 | isA() 74 | .having((e) => e.data.value, 'data.value', 42), 75 | ]); 76 | }); 77 | }); 78 | }); 79 | 80 | group('collection class prefix', () { 81 | test('can be manually specified through the Collection annotation', 82 | () async { 83 | final collection = 84 | await initializeTest(rootRef.doc('123').customClassPrefix); 85 | 86 | await FirebaseFirestore.instance 87 | .collection('root/123/custom-class-prefix') 88 | .add({'value': 42}); 89 | 90 | final snapshot = await collection.get(); 91 | 92 | expect(snapshot.docs, [ 93 | isA() 94 | .having((e) => e.data?.value, 'data.value', 42), 95 | ]); 96 | }); 97 | }); 98 | } 99 | -------------------------------------------------------------------------------- /packages/cloud_firestore_odm/example/integration_test/query_reference_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021, the Chromium project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:typed_data'; 6 | 7 | import 'package:cloud_firestore/cloud_firestore.dart'; 8 | import 'package:cloud_firestore_odm_example/integration/named_query.dart'; 9 | import 'package:cloud_firestore_odm_example/integration/query.dart'; 10 | import 'package:cloud_firestore_odm_example/movie.dart'; 11 | import 'package:firebase_core/firebase_core.dart'; 12 | import 'package:flutter_test/flutter_test.dart'; 13 | import 'package:http/http.dart' as http; 14 | 15 | import 'common.dart'; 16 | 17 | void main() { 18 | group('QueryReference', () { 19 | late FirebaseFirestore customFirestore; 20 | 21 | setUpAll(() async { 22 | customFirestore = FirebaseFirestore.instanceFor( 23 | app: await Firebase.initializeApp( 24 | name: 'custom-query-app', 25 | options: FirebaseOptions( 26 | apiKey: Firebase.app().options.apiKey, 27 | appId: Firebase.app().options.appId, 28 | messagingSenderId: Firebase.app().options.messagingSenderId, 29 | projectId: Firebase.app().options.projectId, 30 | ), 31 | ), 32 | ); 33 | }); 34 | 35 | group('root query', () { 36 | test('overrides ==', () { 37 | expect( 38 | MovieCollectionReference().limit(1), 39 | MovieCollectionReference(FirebaseFirestore.instance).limit(1), 40 | ); 41 | expect( 42 | MovieCollectionReference().limit(1), 43 | isNot(MovieCollectionReference().limit(2)), 44 | ); 45 | 46 | expect( 47 | MovieCollectionReference(customFirestore).limit(1), 48 | isNot(MovieCollectionReference().limit(1)), 49 | ); 50 | expect( 51 | MovieCollectionReference(customFirestore).limit(1), 52 | MovieCollectionReference(customFirestore).limit(1), 53 | ); 54 | }); 55 | }); 56 | 57 | group('sub query', () { 58 | test('overrides ==', () { 59 | expect( 60 | MovieCollectionReference().doc('123').comments.limit(1), 61 | MovieCollectionReference(FirebaseFirestore.instance) 62 | .doc('123') 63 | .comments 64 | .limit(1), 65 | ); 66 | expect( 67 | MovieCollectionReference().doc('123').comments.limit(1), 68 | isNot(MovieCollectionReference().doc('456').comments.limit(1)), 69 | ); 70 | expect( 71 | MovieCollectionReference().doc('123').comments.limit(1), 72 | isNot(MovieCollectionReference().doc('123').comments.limit(2)), 73 | ); 74 | 75 | expect( 76 | MovieCollectionReference(customFirestore) 77 | .doc('123') 78 | .comments 79 | .limit(1), 80 | isNot(MovieCollectionReference().doc('123').comments.limit(1)), 81 | ); 82 | expect( 83 | MovieCollectionReference(customFirestore) 84 | .doc('123') 85 | .comments 86 | .limit(1), 87 | MovieCollectionReference(customFirestore) 88 | .doc('123') 89 | .comments 90 | .limit(1), 91 | ); 92 | }); 93 | }); 94 | 95 | test('supports Durations', () async { 96 | final ref = await initializeTest(durationQueryRef); 97 | 98 | await ref.add(DurationQuery(const Duration(days: 1))); 99 | await ref.add(DurationQuery(const Duration(days: 2))); 100 | await ref.add(DurationQuery(const Duration(days: 3))); 101 | 102 | final snapshot = 103 | await ref.orderByDuration(startAt: const Duration(days: 2)).get(); 104 | 105 | expect(snapshot.docs.length, 2); 106 | 107 | expect(snapshot.docs[0].data.duration, const Duration(days: 2)); 108 | expect(snapshot.docs[1].data.duration, const Duration(days: 3)); 109 | }); 110 | 111 | test('supports DateTimes', () async { 112 | final ref = await initializeTest(dateTimeQueryRef); 113 | 114 | await ref.add(DateTimeQuery(DateTime(1990))); 115 | await ref.add(DateTimeQuery(DateTime(2000))); 116 | await ref.add(DateTimeQuery(DateTime(2010))); 117 | 118 | final snapshot = await ref.orderByTime(startAt: DateTime(2000)).get(); 119 | 120 | expect(snapshot.docs.length, 2); 121 | 122 | expect(snapshot.docs[0].data.time, DateTime(2000)); 123 | expect(snapshot.docs[1].data.time, DateTime(2010)); 124 | }); 125 | 126 | test('supports Timestamp', () async { 127 | final ref = await initializeTest(timestampQueryRef); 128 | 129 | await ref.add(TimestampQuery(Timestamp.fromDate(DateTime(1990)))); 130 | await ref.add(TimestampQuery(Timestamp.fromDate(DateTime(2000)))); 131 | await ref.add(TimestampQuery(Timestamp.fromDate(DateTime(2010)))); 132 | 133 | final snapshot = await ref 134 | .orderByTime(startAt: Timestamp.fromDate(DateTime(2000))) 135 | .get(); 136 | 137 | expect(snapshot.docs.length, 2); 138 | 139 | expect(snapshot.docs[0].data.time, Timestamp.fromDate(DateTime(2000))); 140 | expect(snapshot.docs[1].data.time, Timestamp.fromDate(DateTime(2010))); 141 | }); 142 | 143 | test('supports GeoPoint', () async { 144 | final ref = await initializeTest(geoPointQueryRef); 145 | 146 | await ref.add(GeoPointQuery(const GeoPoint(19, 0))); 147 | await ref.add(GeoPointQuery(const GeoPoint(20, 0))); 148 | await ref.add(GeoPointQuery(const GeoPoint(20, 0))); 149 | 150 | final snapshot = 151 | await ref.orderByPoint(startAt: const GeoPoint(20, 0)).get(); 152 | 153 | expect(snapshot.docs.length, 2); 154 | 155 | expect(snapshot.docs[0].data.point, const GeoPoint(20, 0)); 156 | expect(snapshot.docs[1].data.point, const GeoPoint(20, 0)); 157 | }); 158 | 159 | test( 160 | 'supports DocumentReference', 161 | () async { 162 | final ref = await initializeTest(documentReferenceRef); 163 | 164 | await ref.add( 165 | DocumentReferenceQuery(FirebaseFirestore.instance.doc('foo/a')), 166 | ); 167 | await ref.add( 168 | DocumentReferenceQuery(FirebaseFirestore.instance.doc('foo/b')), 169 | ); 170 | await ref.add( 171 | DocumentReferenceQuery(FirebaseFirestore.instance.doc('foo/c')), 172 | ); 173 | 174 | final snapshot = await ref 175 | .orderByRef(startAt: FirebaseFirestore.instance.doc('foo/b')) 176 | .get(); 177 | 178 | expect(snapshot.docs.length, 2); 179 | 180 | expect( 181 | snapshot.docs[0].data.ref, 182 | FirebaseFirestore.instance.doc('foo/b'), 183 | ); 184 | expect( 185 | snapshot.docs[1].data.ref, 186 | FirebaseFirestore.instance.doc('foo/c'), 187 | ); 188 | }, 189 | skip: 'Blocked by FlutterFire support for querying document references', 190 | ); 191 | }); 192 | 193 | group('FirebaeFirestore.myCustomNamedQuery()', () { 194 | Future loadBundleSetup() async { 195 | // endpoint serves a bundle with 3 documents each containing 196 | // a 'number' property that increments in value 1-3. 197 | final url = Uri.https('api.rnfirebase.io', '/firestore/bundle-4'); 198 | final response = await http.get(url); 199 | final string = response.body; 200 | return Uint8List.fromList(string.codeUnits); 201 | } 202 | 203 | test('myCustomNamedQuery() successful', () async { 204 | final buffer = await loadBundleSetup(); 205 | final task = FirebaseFirestore.instance.loadBundle(buffer); 206 | 207 | // ensure the bundle has been completely cached 208 | await task.stream.last; 209 | 210 | // namedQuery 'named-bundle-test' which returns a QuerySnaphot of the same 3 documents 211 | // with 'number' property 212 | final snapshot = await FirebaseFirestore.instance.namedBundleTest4Get( 213 | options: const GetOptions(source: Source.cache), 214 | ); 215 | 216 | expect( 217 | snapshot.docs.map((document) => document.data.number), 218 | everyElement(anyOf(1, 2, 3)), 219 | ); 220 | }); 221 | }); 222 | } 223 | -------------------------------------------------------------------------------- /packages/cloud_firestore_odm/example/lib/integration.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2022, the Chromium project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:cloud_firestore/cloud_firestore.dart'; 6 | import 'package:cloud_firestore_odm/cloud_firestore_odm.dart'; 7 | import 'package:json_annotation/json_annotation.dart'; 8 | 9 | part 'integration.g.dart'; 10 | 11 | @JsonSerializable() 12 | class EmptyModel { 13 | EmptyModel(); 14 | 15 | factory EmptyModel.fromJson(Map json) => 16 | _$EmptyModelFromJson(json); 17 | 18 | Map toJson() => _$EmptyModelToJson(this); 19 | } 20 | 21 | @Collection('firestore-example-app/test/config') 22 | final emptyModelRef = EmptyModelCollectionReference(); 23 | 24 | @Collection('firestore-example-app/test/advanced') 25 | @JsonSerializable(fieldRename: FieldRename.snake) 26 | class AdvancedJson { 27 | AdvancedJson({this.firstName, this.lastName, this.ignored}); 28 | 29 | final String? firstName; 30 | 31 | @JsonKey(name: 'LAST_NAME') 32 | final String? lastName; 33 | 34 | @JsonKey(includeFromJson: false, includeToJson: false) 35 | final String? ignored; 36 | 37 | Map toJson() => _$AdvancedJsonToJson(this); 38 | 39 | @override 40 | bool operator ==(Object other) { 41 | return other is AdvancedJson && 42 | other.lastName == lastName && 43 | other.firstName == firstName && 44 | other.ignored == ignored; 45 | } 46 | 47 | @override 48 | int get hashCode => Object.hashAll([firstName, lastName, ignored]); 49 | } 50 | 51 | // This tests that the generated code compiles 52 | @Collection<_PrivateAdvancedJson>('firestore-example-app/test/private-advanced') 53 | @JsonSerializable(fieldRename: FieldRename.snake) 54 | class _PrivateAdvancedJson { 55 | _PrivateAdvancedJson({ 56 | this.firstName, 57 | this.lastName, 58 | // ignore: unused_element, unused_element_parameter 59 | this.ignored, 60 | }); 61 | 62 | final String? firstName; 63 | 64 | @JsonKey(name: 'LAST_NAME') 65 | final String? lastName; 66 | 67 | @JsonKey(includeFromJson: false, includeToJson: false) 68 | final String? ignored; 69 | 70 | Map toJson() => _$PrivateAdvancedJsonToJson(this); 71 | 72 | @override 73 | bool operator ==(Object other) { 74 | return other is AdvancedJson && 75 | other.lastName == lastName && 76 | other.firstName == firstName && 77 | other.ignored == ignored; 78 | } 79 | 80 | @override 81 | int get hashCode => Object.hashAll([firstName, lastName, ignored]); 82 | } 83 | -------------------------------------------------------------------------------- /packages/cloud_firestore_odm/example/lib/integration/enums.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2022, the Chromium project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:cloud_firestore/cloud_firestore.dart'; 6 | import 'package:cloud_firestore_odm/cloud_firestore_odm.dart'; 7 | import 'package:json_annotation/json_annotation.dart'; 8 | 9 | part 'enums.g.dart'; 10 | 11 | enum TestEnum { 12 | one, 13 | two, 14 | three; 15 | } 16 | 17 | @JsonSerializable() 18 | class Enums { 19 | Enums({ 20 | required this.id, 21 | this.enumValue = TestEnum.one, 22 | this.nullableEnumValue, 23 | this.enumList = const [], 24 | this.nullableEnumList, 25 | }); 26 | 27 | factory Enums.fromJson(Map json) => _$EnumsFromJson(json); 28 | 29 | Map toJson() => _$EnumsToJson(this); 30 | 31 | final String id; 32 | final TestEnum enumValue; 33 | final TestEnum? nullableEnumValue; 34 | final List enumList; 35 | final List? nullableEnumList; 36 | } 37 | 38 | @Collection('firestore-example-app') 39 | final enumsRef = EnumsCollectionReference(); 40 | -------------------------------------------------------------------------------- /packages/cloud_firestore_odm/example/lib/integration/freezed.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2022, the Chromium project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | // ignore_for_file: invalid_annotation_target 6 | 7 | import 'package:cloud_firestore/cloud_firestore.dart'; 8 | import 'package:cloud_firestore_odm/cloud_firestore_odm.dart'; 9 | import 'package:freezed_annotation/freezed_annotation.dart'; 10 | 11 | part 'freezed.freezed.dart'; 12 | part 'freezed.g.dart'; 13 | 14 | @Collection('freezed-test') 15 | @freezed 16 | class Person with _$Person { 17 | @JsonSerializable(fieldRename: FieldRename.snake) 18 | factory Person({ 19 | required String firstName, 20 | @JsonKey(name: 'LAST_NAME') required String lastName, 21 | @JsonKey(includeFromJson: false, includeToJson: false) int? ignored, 22 | }) = _Person; 23 | 24 | factory Person.fromJson(Map json) => _$PersonFromJson(json); 25 | } 26 | 27 | final personRef = PersonCollectionReference(); 28 | 29 | @Collection('freezed-test') 30 | @freezed 31 | class PublicRedirected with _$PublicRedirected { 32 | factory PublicRedirected({required String value}) = PublicRedirected2; 33 | 34 | factory PublicRedirected.fromJson(Map json) => 35 | _$PublicRedirectedFromJson(json); 36 | } 37 | -------------------------------------------------------------------------------- /packages/cloud_firestore_odm/example/lib/integration/freezed.freezed.dart: -------------------------------------------------------------------------------- 1 | // coverage:ignore-file 2 | // GENERATED CODE - DO NOT MODIFY BY HAND 3 | // ignore_for_file: type=lint 4 | // ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark 5 | 6 | part of 'freezed.dart'; 7 | 8 | // ************************************************************************** 9 | // FreezedGenerator 10 | // ************************************************************************** 11 | 12 | T _$identity(T value) => value; 13 | 14 | final _privateConstructorUsedError = UnsupportedError( 15 | 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); 16 | 17 | Person _$PersonFromJson(Map json) { 18 | return _Person.fromJson(json); 19 | } 20 | 21 | /// @nodoc 22 | mixin _$Person { 23 | String get firstName => throw _privateConstructorUsedError; 24 | @JsonKey(name: 'LAST_NAME') 25 | String get lastName => throw _privateConstructorUsedError; 26 | @JsonKey(includeFromJson: false, includeToJson: false) 27 | int? get ignored => throw _privateConstructorUsedError; 28 | 29 | /// Serializes this Person to a JSON map. 30 | Map toJson() => throw _privateConstructorUsedError; 31 | 32 | /// Create a copy of Person 33 | /// with the given fields replaced by the non-null parameter values. 34 | @JsonKey(includeFromJson: false, includeToJson: false) 35 | $PersonCopyWith get copyWith => throw _privateConstructorUsedError; 36 | } 37 | 38 | /// @nodoc 39 | abstract class $PersonCopyWith<$Res> { 40 | factory $PersonCopyWith(Person value, $Res Function(Person) then) = 41 | _$PersonCopyWithImpl<$Res, Person>; 42 | @useResult 43 | $Res call( 44 | {String firstName, 45 | @JsonKey(name: 'LAST_NAME') String lastName, 46 | @JsonKey(includeFromJson: false, includeToJson: false) int? ignored}); 47 | } 48 | 49 | /// @nodoc 50 | class _$PersonCopyWithImpl<$Res, $Val extends Person> 51 | implements $PersonCopyWith<$Res> { 52 | _$PersonCopyWithImpl(this._value, this._then); 53 | 54 | // ignore: unused_field 55 | final $Val _value; 56 | // ignore: unused_field 57 | final $Res Function($Val) _then; 58 | 59 | /// Create a copy of Person 60 | /// with the given fields replaced by the non-null parameter values. 61 | @pragma('vm:prefer-inline') 62 | @override 63 | $Res call({ 64 | Object? firstName = null, 65 | Object? lastName = null, 66 | Object? ignored = freezed, 67 | }) { 68 | return _then(_value.copyWith( 69 | firstName: null == firstName 70 | ? _value.firstName 71 | : firstName // ignore: cast_nullable_to_non_nullable 72 | as String, 73 | lastName: null == lastName 74 | ? _value.lastName 75 | : lastName // ignore: cast_nullable_to_non_nullable 76 | as String, 77 | ignored: freezed == ignored 78 | ? _value.ignored 79 | : ignored // ignore: cast_nullable_to_non_nullable 80 | as int?, 81 | ) as $Val); 82 | } 83 | } 84 | 85 | /// @nodoc 86 | abstract class _$$PersonImplCopyWith<$Res> implements $PersonCopyWith<$Res> { 87 | factory _$$PersonImplCopyWith( 88 | _$PersonImpl value, $Res Function(_$PersonImpl) then) = 89 | __$$PersonImplCopyWithImpl<$Res>; 90 | @override 91 | @useResult 92 | $Res call( 93 | {String firstName, 94 | @JsonKey(name: 'LAST_NAME') String lastName, 95 | @JsonKey(includeFromJson: false, includeToJson: false) int? ignored}); 96 | } 97 | 98 | /// @nodoc 99 | class __$$PersonImplCopyWithImpl<$Res> 100 | extends _$PersonCopyWithImpl<$Res, _$PersonImpl> 101 | implements _$$PersonImplCopyWith<$Res> { 102 | __$$PersonImplCopyWithImpl( 103 | _$PersonImpl _value, $Res Function(_$PersonImpl) _then) 104 | : super(_value, _then); 105 | 106 | /// Create a copy of Person 107 | /// with the given fields replaced by the non-null parameter values. 108 | @pragma('vm:prefer-inline') 109 | @override 110 | $Res call({ 111 | Object? firstName = null, 112 | Object? lastName = null, 113 | Object? ignored = freezed, 114 | }) { 115 | return _then(_$PersonImpl( 116 | firstName: null == firstName 117 | ? _value.firstName 118 | : firstName // ignore: cast_nullable_to_non_nullable 119 | as String, 120 | lastName: null == lastName 121 | ? _value.lastName 122 | : lastName // ignore: cast_nullable_to_non_nullable 123 | as String, 124 | ignored: freezed == ignored 125 | ? _value.ignored 126 | : ignored // ignore: cast_nullable_to_non_nullable 127 | as int?, 128 | )); 129 | } 130 | } 131 | 132 | /// @nodoc 133 | 134 | @JsonSerializable(fieldRename: FieldRename.snake) 135 | class _$PersonImpl implements _Person { 136 | _$PersonImpl( 137 | {required this.firstName, 138 | @JsonKey(name: 'LAST_NAME') required this.lastName, 139 | @JsonKey(includeFromJson: false, includeToJson: false) this.ignored}); 140 | 141 | factory _$PersonImpl.fromJson(Map json) => 142 | _$$PersonImplFromJson(json); 143 | 144 | @override 145 | final String firstName; 146 | @override 147 | @JsonKey(name: 'LAST_NAME') 148 | final String lastName; 149 | @override 150 | @JsonKey(includeFromJson: false, includeToJson: false) 151 | final int? ignored; 152 | 153 | @override 154 | String toString() { 155 | return 'Person(firstName: $firstName, lastName: $lastName, ignored: $ignored)'; 156 | } 157 | 158 | @override 159 | bool operator ==(Object other) { 160 | return identical(this, other) || 161 | (other.runtimeType == runtimeType && 162 | other is _$PersonImpl && 163 | (identical(other.firstName, firstName) || 164 | other.firstName == firstName) && 165 | (identical(other.lastName, lastName) || 166 | other.lastName == lastName) && 167 | (identical(other.ignored, ignored) || other.ignored == ignored)); 168 | } 169 | 170 | @JsonKey(includeFromJson: false, includeToJson: false) 171 | @override 172 | int get hashCode => Object.hash(runtimeType, firstName, lastName, ignored); 173 | 174 | /// Create a copy of Person 175 | /// with the given fields replaced by the non-null parameter values. 176 | @JsonKey(includeFromJson: false, includeToJson: false) 177 | @override 178 | @pragma('vm:prefer-inline') 179 | _$$PersonImplCopyWith<_$PersonImpl> get copyWith => 180 | __$$PersonImplCopyWithImpl<_$PersonImpl>(this, _$identity); 181 | 182 | @override 183 | Map toJson() { 184 | return _$$PersonImplToJson( 185 | this, 186 | ); 187 | } 188 | } 189 | 190 | abstract class _Person implements Person { 191 | factory _Person( 192 | {required final String firstName, 193 | @JsonKey(name: 'LAST_NAME') required final String lastName, 194 | @JsonKey(includeFromJson: false, includeToJson: false) 195 | final int? ignored}) = _$PersonImpl; 196 | 197 | factory _Person.fromJson(Map json) = _$PersonImpl.fromJson; 198 | 199 | @override 200 | String get firstName; 201 | @override 202 | @JsonKey(name: 'LAST_NAME') 203 | String get lastName; 204 | @override 205 | @JsonKey(includeFromJson: false, includeToJson: false) 206 | int? get ignored; 207 | 208 | /// Create a copy of Person 209 | /// with the given fields replaced by the non-null parameter values. 210 | @override 211 | @JsonKey(includeFromJson: false, includeToJson: false) 212 | _$$PersonImplCopyWith<_$PersonImpl> get copyWith => 213 | throw _privateConstructorUsedError; 214 | } 215 | 216 | PublicRedirected _$PublicRedirectedFromJson(Map json) { 217 | return PublicRedirected2.fromJson(json); 218 | } 219 | 220 | /// @nodoc 221 | mixin _$PublicRedirected { 222 | String get value => throw _privateConstructorUsedError; 223 | 224 | /// Serializes this PublicRedirected to a JSON map. 225 | Map toJson() => throw _privateConstructorUsedError; 226 | 227 | /// Create a copy of PublicRedirected 228 | /// with the given fields replaced by the non-null parameter values. 229 | @JsonKey(includeFromJson: false, includeToJson: false) 230 | $PublicRedirectedCopyWith get copyWith => 231 | throw _privateConstructorUsedError; 232 | } 233 | 234 | /// @nodoc 235 | abstract class $PublicRedirectedCopyWith<$Res> { 236 | factory $PublicRedirectedCopyWith( 237 | PublicRedirected value, $Res Function(PublicRedirected) then) = 238 | _$PublicRedirectedCopyWithImpl<$Res, PublicRedirected>; 239 | @useResult 240 | $Res call({String value}); 241 | } 242 | 243 | /// @nodoc 244 | class _$PublicRedirectedCopyWithImpl<$Res, $Val extends PublicRedirected> 245 | implements $PublicRedirectedCopyWith<$Res> { 246 | _$PublicRedirectedCopyWithImpl(this._value, this._then); 247 | 248 | // ignore: unused_field 249 | final $Val _value; 250 | // ignore: unused_field 251 | final $Res Function($Val) _then; 252 | 253 | /// Create a copy of PublicRedirected 254 | /// with the given fields replaced by the non-null parameter values. 255 | @pragma('vm:prefer-inline') 256 | @override 257 | $Res call({ 258 | Object? value = null, 259 | }) { 260 | return _then(_value.copyWith( 261 | value: null == value 262 | ? _value.value 263 | : value // ignore: cast_nullable_to_non_nullable 264 | as String, 265 | ) as $Val); 266 | } 267 | } 268 | 269 | /// @nodoc 270 | abstract class _$$PublicRedirected2ImplCopyWith<$Res> 271 | implements $PublicRedirectedCopyWith<$Res> { 272 | factory _$$PublicRedirected2ImplCopyWith(_$PublicRedirected2Impl value, 273 | $Res Function(_$PublicRedirected2Impl) then) = 274 | __$$PublicRedirected2ImplCopyWithImpl<$Res>; 275 | @override 276 | @useResult 277 | $Res call({String value}); 278 | } 279 | 280 | /// @nodoc 281 | class __$$PublicRedirected2ImplCopyWithImpl<$Res> 282 | extends _$PublicRedirectedCopyWithImpl<$Res, _$PublicRedirected2Impl> 283 | implements _$$PublicRedirected2ImplCopyWith<$Res> { 284 | __$$PublicRedirected2ImplCopyWithImpl(_$PublicRedirected2Impl _value, 285 | $Res Function(_$PublicRedirected2Impl) _then) 286 | : super(_value, _then); 287 | 288 | /// Create a copy of PublicRedirected 289 | /// with the given fields replaced by the non-null parameter values. 290 | @pragma('vm:prefer-inline') 291 | @override 292 | $Res call({ 293 | Object? value = null, 294 | }) { 295 | return _then(_$PublicRedirected2Impl( 296 | value: null == value 297 | ? _value.value 298 | : value // ignore: cast_nullable_to_non_nullable 299 | as String, 300 | )); 301 | } 302 | } 303 | 304 | /// @nodoc 305 | @JsonSerializable() 306 | class _$PublicRedirected2Impl implements PublicRedirected2 { 307 | _$PublicRedirected2Impl({required this.value}); 308 | 309 | factory _$PublicRedirected2Impl.fromJson(Map json) => 310 | _$$PublicRedirected2ImplFromJson(json); 311 | 312 | @override 313 | final String value; 314 | 315 | @override 316 | String toString() { 317 | return 'PublicRedirected(value: $value)'; 318 | } 319 | 320 | @override 321 | bool operator ==(Object other) { 322 | return identical(this, other) || 323 | (other.runtimeType == runtimeType && 324 | other is _$PublicRedirected2Impl && 325 | (identical(other.value, value) || other.value == value)); 326 | } 327 | 328 | @JsonKey(includeFromJson: false, includeToJson: false) 329 | @override 330 | int get hashCode => Object.hash(runtimeType, value); 331 | 332 | /// Create a copy of PublicRedirected 333 | /// with the given fields replaced by the non-null parameter values. 334 | @JsonKey(includeFromJson: false, includeToJson: false) 335 | @override 336 | @pragma('vm:prefer-inline') 337 | _$$PublicRedirected2ImplCopyWith<_$PublicRedirected2Impl> get copyWith => 338 | __$$PublicRedirected2ImplCopyWithImpl<_$PublicRedirected2Impl>( 339 | this, _$identity); 340 | 341 | @override 342 | Map toJson() { 343 | return _$$PublicRedirected2ImplToJson( 344 | this, 345 | ); 346 | } 347 | } 348 | 349 | abstract class PublicRedirected2 implements PublicRedirected { 350 | factory PublicRedirected2({required final String value}) = 351 | _$PublicRedirected2Impl; 352 | 353 | factory PublicRedirected2.fromJson(Map json) = 354 | _$PublicRedirected2Impl.fromJson; 355 | 356 | @override 357 | String get value; 358 | 359 | /// Create a copy of PublicRedirected 360 | /// with the given fields replaced by the non-null parameter values. 361 | @override 362 | @JsonKey(includeFromJson: false, includeToJson: false) 363 | _$$PublicRedirected2ImplCopyWith<_$PublicRedirected2Impl> get copyWith => 364 | throw _privateConstructorUsedError; 365 | } 366 | -------------------------------------------------------------------------------- /packages/cloud_firestore_odm/example/lib/integration/named_query.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2022, the Chromium project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:cloud_firestore/cloud_firestore.dart'; 6 | import 'package:cloud_firestore_odm/cloud_firestore_odm.dart'; 7 | import 'package:freezed_annotation/freezed_annotation.dart'; 8 | 9 | part 'named_query.g.dart'; 10 | 11 | @NamedQuery('named-bundle-test-4') 12 | @Collection('firestore-example-app/42/named-query-conflict') 13 | @JsonSerializable() 14 | class Conflict { 15 | Conflict(this.number); 16 | 17 | final num number; 18 | } 19 | -------------------------------------------------------------------------------- /packages/cloud_firestore_odm/example/lib/integration/query.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2022, the Chromium project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:cloud_firestore/cloud_firestore.dart'; 6 | import 'package:cloud_firestore_odm/cloud_firestore_odm.dart'; 7 | import 'package:json_annotation/json_annotation.dart'; 8 | 9 | part 'query.g.dart'; 10 | 11 | @Collection('firestore-example-app/42/duration') 12 | final durationQueryRef = DurationQueryCollectionReference(); 13 | 14 | @JsonSerializable(converters: firestoreJsonConverters) 15 | class DurationQuery { 16 | DurationQuery(this.duration); 17 | final Duration duration; 18 | } 19 | 20 | @Collection('firestore-example-app/42/date-time') 21 | final dateTimeQueryRef = DateTimeQueryCollectionReference(); 22 | 23 | @JsonSerializable(converters: firestoreJsonConverters) 24 | class DateTimeQuery { 25 | DateTimeQuery(this.time); 26 | final DateTime time; 27 | } 28 | 29 | class FirestoreDateTimeConverter extends JsonConverter { 30 | const FirestoreDateTimeConverter(); 31 | @override 32 | DateTime fromJson(Timestamp json) => json.toDate(); 33 | 34 | @override 35 | Timestamp toJson(DateTime object) => Timestamp.fromDate(object); 36 | } 37 | 38 | @Collection('firestore-example-app/42/timestamp-time') 39 | final timestampQueryRef = TimestampQueryCollectionReference(); 40 | 41 | @JsonSerializable(converters: firestoreJsonConverters) 42 | class TimestampQuery { 43 | TimestampQuery(this.time); 44 | final Timestamp time; 45 | } 46 | 47 | @Collection('firestore-example-app/42/geopoint-time') 48 | final geoPointQueryRef = GeoPointQueryCollectionReference(); 49 | 50 | @JsonSerializable(converters: firestoreJsonConverters) 51 | class GeoPointQuery { 52 | GeoPointQuery(this.point); 53 | final GeoPoint point; 54 | } 55 | 56 | @Collection('firestore-example-app/42/doc-ref') 57 | final documentReferenceRef = DocumentReferenceQueryCollectionReference(); 58 | 59 | @JsonSerializable(converters: firestoreJsonConverters) 60 | class DocumentReferenceQuery { 61 | DocumentReferenceQuery(this.ref); 62 | 63 | final DocumentReference> ref; 64 | } 65 | -------------------------------------------------------------------------------- /packages/cloud_firestore_odm/example/lib/main.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2020, the Chromium project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:cloud_firestore/cloud_firestore.dart'; 6 | import 'package:firebase_core/firebase_core.dart'; 7 | import 'package:flutter/material.dart'; 8 | 9 | import 'movie_detail_screen.dart'; 10 | import 'movies_screen.dart'; 11 | 12 | /// Requires that a Firestore emulator is running locally. 13 | /// See https://firebase.flutter.dev/docs/firestore/usage#emulator-usage 14 | // ignore: constant_identifier_names 15 | const USE_FIRESTORE_EMULATOR = false; 16 | 17 | Future main() async { 18 | WidgetsFlutterBinding.ensureInitialized(); 19 | await Firebase.initializeApp(); 20 | if (USE_FIRESTORE_EMULATOR) { 21 | FirebaseFirestore.instance.settings = const Settings( 22 | host: 'localhost:8080', 23 | sslEnabled: false, 24 | persistenceEnabled: false, 25 | ); 26 | } 27 | runApp(const FirestoreExampleApp()); 28 | } 29 | 30 | final _movieDetailsUri = RegExp(r'^/movies/([a-zA-Z0-9]+?)$'); 31 | 32 | /// The entry point of the application. 33 | /// 34 | /// Returns a [MaterialApp]. 35 | class FirestoreExampleApp extends StatelessWidget { 36 | const FirestoreExampleApp({Key? key}) : super(key: key); 37 | 38 | @override 39 | Widget build(BuildContext context) { 40 | return MaterialApp( 41 | title: 'Firestore Example App', 42 | theme: ThemeData.dark(), 43 | onGenerateRoute: (settings) { 44 | if (settings.name == null) { 45 | throw UnsupportedError('Routes must be named'); 46 | } 47 | 48 | final match = _movieDetailsUri.firstMatch(settings.name!); 49 | if (match != null) { 50 | return MaterialPageRoute( 51 | builder: (context) { 52 | return MovieDetail(movieID: match.group(1)!); 53 | }, 54 | ); 55 | } 56 | 57 | return null; 58 | }, 59 | home: const FilmList(), 60 | ); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /packages/cloud_firestore_odm/example/lib/movie.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2022, the Chromium project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:cloud_firestore/cloud_firestore.dart'; 6 | import 'package:cloud_firestore_odm/cloud_firestore_odm.dart'; 7 | import 'package:json_annotation/json_annotation.dart'; 8 | 9 | part 'movie.g.dart'; 10 | 11 | @JsonSerializable() 12 | class Movie { 13 | Movie({ 14 | this.genre, 15 | this.tags, 16 | required this.likes, 17 | required this.poster, 18 | required this.rated, 19 | required this.runtime, 20 | required this.title, 21 | required this.year, 22 | required this.id, 23 | }) { 24 | _$assertMovie(this); 25 | } 26 | 27 | @Id() 28 | final String id; 29 | final String poster; 30 | @Min(0) 31 | final int likes; 32 | final String title; 33 | @Min(0) 34 | final int year; 35 | final String runtime; 36 | final String rated; 37 | final List? genre; 38 | final Set? tags; 39 | } 40 | 41 | @Collection('firestore-example-app') 42 | @Collection('firestore-example-app/*/comments', name: 'comments') 43 | final moviesRef = MovieCollectionReference(); 44 | 45 | @JsonSerializable() 46 | class Comment { 47 | Comment({ 48 | required this.authorName, 49 | required this.message, 50 | }); 51 | 52 | final String authorName; 53 | final String message; 54 | } 55 | -------------------------------------------------------------------------------- /packages/cloud_firestore_odm/example/lib/movie_detail_screen.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2022, the Chromium project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:cloud_firestore_odm/cloud_firestore_odm.dart'; 6 | import 'package:flutter/material.dart'; 7 | 8 | import 'movie.dart'; 9 | import 'movie_item.dart'; 10 | 11 | class MovieDetail extends StatelessWidget { 12 | const MovieDetail({ 13 | Key? key, 14 | required this.movieID, 15 | }) : super(key: key); 16 | 17 | final String movieID; 18 | 19 | @override 20 | Widget build(BuildContext context) { 21 | return Scaffold( 22 | appBar: AppBar( 23 | title: Text('Movie $movieID'), 24 | ), 25 | body: Column( 26 | children: [ 27 | FirestoreBuilder( 28 | ref: moviesRef.doc(movieID), 29 | builder: (context, asyncSnapshot, _) { 30 | if (asyncSnapshot.hasError) return const Text('error'); 31 | if (!asyncSnapshot.hasData) return const Text('loading'); 32 | 33 | final snapshot = asyncSnapshot.data!; 34 | 35 | return MovieItem(snapshot.data!, snapshot.reference); 36 | }, 37 | ), 38 | Expanded( 39 | child: FirestoreBuilder( 40 | ref: moviesRef.doc(movieID).comments, 41 | builder: (context, asyncSnapshot, _) { 42 | if (asyncSnapshot.hasError) return const Text('error'); 43 | if (!asyncSnapshot.hasData) return const Text('loading'); 44 | 45 | final snapshot = asyncSnapshot.data!; 46 | 47 | if (snapshot.docs.isEmpty) return Container(); 48 | 49 | return Column( 50 | children: [ 51 | Text('Comments (${snapshot.docs.length}):'), 52 | ListView.builder( 53 | itemCount: snapshot.docs.length, 54 | itemBuilder: (context, index) { 55 | return Text(snapshot.docs[index].data.message); 56 | }, 57 | ), 58 | ], 59 | ); 60 | }, 61 | ), 62 | ), 63 | ], 64 | ), 65 | ); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /packages/cloud_firestore_odm/example/lib/movie_item.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2022, the Chromium project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | // ignore_for_file: avoid_print 6 | 7 | import 'package:cloud_firestore/cloud_firestore.dart'; 8 | import 'package:flutter/material.dart'; 9 | import 'movie.dart'; 10 | 11 | /// A single movie row. 12 | class MovieItem extends StatelessWidget { 13 | MovieItem(this.movie, this.reference, {Key? key}) : super(key: key); 14 | 15 | final Movie movie; 16 | final MovieDocumentReference reference; 17 | 18 | /// Returns the movie poster. 19 | Widget get poster { 20 | return SizedBox( 21 | width: 100, 22 | child: Image.network(movie.poster), 23 | ); 24 | } 25 | 26 | /// Returns movie details. 27 | Widget get details { 28 | return Padding( 29 | padding: const EdgeInsets.only(left: 8, right: 8), 30 | child: Column( 31 | crossAxisAlignment: CrossAxisAlignment.start, 32 | children: [ 33 | title, 34 | metadata, 35 | genres, 36 | Likes( 37 | reference: reference, 38 | currentLikes: movie.likes, 39 | ), 40 | ], 41 | ), 42 | ); 43 | } 44 | 45 | /// Return the movie title. 46 | Widget get title { 47 | return Text( 48 | '${movie.title} (${movie.year})', 49 | style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold), 50 | ); 51 | } 52 | 53 | /// Returns metadata about the movie. 54 | Widget get metadata { 55 | return Padding( 56 | padding: const EdgeInsets.only(top: 8), 57 | child: Row( 58 | children: [ 59 | Padding( 60 | padding: const EdgeInsets.only(right: 8), 61 | child: Text('Rated: ${movie.rated}'), 62 | ), 63 | Text('Runtime: ${movie.runtime}'), 64 | ], 65 | ), 66 | ); 67 | } 68 | 69 | /// Returns a list of genre movie tags. 70 | List get genreItems { 71 | return [ 72 | for (final genre in movie.genre ?? []) 73 | Padding( 74 | padding: const EdgeInsets.only(right: 2), 75 | child: Chip( 76 | backgroundColor: Colors.lightBlue, 77 | label: Text( 78 | genre, 79 | style: const TextStyle(color: Colors.white), 80 | ), 81 | ), 82 | ), 83 | ]; 84 | } 85 | 86 | /// Returns all genres. 87 | Widget get genres { 88 | return Padding( 89 | padding: const EdgeInsets.only(top: 8), 90 | child: Wrap( 91 | children: genreItems, 92 | ), 93 | ); 94 | } 95 | 96 | @override 97 | Widget build(BuildContext context) { 98 | return Padding( 99 | padding: const EdgeInsets.only(bottom: 4, top: 4), 100 | child: Row( 101 | crossAxisAlignment: CrossAxisAlignment.start, 102 | children: [ 103 | poster, 104 | Flexible(child: details), 105 | ], 106 | ), 107 | ); 108 | } 109 | } 110 | 111 | /// Displays and manages the movie 'like' count. 112 | class Likes extends StatefulWidget { 113 | /// Constructs a new [Likes] instance with a given [DocumentReference] and 114 | /// current like count. 115 | Likes({ 116 | Key? key, 117 | required this.reference, 118 | required this.currentLikes, 119 | }) : super(key: key); 120 | 121 | /// The reference relating to the counter. 122 | final MovieDocumentReference reference; 123 | 124 | /// The number of current likes (before manipulation). 125 | final int currentLikes; 126 | 127 | @override 128 | // ignore: library_private_types_in_public_api 129 | _LikesState createState() => _LikesState(); 130 | } 131 | 132 | class _LikesState extends State { 133 | /// A local cache of the current likes, used to immediately render the updated 134 | /// likes count after an update, even while the request isn't completed yet. 135 | late int _likes = widget.currentLikes; 136 | 137 | Future _onLike() async { 138 | final currentLikes = _likes; 139 | 140 | // Increment the 'like' count straight away to show feedback to the user. 141 | setState(() { 142 | _likes = currentLikes + 1; 143 | }); 144 | 145 | try { 146 | // Update the likes using a transaction. 147 | // We use a transaction because multiple users could update the likes count 148 | // simultaneously. As such, our likes count may be different from the likes 149 | // count on the server. 150 | final newLikes = await FirebaseFirestore.instance 151 | .runTransaction((transaction) async { 152 | final movie = await transaction.get(widget.reference.reference); 153 | 154 | if (!movie.exists) { 155 | throw Exception('Document does not exist!'); 156 | } 157 | 158 | final updatedLikes = movie.data()!.likes + 1; 159 | transaction.update( 160 | widget.reference.reference, 161 | {'likes': updatedLikes}, 162 | ); 163 | return updatedLikes; 164 | }); 165 | 166 | // Update with the real count once the transaction has completed. 167 | setState(() => _likes = newLikes); 168 | } catch (e, s) { 169 | print(s); 170 | print('Failed to update likes for document! $e'); 171 | 172 | // If the transaction fails, revert back to the old count 173 | setState(() => _likes = currentLikes); 174 | } 175 | } 176 | 177 | @override 178 | void didUpdateWidget(Likes oldWidget) { 179 | super.didUpdateWidget(oldWidget); 180 | // The likes on the server changed, so we need to update our local cache to 181 | // keep things in sync. Otherwise if another user updates the likes, 182 | // we won't see the update. 183 | if (widget.currentLikes != oldWidget.currentLikes) { 184 | _likes = widget.currentLikes; 185 | } 186 | } 187 | 188 | @override 189 | Widget build(BuildContext context) { 190 | return Row( 191 | children: [ 192 | IconButton( 193 | iconSize: 20, 194 | onPressed: _onLike, 195 | icon: const Icon(Icons.favorite), 196 | ), 197 | Text('$_likes likes'), 198 | ], 199 | ); 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /packages/cloud_firestore_odm/example/lib/movies_screen.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2022, the Chromium project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | // ignore_for_file: library_private_types_in_public_api 6 | 7 | import 'package:cloud_firestore/cloud_firestore.dart'; 8 | import 'package:cloud_firestore_odm/cloud_firestore_odm.dart'; 9 | import 'package:flutter/material.dart'; 10 | 11 | import 'movie.dart'; 12 | import 'movie_item.dart'; 13 | 14 | /// The different ways that we can filter/sort movies. 15 | enum MovieQueryType { 16 | year, 17 | likesAsc, 18 | likesDesc, 19 | score, 20 | sciFi, 21 | family, 22 | } 23 | 24 | extension on MovieQuery { 25 | /// Create a firebase query from a [MovieQuery] 26 | MovieQuery queryBy(MovieQueryType query) { 27 | switch (query) { 28 | case MovieQueryType.family: 29 | return whereGenre(arrayContainsAny: ['family']); 30 | 31 | case MovieQueryType.sciFi: 32 | return whereGenre(arrayContainsAny: ['sci-fi']); 33 | 34 | case MovieQueryType.likesAsc: 35 | case MovieQueryType.likesDesc: 36 | return orderByLikes(descending: query == MovieQueryType.likesDesc); 37 | 38 | case MovieQueryType.year: 39 | return orderByYear(descending: true); 40 | 41 | case MovieQueryType.score: 42 | return orderByRated(descending: true); 43 | } 44 | } 45 | } 46 | 47 | /// Holds all example app films 48 | class FilmList extends StatefulWidget { 49 | const FilmList({Key? key}) : super(key: key); 50 | 51 | @override 52 | _FilmListState createState() => _FilmListState(); 53 | } 54 | 55 | class _FilmListState extends State { 56 | var _query = MovieQueryType.year; 57 | 58 | @override 59 | Widget build(BuildContext context) { 60 | return Scaffold( 61 | appBar: AppBar( 62 | title: Column( 63 | mainAxisSize: MainAxisSize.min, 64 | crossAxisAlignment: CrossAxisAlignment.stretch, 65 | children: [ 66 | const Text('Firestore Example: Movies'), 67 | 68 | // This is a example use for 'snapshots in sync'. 69 | // The view reflects the time of the last Firestore sync; which happens any time a field is updated. 70 | StreamBuilder( 71 | stream: FirebaseFirestore.instance.snapshotsInSync(), 72 | builder: (context, _) { 73 | return Text( 74 | 'Latest Snapshot: ${DateTime.now()}', 75 | style: Theme.of(context).textTheme.bodySmall, 76 | ); 77 | }, 78 | ), 79 | ], 80 | ), 81 | actions: [ 82 | PopupMenuButton( 83 | onSelected: (newQuery) { 84 | setState(() { 85 | _query = newQuery; 86 | }); 87 | }, 88 | icon: const Icon(Icons.sort), 89 | itemBuilder: (BuildContext context) { 90 | return [ 91 | const PopupMenuItem( 92 | value: MovieQueryType.year, 93 | child: Text('Sort by Year'), 94 | ), 95 | const PopupMenuItem( 96 | value: MovieQueryType.score, 97 | child: Text('Sort by Score'), 98 | ), 99 | const PopupMenuItem( 100 | value: MovieQueryType.likesAsc, 101 | child: Text('Sort by Likes ascending'), 102 | ), 103 | const PopupMenuItem( 104 | value: MovieQueryType.likesDesc, 105 | child: Text('Sort by Likes descending'), 106 | ), 107 | const PopupMenuItem( 108 | value: MovieQueryType.family, 109 | child: Text('Filter genre Family'), 110 | ), 111 | const PopupMenuItem( 112 | value: MovieQueryType.sciFi, 113 | child: Text('Filter genre Sci-Fi'), 114 | ), 115 | ]; 116 | }, 117 | ), 118 | PopupMenuButton( 119 | onSelected: (_) => _resetLikes(), 120 | itemBuilder: (BuildContext context) { 121 | return [ 122 | const PopupMenuItem( 123 | value: 'reset_likes', 124 | child: Text('Reset like counts (WriteBatch)'), 125 | ), 126 | ]; 127 | }, 128 | ), 129 | ], 130 | ), 131 | body: FirestoreBuilder( 132 | ref: moviesRef.queryBy(_query), 133 | builder: (context, snapshot, _) { 134 | if (snapshot.hasError) { 135 | return Center( 136 | child: SelectableText(snapshot.error.toString()), 137 | ); 138 | } 139 | 140 | if (!snapshot.hasData) { 141 | return const Center(child: CircularProgressIndicator()); 142 | } 143 | 144 | final data = snapshot.requireData; 145 | 146 | return ListView.builder( 147 | itemCount: data.docs.length, 148 | itemBuilder: (context, index) { 149 | return GestureDetector( 150 | behavior: HitTestBehavior.opaque, 151 | onTap: () => Navigator.pushNamed( 152 | context, 153 | '/movies/${data.docs[index].id}', 154 | ), 155 | child: MovieItem( 156 | data.docs[index].data, 157 | data.docs[index].reference, 158 | ), 159 | ); 160 | }, 161 | ); 162 | }, 163 | ), 164 | ); 165 | } 166 | 167 | Future _resetLikes() async { 168 | final movies = await moviesRef.get(); 169 | final batch = FirebaseFirestore.instance.batch(); 170 | 171 | for (final movie in movies.docs) { 172 | batch.update(movie.reference.reference, {'likes': 0}); 173 | } 174 | await batch.commit(); 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /packages/cloud_firestore_odm/example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: cloud_firestore_odm_example 2 | publish_to: none 3 | 4 | environment: 5 | sdk: '>=2.18.0 <4.0.0' 6 | 7 | dependencies: 8 | cloud_firestore: ^5.0.0 9 | cloud_firestore_odm: ^1.0.0-dev.85 10 | firebase_core: ^3.0.0 11 | flutter: 12 | sdk: flutter 13 | freezed_annotation: ^2.2.0 14 | json_annotation: ^4.8.1 15 | meta: ^1.12.0 16 | 17 | dev_dependencies: 18 | build_runner: ^2.4.2 19 | cloud_firestore_odm_generator: 20 | path: ../../cloud_firestore_odm_generator 21 | cloud_firestore_odm_generator_integration_test: 22 | path: ../../cloud_firestore_odm_generator/cloud_firestore_odm_generator_integration_test 23 | flutter_test: 24 | sdk: flutter 25 | freezed: ^2.3.2 26 | http: ^1.0.0 27 | integration_test: 28 | sdk: flutter 29 | json_serializable: ^6.6.1 30 | mockito: ^5.0.0 31 | 32 | dependency_overrides: 33 | cloud_firestore_odm: 34 | path: ../ 35 | 36 | flutter: 37 | uses-material-design: true 38 | -------------------------------------------------------------------------------- /packages/cloud_firestore_odm/example/test_driver/integration_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2022, the Chromium project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:integration_test/integration_test_driver.dart'; 6 | 7 | Future main() => integrationDriver(); 8 | -------------------------------------------------------------------------------- /packages/cloud_firestore_odm/example/web/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FirebaseExtended/firestoreodm-flutter/cfcf372387c12b6f6f488bd17c8651ca6dda40af/packages/cloud_firestore_odm/example/web/favicon.png -------------------------------------------------------------------------------- /packages/cloud_firestore_odm/example/web/icons/Icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FirebaseExtended/firestoreodm-flutter/cfcf372387c12b6f6f488bd17c8651ca6dda40af/packages/cloud_firestore_odm/example/web/icons/Icon-192.png -------------------------------------------------------------------------------- /packages/cloud_firestore_odm/example/web/icons/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FirebaseExtended/firestoreodm-flutter/cfcf372387c12b6f6f488bd17c8651ca6dda40af/packages/cloud_firestore_odm/example/web/icons/Icon-512.png -------------------------------------------------------------------------------- /packages/cloud_firestore_odm/example/web/icons/Icon-maskable-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FirebaseExtended/firestoreodm-flutter/cfcf372387c12b6f6f488bd17c8651ca6dda40af/packages/cloud_firestore_odm/example/web/icons/Icon-maskable-192.png -------------------------------------------------------------------------------- /packages/cloud_firestore_odm/example/web/icons/Icon-maskable-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FirebaseExtended/firestoreodm-flutter/cfcf372387c12b6f6f488bd17c8651ca6dda40af/packages/cloud_firestore_odm/example/web/icons/Icon-maskable-512.png -------------------------------------------------------------------------------- /packages/cloud_firestore_odm/example/web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | hello_world 30 | 31 | 32 | 33 | 36 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /packages/cloud_firestore_odm/example/web/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hello_world", 3 | "short_name": "hello_world", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "background_color": "#0175C2", 7 | "theme_color": "#0175C2", 8 | "description": "A new Flutter project.", 9 | "orientation": "portrait-primary", 10 | "prefer_related_applications": false, 11 | "icons": [ 12 | { 13 | "src": "icons/Icon-192.png", 14 | "sizes": "192x192", 15 | "type": "image/png" 16 | }, 17 | { 18 | "src": "icons/Icon-512.png", 19 | "sizes": "512x512", 20 | "type": "image/png" 21 | }, 22 | { 23 | "src": "icons/Icon-maskable-192.png", 24 | "sizes": "192x192", 25 | "type": "image/png", 26 | "purpose": "maskable" 27 | }, 28 | { 29 | "src": "icons/Icon-maskable-512.png", 30 | "sizes": "512x512", 31 | "type": "image/png", 32 | "purpose": "maskable" 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /packages/cloud_firestore_odm/lib/annotation.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2022, the Chromium project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | export 'src/validator.dart' show Min, Validator, Max; 6 | 7 | /// {@macro cloud_firestore_odm.named_query} 8 | class NamedQuery { 9 | /// {@template cloud_firestore_odm.named_query} 10 | /// Defines a named query, allowing the ODM to generate utilities to interact 11 | /// with the query in a type-safe way. 12 | /// 13 | /// By doing: 14 | /// 15 | /// ```dart 16 | /// @NamedQuery('my-query-name') 17 | /// @Collection(...) 18 | /// class Anything {} 19 | /// ``` 20 | /// 21 | /// The ODM will generate a `myQueryNameGet` utility, which can be used as followed: 22 | /// 23 | /// ```dart 24 | /// void main() async { 25 | /// Future snapshot = myQueryNameGet(); 26 | /// } 27 | /// ``` 28 | /// 29 | /// 30 | /// **Note**: 31 | /// Named queries **must** be associated with a [Collection] that has a 32 | /// matching generic argument. 33 | /// 34 | /// This is necessary to ensure that `FirestoreDocumentSnapshot.reference` is 35 | /// properly set. 36 | /// 37 | /// {@endtemplate} 38 | const NamedQuery(this.queryName); 39 | 40 | /// The name of the Firestore query that will be performed. 41 | final String queryName; 42 | } 43 | 44 | /// {@template cloud_firestore_odm.collection} 45 | /// Defines a collection reference. 46 | /// 47 | /// To define a collection reference, first it is necessary to define a class 48 | /// representing the content of a document of the collection. 49 | /// 50 | /// That can be done by defining any serializable Dart class, such as by using 51 | /// [json_serializable](https://pub.dev/packages/json_serializable) as followed: 52 | /// 53 | /// ```dart 54 | /// @JsonSerializable() 55 | /// class Person { 56 | /// Person({required this.name, required this.age}); 57 | /// 58 | /// factory Person.fromJson(Map json) => _$PersonFromJson(json); 59 | /// 60 | /// final String name; 61 | /// final String age; 62 | /// 63 | /// Map toJson() => _$PersonToJson(this); 64 | /// } 65 | /// ``` 66 | /// 67 | /// 68 | /// Then, we should define a global variable representing our collection reference, 69 | /// using the `Collection` annotation. 70 | /// 71 | /// To do so, we must specify the path to the collection and the type of the collection 72 | /// content: 73 | /// 74 | /// ```dart 75 | /// @Collection('persons') 76 | /// final personsRef = PersonCollectionReference(); 77 | /// ``` 78 | /// 79 | /// The class `PersonCollectionReference` will be generated from the `Person` class, 80 | /// and will allow manipulating the collection in a type-safe way. For example, to 81 | /// read the person collection, you could do: 82 | /// 83 | /// ```dart 84 | /// void main() async { 85 | /// PersonQuerySnapshot snapshot = await personsRef.get(); 86 | /// 87 | /// for (PersonQueryDocumentSnapshot doc in snapshot.docs) { 88 | /// Person person = doc.data(); 89 | /// print(person.name); 90 | /// } 91 | /// } 92 | /// ``` 93 | /// 94 | /// **Note** 95 | /// Don't forget to include `part "my_file.g.dart"` at the top of your file. 96 | /// 97 | /// 98 | /// ### Obtaining a document reference. 99 | /// 100 | /// 101 | /// It is possible to obtain a document reference from a collection reference. 102 | /// 103 | /// Assuming we have: 104 | /// 105 | /// ```dart 106 | /// @Collection('persons') 107 | /// final personsRef = PersonCollectionReference(); 108 | /// ``` 109 | /// 110 | /// then we can get a document with: 111 | /// 112 | /// ```dart 113 | /// void main() async { 114 | /// PersonDocumentReference doc = personsRef.doc('document-id'); 115 | /// 116 | /// PersonDocumentSnapshot snapshot = await doc.get(); 117 | /// } 118 | /// ``` 119 | /// 120 | /// ### Defining a sub-collection 121 | /// 122 | /// Once you have defined a collection, you may want to define a sub-collection. 123 | /// 124 | /// To do that, you first must create a root collection as described previously. 125 | /// From there, you can add extra `@Collection` annotations to a collection reference 126 | /// for defining sub-collections: 127 | /// 128 | /// ```dart 129 | /// @Collection('persons') 130 | /// @Collection('persons/*/friends', name: 'friends') // defines a sub-collection "friends" 131 | /// final personsRef = PersonCollectionReference(); 132 | /// ``` 133 | /// 134 | /// Then, the sub-collection will be available from a document reference: 135 | /// 136 | /// ```dart 137 | /// void main() async { 138 | /// PersonDocumentReference johnRef = personsRef.doc('john'); 139 | /// 140 | /// FriendQuerySnapshot johnFriends = await johnRef.friends.get(); 141 | /// } 142 | /// ``` 143 | /// {@endtemplate} 144 | class Collection { 145 | /// {@macro cloud_firestore_odm.collection} 146 | const Collection(this.path, {this.name, this.prefix}); 147 | 148 | /// Decode a [Collection] from a [Map] 149 | /// 150 | /// This is internally used by the code-generator to decode configs from the `build.yaml` 151 | Collection.fromJson(Map json) 152 | : this( 153 | json['path']! as String, 154 | name: json['name'] as String?, 155 | prefix: json['prefix'] as String?, 156 | ); 157 | 158 | /// The firestore collection path 159 | final String path; 160 | 161 | /// The name of the generated collection field. Defaults to the last part of 162 | /// the collection [path]. 163 | final String? name; 164 | 165 | /// The prefix to use for generated class names. Defaults to the type of [T]. 166 | final String? prefix; 167 | } 168 | 169 | /// {@macro cloud_firestore_odm.id} 170 | class Id { 171 | /// {@template cloud_firestore_odm.id} 172 | /// Marks a property as the document ID of a document. 173 | /// 174 | /// By default, the document ID is not present in the firestore object once decoded. 175 | /// 176 | /// While you can acccess it using the `DocumentSnapshot`, it isn't always convenient. 177 | /// A solution to that is to use the `@Id` annotation, to tell Firestore that a 178 | /// a given property in a class would be the document ID: 179 | /// 180 | /// ```dart 181 | /// @Collection('users') 182 | /// @firestoreSerializable 183 | /// class Person { 184 | /// Person({ 185 | /// required this.name, 186 | /// required this.age, 187 | /// required this.id, 188 | /// }); 189 | /// 190 | /// // By adding this annotation, this property will not be considered as part 191 | /// // of the Firestore document, but instead represent the document ID. 192 | /// @Id() 193 | /// final String id; 194 | /// 195 | /// final String name; 196 | /// final int age; 197 | /// } 198 | /// ``` 199 | /// 200 | /// There are a few restrictions when using this annotation: 201 | /// 202 | /// - It can be used only once within an object 203 | /// - The annotated property must be of type `String`. 204 | /// {@endtemplate} 205 | const Id(); 206 | } 207 | -------------------------------------------------------------------------------- /packages/cloud_firestore_odm/lib/cloud_firestore_odm.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2022, the Chromium project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:cloud_firestore/cloud_firestore.dart'; 6 | import 'package:json_annotation/json_annotation.dart'; 7 | 8 | export 'annotation.dart'; 9 | 10 | export 'src/firestore_builder.dart' show FirestoreBuilder; 11 | export 'src/firestore_reference.dart' 12 | show 13 | FirestoreCollectionReference, 14 | FirestoreDocumentChange, 15 | FirestoreDocumentReference, 16 | FirestoreDocumentSnapshot, 17 | FirestoreListenable, 18 | FirestoreQueryDocumentSnapshot, 19 | FirestoreQuerySnapshot, 20 | FirestoreReference, 21 | QueryReference, 22 | $QueryCursor; 23 | 24 | /// The list of all [JsonConverter]s that cloud_firestore_odm offers. 25 | /// 26 | /// This list is meant to be passed to [JsonSerializable] as followed: 27 | /// 28 | /// ```dart 29 | /// @JsonSerializable(converters: firestoreJsonConverters) 30 | /// ``` 31 | const List> firestoreJsonConverters = [ 32 | FirestoreDateTimeConverter(), 33 | FirestoreTimestampConverter(), 34 | FirestoreGeoPointConverter(), 35 | FirestoreDocumentReferenceConverter(), 36 | ]; 37 | 38 | /// A [JsonConverter] that adds support for [Timestamp] objects within ODM models. 39 | class FirestoreTimestampConverter extends JsonConverter { 40 | const FirestoreTimestampConverter(); 41 | @override 42 | Timestamp fromJson(Timestamp json) => json; 43 | 44 | @override 45 | Timestamp toJson(Timestamp object) => object; 46 | } 47 | 48 | /// A [JsonConverter] that adds support for [GeoPoint] objects within ODM models. 49 | class FirestoreGeoPointConverter extends JsonConverter { 50 | const FirestoreGeoPointConverter(); 51 | @override 52 | GeoPoint fromJson(GeoPoint json) => json; 53 | 54 | @override 55 | GeoPoint toJson(GeoPoint object) => object; 56 | } 57 | 58 | /// A [JsonConverter] that adds support for [DateTime] objects within ODM models. 59 | class FirestoreDateTimeConverter extends JsonConverter { 60 | const FirestoreDateTimeConverter(); 61 | @override 62 | DateTime fromJson(Timestamp json) => json.toDate(); 63 | 64 | @override 65 | Timestamp toJson(DateTime object) => Timestamp.fromDate(object); 66 | } 67 | 68 | /// A [JsonConverter] that adds support for [DocumentReference] objects within 69 | /// ODM models. 70 | /// 71 | /// The document reference must receive a `Map` as generic 72 | /// argument. References coming from `withConverter` are not supported. 73 | class FirestoreDocumentReferenceConverter extends JsonConverter< 74 | DocumentReference>, 75 | DocumentReference>> { 76 | const FirestoreDocumentReferenceConverter(); 77 | 78 | @override 79 | DocumentReference> fromJson( 80 | DocumentReference> json, 81 | ) { 82 | return json; 83 | } 84 | 85 | @override 86 | DocumentReference> toJson( 87 | DocumentReference> object, 88 | ) { 89 | return object; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /packages/cloud_firestore_odm/lib/src/firestore_builder.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2022, the Chromium project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | 7 | import 'package:flutter/material.dart'; 8 | 9 | import 'firestore_reference.dart'; 10 | 11 | /// {@macro cloud_firestore_odm.firestore_builder} 12 | class FirestoreBuilder extends StatefulWidget { 13 | /// {@template cloud_firestore_odm.firestore_builder} 14 | /// Listens to [ref] and build widgets out of the latest value emitted. 15 | /// 16 | /// This is a better solution than [StreamBuilder] for listening a Firestore 17 | /// reference, as [FirestoreBuilder] will cache the stream created with `ref.snapshots`, 18 | /// which could otherwise result in a billable operation. 19 | /// {@endtemplate} 20 | const FirestoreBuilder({ 21 | Key? key, 22 | required this.ref, 23 | required this.builder, 24 | this.child, 25 | }) : super(key: key); 26 | 27 | /// The listened reference 28 | final FirestoreListenable ref; 29 | 30 | /// A function that builds widgets based on the latest snapshot from [ref]. 31 | final Widget Function( 32 | BuildContext context, 33 | AsyncSnapshot snapshot, 34 | Widget? child, 35 | ) builder; 36 | 37 | /// An optional child for performance optimization when a part of the 38 | /// tree is not built based on [ref]. 39 | final Widget? child; 40 | 41 | @override 42 | // ignore: library_private_types_in_public_api 43 | _FirestoreBuilderState createState() => 44 | _FirestoreBuilderState(); 45 | } 46 | 47 | class _FirestoreBuilderState 48 | extends State> { 49 | FirestoreReference? _streamCacheKey; 50 | late Stream _streamCache; 51 | Stream get _stream { 52 | final ref = _getReference(widget.ref); 53 | 54 | if (ref == _streamCacheKey) return _streamCache; 55 | 56 | _streamCacheKey = ref; 57 | return _streamCache = ref.snapshots(); 58 | } 59 | 60 | var _lastSnapshot = const AsyncSnapshot.nothing(); 61 | Stream? _listenableCacheKey; 62 | VoidCallback? _removeListener; 63 | void _listenStream(Stream stream) { 64 | if (stream == _listenableCacheKey) { 65 | return; 66 | } 67 | 68 | _removeListener?.call(); 69 | _listenableCacheKey = stream; 70 | 71 | setSnapshot(_lastSnapshot.inState(ConnectionState.waiting)); 72 | 73 | final sub = stream.listen( 74 | (event) { 75 | setSnapshot(AsyncSnapshot.withData(ConnectionState.active, event)); 76 | }, 77 | onError: (Object err, StackTrace stack) { 78 | setSnapshot( 79 | AsyncSnapshot.withError(ConnectionState.active, err, stack), 80 | ); 81 | }, 82 | ); 83 | _removeListener = sub.cancel; 84 | } 85 | 86 | var _hasSelectedValue = false; 87 | late AsyncSnapshot currentValue; 88 | 89 | void setSnapshot(AsyncSnapshot snapshot) { 90 | _lastSnapshot = snapshot; 91 | 92 | final listenable = widget.ref; 93 | 94 | if (listenable is! FirestoreSelector) { 95 | setState(() { 96 | currentValue = snapshot.whenData((val) => val as Snapshot); 97 | }); 98 | return; 99 | } 100 | 101 | // ignore: invalid_use_of_protected_member 102 | final newSnapshot = snapshot.whenData(listenable.runSelector); 103 | 104 | if (!_hasSelectedValue || 105 | !newSnapshot.hasData || 106 | !currentValue.hasData || 107 | newSnapshot.data != currentValue.data) { 108 | if (newSnapshot.connectionState == ConnectionState.active) { 109 | _hasSelectedValue = true; 110 | } 111 | setState(() => currentValue = newSnapshot); 112 | } 113 | } 114 | 115 | @override 116 | void initState() { 117 | super.initState(); 118 | _listenStream(_stream); 119 | } 120 | 121 | @override 122 | void didUpdateWidget(covariant FirestoreBuilder oldWidget) { 123 | super.didUpdateWidget(oldWidget); 124 | _hasSelectedValue = false; 125 | _listenStream(_stream); 126 | setSnapshot(_lastSnapshot); 127 | } 128 | 129 | @override 130 | void dispose() { 131 | _removeListener?.call(); 132 | super.dispose(); 133 | } 134 | 135 | @override 136 | Widget build(BuildContext context) { 137 | return widget.builder(context, currentValue, widget.child); 138 | } 139 | } 140 | 141 | FirestoreReference _getReference( 142 | FirestoreListenable listenable, 143 | ) { 144 | if (listenable is FirestoreReference) { 145 | return listenable; 146 | } else if (listenable is FirestoreSelector) { 147 | return listenable.reference; 148 | } else { 149 | throw UnsupportedError( 150 | 'Unknown reference type: ${listenable.runtimeType}', 151 | ); 152 | } 153 | } 154 | 155 | extension on AsyncSnapshot { 156 | AsyncSnapshot whenData(Res Function(T val) onData) { 157 | if (hasError) { 158 | return AsyncSnapshot.withError(connectionState, error!, stackTrace!); 159 | } 160 | if (hasData) { 161 | try { 162 | return AsyncSnapshot.withData( 163 | connectionState, 164 | onData(requireData), 165 | ); 166 | } catch (error, stackTrace) { 167 | return AsyncSnapshot.withError(connectionState, error, stackTrace); 168 | } 169 | } 170 | 171 | return AsyncSnapshot.nothing().inState(connectionState); 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /packages/cloud_firestore_odm/lib/src/validator.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2022, the Chromium project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | /// A class used to assert that a value respects some rules. 6 | /// 7 | /// As opposed to `assert`, this class works in release mode too. 8 | // ignore: one_member_abstracts 9 | abstract class Validator { 10 | /// A class used to assert that a value respects some rules. 11 | /// 12 | /// As opposed to `assert`, this class works in release mode too. 13 | const Validator(); 14 | 15 | void validate(Object? value, String propertyName); 16 | } 17 | 18 | class Min extends Validator { 19 | const Min(this.minValue); 20 | 21 | final num minValue; 22 | 23 | @override 24 | void validate(Object? value, String propertyName) { 25 | if (value is num && value < minValue) { 26 | throw ArgumentError.value(value, propertyName); 27 | } 28 | } 29 | } 30 | 31 | class Max extends Validator { 32 | const Max(this.maxValue); 33 | 34 | final num maxValue; 35 | 36 | @override 37 | void validate(Object? value, String propertyName) { 38 | if (value is num && value > maxValue) { 39 | throw ArgumentError.value(value, propertyName); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /packages/cloud_firestore_odm/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: cloud_firestore_odm 2 | description: An ODM for Firebase Cloud Firestore (cloud_firestore). 3 | homepage: https://github.com/firebaseextended/firestoreodm-flutter 4 | repository: https://github.com/firebaseextended/firestoreodm-flutter 5 | version: 1.0.0-dev.88 6 | 7 | false_secrets: 8 | - example/** 9 | 10 | environment: 11 | sdk: '>=2.18.0 <4.0.0' 12 | 13 | dependencies: 14 | cloud_firestore: ^5.0.0 15 | flutter: 16 | sdk: flutter 17 | json_annotation: ^4.8.1 18 | meta: ^1.12.0 19 | 20 | dev_dependencies: 21 | flutter_test: 22 | sdk: flutter 23 | -------------------------------------------------------------------------------- /packages/cloud_firestore_odm/test/common.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2022, the Chromium project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | 7 | import 'package:cloud_firestore/cloud_firestore.dart'; 8 | import 'package:cloud_firestore_odm/cloud_firestore_odm.dart'; 9 | import 'package:flutter/foundation.dart'; 10 | 11 | // ignore: subtype_of_sealed_class 12 | class _FakeQueryRef implements Query { 13 | @override 14 | dynamic noSuchMethod(Invocation invocation) { 15 | throw UnimplementedError(); 16 | } 17 | } 18 | 19 | class FakeCollectionReference 20 | extends FirestoreCollectionReference> { 21 | FakeCollectionReference(this.valueListenable) 22 | : super($referenceWithoutCursor: _FakeQueryRef()); 23 | 24 | @override 25 | CollectionReference get reference => throw UnimplementedError(); 26 | 27 | final ValueListenable> valueListenable; 28 | 29 | @override 30 | dynamic noSuchMethod(Invocation invocation) { 31 | throw UnimplementedError(); 32 | } 33 | 34 | @override 35 | Future> get([GetOptions? options]) async { 36 | return FakeQuerySnapshot( 37 | valueListenable.value 38 | .map(FakeFirestoreQueryDocumentSnapshot.new) 39 | .toList(), 40 | ); 41 | } 42 | 43 | @override 44 | Stream> snapshots([GetOptions? options]) { 45 | late StreamController> controller; 46 | 47 | void listener() { 48 | controller.add( 49 | FakeQuerySnapshot( 50 | valueListenable.value 51 | .map(FakeFirestoreQueryDocumentSnapshot.new) 52 | .toList(), 53 | ), 54 | ); 55 | } 56 | 57 | controller = StreamController( 58 | sync: true, 59 | onListen: () { 60 | valueListenable.addListener(listener); 61 | listener(); 62 | }, 63 | onCancel: () { 64 | valueListenable.removeListener(listener); 65 | controller.close(); 66 | }, 67 | ); 68 | 69 | return controller.stream; 70 | } 71 | } 72 | 73 | class FakeQuerySnapshot extends FirestoreQuerySnapshot> { 75 | FakeQuerySnapshot(this.docs); 76 | 77 | @override 78 | final List> docs; 79 | 80 | @override 81 | dynamic noSuchMethod(Invocation invocation) { 82 | throw UnimplementedError(); 83 | } 84 | } 85 | 86 | class FakeFirestoreQueryDocumentSnapshot 87 | extends FirestoreQueryDocumentSnapshot { 88 | FakeFirestoreQueryDocumentSnapshot(this.data); 89 | 90 | @override 91 | final Value data; 92 | 93 | @override 94 | dynamic noSuchMethod(Invocation invocation) { 95 | throw UnimplementedError(); 96 | } 97 | } 98 | 99 | class FakeDocumentReference 100 | extends FirestoreDocumentReference> { 101 | FakeDocumentReference( 102 | this.valueListenable, { 103 | this.errorListenable, 104 | this.emitCurrentValue = true, 105 | }); 106 | 107 | final ValueListenable valueListenable; 108 | final ValueListenable? errorListenable; 109 | final bool emitCurrentValue; 110 | 111 | @override 112 | dynamic noSuchMethod(Invocation invocation) { 113 | throw UnimplementedError(); 114 | } 115 | 116 | @override 117 | Future> get([GetOptions? options]) async { 118 | return FakeDocumentSnapshot(valueListenable.value); 119 | } 120 | 121 | @override 122 | Stream> snapshots([GetOptions? options]) { 123 | late StreamController> controller; 124 | 125 | void listener() { 126 | controller.add(FakeDocumentSnapshot(valueListenable.value)); 127 | } 128 | 129 | void onError() { 130 | controller.addError(errorListenable!.value); 131 | } 132 | 133 | controller = StreamController( 134 | sync: true, 135 | onListen: () { 136 | valueListenable.addListener(listener); 137 | errorListenable?.addListener(onError); 138 | if (emitCurrentValue) Future(listener); 139 | }, 140 | onCancel: () { 141 | valueListenable.removeListener(listener); 142 | errorListenable?.removeListener(onError); 143 | controller.close(); 144 | }, 145 | ); 146 | 147 | return controller.stream; 148 | } 149 | } 150 | 151 | class FakeDocumentSnapshot extends FirestoreDocumentSnapshot { 152 | FakeDocumentSnapshot(this.data); 153 | 154 | @override 155 | final Value data; 156 | 157 | @override 158 | dynamic noSuchMethod(Invocation invocation) { 159 | throw UnimplementedError(); 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /packages/cloud_firestore_odm_generator/.gitignore: -------------------------------------------------------------------------------- 1 | # flutter folders & files 2 | .dart_tool/ 3 | .flutter-plugins 4 | .flutter-plugins-dependencies 5 | .packages 6 | .pub-cache/ 7 | .pub/ 8 | 9 | # Needed because of https://github.com/dart-lang/graphs/issues/86 10 | !pubspec_override.yaml -------------------------------------------------------------------------------- /packages/cloud_firestore_odm_generator/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2021, the Chromium project authors. All rights reserved. 2 | Redistribution and use in source and binary forms, with or without 3 | modification, are permitted provided that the following conditions are 4 | met: 5 | 6 | * Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above 9 | copyright notice, this list of conditions and the following 10 | disclaimer in the documentation and/or other materials provided 11 | with the distribution. 12 | * Neither the name of Google Inc. nor the names of its 13 | contributors may be used to endorse or promote products derived 14 | from this software without specific prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 20 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 21 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 22 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 26 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | -------------------------------------------------------------------------------- /packages/cloud_firestore_odm_generator/README.md: -------------------------------------------------------------------------------- 1 | # Cloud Firestore ODM Generator 2 | 3 | A package that provides generated Cloud Firestore bindings for Dart classes, allowing type-safe 4 | queries and updates when using the [Cloud Firestore API](https://firebase.google.com/docs/firestore/). 5 | 6 | To learn more about Firebase Cloud Firestore, please visit the [Firebase website](https://firebase.google.com/products/firestore) 7 | 8 | [![pub package](https://img.shields.io/pub/v/cloud_firestore_odm.svg)](https://pub.dev/packages/cloud_firestore_odm) 9 | 10 | ## Getting Started 11 | 12 | To get started with Cloud Firestore for Flutter, please [see the documentation](https://firebase.flutter.dev/docs/firestore/overview). 13 | 14 | ## Usage 15 | 16 | To use this package, please visit the [Firestore Usage documentation](https://firebase.flutter.dev/docs/firestore/usage) 17 | 18 | ## Issues and feedback 19 | 20 | Please file FlutterFire specific issues, bugs, or feature requests in our [issue tracker](https://github.com/firebaseextended/firestoreodm-flutter/issues/new). 21 | 22 | Plugin issues that are not specific to FlutterFire can be filed in the [Flutter issue tracker](https://github.com/flutter/flutter/issues/new). 23 | 24 | To contribute a change to this plugin, 25 | please review our [contribution guide](https://github.com/firebaseextended/firestoreodm-flutter/blob/main/docs/contributing.md) 26 | and open a [pull request](https://github.com/firebaseextended/firestoreodm-flutter/pulls). 27 | -------------------------------------------------------------------------------- /packages/cloud_firestore_odm_generator/build.yaml: -------------------------------------------------------------------------------- 1 | targets: 2 | $default: 3 | builders: 4 | cloud_firestore_odm_generator: 5 | enabled: true 6 | json_serializable: 7 | options: 8 | create_field_map: true 9 | create_per_field_to_json: true 10 | 11 | builders: 12 | cloud_firestore_odm_generator: 13 | import: "package:cloud_firestore_odm_generator/cloud_firestore_odm_generator.dart" 14 | builder_factories: ["firebase"] 15 | build_extensions: { ".dart": [".firebase.g.part"] } 16 | auto_apply: dependents 17 | build_to: cache 18 | applies_builders: ["source_gen|combining_builder"] 19 | required_inputs: [".freezed.dart"] 20 | -------------------------------------------------------------------------------- /packages/cloud_firestore_odm_generator/cloud_firestore_odm_generator_integration_test/.gitignore: -------------------------------------------------------------------------------- 1 | # flutter native folders 2 | web/ 3 | ios/ 4 | macos/ 5 | 6 | 7 | # Miscellaneous 8 | *.class 9 | *.log 10 | *.pyc 11 | *.swp 12 | .DS_Store 13 | .atom/ 14 | .buildlog/ 15 | .history 16 | .svn/ 17 | 18 | # IntelliJ related 19 | *.iml 20 | *.ipr 21 | *.iws 22 | .idea/ 23 | 24 | # The .vscode folder contains launch configuration and tasks you configure in 25 | # VS Code which you may wish to be included in version control, so this line 26 | # is commented out by default. 27 | #.vscode/ 28 | 29 | # Flutter/Dart/Pub related 30 | **/doc/api/ 31 | **/ios/Flutter/.last_build_id 32 | .dart_tool/ 33 | .flutter-plugins 34 | .flutter-plugins-dependencies 35 | .packages 36 | .pub-cache/ 37 | .pub/ 38 | /build/ 39 | 40 | # Web related 41 | lib/generated_plugin_registrant.dart 42 | 43 | # Symbolication related 44 | app.*.symbols 45 | 46 | # Obfuscation related 47 | app.*.map.json 48 | 49 | # Android Studio will place build artifacts here 50 | /android/app/debug 51 | /android/app/profile 52 | /android/app/release 53 | -------------------------------------------------------------------------------- /packages/cloud_firestore_odm_generator/cloud_firestore_odm_generator_integration_test/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 64401fb8dfce47bb0b31f9d513adb10a61c5451c 8 | channel: master 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /packages/cloud_firestore_odm_generator/cloud_firestore_odm_generator_integration_test/build.yaml: -------------------------------------------------------------------------------- 1 | targets: 2 | $default: 3 | builders: 4 | json_serializable: 5 | options: 6 | create_field_map: true 7 | create_per_field_to_json: true -------------------------------------------------------------------------------- /packages/cloud_firestore_odm_generator/cloud_firestore_odm_generator_integration_test/lib/freezed.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2022, the Chromium project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | // ignore_for_file: invalid_annotation_target 6 | 7 | import 'package:cloud_firestore/cloud_firestore.dart'; 8 | import 'package:cloud_firestore_odm/cloud_firestore_odm.dart'; 9 | import 'package:freezed_annotation/freezed_annotation.dart'; 10 | 11 | part 'freezed.freezed.dart'; 12 | part 'freezed.g.dart'; 13 | 14 | @Collection('freezed-test') 15 | @freezed 16 | class Person with _$Person { 17 | @JsonSerializable(fieldRename: FieldRename.snake) 18 | factory Person({ 19 | required String firstName, 20 | @JsonKey(name: 'LAST_NAME') required String lastName, 21 | @JsonKey(includeFromJson: false, includeToJson: false) int? ignored, 22 | }) = _Person; 23 | 24 | factory Person.fromJson(Map json) => _$PersonFromJson(json); 25 | } 26 | 27 | final personRef = PersonCollectionReference(); 28 | 29 | @Collection('freezed-test') 30 | @freezed 31 | class PublicRedirected with _$PublicRedirected { 32 | factory PublicRedirected({required String value}) = PublicRedirected2; 33 | 34 | factory PublicRedirected.fromJson(Map json) => 35 | _$PublicRedirectedFromJson(json); 36 | } 37 | -------------------------------------------------------------------------------- /packages/cloud_firestore_odm_generator/cloud_firestore_odm_generator_integration_test/lib/simple.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2022, the Chromium project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:cloud_firestore/cloud_firestore.dart'; 6 | import 'package:cloud_firestore_odm/cloud_firestore_odm.dart'; 7 | import 'package:json_annotation/json_annotation.dart'; 8 | 9 | part 'simple.g.dart'; 10 | 11 | final ignoredGetterRef = IgnoredGetterCollectionReference(); 12 | 13 | @Collection('firestore-example-app/test/getter') 14 | @JsonSerializable() 15 | class IgnoredGetter { 16 | IgnoredGetter(this.value); 17 | 18 | final int value; 19 | 20 | int get count => 42; 21 | 22 | @JsonKey(includeFromJson: false, includeToJson: false) 23 | int get count2 => 42; 24 | 25 | @JsonKey(includeFromJson: true, includeToJson: true) 26 | int get count3 => 42; 27 | 28 | static const staticGetter = 42; 29 | } 30 | 31 | @Collection('root') 32 | @JsonSerializable() 33 | class Model { 34 | Model(this.value); 35 | final String value; 36 | } 37 | 38 | enum TestEnum { 39 | one, 40 | two, 41 | three; 42 | } 43 | 44 | @JsonSerializable() 45 | class Nested { 46 | Nested({ 47 | required this.value, 48 | required this.simple, 49 | required this.valueList, 50 | required this.boolList, 51 | required this.stringList, 52 | required this.numList, 53 | required this.objectList, 54 | required this.dynamicList, 55 | required this.boolSet, 56 | required this.enumValue, 57 | required this.nullableEnumValue, 58 | required this.enumList, 59 | required this.nullableEnumList, 60 | }); 61 | 62 | factory Nested.fromJson(Map json) => _$NestedFromJson(json); 63 | 64 | Map toJson() => _$NestedToJson(this); 65 | 66 | final Nested? value; 67 | final int? simple; 68 | final List? valueList; 69 | final List? boolList; 70 | final List? stringList; 71 | final List? numList; 72 | final List? objectList; 73 | final List? dynamicList; 74 | final Set? boolSet; 75 | final TestEnum enumValue; 76 | final TestEnum? nullableEnumValue; 77 | final List enumList; 78 | final List? nullableEnumList; 79 | } 80 | 81 | @Collection('nested') 82 | final nestedRef = NestedCollectionReference(); 83 | 84 | @JsonSerializable() 85 | class EmptyModel { 86 | EmptyModel(); 87 | 88 | factory EmptyModel.fromJson(Map json) => 89 | _$EmptyModelFromJson(json); 90 | 91 | Map toJson() => _$EmptyModelToJson(this); 92 | } 93 | 94 | @Collection('config') 95 | final emptyModelRef = EmptyModelCollectionReference(); 96 | 97 | @JsonSerializable() 98 | class MinValidation { 99 | MinValidation(this.intNbr, this.doubleNbr, this.numNbr) { 100 | _$assertMinValidation(this); 101 | } 102 | 103 | @Min(0) 104 | @Max(42) 105 | final int intNbr; 106 | 107 | @Min(10) 108 | final double doubleNbr; 109 | @Min(-10) 110 | final num numNbr; 111 | } 112 | 113 | @JsonSerializable() 114 | class Root { 115 | Root(this.nonNullable, this.nullable); 116 | 117 | factory Root.fromJson(Map json) => _$RootFromJson(json); 118 | 119 | final String nonNullable; 120 | final int? nullable; 121 | 122 | Map toJson() => _$RootToJson(this); 123 | } 124 | 125 | @JsonSerializable() 126 | class OptionalJson { 127 | OptionalJson(this.value); 128 | 129 | final int value; 130 | } 131 | 132 | @Collection('root') 133 | final optionalJsonRef = OptionalJsonCollectionReference(); 134 | 135 | @JsonSerializable() 136 | class MixedJson { 137 | MixedJson(this.value); 138 | 139 | factory MixedJson.fromJson(Map json) => 140 | MixedJson(json['foo']! as int); 141 | 142 | final int value; 143 | 144 | Map toJson() => {'foo': value}; 145 | } 146 | 147 | @Collection('root') 148 | final mixedJsonRef = MixedJsonCollectionReference(); 149 | 150 | @JsonSerializable() 151 | class Sub { 152 | Sub(this.nonNullable, this.nullable); 153 | 154 | factory Sub.fromJson(Map json) => _$SubFromJson(json); 155 | 156 | final String nonNullable; 157 | final int? nullable; 158 | 159 | Map toJson() => _$SubToJson(this); 160 | } 161 | 162 | @JsonSerializable() 163 | class CustomSubName { 164 | CustomSubName(this.value); 165 | 166 | factory CustomSubName.fromJson(Map json) => 167 | _$CustomSubNameFromJson(json); 168 | 169 | final num value; 170 | 171 | Map toJson() => _$CustomSubNameToJson(this); 172 | } 173 | 174 | @JsonSerializable() 175 | class AsCamelCase { 176 | AsCamelCase(this.value); 177 | 178 | factory AsCamelCase.fromJson(Map json) => 179 | _$AsCamelCaseFromJson(json); 180 | 181 | final num value; 182 | 183 | Map toJson() => _$AsCamelCaseToJson(this); 184 | } 185 | 186 | @JsonSerializable() 187 | class CustomClassPrefix { 188 | CustomClassPrefix(this.value); 189 | 190 | factory CustomClassPrefix.fromJson(Map json) => 191 | _$CustomClassPrefixFromJson(json); 192 | 193 | final num value; 194 | 195 | Map toJson() => _$CustomClassPrefixToJson(this); 196 | } 197 | 198 | @Collection('root') 199 | @Collection('root/*/sub') 200 | @Collection('root/*/as-camel-case') 201 | @Collection('root/*/custom-sub-name', name: 'thisIsACustomName') 202 | @Collection( 203 | 'root/*/custom-class-prefix', 204 | prefix: 'ThisIsACustomPrefix', 205 | ) 206 | final rootRef = RootCollectionReference(); 207 | 208 | @JsonSerializable() 209 | class ExplicitPath { 210 | ExplicitPath(this.value); 211 | 212 | factory ExplicitPath.fromJson(Map json) => 213 | _$ExplicitPathFromJson(json); 214 | 215 | final num value; 216 | 217 | Map toJson() => _$ExplicitPathToJson(this); 218 | } 219 | 220 | @JsonSerializable() 221 | class ExplicitSubPath { 222 | ExplicitSubPath(this.value); 223 | 224 | factory ExplicitSubPath.fromJson(Map json) => 225 | _$ExplicitSubPathFromJson(json); 226 | 227 | final num value; 228 | 229 | Map toJson() => _$ExplicitSubPathToJson(this); 230 | } 231 | 232 | @Collection('root/doc/path') 233 | @Collection('root/doc/path/*/sub') 234 | final explicitRef = ExplicitPathCollectionReference(); 235 | 236 | abstract class BaseClass { 237 | const BaseClass(this.instanceGetter); 238 | 239 | static const staticGetter = 42; 240 | final int instanceGetter; 241 | } 242 | 243 | @JsonSerializable() 244 | class SubClass extends BaseClass { 245 | SubClass(super.instanceGetter); 246 | 247 | factory SubClass.fromJson(Map json) => 248 | _$SubClassFromJson(json); 249 | 250 | Map toJson() => _$SubClassToJson(this); 251 | } 252 | 253 | @Collection('root') 254 | final subClassRef = SubClassCollectionReference(); 255 | -------------------------------------------------------------------------------- /packages/cloud_firestore_odm_generator/cloud_firestore_odm_generator_integration_test/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: cloud_firestore_odm_generator_integration_test 2 | publish_to: none 3 | 4 | environment: 5 | sdk: '>=2.18.0 <4.0.0' 6 | 7 | dependencies: 8 | cloud_firestore: ^5.0.0 9 | cloud_firestore_odm: ^1.0.0-dev.85 10 | flutter: 11 | sdk: flutter 12 | freezed_annotation: ^2.2.0 13 | json_annotation: ^4.8.1 14 | meta: ^1.12.0 15 | 16 | dev_dependencies: 17 | build_runner: ^2.4.2 18 | cloud_firestore_odm_generator: 19 | path: ../ 20 | flutter_test: 21 | sdk: flutter 22 | freezed: ^2.3.2 23 | json_serializable: '>=6.6.1 <7.0.0' 24 | 25 | dependency_overrides: 26 | cloud_firestore_odm: 27 | path: ../../cloud_firestore_odm 28 | 29 | flutter: 30 | uses-material-design: false 31 | -------------------------------------------------------------------------------- /packages/cloud_firestore_odm_generator/cloud_firestore_odm_generator_integration_test/test/collection_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2022, the Chromium project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:cloud_firestore_odm_generator_integration_test/simple.dart'; 6 | import 'package:flutter_test/flutter_test.dart'; 7 | 8 | import 'setup_firestore_mock.dart'; 9 | 10 | void main() { 11 | setUpAll(setupCloudFirestoreMocks); 12 | 13 | test('can specify @Collection on the model itself', () { 14 | expect( 15 | ModelCollectionReference().path, 16 | 'root', 17 | ); 18 | }); 19 | 20 | group('orderBy', () { 21 | testWidgets('applies `descending`', (tester) async { 22 | expect( 23 | rootRef.orderByNullable(descending: true), 24 | rootRef.orderByNullable(descending: true), 25 | ); 26 | expect( 27 | rootRef.orderByNullable(descending: true), 28 | isNot(rootRef.orderByNullable()), 29 | ); 30 | expect( 31 | rootRef.orderByNullable(), 32 | rootRef.orderByNullable(), 33 | ); 34 | }); 35 | }); 36 | 37 | group('doc', () { 38 | test('asserts that the path does not point to a separate collection', 39 | () async { 40 | rootRef.doc('42'); 41 | 42 | expect( 43 | () => rootRef.doc('42/123'), 44 | throwsAssertionError, 45 | ); 46 | expect( 47 | () => rootRef.doc('42/123/456'), 48 | throwsAssertionError, 49 | ); 50 | }); 51 | }); 52 | } 53 | -------------------------------------------------------------------------------- /packages/cloud_firestore_odm_generator/cloud_firestore_odm_generator_integration_test/test/setup_firestore_mock.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2022, the Chromium project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:firebase_core/firebase_core.dart'; 6 | import 'package:firebase_core_platform_interface/firebase_core_platform_interface.dart'; 7 | import 'package:flutter_test/flutter_test.dart'; 8 | 9 | Future setupCloudFirestoreMocks() async { 10 | TestWidgetsFlutterBinding.ensureInitialized(); 11 | 12 | setupFirebaseCoreMocks(); 13 | await Firebase.initializeApp(); 14 | } 15 | -------------------------------------------------------------------------------- /packages/cloud_firestore_odm_generator/lib/cloud_firestore_odm_generator.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2022, the Chromium project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:build/build.dart'; 6 | import 'package:source_gen/source_gen.dart'; 7 | 8 | import 'src/collection_generator.dart'; 9 | import 'src/validator_generator.dart'; 10 | 11 | /// Builds generators for `build_runner` to run 12 | Builder firebase(BuilderOptions options) { 13 | return SharedPartBuilder( 14 | [ 15 | CollectionGenerator(), 16 | ValidatorGenerator(), 17 | ], 18 | 'firebase', 19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /packages/cloud_firestore_odm_generator/lib/src/collection_generator.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2022, the Chromium project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:analyzer/dart/element/element.dart'; 6 | import 'package:analyzer/dart/element/type.dart'; 7 | import 'package:build/build.dart'; 8 | import 'package:cloud_firestore_odm/annotation.dart'; 9 | import 'package:freezed_annotation/freezed_annotation.dart'; 10 | import 'package:source_gen/source_gen.dart'; 11 | 12 | import 'collection_data.dart'; 13 | import 'named_query_data.dart'; 14 | import 'parse_generator.dart'; 15 | import 'templates/collection_reference.dart'; 16 | import 'templates/document_reference.dart'; 17 | import 'templates/document_snapshot.dart'; 18 | import 'templates/named_query.dart'; 19 | import 'templates/query_document_snapshot.dart'; 20 | import 'templates/query_reference.dart'; 21 | import 'templates/query_snapshot.dart'; 22 | 23 | const namedQueryChecker = TypeChecker.fromRuntime(NamedQuery); 24 | 25 | class QueryingField { 26 | QueryingField( 27 | this.name, 28 | this.type, { 29 | required this.field, 30 | required this.updatable, 31 | required this.whereDoc, 32 | required this.orderByDoc, 33 | }); 34 | 35 | final String name; 36 | final DartType type; 37 | final String field; 38 | final bool updatable; 39 | final String whereDoc; 40 | final String orderByDoc; 41 | } 42 | 43 | class Data { 44 | Data(this.roots, this.subCollections); 45 | 46 | final List roots; 47 | final List subCollections; 48 | 49 | late final allCollections = [...roots, ...subCollections]; 50 | 51 | @override 52 | String toString() { 53 | return 'Data(roots: $roots, subCollections: $subCollections)'; 54 | } 55 | } 56 | 57 | class GlobalData { 58 | /// All the [Collection.prefix] in the library. 59 | final classPrefixesForLibrary = >{}; 60 | 61 | /// The list of all [NamedQuery] in the library. 62 | final namedQueries = []; 63 | } 64 | 65 | @immutable 66 | class CollectionGenerator 67 | extends ParserGenerator> { 68 | @override 69 | GlobalData parseGlobalData(LibraryElement library) { 70 | final globalData = GlobalData(); 71 | 72 | for (final element in library.topLevelElements) { 73 | for (final queryAnnotation in namedQueryChecker.annotationsOf(element)) { 74 | final queryData = NamedQueryData.fromAnnotation(queryAnnotation); 75 | 76 | final hasCollectionWithMatchingModelType = 77 | collectionChecker.annotationsOf(element).any( 78 | (annotation) { 79 | final collectionType = 80 | CollectionData.modelTypeOfAnnotation(annotation); 81 | return collectionType == queryData.type; 82 | }, 83 | ); 84 | 85 | if (!hasCollectionWithMatchingModelType) { 86 | throw InvalidGenerationSourceError( 87 | 'The named query "${queryData.queryName}" has no matching @Collection. ' 88 | 'Named queries must be associated with a @Collection with the same generic type.', 89 | element: element, 90 | ); 91 | } 92 | 93 | globalData.namedQueries.add(queryData); 94 | } 95 | } 96 | 97 | return globalData; 98 | } 99 | 100 | @override 101 | Future parseElement( 102 | BuildStep buildStep, 103 | GlobalData globalData, 104 | Element element, 105 | ) async { 106 | final library = await buildStep.inputLibrary; 107 | final collectionAnnotations = collectionChecker.annotationsOf(element).map( 108 | (annotation) { 109 | return CollectionData.fromAnnotation( 110 | annotatedElement: element, 111 | globalData: globalData, 112 | libraryElement: library, 113 | annotation: annotation, 114 | ); 115 | }, 116 | ).toList(); 117 | 118 | return CollectionGraph.parse(collectionAnnotations); 119 | } 120 | 121 | @override 122 | Iterable generateForAll(GlobalData globalData) sync* { 123 | yield ''' 124 | // GENERATED CODE - DO NOT MODIFY BY HAND 125 | // ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, require_trailing_commas, prefer_single_quotes, prefer_double_quotes, use_super_parameters, duplicate_ignore 126 | // ignore_for_file: type=lint 127 | // ignore_for_file: invalid_use_of_internal_member 128 | '''; 129 | 130 | yield ''' 131 | class _Sentinel { 132 | const _Sentinel(); 133 | } 134 | 135 | const _sentinel = _Sentinel(); 136 | '''; 137 | 138 | for (final namedQuery in globalData.namedQueries) { 139 | yield NamedQueryTemplate(namedQuery, globalData); 140 | } 141 | } 142 | 143 | @override 144 | Iterable generateForData( 145 | GlobalData globalData, 146 | CollectionGraph data, 147 | ) sync* { 148 | for (final collection in data.allCollections) { 149 | yield CollectionReferenceTemplate(collection); 150 | yield DocumentReferenceTemplate(collection); 151 | yield QueryTemplate(collection); 152 | 153 | yield DocumentSnapshotTemplate( 154 | documentSnapshotName: collection.documentSnapshotName, 155 | documentReferenceName: collection.documentReferenceName, 156 | type: collection.type, 157 | ); 158 | yield QuerySnapshotTemplate( 159 | documentSnapshotName: collection.documentSnapshotName, 160 | queryDocumentSnapshotName: collection.queryDocumentSnapshotName, 161 | querySnapshotName: collection.querySnapshotName, 162 | type: collection.type, 163 | ); 164 | yield QueryDocumentSnapshotTemplate( 165 | documentSnapshotName: collection.documentSnapshotName, 166 | documentReferenceName: collection.documentReferenceName, 167 | queryDocumentSnapshotName: collection.queryDocumentSnapshotName, 168 | type: collection.type, 169 | ); 170 | } 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /packages/cloud_firestore_odm_generator/lib/src/named_query_data.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2022, the Chromium project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:analyzer/dart/constant/value.dart'; 6 | import 'package:analyzer/dart/element/type.dart'; 7 | import 'package:recase/recase.dart'; 8 | 9 | import 'names.dart'; 10 | 11 | class NamedQueryData with Names { 12 | NamedQueryData(this.queryName, {required this.type}); 13 | 14 | factory NamedQueryData.fromAnnotation(DartObject dartObject) { 15 | final queryName = dartObject.getField('queryName')!.toStringValue()!; 16 | 17 | final genericType = 18 | (dartObject.type! as InterfaceType).typeArguments.single; 19 | 20 | return NamedQueryData(queryName, type: genericType); 21 | } 22 | 23 | @override 24 | final DartType type; 25 | 26 | final String queryName; 27 | 28 | late final String namedQueryGetName = '${ReCase(queryName).camelCase}Get'; 29 | late final String namedQueryExtensionName = 30 | '${ReCase(queryName).pascalCase}Extrension'; 31 | 32 | @override 33 | String? get collectionPrefix => null; 34 | } 35 | -------------------------------------------------------------------------------- /packages/cloud_firestore_odm_generator/lib/src/names.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2022, the Chromium project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:analyzer/dart/element/type.dart'; 6 | 7 | /// A mixin for obtaining the class name of collections/documents/snapshots/etc 8 | /// based on annotation metadata. 9 | mixin Names { 10 | String? get collectionPrefix; 11 | DartType get type; 12 | 13 | late final String classPrefix = collectionPrefix ?? 14 | type.getDisplayString().replaceFirstMapped( 15 | RegExp('[a-zA-Z]'), 16 | (match) => match.group(0)!.toUpperCase(), 17 | ); 18 | 19 | late final String collectionReferenceInterfaceName = 20 | '${classPrefix}CollectionReference'; 21 | late final String collectionReferenceImplName = 22 | '_\$${classPrefix}CollectionReference'; 23 | late final String documentReferenceName = '${classPrefix}DocumentReference'; 24 | late final String queryReferenceInterfaceName = '${classPrefix}Query'; 25 | late final String queryReferenceImplName = '_\$${classPrefix}Query'; 26 | late final String querySnapshotName = '${classPrefix}QuerySnapshot'; 27 | late final String queryDocumentSnapshotName = 28 | '${classPrefix}QueryDocumentSnapshot'; 29 | late final String documentSnapshotName = '${classPrefix}DocumentSnapshot'; 30 | late final String originalDocumentSnapshotName = 'DocumentSnapshot<$type>'; 31 | } 32 | -------------------------------------------------------------------------------- /packages/cloud_firestore_odm_generator/lib/src/parse_generator.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2022, the Chromium project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | 7 | import 'package:analyzer/dart/element/element.dart'; 8 | import 'package:build/build.dart'; 9 | import 'package:source_gen/source_gen.dart'; 10 | 11 | abstract class ParserGenerator 12 | extends GeneratorForAnnotation { 13 | @override 14 | FutureOr generate( 15 | // ignore: avoid_renaming_method_parameters 16 | LibraryReader oldLibrary, 17 | BuildStep buildStep, 18 | ) async { 19 | final library = await buildStep.resolver.libraryFor( 20 | await buildStep.resolver.assetIdForElement(oldLibrary.element), 21 | ); 22 | 23 | final generationBuffer = StringBuffer(); 24 | // A set used to remove duplicate generations. This is for scenarios where 25 | // two annotations within the library want to generate the same code 26 | final generatedCache = {}; 27 | 28 | final globalData = parseGlobalData(library); 29 | 30 | var hasGeneratedGlobalCode = false; 31 | 32 | for (final element 33 | in library.topLevelElements.where(typeChecker.hasAnnotationOf)) { 34 | if (!hasGeneratedGlobalCode) { 35 | hasGeneratedGlobalCode = true; 36 | for (final generated 37 | in generateForAll(globalData).map((e) => e.toString())) { 38 | assert(generated.length == generated.trim().length); 39 | if (generatedCache.add(generated)) { 40 | generationBuffer.writeln(generated); 41 | } 42 | } 43 | } 44 | 45 | final data = await parseElement(buildStep, globalData, element); 46 | if (data == null) continue; 47 | 48 | for (final generated 49 | in generateForData(globalData, data).map((e) => e.toString())) { 50 | assert(generated.length == generated.trim().length); 51 | 52 | if (generatedCache.add(generated)) { 53 | generationBuffer.writeln(generated); 54 | } 55 | } 56 | } 57 | 58 | return generationBuffer.toString(); 59 | } 60 | 61 | Iterable generateForAll(GlobalData globalData) sync* {} 62 | 63 | GlobalData parseGlobalData(LibraryElement library); 64 | 65 | FutureOr parseElement( 66 | BuildStep buildStep, 67 | GlobalData globalData, 68 | Element element, 69 | ); 70 | 71 | Iterable generateForData(GlobalData globalData, Data data); 72 | 73 | @override 74 | Stream generateForAnnotatedElement( 75 | Element element, 76 | ConstantReader annotation, 77 | BuildStep buildStep, 78 | ) async* { 79 | // implemented for source_gen_test – otherwise unused 80 | final globalData = parseGlobalData(element.library!); 81 | final data = parseElement(buildStep, globalData, element); 82 | 83 | if (data == null) return; 84 | 85 | for (final value in generateForData(globalData, await data)) { 86 | yield value.toString(); 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /packages/cloud_firestore_odm_generator/lib/src/templates/collection_reference.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2022, the Chromium project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import '../collection_data.dart'; 6 | 7 | class CollectionReferenceTemplate { 8 | CollectionReferenceTemplate(this.data); 9 | 10 | final CollectionData data; 11 | 12 | @override 13 | String toString() { 14 | final idKey = data.idKey; 15 | 16 | String fromFirestoreBody; 17 | String toFirestoreBody; 18 | if (idKey != null) { 19 | fromFirestoreBody = 20 | 'return ${data.fromJson("{'$idKey': snapshot.id, ...?snapshot.data()}")};'; 21 | toFirestoreBody = 22 | "return {...${data.toJson('value')}}..remove('$idKey');"; 23 | } else { 24 | fromFirestoreBody = 'return ${data.fromJson('snapshot.data()!')};'; 25 | toFirestoreBody = 'return ${data.toJson('value')};'; 26 | } 27 | 28 | return ''' 29 | /// A collection reference object can be used for adding documents, 30 | /// getting document references, and querying for documents 31 | /// (using the methods inherited from Query). 32 | abstract class ${data.collectionReferenceInterfaceName} 33 | implements 34 | ${data.queryReferenceInterfaceName}, 35 | FirestoreCollectionReference<${data.type}, ${data.querySnapshotName}> { 36 | ${data.parent != null ? _subCollectionConstructors(data, abstract: true) : _rootCollectionConstructors(data, abstract: true)} 37 | 38 | static ${data.type} fromFirestore( 39 | DocumentSnapshot> snapshot, 40 | SnapshotOptions? options, 41 | ) { 42 | $fromFirestoreBody 43 | } 44 | 45 | static Map toFirestore( 46 | ${data.type} value, 47 | SetOptions? options, 48 | ) { 49 | $toFirestoreBody 50 | } 51 | 52 | @override 53 | CollectionReference<${data.type}> get reference; 54 | 55 | ${_parentProperty(data, abstract: true)} 56 | 57 | @override 58 | ${data.documentReferenceName} doc([String? id]); 59 | 60 | /// Add a new document to this collection with the specified data, 61 | /// assigning it a document ID automatically. 62 | Future<${data.documentReferenceName}> add(${data.type} value); 63 | } 64 | 65 | class ${data.collectionReferenceImplName} 66 | extends ${data.queryReferenceImplName} 67 | implements ${data.collectionReferenceInterfaceName} { 68 | ${data.parent != null ? _subCollectionConstructors(data) : _rootCollectionConstructors(data)} 69 | 70 | 71 | ${_parentProperty(data)} 72 | 73 | String get path => reference.path; 74 | 75 | @override 76 | CollectionReference<${data.type}> get reference => super.reference as CollectionReference<${data.type}>; 77 | 78 | @override 79 | ${data.documentReferenceName} doc([String? id]) { 80 | assert( 81 | id == null || id.split('/').length == 1, 82 | 'The document ID cannot be from a different collection', 83 | ); 84 | return ${data.documentReferenceName}( 85 | reference.doc(id), 86 | ); 87 | } 88 | 89 | @override 90 | Future<${data.documentReferenceName}> add(${data.type} value) { 91 | return reference 92 | .add(value) 93 | .then((ref) => ${data.documentReferenceName}(ref)); 94 | } 95 | 96 | ${_equalAndHashCode(data)} 97 | } 98 | '''; 99 | } 100 | 101 | String _equalAndHashCode(CollectionData data) { 102 | final propertyNames = [ 103 | 'runtimeType', 104 | 'reference', 105 | ]; 106 | 107 | return ''' 108 | @override 109 | bool operator ==(Object other) { 110 | return other is ${data.collectionReferenceImplName} 111 | && ${propertyNames.map((p) => 'other.$p == $p').join(' && ')}; 112 | } 113 | 114 | @override 115 | int get hashCode => Object.hash(${propertyNames.join(', ')}); 116 | '''; 117 | } 118 | 119 | String _parentProperty(CollectionData data, {bool abstract = false}) { 120 | if (data.parent == null) return ''; 121 | 122 | if (abstract) { 123 | return ''' 124 | /// A reference to the containing [${data.parent!.documentReferenceName}] if this is a subcollection. 125 | ${data.parent!.documentReferenceName} get parent; 126 | '''; 127 | } 128 | 129 | return ''' 130 | @override 131 | final ${data.parent!.documentReferenceName} parent; 132 | '''; 133 | } 134 | 135 | String _subCollectionConstructors( 136 | CollectionData data, { 137 | bool abstract = false, 138 | }) { 139 | final parent = data.parent!; 140 | 141 | final pathSplit = data.path.split('/'); 142 | var path = ''; 143 | var nextParent = 'parent'; 144 | for (var i = pathSplit.length - 1; i >= 0; i--) { 145 | if (pathSplit[i] == '*') { 146 | path = '\${$nextParent.id}$path'; 147 | nextParent = 'parent.$nextParent'; 148 | } 149 | } 150 | 151 | if (abstract) { 152 | return ''' 153 | factory ${data.collectionReferenceInterfaceName}( 154 | DocumentReference<${parent.type}> parent, 155 | ) = ${data.collectionReferenceImplName};'''; 156 | } 157 | 158 | return ''' 159 | factory ${data.collectionReferenceImplName}( 160 | DocumentReference<${parent.type}> parent, 161 | ) { 162 | return ${data.collectionReferenceImplName}._( 163 | ${parent.documentReferenceName}(parent), 164 | parent 165 | .collection('${pathSplit.last}') 166 | .withConverter( 167 | fromFirestore: ${data.collectionReferenceInterfaceName}.fromFirestore, 168 | toFirestore: ${data.collectionReferenceInterfaceName}.toFirestore, 169 | ), 170 | ); 171 | } 172 | 173 | ${data.collectionReferenceImplName}._( 174 | this.parent, 175 | CollectionReference<${data.type}> reference, 176 | ) : super(reference, \$referenceWithoutCursor: reference); 177 | '''; 178 | } 179 | 180 | String _rootCollectionConstructors( 181 | CollectionData data, { 182 | bool abstract = false, 183 | }) { 184 | if (abstract) { 185 | return ''' 186 | factory ${data.collectionReferenceInterfaceName}([ 187 | FirebaseFirestore? firestore, 188 | ]) = ${data.collectionReferenceImplName};'''; 189 | } 190 | 191 | return ''' 192 | factory ${data.collectionReferenceImplName}([FirebaseFirestore? firestore]) { 193 | firestore ??= FirebaseFirestore.instance; 194 | 195 | return ${data.collectionReferenceImplName}._( 196 | firestore 197 | .collection('${data.path}') 198 | .withConverter( 199 | fromFirestore: ${data.collectionReferenceInterfaceName}.fromFirestore, 200 | toFirestore: ${data.collectionReferenceInterfaceName}.toFirestore, 201 | ), 202 | ); 203 | } 204 | 205 | ${data.collectionReferenceImplName}._( 206 | CollectionReference<${data.type}> reference, 207 | ) : super(reference, \$referenceWithoutCursor: reference); 208 | '''; 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /packages/cloud_firestore_odm_generator/lib/src/templates/document_reference.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2022, the Chromium project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import '../collection_data.dart'; 6 | 7 | class DocumentReferenceTemplate { 8 | DocumentReferenceTemplate(this.data); 9 | 10 | final CollectionData data; 11 | 12 | @override 13 | String toString() { 14 | return ''' 15 | abstract class ${data.documentReferenceName} extends FirestoreDocumentReference<${data.type}, ${data.documentSnapshotName}> { 16 | factory ${data.documentReferenceName}(DocumentReference<${data.type}> reference) = _\$${data.documentReferenceName}; 17 | 18 | DocumentReference<${data.type}> get reference; 19 | 20 | ${_parent(data)} 21 | 22 | ${_subCollections(data)} 23 | 24 | @override 25 | Stream<${data.documentSnapshotName}> snapshots(); 26 | 27 | @override 28 | Future<${data.documentSnapshotName}> get([GetOptions? options]); 29 | 30 | @override 31 | Future delete(); 32 | 33 | ${_setPrototype(data)} 34 | 35 | ${_updatePrototype(data)} 36 | } 37 | 38 | class _\$${data.documentReferenceName} 39 | extends FirestoreDocumentReference<${data.type}, ${data.documentSnapshotName}> 40 | implements ${data.documentReferenceName} { 41 | _\$${data.documentReferenceName}(this.reference); 42 | 43 | @override 44 | final DocumentReference<${data.type}> reference; 45 | 46 | ${_parent(data)} 47 | 48 | ${_subCollections(data)} 49 | 50 | @override 51 | Stream<${data.documentSnapshotName}> snapshots() { 52 | return reference.snapshots().map(${data.documentSnapshotName}._); 53 | } 54 | 55 | @override 56 | Future<${data.documentSnapshotName}> get([GetOptions? options]) { 57 | return reference.get(options).then(${data.documentSnapshotName}._); 58 | } 59 | 60 | @override 61 | Future<${data.documentSnapshotName}> transactionGet(Transaction transaction) { 62 | return transaction.get(reference).then(${data.documentSnapshotName}._); 63 | } 64 | 65 | ${_set(data)} 66 | 67 | ${_update(data)} 68 | 69 | ${_equalAndHashCode(data)} 70 | } 71 | '''; 72 | } 73 | 74 | String _parameters( 75 | CollectionData data, { 76 | bool includeFields = true, 77 | bool useSentinel = false, 78 | bool includeFieldValues = true, 79 | bool fieldValuesNullable = false, 80 | }) { 81 | final parameters = []; 82 | 83 | for (final field in data.updatableFields) { 84 | if (!field.updatable) continue; 85 | 86 | final fieldType = field.type.getDisplayString(); 87 | if (includeFields) { 88 | final type = useSentinel ? 'Object?' : fieldType; 89 | final defaultValue = useSentinel ? ' = _sentinel' : ''; 90 | parameters.add('$type ${field.name}$defaultValue,'); 91 | } 92 | if (includeFieldValues) { 93 | final suffix = fieldValuesNullable ? '?' : ''; 94 | parameters.add('FieldValue$suffix ${field.name}FieldValue,'); 95 | } 96 | } 97 | 98 | return parameters.join('\n'); 99 | } 100 | 101 | // TODO support nested objects 102 | String _json( 103 | CollectionData data, { 104 | bool includeFields = true, 105 | bool includeFieldValues = true, 106 | }) { 107 | return [ 108 | for (final field in data.updatableFields) ...[ 109 | if (includeFields) 110 | ''' 111 | if (${field.name} != _sentinel) 112 | ${field.field}: ${data.perFieldToJson(field.name)}(${field.name} as ${field.type}), 113 | ''', 114 | if (includeFieldValues) 115 | ''' 116 | if (${field.name}FieldValue != null) 117 | ${field.field}: ${field.name}FieldValue , 118 | ''', 119 | ], 120 | ].join('\n'); 121 | } 122 | 123 | String _asserts(CollectionData dat) { 124 | return [ 125 | for (final field in data.updatableFields) 126 | ''' 127 | assert( 128 | ${field.name} == _sentinel || ${field.name}FieldValue == null, 129 | "Cannot specify both ${field.name} and ${field.name}FieldValue", 130 | );''', 131 | ].join('\n'); 132 | } 133 | 134 | String _setPrototype(CollectionData data) { 135 | if (data.updatableFields.isEmpty) return ''; 136 | 137 | final type = data.type.getDisplayString(); 138 | final parameters = _parameters(data, includeFields: false); 139 | 140 | const fieldValueDoc = ''' 141 | /// Any [FieldValue]s provided will replace the corresponding fields in the 142 | /// [model] during serialization.'''; 143 | 144 | return ''' 145 | /// Sets data on the document, overwriting any existing data. If the document 146 | /// does not yet exist, it will be created. 147 | /// 148 | /// If [SetOptions] are provided, the data can be merged into an existing 149 | /// document instead of overwriting. 150 | /// 151 | $fieldValueDoc 152 | Future set( 153 | $type model, { 154 | SetOptions? options, 155 | $parameters 156 | }); 157 | 158 | /// Writes to the document using the transaction API. 159 | /// 160 | /// If the document does not exist yet, it will be created. If you pass 161 | /// [SetOptions], the provided data can be merged into the existing document. 162 | /// 163 | $fieldValueDoc 164 | void transactionSet( 165 | Transaction transaction, 166 | $type model, { 167 | SetOptions? options, 168 | $parameters 169 | }); 170 | 171 | /// Writes to the document using the batch API. 172 | /// 173 | /// If the document does not exist yet, it will be created. If you pass 174 | /// [SetOptions], the provided data can be merged into the existing document. 175 | /// 176 | $fieldValueDoc 177 | void batchSet( 178 | WriteBatch batch, 179 | $type model, { 180 | SetOptions? options, 181 | $parameters 182 | }); 183 | '''; 184 | } 185 | 186 | String _set(CollectionData data) { 187 | if (data.updatableFields.isEmpty) return ''; 188 | 189 | final type = data.type.getDisplayString(); 190 | final parameters = _parameters( 191 | data, 192 | includeFields: false, 193 | fieldValuesNullable: true, 194 | ); 195 | final fieldValuesJson = _json(data, includeFields: false); 196 | final json = ''' 197 | { 198 | ...${data.toJson('model')}, 199 | $fieldValuesJson 200 | }'''; 201 | 202 | return ''' 203 | Future set( 204 | $type model, { 205 | SetOptions? options, 206 | $parameters 207 | }) async { 208 | final json = $json; 209 | 210 | final castedReference = reference.withConverter>( 211 | fromFirestore: (snapshot, options) => throw UnimplementedError(), 212 | toFirestore: (value, options) => value, 213 | ); 214 | return castedReference.set(json, options); 215 | } 216 | 217 | void transactionSet( 218 | Transaction transaction, 219 | $type model, { 220 | SetOptions? options, 221 | $parameters 222 | }) { 223 | final json = $json; 224 | 225 | transaction.set(reference, json, options); 226 | } 227 | 228 | void batchSet( 229 | WriteBatch batch, 230 | $type model, { 231 | SetOptions? options, 232 | $parameters 233 | }) { 234 | final json = $json; 235 | 236 | batch.set(reference, json, options); 237 | } 238 | '''; 239 | } 240 | 241 | String _updatePrototype(CollectionData data) { 242 | if (data.updatableFields.isEmpty) return ''; 243 | 244 | final parameters = _parameters(data); 245 | 246 | return ''' 247 | /// Updates data on the document. Data will be merged with any existing 248 | /// document data. 249 | /// 250 | /// If no document exists yet, the update will fail. 251 | Future update({$parameters}); 252 | 253 | /// Updates fields in the current document using the transaction API. 254 | /// 255 | /// The update will fail if applied to a document that does not exist. 256 | void transactionUpdate(Transaction transaction, {$parameters}); 257 | 258 | /// Updates fields in the current document using the batch API. 259 | /// 260 | /// The update will fail if applied to a document that does not exist. 261 | void batchUpdate(WriteBatch batch, {$parameters}); 262 | '''; 263 | } 264 | 265 | String _update(CollectionData data) { 266 | if (data.updatableFields.isEmpty) return ''; 267 | 268 | final parameters = _parameters( 269 | data, 270 | useSentinel: true, 271 | fieldValuesNullable: true, 272 | ); 273 | final json = _json(data); 274 | final asserts = _asserts(data); 275 | 276 | return ''' 277 | Future update({$parameters}) async { 278 | $asserts 279 | final json = {$json}; 280 | 281 | return reference.update(json); 282 | } 283 | 284 | void transactionUpdate(Transaction transaction, {$parameters}) { 285 | $asserts 286 | final json = {$json}; 287 | 288 | transaction.update(reference, json); 289 | } 290 | 291 | void batchUpdate(WriteBatch batch, {$parameters}) { 292 | $asserts 293 | final json = {$json}; 294 | 295 | batch.update(reference, json); 296 | } 297 | '''; 298 | } 299 | 300 | String _parent(CollectionData data) { 301 | final doc = 302 | '/// A reference to the [${data.collectionReferenceInterfaceName}] containing this document.'; 303 | if (data.parent == null) { 304 | return ''' 305 | $doc 306 | ${data.collectionReferenceInterfaceName} get parent { 307 | return ${data.collectionReferenceImplName}(reference.firestore); 308 | } 309 | '''; 310 | } 311 | 312 | final parent = data.parent!; 313 | return ''' 314 | $doc 315 | ${data.collectionReferenceInterfaceName} get parent { 316 | return ${data.collectionReferenceImplName}( 317 | reference.parent.parent!.withConverter<${parent.type}>( 318 | fromFirestore: ${parent.collectionReferenceInterfaceName}.fromFirestore, 319 | toFirestore: ${parent.collectionReferenceInterfaceName}.toFirestore, 320 | ), 321 | ); 322 | } 323 | '''; 324 | } 325 | 326 | String _subCollections(CollectionData data) { 327 | final buffer = StringBuffer(); 328 | 329 | for (final child in data.children) { 330 | buffer.writeln( 331 | ''' 332 | late final ${child.collectionReferenceInterfaceName} ${child.collectionName} = ${child.collectionReferenceImplName}( 333 | reference, 334 | ); 335 | ''', 336 | ); 337 | } 338 | 339 | return buffer.toString(); 340 | } 341 | 342 | String _equalAndHashCode(CollectionData data) { 343 | final propertyNames = [ 344 | 'runtimeType', 345 | 'parent', 346 | 'id', 347 | ]; 348 | 349 | return ''' 350 | @override 351 | bool operator ==(Object other) { 352 | return other is ${data.documentReferenceName} 353 | && ${propertyNames.map((p) => 'other.$p == $p').join(' && ')}; 354 | } 355 | 356 | @override 357 | int get hashCode => Object.hash(${propertyNames.join(',')}); 358 | '''; 359 | } 360 | } 361 | -------------------------------------------------------------------------------- /packages/cloud_firestore_odm_generator/lib/src/templates/document_snapshot.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2022, the Chromium project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:analyzer/dart/element/type.dart'; 6 | 7 | class DocumentSnapshotTemplate { 8 | DocumentSnapshotTemplate({ 9 | required this.documentSnapshotName, 10 | required this.documentReferenceName, 11 | required this.type, 12 | }); 13 | 14 | final String documentSnapshotName; 15 | final String documentReferenceName; 16 | final DartType type; 17 | 18 | @override 19 | String toString() { 20 | return ''' 21 | class $documentSnapshotName extends FirestoreDocumentSnapshot<$type> { 22 | $documentSnapshotName._(this.snapshot): data = snapshot.data(); 23 | 24 | @override 25 | final DocumentSnapshot<$type> snapshot; 26 | 27 | @override 28 | $documentReferenceName get reference { 29 | return $documentReferenceName( 30 | snapshot.reference, 31 | ); 32 | } 33 | 34 | @override 35 | final $type? data; 36 | } 37 | '''; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /packages/cloud_firestore_odm_generator/lib/src/templates/named_query.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2022, the Chromium project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import '../collection_generator.dart'; 6 | import '../named_query_data.dart'; 7 | 8 | class NamedQueryTemplate { 9 | NamedQueryTemplate(this.data, this.globalData); 10 | 11 | final NamedQueryData data; 12 | final GlobalData globalData; 13 | 14 | @override 15 | String toString() { 16 | return ''' 17 | /// Adds [${data.namedQueryGetName}] to [FirebaseFirestore]. 18 | extension ${data.namedQueryExtensionName} on FirebaseFirestore { 19 | /// Performs [FirebaseFirestore.namedQueryGet] and decode the result into 20 | /// a [${data.type}] snashot. 21 | Future<${data.querySnapshotName}> ${data.namedQueryGetName}({ 22 | GetOptions options = const GetOptions(), 23 | }) async { 24 | final snapshot = await namedQueryWithConverterGet( 25 | r'${data.queryName}', 26 | fromFirestore: ${data.collectionReferenceInterfaceName}.fromFirestore, 27 | toFirestore: ${data.collectionReferenceInterfaceName}.toFirestore, 28 | options: options, 29 | ); 30 | return ${data.querySnapshotName}._fromQuerySnapshot(snapshot); 31 | } 32 | } 33 | '''; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/cloud_firestore_odm_generator/lib/src/templates/query_document_snapshot.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2022, the Chromium project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:analyzer/dart/element/type.dart'; 6 | 7 | class QueryDocumentSnapshotTemplate { 8 | QueryDocumentSnapshotTemplate({ 9 | required this.queryDocumentSnapshotName, 10 | required this.documentSnapshotName, 11 | required this.documentReferenceName, 12 | required this.type, 13 | }); 14 | 15 | final String queryDocumentSnapshotName; 16 | final String documentSnapshotName; 17 | final String documentReferenceName; 18 | final DartType type; 19 | 20 | @override 21 | String toString() { 22 | return ''' 23 | class $queryDocumentSnapshotName extends FirestoreQueryDocumentSnapshot<$type> implements $documentSnapshotName { 24 | $queryDocumentSnapshotName._(this.snapshot): data = snapshot.data(); 25 | 26 | @override 27 | final QueryDocumentSnapshot<$type> snapshot; 28 | 29 | @override 30 | final $type data; 31 | 32 | @override 33 | $documentReferenceName get reference { 34 | return $documentReferenceName(snapshot.reference); 35 | } 36 | } 37 | '''; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /packages/cloud_firestore_odm_generator/lib/src/templates/query_snapshot.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2022, the Chromium project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:analyzer/dart/element/type.dart'; 6 | 7 | class QuerySnapshotTemplate { 8 | QuerySnapshotTemplate({ 9 | required this.queryDocumentSnapshotName, 10 | required this.querySnapshotName, 11 | required this.documentSnapshotName, 12 | required this.type, 13 | }); 14 | 15 | final String queryDocumentSnapshotName; 16 | final String querySnapshotName; 17 | final String documentSnapshotName; 18 | final DartType type; 19 | 20 | @override 21 | String toString() { 22 | return ''' 23 | class $querySnapshotName extends FirestoreQuerySnapshot<$type, $queryDocumentSnapshotName> { 24 | $querySnapshotName._( 25 | this.snapshot, 26 | this.docs, 27 | this.docChanges, 28 | ); 29 | 30 | factory $querySnapshotName._fromQuerySnapshot( 31 | QuerySnapshot<$type> snapshot, 32 | ) { 33 | final docs = snapshot 34 | .docs 35 | .map($queryDocumentSnapshotName._) 36 | .toList(); 37 | 38 | final docChanges = snapshot.docChanges.map((change) { 39 | return _decodeDocumentChange( 40 | change, 41 | $documentSnapshotName._, 42 | ); 43 | }).toList(); 44 | 45 | return $querySnapshotName._( 46 | snapshot, 47 | docs, 48 | docChanges, 49 | ); 50 | } 51 | 52 | static FirestoreDocumentChange<$documentSnapshotName> _decodeDocumentChange( 53 | DocumentChange docChange, 54 | $documentSnapshotName Function(DocumentSnapshot doc) decodeDoc, 55 | ) { 56 | return FirestoreDocumentChange<$documentSnapshotName>( 57 | type: docChange.type, 58 | oldIndex: docChange.oldIndex, 59 | newIndex: docChange.newIndex, 60 | doc: decodeDoc(docChange.doc), 61 | ); 62 | } 63 | 64 | final QuerySnapshot<$type> snapshot; 65 | 66 | @override 67 | final List<$queryDocumentSnapshotName> docs; 68 | 69 | @override 70 | final List> docChanges; 71 | } 72 | '''; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /packages/cloud_firestore_odm_generator/lib/src/validator_generator.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2022, the Chromium project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | 7 | import 'package:analyzer/dart/element/element.dart'; 8 | import 'package:build/build.dart'; 9 | import 'package:source_gen/source_gen.dart'; 10 | 11 | class ValidatorGenerator extends Generator { 12 | @override 13 | FutureOr generate(LibraryReader library, BuildStep buildStep) { 14 | final buffer = StringBuffer(); 15 | 16 | for (final classElement in library.classes) { 17 | final validations = classElement.fields.expand((field) sync* { 18 | final validators = field.metadata.where(isValidatorAnnotation); 19 | 20 | for (final validator in validators) { 21 | yield "${validator.toSource().replaceFirst('@', 'const ')}.validate(instance.${field.name}, '${field.name}');"; 22 | } 23 | }).toList(); 24 | 25 | if (validations.isNotEmpty) { 26 | buffer 27 | ..write( 28 | 'void _\$assert${classElement.name}(${classElement.name} instance) {', 29 | ) 30 | ..writeAll(validations) 31 | ..write('}'); 32 | } 33 | } 34 | 35 | return buffer.toString(); 36 | } 37 | } 38 | 39 | bool isValidatorAnnotation(ElementAnnotation annotation) { 40 | final element = annotation.element; 41 | if (element == null || element is! ConstructorElement) return false; 42 | 43 | return element.enclosingElement.allSupertypes.any((superType) { 44 | return superType.element.name == 'Validator' && 45 | superType.element.librarySource.uri.toString() == 46 | 'package:cloud_firestore_odm/src/validator.dart'; 47 | }); 48 | } 49 | -------------------------------------------------------------------------------- /packages/cloud_firestore_odm_generator/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: cloud_firestore_odm_generator 2 | description: A code generator for cloud_firestore_odm. 3 | homepage: https://firebase.flutter.dev/docs/firestore/odm 4 | repository: https://github.com/firebaseextended/firestoreodm-flutter 5 | version: 1.0.0-dev.90 6 | 7 | environment: 8 | sdk: ">=2.18.0 <4.0.0" 9 | 10 | dependencies: 11 | analyzer: ">=5.12.0 <7.0.0" 12 | build: ^2.0.1 13 | build_config: ^1.0.0 14 | cloud_firestore_odm: ^1.0.0-dev.87 15 | collection: ^1.15.0 16 | freezed_annotation: ">=1.0.0 <3.0.0" 17 | # Can be removed once this is fixed https://github.com/dart-lang/graphs/issues/86 18 | graphs: ^2.0.0 19 | json_annotation: ^4.8.1 20 | meta: ^1.8.0 21 | recase: ^4.0.0 22 | source_gen: ^1.3.2 23 | source_helper: ^1.3.4 24 | 25 | dev_dependencies: 26 | build_runner: ^2.4.2 27 | expect_error: ^1.0.5 28 | json_serializable: ">=6.3.0 <7.0.0" 29 | matcher: ^0.12.10 30 | test: ^1.16.8 31 | -------------------------------------------------------------------------------- /packages/cloud_firestore_odm_generator/test/collection_reference_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2022, the Chromium project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:expect_error/expect_error.dart'; 6 | import 'package:test/test.dart'; 7 | 8 | Future main() async { 9 | final library = await Library.custom( 10 | packageName: 'cloud_firestore_odm_generator_integration_test', 11 | packageRoot: 'cloud_firestore_odm_generator_integration_test', 12 | path: 'lib/__test__.dart', 13 | ); 14 | 15 | group('root collections', () { 16 | test('have no parent', () { 17 | expect( 18 | library.withCode( 19 | ''' 20 | import 'simple.dart'; 21 | 22 | void main() { 23 | // expect-error: UNDEFINED_GETTER 24 | rootRef.parent; 25 | } 26 | ''', 27 | ), 28 | compiles, 29 | ); 30 | }); 31 | 32 | test('property type offset queries from value', () { 33 | expect( 34 | library.withCode( 35 | ''' 36 | import 'simple.dart'; 37 | 38 | void main() { 39 | // expect-error: ARGUMENT_TYPE_NOT_ASSIGNABLE 40 | rootRef.orderByNullable(startAt: true); 41 | // expect-error: ARGUMENT_TYPE_NOT_ASSIGNABLE 42 | rootRef.orderByNullable(startAfter: true); 43 | // expect-error: ARGUMENT_TYPE_NOT_ASSIGNABLE 44 | rootRef.orderByNullable(endAt: true); 45 | // expect-error: ARGUMENT_TYPE_NOT_ASSIGNABLE 46 | rootRef.orderByNullable(endBefore: true); 47 | 48 | // expect-error: ARGUMENT_TYPE_NOT_ASSIGNABLE 49 | rootRef.orderByNonNullable(startAt: null); 50 | // expect-error: ARGUMENT_TYPE_NOT_ASSIGNABLE 51 | rootRef.orderByNonNullable(startAfter: null); 52 | // expect-error: ARGUMENT_TYPE_NOT_ASSIGNABLE 53 | rootRef.orderByNonNullable(endAt: null); 54 | // expect-error: ARGUMENT_TYPE_NOT_ASSIGNABLE 55 | rootRef.orderByNonNullable(endBefore: null); 56 | } 57 | ''', 58 | ), 59 | compiles, 60 | ); 61 | }); 62 | }); 63 | } 64 | -------------------------------------------------------------------------------- /packages/cloud_firestore_odm_generator/test/document_reference_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2022, the Chromium project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:expect_error/expect_error.dart'; 6 | import 'package:test/test.dart'; 7 | 8 | Future main() async { 9 | final library = await Library.custom( 10 | packageName: 'cloud_firestore_odm_generator_integration_test', 11 | packageRoot: 'cloud_firestore_odm_generator_integration_test', 12 | path: 'lib/__test__.dart', 13 | ); 14 | 15 | group('where(arrayContains)', () { 16 | test('is typed', () { 17 | expect( 18 | library.withCode( 19 | ''' 20 | import 'simple.dart'; 21 | 22 | void main() { 23 | nestedRef.whereValue(); 24 | nestedRef.whereSimple( 25 | // expect-error: UNDEFINED_NAMED_PARAMETER 26 | arrayContains: null, 27 | ); 28 | nestedRef.whereValueList(); 29 | 30 | nestedRef.whereNumList(arrayContains: 42); 31 | nestedRef.whereNumList( 32 | // expect-error: ARGUMENT_TYPE_NOT_ASSIGNABLE 33 | arrayContains: 'string', 34 | ); 35 | } 36 | ''', 37 | ), 38 | compiles, 39 | ); 40 | }); 41 | }); 42 | 43 | group('update', () { 44 | test('rejects complex object list but allows primitive lists', () { 45 | expect( 46 | library.withCode( 47 | ''' 48 | import 'simple.dart'; 49 | 50 | void main() { 51 | nestedRef.doc('42').update( 52 | value: null, 53 | ); 54 | nestedRef.doc('42').update( 55 | valueList: null, 56 | ); 57 | nestedRef.doc('42').update( 58 | boolList: null, 59 | ); 60 | nestedRef.doc('42').update( 61 | stringList: null, 62 | ); 63 | nestedRef.doc('42').update( 64 | numList: null, 65 | ); 66 | nestedRef.doc('42').update( 67 | objectList: null, 68 | ); 69 | nestedRef.doc('42').update( 70 | dynamicList: null, 71 | ); 72 | } 73 | ''', 74 | ), 75 | compiles, 76 | ); 77 | }); 78 | 79 | test('types parameters', () { 80 | expect( 81 | library.withCode( 82 | ''' 83 | import 'simple.dart'; 84 | 85 | void main() { 86 | rootRef.doc('42').update( 87 | nullable: null, 88 | ); 89 | rootRef.doc('42').update( 90 | nullable: 42, 91 | ); 92 | rootRef.doc('42').update( 93 | // expect-error: ARGUMENT_TYPE_NOT_ASSIGNABLE 94 | nullable: 'string', 95 | ); 96 | 97 | rootRef.doc('42').update( 98 | // expect-error: ARGUMENT_TYPE_NOT_ASSIGNABLE 99 | nonNullable: null, 100 | ); 101 | rootRef.doc('42').update( 102 | nonNullable: '42', 103 | ); 104 | rootRef.doc('42').update( 105 | // expect-error: ARGUMENT_TYPE_NOT_ASSIGNABLE 106 | nonNullable: 42, 107 | ); 108 | } 109 | ''', 110 | ), 111 | compiles, 112 | ); 113 | }); 114 | }); 115 | 116 | group('set', () { 117 | test('types parameters', () { 118 | expect( 119 | library.withCode( 120 | ''' 121 | import 'simple.dart'; 122 | 123 | void main() { 124 | Root root = null as Root; 125 | 126 | rootRef.doc('42').set(root); 127 | 128 | rootRef.doc('42') 129 | // expect-error: ARGUMENT_TYPE_NOT_ASSIGNABLE 130 | .set(42); 131 | } 132 | ''', 133 | ), 134 | compiles, 135 | ); 136 | }); 137 | }); 138 | } 139 | -------------------------------------------------------------------------------- /packages/cloud_firestore_odm_generator/test/query_reference_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2022, the Chromium project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:expect_error/expect_error.dart'; 6 | import 'package:test/test.dart'; 7 | 8 | Future main() async { 9 | final library = await Library.custom( 10 | packageName: 'cloud_firestore_odm_generator_integration_test', 11 | packageRoot: 'cloud_firestore_odm_generator_integration_test', 12 | path: 'lib/__test__.dart', 13 | ); 14 | 15 | group('query', () { 16 | test('does not generate utilities for getters', () { 17 | expect( 18 | library.withCode( 19 | ''' 20 | import 'simple.dart'; 21 | 22 | void main() { 23 | ignoredGetterRef.whereValue(); 24 | // expect-error: UNDEFINED_METHOD 25 | ignoredGetterRef.whereCount(); 26 | // expect-error: UNDEFINED_METHOD 27 | ignoredGetterRef.whereCount2(); 28 | // expect-error: UNDEFINED_METHOD 29 | ignoredGetterRef.whereCount3(); 30 | // expect-error: UNDEFINED_METHOD 31 | ignoredGetterRef.whereHashCode(); 32 | // expect-error: UNDEFINED_METHOD 33 | ignoredGetterRef.whereStaticGetter(); 34 | 35 | subClassRef.whereInstanceGetter(); 36 | // expect-error: UNDEFINED_METHOD 37 | subClassRef.whereStaticGetter(); 38 | } 39 | ''', 40 | ), 41 | compiles, 42 | ); 43 | }); 44 | 45 | test('property type offset queries from value', () { 46 | expect( 47 | library.withCode( 48 | ''' 49 | import 'simple.dart'; 50 | 51 | void main() { 52 | // expect-error: ARGUMENT_TYPE_NOT_ASSIGNABLE 53 | rootRef.limit(0).orderByNullable(startAt: true); 54 | // expect-error: ARGUMENT_TYPE_NOT_ASSIGNABLE 55 | rootRef.limit(0).orderByNullable(startAfter: true); 56 | // expect-error: ARGUMENT_TYPE_NOT_ASSIGNABLE 57 | rootRef.limit(0).orderByNullable(endAt: true); 58 | // expect-error: ARGUMENT_TYPE_NOT_ASSIGNABLE 59 | rootRef.limit(0).orderByNullable(endBefore: true); 60 | 61 | // expect-error: ARGUMENT_TYPE_NOT_ASSIGNABLE 62 | rootRef.limit(0).orderByNonNullable(startAt: null); 63 | // expect-error: ARGUMENT_TYPE_NOT_ASSIGNABLE 64 | rootRef.limit(0).orderByNonNullable(startAfter: null); 65 | // expect-error: ARGUMENT_TYPE_NOT_ASSIGNABLE 66 | rootRef.limit(0).orderByNonNullable(endAt: null); 67 | // expect-error: ARGUMENT_TYPE_NOT_ASSIGNABLE 68 | rootRef.limit(0).orderByNonNullable(endBefore: null); 69 | } 70 | ''', 71 | ), 72 | compiles, 73 | ); 74 | }); 75 | 76 | test('supports Freezed', () { 77 | expect( 78 | library.withCode( 79 | ''' 80 | import 'freezed.dart'; 81 | 82 | void main() { 83 | personRef.whereFirstName(isEqualTo: 'foo'); 84 | personRef.orderByFirstName(); 85 | 86 | personRef.doc('42').update(firstName: 'foo'); 87 | personRef.doc('42') 88 | // expect-error: UNDEFINED_NAMED_PARAMETER 89 | .update(ignored: 42); 90 | 91 | // expect-error: UNDEFINED_METHOD 92 | personRef.orderByIgnored(); 93 | // expect-error: UNDEFINED_METHOD 94 | personRef.whereIgnored(); 95 | } 96 | ''', 97 | ), 98 | compiles, 99 | ); 100 | }); 101 | }); 102 | } 103 | --------------------------------------------------------------------------------