├── .gitignore ├── .metadata ├── CHANGELOG.md ├── LICENSE ├── README.md ├── example ├── .gitignore ├── .metadata ├── README.md ├── android │ ├── app │ │ ├── build.gradle │ │ └── src │ │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ │ ├── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── kotlin │ │ │ │ └── com │ │ │ │ │ └── wisecrab │ │ │ │ │ └── form_validators_example │ │ │ │ │ └── MainActivity.kt │ │ │ └── res │ │ │ │ ├── drawable │ │ │ │ └── launch_background.xml │ │ │ │ ├── mipmap-hdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-mdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ └── values │ │ │ │ └── styles.xml │ │ │ └── profile │ │ │ └── AndroidManifest.xml │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ └── settings.gradle ├── ios │ ├── Flutter │ │ ├── AppFrameworkInfo.plist │ │ ├── Debug.xcconfig │ │ ├── Release.xcconfig │ │ └── flutter_export_environment.sh │ ├── Podfile │ ├── Runner.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ └── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ ├── Runner.xcworkspace │ │ └── contents.xcworkspacedata │ └── Runner │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ └── Icon-App-83.5x83.5@2x.png │ │ └── LaunchImage.imageset │ │ │ ├── Contents.json │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ └── README.md │ │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ │ ├── Info.plist │ │ └── Runner-Bridging-Header.h ├── lib │ ├── compose_validation_container.dart │ ├── email_validation_container.dart │ ├── main.dart │ ├── max_length_validation_container.dart │ ├── max_validation_container.dart │ ├── min_length_validation_container.dart │ ├── min_validation_container.dart │ ├── pattern_validation_container.dart │ └── required_validation_container.dart ├── pubspec.lock ├── pubspec.yaml └── test │ └── widget_test.dart ├── lib └── wc_form_validators.dart ├── pubspec.lock ├── pubspec.yaml └── test └── form_validators_test.dart /.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 | .dart_tool/ 26 | .flutter-plugins 27 | .packages 28 | .pub-cache/ 29 | .pub/ 30 | build/ 31 | 32 | # Android related 33 | **/android/**/gradle-wrapper.jar 34 | **/android/.gradle 35 | **/android/captures/ 36 | **/android/gradlew 37 | **/android/gradlew.bat 38 | **/android/local.properties 39 | **/android/**/GeneratedPluginRegistrant.java 40 | 41 | # iOS/XCode related 42 | **/ios/**/*.mode1v3 43 | **/ios/**/*.mode2v3 44 | **/ios/**/*.moved-aside 45 | **/ios/**/*.pbxuser 46 | **/ios/**/*.perspectivev3 47 | **/ios/**/*sync/ 48 | **/ios/**/.sconsign.dblite 49 | **/ios/**/.tags* 50 | **/ios/**/.vagrant/ 51 | **/ios/**/DerivedData/ 52 | **/ios/**/Icon? 53 | **/ios/**/Pods/ 54 | **/ios/**/.symlinks/ 55 | **/ios/**/profile 56 | **/ios/**/xcuserdata 57 | **/ios/.generated/ 58 | **/ios/Flutter/App.framework 59 | **/ios/Flutter/Flutter.framework 60 | **/ios/Flutter/Generated.xcconfig 61 | **/ios/Flutter/app.flx 62 | **/ios/Flutter/app.zip 63 | **/ios/Flutter/flutter_assets/ 64 | **/ios/ServiceDefinitions.json 65 | **/ios/Runner/GeneratedPluginRegistrant.* 66 | 67 | # Exceptions to above rules. 68 | !**/ios/**/default.mode1v3 69 | !**/ios/**/default.mode2v3 70 | !**/ios/**/default.pbxuser 71 | !**/ios/**/default.perspectivev3 72 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 73 | -------------------------------------------------------------------------------- /.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: 20e59316b8b8474554b38493b8ca888794b0234a 8 | channel: stable 9 | 10 | project_type: package 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [1.1.0] 2 | 3 | * mustMatch validator added 4 | 5 | ## [1.0.1] 6 | 7 | * SDK updated to >=2.12.0 <4.0.0 8 | * License updated 9 | 10 | ## [1.0.0] 11 | 12 | * Null-safety support added 13 | 14 | ## [0.1.1] 15 | 16 | * Documentation updated 17 | * License added 18 | 19 | ## [0.1.0] 20 | 21 | * Initial release 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2020 Invertase Limited 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Form Validators 2 | A Flutter plugin which provides a set of validators that can be used by form fields. 3 | 4 | ![Build Status](https://img.shields.io/badge/build-passing-green) 5 | ![Unit Test](https://img.shields.io/badge/unit%20tests-passing-green) 6 | [![Author](https://img.shields.io/badge/author-wisecrab-green)](https://wisecrab.com) 7 | 8 | ## About 9 | This flutter plugin provides utility functions to make form validation easy. This plugin was inspired by Angular Validators class. 10 | 11 | ## Version 12 | This plugin supports dart version 2.2+ 13 | 14 | ## How to use Form Validators 15 | 16 | ### For Example: 17 | #### Validating if TextFormField is non-empty and has a valid email address 18 | ```dart 19 | TextFormField( 20 | decoration: InputDecoration( 21 | labelText: 'Email', 22 | ), 23 | validator: Validators.compose([ 24 | Validators.required('Email is required'), 25 | Validators.email('Invalid email address'), 26 | ]), 27 | ), 28 | ``` 29 | 30 | ## Api Overview 31 | ### Utility Functions 32 | - [Required](#required) 33 | - [Minimum](#minimum) 34 | - [Maximum](#maximum) 35 | - [Email](#email) 36 | - [Minimum Length](#minimum-length) 37 | - [Maximum Length](#maximum-length) 38 | - [Pattern](#pattern) 39 | - [Must Match](#must-match) 40 | - [Compose](#compose) 41 | 42 | All validator functions have return type of `FormFieldValidator` which is a required type for `validator` field in `TextFormField`. 43 | 44 | ___ 45 | 46 | ### Required 47 | `Validators.required(String errorMessage)` is validator that requires the field have a non-empty value. 48 | 49 | #### Example 50 | This code will validate the name and show error if it is empty. 51 | ```dart 52 | TextFormField( 53 | decoration: InputDecoration( 54 | labelText: 'Name', 55 | ), 56 | validator: Validators.required('Name is required'), 57 | ), 58 | ``` 59 | #### Parameters 60 | 61 | | Params | Description | 62 | | ------------ | -------------------------------------------------------------------------------------------- | 63 | | errorMessage | `String` value is passed to this parameter to show an error in case of a validation failure. | 64 | 65 | ___ 66 | 67 | ### Minimum 68 | `Validators.min(double min, String errorMessage)` is a validator that requires the field's value be greater than or equal to the provided number (`double min`). TextFormField's value must be an integer or double otherwise this validator will throw a parse exception. 69 | 70 | #### Example 71 | This code will validate a TextFormField's value and show an error in case its value is non-empty and less than 5. 72 | ```dart 73 | TextFormField( 74 | keyboardType: TextInputType.numberWithOptions( 75 | decimal: true, 76 | signed: true, 77 | ), 78 | decoration: InputDecoration( 79 | labelText: 'Minimum 5', 80 | ), 81 | validator: Validators.min(5, 'Value less than 5 not allowed'), 82 | ), 83 | 84 | ``` 85 | 86 | #### Parameters 87 | `Validators.min` takes two parameters. 88 | 89 | | Params | Description | 90 | | ------------ | ------------------------------------------------------------------------------------------------------------------------------------- | 91 | | min | `double` value is passed to this param. Validator will return an error if TextFormField is non-empty and its value is less than `min` | 92 | | errorMessage | `String` value is passed to this parameter to show an error in case of a validation failure | 93 | 94 | --- 95 | 96 | ### Maximum 97 | `Validators.max(double max, String errorMessage)` is a validator that requires the field's value to be less than or equal to the provided number (`double max`). TextFormField's value must be an integer or double otherwise this validator will throw a parse exception. 98 | 99 | #### Example 100 | This code will validate TextFormField's value and show an error in case its value is non-empty and greater than 5. 101 | ```dart 102 | TextFormField( 103 | keyboardType: TextInputType.numberWithOptions( 104 | decimal: true, 105 | signed: true, 106 | ), 107 | decoration: InputDecoration( 108 | labelText: 'Maximum 5', 109 | ), 110 | validator: Validators.max(5, 'Value greater than 5 not allowed'), 111 | ), 112 | 113 | ``` 114 | 115 | #### Parameters 116 | `Validators.max` takes two parameters. 117 | 118 | | Params | Description | 119 | | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------- | 120 | | max | `double` value is passed to this param. Validator will return an error if TextFormField is non-empty and its value is greater than `max` | 121 | | errorMessage | `String` value is passed to this parameter to show an error in case of validation failure | 122 | 123 | --- 124 | 125 | ### Email 126 | `Validators.email(String errorMessage)` is a validator that requires the field's value pass an email validation test. 127 | 128 | Its uses regex of HTML5 for email validation. 129 | Its regex is ```^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,253}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,253}[a-zA-Z0-9])?)*$``` 130 | 131 | If you want to use custom regex for email validation, please take a look at [Pattern](#pattern). 132 | 133 | #### Example 134 | This code will validate the email and show an error if TextFormField's value is non-empty and the email address is invalid. 135 | ```dart 136 | TextFormField( 137 | decoration: InputDecoration( 138 | labelText: 'Email', 139 | ), 140 | validator: Validators.email('Invalid email address'), 141 | ), 142 | ``` 143 | 144 | #### Parameters 145 | 146 | | Params | Description | 147 | | ------------ | ------------------------------------------------------------------------------------------ | 148 | | errorMessage | `String` value is passed to this parameter to show an error in case of validation failure. | 149 | 150 | 151 | ### Minimum Length 152 | `Validators.minLength(int minLength, String errorMessage)` is a validator that requires the length of the field's value to be greater than or equal to the provided minimum length. 153 | 154 | #### Example 155 | This code will validate the TextFormField's value and show an error in case field's value is non-empty and its character length is less than 5. 156 | ```dart 157 | TextFormField( 158 | decoration: InputDecoration( 159 | labelText: 'Minimum length 5', 160 | ), 161 | validator: Validators.minLength(5, 'Characters are less than 5'), 162 | ), 163 | ``` 164 | 165 | #### Parameters 166 | 167 | | Params | Description | 168 | | ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------- | 169 | | minLength | `int` value is passed to this param. Validator will return an error if TextFormField is non-empty and its number of characters is less than `minLength`. | 170 | | errorMessage | `String` value is passed to this parameter to show error in case of validation failure. | 171 | 172 | 173 | ### Maximum Length 174 | `Validators.maxLength(int maxLength, String errorMessage)` is a validator that requires the length of the field's value to be less than or equal to the provided maximum length. 175 | 176 | #### Example 177 | This code will validate TextFormField's value and show an error in case the field's value is non-empty and its character length is greater than 5. 178 | ```dart 179 | TextFormField( 180 | decoration: InputDecoration( 181 | labelText: 'Maximum length 5', 182 | ), 183 | validator: Validators.maxLength(5, 'Characters are greater than 5'), 184 | ), 185 | ``` 186 | 187 | #### Parameters 188 | 189 | | Params | Description | 190 | | ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 191 | | maxLength | `int` value is passed to this param. The validator will return an error if the TextFormField is non-empty and its number of characters are greater than `maxLength`. | 192 | | errorMessage | `String` value is passed to this parameter to show error in case of validation failure. | 193 | 194 | --- 195 | 196 | ### Pattern 197 | `Validators.patternString(String pattern, String errorMessage)` and `Validators.patternRegExp(RegExp pattern, String errorMessage)` are validators that require the field's value to match a regex pattern. 198 | 199 | In case of `Validators.patternString`, you need to pass regex like this `r"^[A-Za-z]+$"` 200 | 201 | #### Examples 202 | This code will validate the TextFormField's value and show an error in case the field's value is non-empty and it contains characters other than letters. 203 | ###### patternRegExp 204 | ```dart 205 | TextFormField( 206 | decoration: InputDecoration( 207 | labelText: 'Pattern r"^[A-Za-z]+\$"', 208 | ), 209 | validator: Validators.patternRegExp( 210 | RegExp(r"^[A-Za-z]+$"), 'Only alphabets are allowed'), 211 | ), 212 | 213 | ``` 214 | 215 | ###### patternString 216 | ```dart 217 | TextFormField( 218 | decoration: InputDecoration( 219 | labelText: 'Pattern r"^[A-Za-z]+\$"', 220 | ), 221 | validator: Validators.patternString( 222 | r"^[A-Za-z]+$", 'Only alphabets are allowed'), 223 | ), 224 | ``` 225 | 226 | #### Parameters 227 | 228 | | Params | Description | 229 | | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 230 | | pattern | `String` or `RegExp` the value is passed to this param. The validator will return an error if the TextFormField is non-empty and it's not matching the `pattern` that you have provided. | 231 | | errorMessage | `String` value is passed to this parameter to show error in case of a validation failure. | 232 | 233 | --- 234 | 235 | ### Must Match 236 | `Validators.mustMatch(String expectedValue, String errorMessage)` is a validator that requires the field's value to match another value. This is particularly useful for confirmation fields like password confirmation, email confirmation, etc. 237 | 238 | #### Example 239 | This code will validate that the confirmation field matches the original value. 240 | ```dart 241 | TextFormField( 242 | decoration: InputDecoration( 243 | labelText: 'Confirm Value', 244 | ), 245 | validator: Validators.compose([ 246 | Validators.required('Confirmation is required'), 247 | Validators.mustMatch('originalValue', 'Values do not match'), 248 | ]), 249 | ), 250 | ``` 251 | 252 | #### Parameters 253 | 254 | | Params | Description | 255 | | -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | 256 | | expectedValue | `String` value is passed to this parameter. The validator will return an error if TextFormField is non-empty and its value doesn't match `expectedValue`. | 257 | | errorMessage | `String` value is passed to this parameter to show error in case of a validation failure. | 258 | 259 | --- 260 | 261 | ### Compose 262 | `Validators.compose(List> validators)` composes multiple validators into a single validator. 263 | 264 | #### How it works 265 | This function takes multiple validators as a list and will validate each validator in the provided sequence. If any of its validator returns an error message, it won't check the remaining validators and will return this error to the `TextFormField`. 266 | 267 | #### Example 268 | This code will validate that _Name_ is non-empty and has a character length between 5 and 10. 269 | ```dart 270 | TextFormField( 271 | decoration: InputDecoration( 272 | labelText: 'Name', 273 | ), 274 | validator: Validators.compose([ 275 | Validators.required('Name is required'), 276 | Validators.minLength(5, 'Name cannot be less than 5 characters'), 277 | Validators.maxLength(10, 'Name cannot be greater than 10 characters'), 278 | ]), 279 | ), 280 | ``` 281 | 282 | #### Parameters 283 | 284 | | Params | Description | 285 | | ---------- | ---------------------------------------------------------------------------------------------------------------------------------- | 286 | | validators | `List>` value is passed to this param containing validators that you want to combine into one validator | 287 | 288 | 289 | --- 290 | 291 | #### **_Note:_** 292 | 293 | >If `TextFormField`'s value is empty, then validators [Minimum](#minimum), [Maximum](#maximum), [Email](#email), [Minimum Length](#minimum-length), [Maximum Length](#maximum-length), [Must Match](#must-match) and [Pattern](#pattern) won't return any error because it considers `TextFormField` as optional. Use these validators in combination with the [Required](#required) validator if the specified `TextFormField` is compulsory and you want a validation failure if the field is empty. Check [Compose](#compose) validator to find out how to merge multiple validators. 294 | 295 | ___ 296 | 297 | 298 | ## Contributors 299 | 300 | ![Contributors](https://img.shields.io/badge/contributors-2-blue?style=flat-square) 301 | 302 | - [Ali Abbas](https://github.com/aarajput) 303 | - [Ali Hamza](https://github.com/alihamza0173) 304 | 305 | --- -------------------------------------------------------------------------------- /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 | .dart_tool/ 26 | .flutter-plugins 27 | .packages 28 | .pub-cache/ 29 | .pub/ 30 | /build/ 31 | 32 | # Android related 33 | **/android/**/gradle-wrapper.jar 34 | **/android/.gradle 35 | **/android/captures/ 36 | **/android/gradlew 37 | **/android/gradlew.bat 38 | **/android/local.properties 39 | **/android/**/GeneratedPluginRegistrant.java 40 | 41 | # iOS/XCode related 42 | **/ios/**/*.mode1v3 43 | **/ios/**/*.mode2v3 44 | **/ios/**/*.moved-aside 45 | **/ios/**/*.pbxuser 46 | **/ios/**/*.perspectivev3 47 | **/ios/**/*sync/ 48 | **/ios/**/.sconsign.dblite 49 | **/ios/**/.tags* 50 | **/ios/**/.vagrant/ 51 | **/ios/**/DerivedData/ 52 | **/ios/**/Icon? 53 | **/ios/**/Pods/ 54 | **/ios/**/.symlinks/ 55 | **/ios/**/profile 56 | **/ios/**/xcuserdata 57 | **/ios/.generated/ 58 | **/ios/Flutter/App.framework 59 | **/ios/Flutter/Flutter.framework 60 | **/ios/Flutter/Generated.xcconfig 61 | **/ios/Flutter/app.flx 62 | **/ios/Flutter/app.zip 63 | **/ios/Flutter/flutter_assets/ 64 | **/ios/ServiceDefinitions.json 65 | **/ios/Runner/GeneratedPluginRegistrant.* 66 | 67 | # Exceptions to above rules. 68 | !**/ios/**/default.mode1v3 69 | !**/ios/**/default.mode2v3 70 | !**/ios/**/default.pbxuser 71 | !**/ios/**/default.perspectivev3 72 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 73 | -------------------------------------------------------------------------------- /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 and should not be manually edited. 5 | 6 | version: 7 | revision: 20e59316b8b8474554b38493b8ca888794b0234a 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | ## Examples 2 | ### Utility Functions 3 | - [Required](#required) 4 | - [Minimum](#minimum) 5 | - [Maximum](#maximum) 6 | - [Email](#email) 7 | - [Minimum Length](#minimum-length) 8 | - [Maximum Length](#maximum-length) 9 | - [Pattern](#pattern) 10 | - [Compose](#compose) 11 | 12 | 13 | 14 | ### Required 15 | ###### lib/required_validation_container.dart 16 | ```dart 17 | Form( 18 | key: formKey, 19 | child: Column( 20 | children: [ 21 | TextFormField( 22 | decoration: InputDecoration( 23 | labelText: 'Required', 24 | ), 25 | validator: Validators.required('Field is required'), 26 | ), 27 | RaisedButton( 28 | child: Text('Validate Required Field'), 29 | onPressed: () { 30 | formKey.currentState.validate(); 31 | }, 32 | ) 33 | ], 34 | ), 35 | ); 36 | ``` 37 | 38 | ### Minimum 39 | ###### lib/min_validation_container.dart 40 | ```dart 41 | Form( 42 | key: formKey, 43 | child: Column( 44 | children: [ 45 | TextFormField( 46 | keyboardType: TextInputType.numberWithOptions( 47 | decimal: true, 48 | signed: true, 49 | ), 50 | decoration: InputDecoration( 51 | labelText: 'Minimum 5', 52 | ), 53 | validator: Validators.min(5, 'Value less than 5 not allowed'), 54 | ), 55 | RaisedButton( 56 | child: Text('Validate Minimum 5 Field'), 57 | onPressed: () { 58 | formKey.currentState.validate(); 59 | }, 60 | ) 61 | ], 62 | ), 63 | ); 64 | ``` 65 | 66 | 67 | ### Maximum 68 | ###### lib/max_validation_container.dart 69 | ```dart 70 | Form( 71 | key: formKey, 72 | child: Column( 73 | children: [ 74 | TextFormField( 75 | keyboardType: TextInputType.numberWithOptions( 76 | decimal: true, 77 | signed: true, 78 | ), 79 | decoration: InputDecoration( 80 | labelText: 'Maximum 5', 81 | ), 82 | validator: Validators.max(5, 'Value greater than 5 not allowed'), 83 | ), 84 | RaisedButton( 85 | child: Text('Validate Maximum 5 Field'), 86 | onPressed: () { 87 | formKey.currentState.validate(); 88 | }, 89 | ) 90 | ], 91 | ), 92 | ); 93 | ``` 94 | 95 | ### Email 96 | ###### lib/email_validation_container.dart 97 | ```dart 98 | Form( 99 | key: formKey, 100 | child: Column( 101 | children: [ 102 | TextFormField( 103 | decoration: InputDecoration( 104 | labelText: 'Email', 105 | ), 106 | validator: Validators.email('Invalid email address'), 107 | ), 108 | RaisedButton( 109 | child: Text('Validate email Field'), 110 | onPressed: () { 111 | formKey.currentState.validate(); 112 | }, 113 | ) 114 | ], 115 | ), 116 | ); 117 | ``` 118 | 119 | ### Minimum Length 120 | ###### lib/min_length_validation_container.dart 121 | ```dart 122 | Form( 123 | key: formKey, 124 | child: Column( 125 | children: [ 126 | TextFormField( 127 | decoration: InputDecoration( 128 | labelText: 'Minimum length 5', 129 | ), 130 | validator: Validators.minLength(5, 'Characters are less than 5'), 131 | ), 132 | RaisedButton( 133 | child: Text('Validate Minimum length 5 Field'), 134 | onPressed: () { 135 | formKey.currentState.validate(); 136 | }, 137 | ) 138 | ], 139 | ), 140 | ); 141 | ``` 142 | 143 | ### Maximum Length 144 | ###### lib/max_length_validation_container.dart 145 | ```dart 146 | Form( 147 | key: formKey, 148 | child: Column( 149 | children: [ 150 | TextFormField( 151 | decoration: InputDecoration( 152 | labelText: 'Maximum length 5', 153 | ), 154 | validator: Validators.maxLength(5, 'Characters are greater than 5'), 155 | ), 156 | RaisedButton( 157 | child: Text('Validate Maximum length 5 Field'), 158 | onPressed: () { 159 | formKey.currentState.validate(); 160 | }, 161 | ) 162 | ], 163 | ), 164 | ); 165 | ``` 166 | 167 | ### Pattern 168 | ###### lib/pattern_validation_container.dart 169 | ```dart 170 | Form( 171 | key: formKey, 172 | child: Column( 173 | children: [ 174 | TextFormField( 175 | decoration: InputDecoration( 176 | labelText: 'Pattern r"^[A-Za-z]+\$"', 177 | ), 178 | validator: Validators.patternRegExp( 179 | RegExp(r"^[A-Za-z]+$"), 'Only alphabets are allowed'), 180 | ), 181 | RaisedButton( 182 | child: Text('Validate Pattern Field'), 183 | onPressed: () { 184 | formKey.currentState.validate(); 185 | }, 186 | ) 187 | ], 188 | ), 189 | ); 190 | ``` 191 | 192 | 193 | ### Compose 194 | ###### lib/compose_validation_container.dart 195 | ```dart 196 | Form( 197 | key: formKey, 198 | child: Column( 199 | children: [ 200 | TextFormField( 201 | decoration: InputDecoration( 202 | labelText: 'Compose', 203 | ), 204 | validator: Validators.compose([ 205 | Validators.required('Compose is required'), 206 | Validators.minLength(5, 'Characters cannot be less than 5'), 207 | Validators.maxLength(10, 'Characters cannot be greater than 10'), 208 | ]), 209 | ), 210 | RaisedButton( 211 | child: Text('Validate compose Field'), 212 | onPressed: () { 213 | formKey.currentState.validate(); 214 | }, 215 | ) 216 | ], 217 | ), 218 | ); 219 | ``` -------------------------------------------------------------------------------- /example/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply plugin: 'kotlin-android' 26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 27 | 28 | android { 29 | compileSdkVersion 28 30 | 31 | sourceSets { 32 | main.java.srcDirs += 'src/main/kotlin' 33 | } 34 | 35 | lintOptions { 36 | disable 'InvalidPackage' 37 | } 38 | 39 | defaultConfig { 40 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 41 | applicationId "com.wisecrab.form_validators_example" 42 | minSdkVersion 16 43 | targetSdkVersion 28 44 | versionCode flutterVersionCode.toInteger() 45 | versionName flutterVersionName 46 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 47 | } 48 | 49 | buildTypes { 50 | release { 51 | // TODO: Add your own signing config for the release build. 52 | // Signing with the debug keys for now, so `flutter run --release` works. 53 | signingConfig signingConfigs.debug 54 | } 55 | } 56 | } 57 | 58 | flutter { 59 | source '../..' 60 | } 61 | 62 | dependencies { 63 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 64 | testImplementation 'junit:junit:4.12' 65 | androidTestImplementation 'androidx.test:runner:1.1.1' 66 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' 67 | } 68 | -------------------------------------------------------------------------------- /example/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 9 | 13 | 20 | 24 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /example/android/app/src/main/kotlin/com/wisecrab/form_validators_example/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.wisecrab.form_validators_example 2 | 3 | import android.os.Bundle 4 | 5 | import io.flutter.app.FlutterActivity 6 | import io.flutter.plugins.GeneratedPluginRegistrant 7 | 8 | class MainActivity: FlutterActivity() { 9 | override fun onCreate(savedInstanceState: Bundle?) { 10 | super.onCreate(savedInstanceState) 11 | GeneratedPluginRegistrant.registerWith(this) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aarajput/flutter_form_validators/6f35ba80e06010aba6935961650d673209027003/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aarajput/flutter_form_validators/6f35ba80e06010aba6935961650d673209027003/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aarajput/flutter_form_validators/6f35ba80e06010aba6935961650d673209027003/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aarajput/flutter_form_validators/6f35ba80e06010aba6935961650d673209027003/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aarajput/flutter_form_validators/6f35ba80e06010aba6935961650d673209027003/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | -------------------------------------------------------------------------------- /example/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.2.71' 3 | repositories { 4 | google() 5 | jcenter() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:3.2.1' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | jcenter() 18 | } 19 | } 20 | 21 | rootProject.buildDir = '../build' 22 | subprojects { 23 | project.buildDir = "${rootProject.buildDir}/${project.name}" 24 | } 25 | subprojects { 26 | project.evaluationDependsOn(':app') 27 | } 28 | 29 | task clean(type: Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | 3 | android.useAndroidX=true 4 | android.enableJetifier=true 5 | -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip 7 | -------------------------------------------------------------------------------- /example/android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() 4 | 5 | def plugins = new Properties() 6 | def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') 7 | if (pluginsFile.exists()) { 8 | pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } 9 | } 10 | 11 | plugins.each { name, path -> 12 | def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() 13 | include ":$name" 14 | project(":$name").projectDir = pluginDirectory 15 | } 16 | -------------------------------------------------------------------------------- /example/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 8.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/ios/Flutter/flutter_export_environment.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # This is a generated file; do not edit or check into version control. 3 | export "FLUTTER_ROOT=/Users/ali/Tools/flutter" 4 | export "FLUTTER_APPLICATION_PATH=/Users/ali/Projects/Dart/open_source/form_validators/example" 5 | export "COCOAPODS_PARALLEL_CODE_SIGN=true" 6 | export "FLUTTER_TARGET=lib/main.dart" 7 | export "FLUTTER_BUILD_DIR=build" 8 | export "FLUTTER_BUILD_NAME=1.0.0" 9 | export "FLUTTER_BUILD_NUMBER=1" 10 | export "DART_OBFUSCATION=false" 11 | export "TRACK_WIDGET_CREATION=true" 12 | export "TREE_SHAKE_ICONS=false" 13 | export "PACKAGE_CONFIG=.dart_tool/package_config.json" 14 | -------------------------------------------------------------------------------- /example/ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '9.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def parse_KV_file(file, separator='=') 14 | file_abs_path = File.expand_path(file) 15 | if !File.exists? file_abs_path 16 | return []; 17 | end 18 | pods_ary = [] 19 | skip_line_start_symbols = ["#", "/"] 20 | File.foreach(file_abs_path) { |line| 21 | next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ } 22 | plugin = line.split(pattern=separator) 23 | if plugin.length == 2 24 | podname = plugin[0].strip() 25 | path = plugin[1].strip() 26 | podpath = File.expand_path("#{path}", file_abs_path) 27 | pods_ary.push({:name => podname, :path => podpath}); 28 | else 29 | puts "Invalid plugin specification: #{line}" 30 | end 31 | } 32 | return pods_ary 33 | end 34 | 35 | target 'Runner' do 36 | use_frameworks! 37 | 38 | # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock 39 | # referring to absolute paths on developers' machines. 40 | system('rm -rf .symlinks') 41 | system('mkdir -p .symlinks/plugins') 42 | 43 | # Flutter Pods 44 | generated_xcode_build_settings = parse_KV_file('./Flutter/Generated.xcconfig') 45 | if generated_xcode_build_settings.empty? 46 | puts "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter pub get is executed first." 47 | end 48 | generated_xcode_build_settings.map { |p| 49 | if p[:name] == 'FLUTTER_FRAMEWORK_DIR' 50 | symlink = File.join('.symlinks', 'flutter') 51 | File.symlink(File.dirname(p[:path]), symlink) 52 | pod 'Flutter', :path => File.join(symlink, File.basename(p[:path])) 53 | end 54 | } 55 | 56 | # Plugin Pods 57 | plugin_pods = parse_KV_file('../.flutter-plugins') 58 | plugin_pods.map { |p| 59 | symlink = File.join('.symlinks', 'plugins', p[:name]) 60 | File.symlink(p[:path], symlink) 61 | pod p[:name], :path => File.join(symlink, 'ios') 62 | } 63 | end 64 | 65 | # Prevent Cocoapods from embedding a second Flutter framework and causing an error with the new Xcode build system. 66 | install! 'cocoapods', :disable_input_output_paths => true 67 | 68 | post_install do |installer| 69 | installer.pods_project.targets.each do |target| 70 | target.build_configurations.each do |config| 71 | config.build_settings['ENABLE_BITCODE'] = 'NO' 72 | end 73 | end 74 | end 75 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 11 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 12 | 3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; }; 13 | 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 14 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 15 | 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; }; 16 | 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 17 | 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB21CF90195004384FC /* Debug.xcconfig */; }; 18 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 19 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 20 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 21 | /* End PBXBuildFile section */ 22 | 23 | /* Begin PBXCopyFilesBuildPhase section */ 24 | 9705A1C41CF9048500538489 /* Embed Frameworks */ = { 25 | isa = PBXCopyFilesBuildPhase; 26 | buildActionMask = 2147483647; 27 | dstPath = ""; 28 | dstSubfolderSpec = 10; 29 | files = ( 30 | 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */, 31 | 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */, 32 | ); 33 | name = "Embed Frameworks"; 34 | runOnlyForDeploymentPostprocessing = 0; 35 | }; 36 | /* End PBXCopyFilesBuildPhase section */ 37 | 38 | /* Begin PBXFileReference section */ 39 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 40 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 41 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 42 | 3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = ""; }; 43 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 44 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 45 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 46 | 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 47 | 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 48 | 9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = ""; }; 49 | 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 50 | 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 51 | 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 52 | 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 53 | 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 54 | /* End PBXFileReference section */ 55 | 56 | /* Begin PBXFrameworksBuildPhase section */ 57 | 97C146EB1CF9000F007C117D /* Frameworks */ = { 58 | isa = PBXFrameworksBuildPhase; 59 | buildActionMask = 2147483647; 60 | files = ( 61 | 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */, 62 | 3B80C3941E831B6300D905FE /* App.framework in Frameworks */, 63 | ); 64 | runOnlyForDeploymentPostprocessing = 0; 65 | }; 66 | /* End PBXFrameworksBuildPhase section */ 67 | 68 | /* Begin PBXGroup section */ 69 | 9740EEB11CF90186004384FC /* Flutter */ = { 70 | isa = PBXGroup; 71 | children = ( 72 | 3B80C3931E831B6300D905FE /* App.framework */, 73 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 74 | 9740EEBA1CF902C7004384FC /* Flutter.framework */, 75 | 9740EEB21CF90195004384FC /* Debug.xcconfig */, 76 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 77 | 9740EEB31CF90195004384FC /* Generated.xcconfig */, 78 | ); 79 | name = Flutter; 80 | sourceTree = ""; 81 | }; 82 | 97C146E51CF9000F007C117D = { 83 | isa = PBXGroup; 84 | children = ( 85 | 9740EEB11CF90186004384FC /* Flutter */, 86 | 97C146F01CF9000F007C117D /* Runner */, 87 | 97C146EF1CF9000F007C117D /* Products */, 88 | ); 89 | sourceTree = ""; 90 | }; 91 | 97C146EF1CF9000F007C117D /* Products */ = { 92 | isa = PBXGroup; 93 | children = ( 94 | 97C146EE1CF9000F007C117D /* Runner.app */, 95 | ); 96 | name = Products; 97 | sourceTree = ""; 98 | }; 99 | 97C146F01CF9000F007C117D /* Runner */ = { 100 | isa = PBXGroup; 101 | children = ( 102 | 97C146FA1CF9000F007C117D /* Main.storyboard */, 103 | 97C146FD1CF9000F007C117D /* Assets.xcassets */, 104 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 105 | 97C147021CF9000F007C117D /* Info.plist */, 106 | 97C146F11CF9000F007C117D /* Supporting Files */, 107 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 108 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 109 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, 110 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, 111 | ); 112 | path = Runner; 113 | sourceTree = ""; 114 | }; 115 | 97C146F11CF9000F007C117D /* Supporting Files */ = { 116 | isa = PBXGroup; 117 | children = ( 118 | ); 119 | name = "Supporting Files"; 120 | sourceTree = ""; 121 | }; 122 | /* End PBXGroup section */ 123 | 124 | /* Begin PBXNativeTarget section */ 125 | 97C146ED1CF9000F007C117D /* Runner */ = { 126 | isa = PBXNativeTarget; 127 | buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; 128 | buildPhases = ( 129 | 9740EEB61CF901F6004384FC /* Run Script */, 130 | 97C146EA1CF9000F007C117D /* Sources */, 131 | 97C146EB1CF9000F007C117D /* Frameworks */, 132 | 97C146EC1CF9000F007C117D /* Resources */, 133 | 9705A1C41CF9048500538489 /* Embed Frameworks */, 134 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 135 | ); 136 | buildRules = ( 137 | ); 138 | dependencies = ( 139 | ); 140 | name = Runner; 141 | productName = Runner; 142 | productReference = 97C146EE1CF9000F007C117D /* Runner.app */; 143 | productType = "com.apple.product-type.application"; 144 | }; 145 | /* End PBXNativeTarget section */ 146 | 147 | /* Begin PBXProject section */ 148 | 97C146E61CF9000F007C117D /* Project object */ = { 149 | isa = PBXProject; 150 | attributes = { 151 | LastUpgradeCheck = 1020; 152 | ORGANIZATIONNAME = "The Chromium Authors"; 153 | TargetAttributes = { 154 | 97C146ED1CF9000F007C117D = { 155 | CreatedOnToolsVersion = 7.3.1; 156 | LastSwiftMigration = 0910; 157 | }; 158 | }; 159 | }; 160 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; 161 | compatibilityVersion = "Xcode 3.2"; 162 | developmentRegion = en; 163 | hasScannedForEncodings = 0; 164 | knownRegions = ( 165 | en, 166 | Base, 167 | ); 168 | mainGroup = 97C146E51CF9000F007C117D; 169 | productRefGroup = 97C146EF1CF9000F007C117D /* Products */; 170 | projectDirPath = ""; 171 | projectRoot = ""; 172 | targets = ( 173 | 97C146ED1CF9000F007C117D /* Runner */, 174 | ); 175 | }; 176 | /* End PBXProject section */ 177 | 178 | /* Begin PBXResourcesBuildPhase section */ 179 | 97C146EC1CF9000F007C117D /* Resources */ = { 180 | isa = PBXResourcesBuildPhase; 181 | buildActionMask = 2147483647; 182 | files = ( 183 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 184 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 185 | 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */, 186 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 187 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, 188 | ); 189 | runOnlyForDeploymentPostprocessing = 0; 190 | }; 191 | /* End PBXResourcesBuildPhase section */ 192 | 193 | /* Begin PBXShellScriptBuildPhase section */ 194 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { 195 | isa = PBXShellScriptBuildPhase; 196 | buildActionMask = 2147483647; 197 | files = ( 198 | ); 199 | inputPaths = ( 200 | ); 201 | name = "Thin Binary"; 202 | outputPaths = ( 203 | ); 204 | runOnlyForDeploymentPostprocessing = 0; 205 | shellPath = /bin/sh; 206 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin"; 207 | }; 208 | 9740EEB61CF901F6004384FC /* Run Script */ = { 209 | isa = PBXShellScriptBuildPhase; 210 | buildActionMask = 2147483647; 211 | files = ( 212 | ); 213 | inputPaths = ( 214 | ); 215 | name = "Run Script"; 216 | outputPaths = ( 217 | ); 218 | runOnlyForDeploymentPostprocessing = 0; 219 | shellPath = /bin/sh; 220 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; 221 | }; 222 | /* End PBXShellScriptBuildPhase section */ 223 | 224 | /* Begin PBXSourcesBuildPhase section */ 225 | 97C146EA1CF9000F007C117D /* Sources */ = { 226 | isa = PBXSourcesBuildPhase; 227 | buildActionMask = 2147483647; 228 | files = ( 229 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 230 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, 231 | ); 232 | runOnlyForDeploymentPostprocessing = 0; 233 | }; 234 | /* End PBXSourcesBuildPhase section */ 235 | 236 | /* Begin PBXVariantGroup section */ 237 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = { 238 | isa = PBXVariantGroup; 239 | children = ( 240 | 97C146FB1CF9000F007C117D /* Base */, 241 | ); 242 | name = Main.storyboard; 243 | sourceTree = ""; 244 | }; 245 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { 246 | isa = PBXVariantGroup; 247 | children = ( 248 | 97C147001CF9000F007C117D /* Base */, 249 | ); 250 | name = LaunchScreen.storyboard; 251 | sourceTree = ""; 252 | }; 253 | /* End PBXVariantGroup section */ 254 | 255 | /* Begin XCBuildConfiguration section */ 256 | 249021D3217E4FDB00AE95B9 /* Profile */ = { 257 | isa = XCBuildConfiguration; 258 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 259 | buildSettings = { 260 | ALWAYS_SEARCH_USER_PATHS = NO; 261 | CLANG_ANALYZER_NONNULL = YES; 262 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 263 | CLANG_CXX_LIBRARY = "libc++"; 264 | CLANG_ENABLE_MODULES = YES; 265 | CLANG_ENABLE_OBJC_ARC = YES; 266 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 267 | CLANG_WARN_BOOL_CONVERSION = YES; 268 | CLANG_WARN_COMMA = YES; 269 | CLANG_WARN_CONSTANT_CONVERSION = YES; 270 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 271 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 272 | CLANG_WARN_EMPTY_BODY = YES; 273 | CLANG_WARN_ENUM_CONVERSION = YES; 274 | CLANG_WARN_INFINITE_RECURSION = YES; 275 | CLANG_WARN_INT_CONVERSION = YES; 276 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 277 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 278 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 279 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 280 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 281 | CLANG_WARN_STRICT_PROTOTYPES = YES; 282 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 283 | CLANG_WARN_UNREACHABLE_CODE = YES; 284 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 285 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 286 | COPY_PHASE_STRIP = NO; 287 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 288 | ENABLE_NS_ASSERTIONS = NO; 289 | ENABLE_STRICT_OBJC_MSGSEND = YES; 290 | GCC_C_LANGUAGE_STANDARD = gnu99; 291 | GCC_NO_COMMON_BLOCKS = YES; 292 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 293 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 294 | GCC_WARN_UNDECLARED_SELECTOR = YES; 295 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 296 | GCC_WARN_UNUSED_FUNCTION = YES; 297 | GCC_WARN_UNUSED_VARIABLE = YES; 298 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 299 | MTL_ENABLE_DEBUG_INFO = NO; 300 | SDKROOT = iphoneos; 301 | TARGETED_DEVICE_FAMILY = "1,2"; 302 | VALIDATE_PRODUCT = YES; 303 | }; 304 | name = Profile; 305 | }; 306 | 249021D4217E4FDB00AE95B9 /* Profile */ = { 307 | isa = XCBuildConfiguration; 308 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 309 | buildSettings = { 310 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 311 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 312 | DEVELOPMENT_TEAM = S8QB4VV633; 313 | ENABLE_BITCODE = NO; 314 | FRAMEWORK_SEARCH_PATHS = ( 315 | "$(inherited)", 316 | "$(PROJECT_DIR)/Flutter", 317 | ); 318 | INFOPLIST_FILE = Runner/Info.plist; 319 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 320 | LIBRARY_SEARCH_PATHS = ( 321 | "$(inherited)", 322 | "$(PROJECT_DIR)/Flutter", 323 | ); 324 | PRODUCT_BUNDLE_IDENTIFIER = com.wisecrab.formValidatorsExample; 325 | PRODUCT_NAME = "$(TARGET_NAME)"; 326 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 327 | SWIFT_VERSION = 4.0; 328 | VERSIONING_SYSTEM = "apple-generic"; 329 | }; 330 | name = Profile; 331 | }; 332 | 97C147031CF9000F007C117D /* Debug */ = { 333 | isa = XCBuildConfiguration; 334 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 335 | buildSettings = { 336 | ALWAYS_SEARCH_USER_PATHS = NO; 337 | CLANG_ANALYZER_NONNULL = YES; 338 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 339 | CLANG_CXX_LIBRARY = "libc++"; 340 | CLANG_ENABLE_MODULES = YES; 341 | CLANG_ENABLE_OBJC_ARC = YES; 342 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 343 | CLANG_WARN_BOOL_CONVERSION = YES; 344 | CLANG_WARN_COMMA = YES; 345 | CLANG_WARN_CONSTANT_CONVERSION = YES; 346 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 347 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 348 | CLANG_WARN_EMPTY_BODY = YES; 349 | CLANG_WARN_ENUM_CONVERSION = YES; 350 | CLANG_WARN_INFINITE_RECURSION = YES; 351 | CLANG_WARN_INT_CONVERSION = YES; 352 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 353 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 354 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 355 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 356 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 357 | CLANG_WARN_STRICT_PROTOTYPES = YES; 358 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 359 | CLANG_WARN_UNREACHABLE_CODE = YES; 360 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 361 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 362 | COPY_PHASE_STRIP = NO; 363 | DEBUG_INFORMATION_FORMAT = dwarf; 364 | ENABLE_STRICT_OBJC_MSGSEND = YES; 365 | ENABLE_TESTABILITY = YES; 366 | GCC_C_LANGUAGE_STANDARD = gnu99; 367 | GCC_DYNAMIC_NO_PIC = NO; 368 | GCC_NO_COMMON_BLOCKS = YES; 369 | GCC_OPTIMIZATION_LEVEL = 0; 370 | GCC_PREPROCESSOR_DEFINITIONS = ( 371 | "DEBUG=1", 372 | "$(inherited)", 373 | ); 374 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 375 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 376 | GCC_WARN_UNDECLARED_SELECTOR = YES; 377 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 378 | GCC_WARN_UNUSED_FUNCTION = YES; 379 | GCC_WARN_UNUSED_VARIABLE = YES; 380 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 381 | MTL_ENABLE_DEBUG_INFO = YES; 382 | ONLY_ACTIVE_ARCH = YES; 383 | SDKROOT = iphoneos; 384 | TARGETED_DEVICE_FAMILY = "1,2"; 385 | }; 386 | name = Debug; 387 | }; 388 | 97C147041CF9000F007C117D /* Release */ = { 389 | isa = XCBuildConfiguration; 390 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 391 | buildSettings = { 392 | ALWAYS_SEARCH_USER_PATHS = NO; 393 | CLANG_ANALYZER_NONNULL = YES; 394 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 395 | CLANG_CXX_LIBRARY = "libc++"; 396 | CLANG_ENABLE_MODULES = YES; 397 | CLANG_ENABLE_OBJC_ARC = YES; 398 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 399 | CLANG_WARN_BOOL_CONVERSION = YES; 400 | CLANG_WARN_COMMA = YES; 401 | CLANG_WARN_CONSTANT_CONVERSION = YES; 402 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 403 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 404 | CLANG_WARN_EMPTY_BODY = YES; 405 | CLANG_WARN_ENUM_CONVERSION = YES; 406 | CLANG_WARN_INFINITE_RECURSION = YES; 407 | CLANG_WARN_INT_CONVERSION = YES; 408 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 409 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 410 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 411 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 412 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 413 | CLANG_WARN_STRICT_PROTOTYPES = YES; 414 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 415 | CLANG_WARN_UNREACHABLE_CODE = YES; 416 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 417 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 418 | COPY_PHASE_STRIP = NO; 419 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 420 | ENABLE_NS_ASSERTIONS = NO; 421 | ENABLE_STRICT_OBJC_MSGSEND = YES; 422 | GCC_C_LANGUAGE_STANDARD = gnu99; 423 | GCC_NO_COMMON_BLOCKS = YES; 424 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 425 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 426 | GCC_WARN_UNDECLARED_SELECTOR = YES; 427 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 428 | GCC_WARN_UNUSED_FUNCTION = YES; 429 | GCC_WARN_UNUSED_VARIABLE = YES; 430 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 431 | MTL_ENABLE_DEBUG_INFO = NO; 432 | SDKROOT = iphoneos; 433 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 434 | TARGETED_DEVICE_FAMILY = "1,2"; 435 | VALIDATE_PRODUCT = YES; 436 | }; 437 | name = Release; 438 | }; 439 | 97C147061CF9000F007C117D /* Debug */ = { 440 | isa = XCBuildConfiguration; 441 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 442 | buildSettings = { 443 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 444 | CLANG_ENABLE_MODULES = YES; 445 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 446 | ENABLE_BITCODE = NO; 447 | FRAMEWORK_SEARCH_PATHS = ( 448 | "$(inherited)", 449 | "$(PROJECT_DIR)/Flutter", 450 | ); 451 | INFOPLIST_FILE = Runner/Info.plist; 452 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 453 | LIBRARY_SEARCH_PATHS = ( 454 | "$(inherited)", 455 | "$(PROJECT_DIR)/Flutter", 456 | ); 457 | PRODUCT_BUNDLE_IDENTIFIER = com.wisecrab.formValidatorsExample; 458 | PRODUCT_NAME = "$(TARGET_NAME)"; 459 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 460 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 461 | SWIFT_SWIFT3_OBJC_INFERENCE = On; 462 | SWIFT_VERSION = 4.0; 463 | VERSIONING_SYSTEM = "apple-generic"; 464 | }; 465 | name = Debug; 466 | }; 467 | 97C147071CF9000F007C117D /* Release */ = { 468 | isa = XCBuildConfiguration; 469 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 470 | buildSettings = { 471 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 472 | CLANG_ENABLE_MODULES = YES; 473 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 474 | ENABLE_BITCODE = NO; 475 | FRAMEWORK_SEARCH_PATHS = ( 476 | "$(inherited)", 477 | "$(PROJECT_DIR)/Flutter", 478 | ); 479 | INFOPLIST_FILE = Runner/Info.plist; 480 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 481 | LIBRARY_SEARCH_PATHS = ( 482 | "$(inherited)", 483 | "$(PROJECT_DIR)/Flutter", 484 | ); 485 | PRODUCT_BUNDLE_IDENTIFIER = com.wisecrab.formValidatorsExample; 486 | PRODUCT_NAME = "$(TARGET_NAME)"; 487 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 488 | SWIFT_SWIFT3_OBJC_INFERENCE = On; 489 | SWIFT_VERSION = 4.0; 490 | VERSIONING_SYSTEM = "apple-generic"; 491 | }; 492 | name = Release; 493 | }; 494 | /* End XCBuildConfiguration section */ 495 | 496 | /* Begin XCConfigurationList section */ 497 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { 498 | isa = XCConfigurationList; 499 | buildConfigurations = ( 500 | 97C147031CF9000F007C117D /* Debug */, 501 | 97C147041CF9000F007C117D /* Release */, 502 | 249021D3217E4FDB00AE95B9 /* Profile */, 503 | ); 504 | defaultConfigurationIsVisible = 0; 505 | defaultConfigurationName = Release; 506 | }; 507 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { 508 | isa = XCConfigurationList; 509 | buildConfigurations = ( 510 | 97C147061CF9000F007C117D /* Debug */, 511 | 97C147071CF9000F007C117D /* Release */, 512 | 249021D4217E4FDB00AE95B9 /* Profile */, 513 | ); 514 | defaultConfigurationIsVisible = 0; 515 | defaultConfigurationName = Release; 516 | }; 517 | /* End XCConfigurationList section */ 518 | 519 | }; 520 | rootObject = 97C146E61CF9000F007C117D /* Project object */; 521 | } 522 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aarajput/flutter_form_validators/6f35ba80e06010aba6935961650d673209027003/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aarajput/flutter_form_validators/6f35ba80e06010aba6935961650d673209027003/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aarajput/flutter_form_validators/6f35ba80e06010aba6935961650d673209027003/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aarajput/flutter_form_validators/6f35ba80e06010aba6935961650d673209027003/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aarajput/flutter_form_validators/6f35ba80e06010aba6935961650d673209027003/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aarajput/flutter_form_validators/6f35ba80e06010aba6935961650d673209027003/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aarajput/flutter_form_validators/6f35ba80e06010aba6935961650d673209027003/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aarajput/flutter_form_validators/6f35ba80e06010aba6935961650d673209027003/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aarajput/flutter_form_validators/6f35ba80e06010aba6935961650d673209027003/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aarajput/flutter_form_validators/6f35ba80e06010aba6935961650d673209027003/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aarajput/flutter_form_validators/6f35ba80e06010aba6935961650d673209027003/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aarajput/flutter_form_validators/6f35ba80e06010aba6935961650d673209027003/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aarajput/flutter_form_validators/6f35ba80e06010aba6935961650d673209027003/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aarajput/flutter_form_validators/6f35ba80e06010aba6935961650d673209027003/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aarajput/flutter_form_validators/6f35ba80e06010aba6935961650d673209027003/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aarajput/flutter_form_validators/6f35ba80e06010aba6935961650d673209027003/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aarajput/flutter_form_validators/6f35ba80e06010aba6935961650d673209027003/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aarajput/flutter_form_validators/6f35ba80e06010aba6935961650d673209027003/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | form_validators_example 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /example/ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" -------------------------------------------------------------------------------- /example/lib/compose_validation_container.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:wc_form_validators/wc_form_validators.dart'; 3 | 4 | class ComposeValidationContainer extends StatefulWidget { 5 | @override 6 | _ComposeValidationContainerState createState() => 7 | _ComposeValidationContainerState(); 8 | } 9 | 10 | class _ComposeValidationContainerState 11 | extends State { 12 | final formKey = GlobalKey(); 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | return Form( 17 | key: formKey, 18 | child: Column( 19 | children: [ 20 | TextFormField( 21 | decoration: InputDecoration( 22 | labelText: 'Compose', 23 | ), 24 | validator: Validators.compose([ 25 | Validators.required('Compose is required'), 26 | Validators.minLength(5, 'Characters cannot be less than 5'), 27 | Validators.maxLength(10, 'Characters cannot be greater than 10'), 28 | ]), 29 | ), 30 | ElevatedButton( 31 | child: Text('Validate compose Field'), 32 | onPressed: () { 33 | formKey.currentState?.validate(); 34 | }, 35 | ) 36 | ], 37 | ), 38 | ); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /example/lib/email_validation_container.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:wc_form_validators/wc_form_validators.dart'; 3 | 4 | class EmailValidationContainer extends StatefulWidget { 5 | @override 6 | _EmailValidationContainerState createState() => 7 | _EmailValidationContainerState(); 8 | } 9 | 10 | class _EmailValidationContainerState extends State { 11 | final formKey = GlobalKey(); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return Form( 16 | key: formKey, 17 | child: Column( 18 | children: [ 19 | TextFormField( 20 | decoration: InputDecoration( 21 | labelText: 'Email', 22 | ), 23 | validator: Validators.email('Invalid email address'), 24 | ), 25 | ElevatedButton( 26 | child: Text('Validate email Field'), 27 | onPressed: () { 28 | formKey.currentState?.validate(); 29 | }, 30 | ) 31 | ], 32 | ), 33 | ); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /example/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'required_validation_container.dart'; 4 | import 'min_validation_container.dart'; 5 | import 'max_validation_container.dart'; 6 | import 'email_validation_container.dart'; 7 | import 'min_length_validation_container.dart'; 8 | import 'max_length_validation_container.dart'; 9 | import 'pattern_validation_container.dart'; 10 | import 'compose_validation_container.dart'; 11 | 12 | void main() => runApp(MyApp()); 13 | 14 | class MyApp extends StatelessWidget { 15 | @override 16 | Widget build(BuildContext context) { 17 | return MaterialApp( 18 | theme: ThemeData( 19 | primarySwatch: Colors.blue, 20 | inputDecorationTheme: InputDecorationTheme( 21 | border: OutlineInputBorder(), 22 | ), 23 | buttonTheme: ButtonThemeData( 24 | textTheme: ButtonTextTheme.primary, 25 | ), 26 | ), 27 | home: Scaffold( 28 | appBar: AppBar( 29 | title: Text('Form Validtors Example'), 30 | ), 31 | body: _Body(), 32 | ), 33 | ); 34 | } 35 | } 36 | 37 | class _Body extends StatelessWidget { 38 | @override 39 | Widget build(BuildContext context) { 40 | return ListView( 41 | padding: EdgeInsets.symmetric( 42 | horizontal: 16, 43 | vertical: 8, 44 | ), 45 | children: [ 46 | SizedBox(height: 8), 47 | RequiredValidationContainer(), 48 | SizedBox(height: 8), 49 | Divider(), 50 | SizedBox(height: 8), 51 | MinValidationContainer(), 52 | SizedBox(height: 8), 53 | Divider(), 54 | SizedBox(height: 8), 55 | MaxValidationContainer(), 56 | SizedBox(height: 8), 57 | Divider(), 58 | SizedBox(height: 8), 59 | EmailValidationContainer(), 60 | SizedBox(height: 8), 61 | Divider(), 62 | SizedBox(height: 8), 63 | MinLengthValidationContainer(), 64 | SizedBox(height: 8), 65 | Divider(), 66 | SizedBox(height: 8), 67 | MaxLengthValidationContainer(), 68 | SizedBox(height: 8), 69 | Divider(), 70 | SizedBox(height: 8), 71 | PatternValidationContainer(), 72 | SizedBox(height: 8), 73 | Divider(), 74 | SizedBox(height: 8), 75 | ComposeValidationContainer(), 76 | ], 77 | ); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /example/lib/max_length_validation_container.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:wc_form_validators/wc_form_validators.dart'; 3 | 4 | class MaxLengthValidationContainer extends StatefulWidget { 5 | @override 6 | _MaxLengthValidationContainerState createState() => 7 | _MaxLengthValidationContainerState(); 8 | } 9 | 10 | class _MaxLengthValidationContainerState 11 | extends State { 12 | final formKey = GlobalKey(); 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | return Form( 17 | key: formKey, 18 | child: Column( 19 | children: [ 20 | TextFormField( 21 | decoration: InputDecoration( 22 | labelText: 'Maximum length 5', 23 | ), 24 | validator: Validators.maxLength(5, 'Characters are greater than 5'), 25 | ), 26 | ElevatedButton( 27 | child: Text('Validate Maximum length 5 Field'), 28 | onPressed: () { 29 | formKey.currentState?.validate(); 30 | }, 31 | ) 32 | ], 33 | ), 34 | ); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /example/lib/max_validation_container.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:wc_form_validators/wc_form_validators.dart'; 3 | 4 | class MaxValidationContainer extends StatefulWidget { 5 | @override 6 | _MaxValidationContainerState createState() => _MaxValidationContainerState(); 7 | } 8 | 9 | class _MaxValidationContainerState extends State { 10 | final formKey = GlobalKey(); 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | return Form( 15 | key: formKey, 16 | child: Column( 17 | children: [ 18 | TextFormField( 19 | keyboardType: TextInputType.numberWithOptions( 20 | decimal: true, 21 | signed: true, 22 | ), 23 | decoration: InputDecoration( 24 | labelText: 'Maximum 5', 25 | ), 26 | validator: Validators.max(5, 'Value greater than 5 not allowed'), 27 | ), 28 | ElevatedButton( 29 | child: Text('Validate Maximum 5 Field'), 30 | onPressed: () { 31 | formKey.currentState?.validate(); 32 | }, 33 | ) 34 | ], 35 | ), 36 | ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /example/lib/min_length_validation_container.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:wc_form_validators/wc_form_validators.dart'; 3 | 4 | class MinLengthValidationContainer extends StatefulWidget { 5 | @override 6 | _MinLengthValidationContainerState createState() => 7 | _MinLengthValidationContainerState(); 8 | } 9 | 10 | class _MinLengthValidationContainerState 11 | extends State { 12 | final formKey = GlobalKey(); 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | return Form( 17 | key: formKey, 18 | child: Column( 19 | children: [ 20 | TextFormField( 21 | decoration: InputDecoration( 22 | labelText: 'Minimum length 5', 23 | ), 24 | validator: Validators.minLength(5, 'Characters are less than 5'), 25 | ), 26 | ElevatedButton( 27 | child: Text('Validate Minimum length 5 Field'), 28 | onPressed: () { 29 | formKey.currentState?.validate(); 30 | }, 31 | ) 32 | ], 33 | ), 34 | ); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /example/lib/min_validation_container.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:wc_form_validators/wc_form_validators.dart'; 3 | 4 | class MinValidationContainer extends StatefulWidget { 5 | @override 6 | _MinValidationContainerState createState() => _MinValidationContainerState(); 7 | } 8 | 9 | class _MinValidationContainerState extends State { 10 | final formKey = GlobalKey(); 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | return Form( 15 | key: formKey, 16 | child: Column( 17 | children: [ 18 | TextFormField( 19 | keyboardType: TextInputType.numberWithOptions( 20 | decimal: true, 21 | signed: true, 22 | ), 23 | decoration: InputDecoration( 24 | labelText: 'Minimum 5', 25 | ), 26 | validator: Validators.min(5, 'Value less than 5 not allowed'), 27 | ), 28 | ElevatedButton( 29 | child: Text('Validate Minimum 5 Field'), 30 | onPressed: () { 31 | formKey.currentState?.validate(); 32 | }, 33 | ) 34 | ], 35 | ), 36 | ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /example/lib/pattern_validation_container.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:wc_form_validators/wc_form_validators.dart'; 3 | 4 | class PatternValidationContainer extends StatefulWidget { 5 | @override 6 | _PatternValidationContainerState createState() => 7 | _PatternValidationContainerState(); 8 | } 9 | 10 | class _PatternValidationContainerState 11 | extends State { 12 | final formKey = GlobalKey(); 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | return Form( 17 | key: formKey, 18 | child: Column( 19 | children: [ 20 | TextFormField( 21 | decoration: InputDecoration( 22 | labelText: 'Pattern r"^[A-Za-z]+\$"', 23 | ), 24 | validator: Validators.patternRegExp( 25 | RegExp(r"^[A-Za-z]+$"), 'Only alphabets are allowed'), 26 | ), 27 | ElevatedButton( 28 | child: Text('Validate Pattern Field'), 29 | onPressed: () { 30 | formKey.currentState?.validate(); 31 | }, 32 | ) 33 | ], 34 | ), 35 | ); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /example/lib/required_validation_container.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:wc_form_validators/wc_form_validators.dart'; 3 | 4 | class RequiredValidationContainer extends StatefulWidget { 5 | @override 6 | _RequiredValidationContainerState createState() => 7 | _RequiredValidationContainerState(); 8 | } 9 | 10 | class _RequiredValidationContainerState 11 | extends State { 12 | final formKey = GlobalKey(); 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | return Form( 17 | key: formKey, 18 | child: Column( 19 | children: [ 20 | TextFormField( 21 | decoration: InputDecoration( 22 | labelText: 'Required', 23 | ), 24 | validator: Validators.required('Field is required'), 25 | ), 26 | ElevatedButton( 27 | child: Text('Validate Required Field'), 28 | onPressed: () { 29 | formKey.currentState?.validate(); 30 | }, 31 | ) 32 | ], 33 | ), 34 | ); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /example/pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | async: 5 | dependency: transitive 6 | description: 7 | name: async 8 | sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" 9 | url: "https://pub.dev" 10 | source: hosted 11 | version: "2.11.0" 12 | boolean_selector: 13 | dependency: transitive 14 | description: 15 | name: boolean_selector 16 | sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" 17 | url: "https://pub.dev" 18 | source: hosted 19 | version: "2.1.1" 20 | characters: 21 | dependency: transitive 22 | description: 23 | name: characters 24 | sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" 25 | url: "https://pub.dev" 26 | source: hosted 27 | version: "1.3.0" 28 | clock: 29 | dependency: transitive 30 | description: 31 | name: clock 32 | sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf 33 | url: "https://pub.dev" 34 | source: hosted 35 | version: "1.1.1" 36 | collection: 37 | dependency: transitive 38 | description: 39 | name: collection 40 | sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 41 | url: "https://pub.dev" 42 | source: hosted 43 | version: "1.17.2" 44 | fake_async: 45 | dependency: transitive 46 | description: 47 | name: fake_async 48 | sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" 49 | url: "https://pub.dev" 50 | source: hosted 51 | version: "1.3.1" 52 | flutter: 53 | dependency: "direct main" 54 | description: flutter 55 | source: sdk 56 | version: "0.0.0" 57 | flutter_test: 58 | dependency: "direct dev" 59 | description: flutter 60 | source: sdk 61 | version: "0.0.0" 62 | matcher: 63 | dependency: transitive 64 | description: 65 | name: matcher 66 | sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" 67 | url: "https://pub.dev" 68 | source: hosted 69 | version: "0.12.16" 70 | material_color_utilities: 71 | dependency: transitive 72 | description: 73 | name: material_color_utilities 74 | sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" 75 | url: "https://pub.dev" 76 | source: hosted 77 | version: "0.5.0" 78 | meta: 79 | dependency: transitive 80 | description: 81 | name: meta 82 | sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" 83 | url: "https://pub.dev" 84 | source: hosted 85 | version: "1.9.1" 86 | path: 87 | dependency: transitive 88 | description: 89 | name: path 90 | sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" 91 | url: "https://pub.dev" 92 | source: hosted 93 | version: "1.8.3" 94 | sky_engine: 95 | dependency: transitive 96 | description: flutter 97 | source: sdk 98 | version: "0.0.99" 99 | source_span: 100 | dependency: transitive 101 | description: 102 | name: source_span 103 | sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" 104 | url: "https://pub.dev" 105 | source: hosted 106 | version: "1.10.0" 107 | stack_trace: 108 | dependency: transitive 109 | description: 110 | name: stack_trace 111 | sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 112 | url: "https://pub.dev" 113 | source: hosted 114 | version: "1.11.0" 115 | stream_channel: 116 | dependency: transitive 117 | description: 118 | name: stream_channel 119 | sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" 120 | url: "https://pub.dev" 121 | source: hosted 122 | version: "2.1.1" 123 | string_scanner: 124 | dependency: transitive 125 | description: 126 | name: string_scanner 127 | sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" 128 | url: "https://pub.dev" 129 | source: hosted 130 | version: "1.2.0" 131 | term_glyph: 132 | dependency: transitive 133 | description: 134 | name: term_glyph 135 | sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 136 | url: "https://pub.dev" 137 | source: hosted 138 | version: "1.2.1" 139 | test_api: 140 | dependency: transitive 141 | description: 142 | name: test_api 143 | sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8" 144 | url: "https://pub.dev" 145 | source: hosted 146 | version: "0.6.0" 147 | vector_math: 148 | dependency: transitive 149 | description: 150 | name: vector_math 151 | sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" 152 | url: "https://pub.dev" 153 | source: hosted 154 | version: "2.1.4" 155 | wc_form_validators: 156 | dependency: "direct dev" 157 | description: 158 | path: ".." 159 | relative: true 160 | source: path 161 | version: "1.0.0" 162 | web: 163 | dependency: transitive 164 | description: 165 | name: web 166 | sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 167 | url: "https://pub.dev" 168 | source: hosted 169 | version: "0.1.4-beta" 170 | sdks: 171 | dart: ">=3.1.5 <4.0.0" 172 | -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: form_validators_example 2 | description: Demonstrates how to use the form_validators plugin. 3 | publish_to: 'none' 4 | 5 | environment: 6 | sdk: ">=3.1.5 <4.0.0" 7 | 8 | dependencies: 9 | flutter: 10 | sdk: flutter 11 | 12 | dev_dependencies: 13 | flutter_test: 14 | sdk: flutter 15 | 16 | wc_form_validators: 17 | path: ../ 18 | 19 | # For information on the generic Dart part of this file, see the 20 | # following page: https://dart.dev/tools/pub/pubspec 21 | 22 | # The following section is specific to Flutter. 23 | flutter: 24 | 25 | # The following line ensures that the Material Icons font is 26 | # included with your application, so that you can use the icons in 27 | # the material Icons class. 28 | uses-material-design: true 29 | 30 | # To add assets to your application, add an assets section, like this: 31 | # assets: 32 | # - images/a_dot_burr.jpeg 33 | # - images/a_dot_ham.jpeg 34 | 35 | # An image asset can refer to one or more resolution-specific "variants", see 36 | # https://flutter.dev/assets-and-images/#resolution-aware. 37 | 38 | # For details regarding adding assets from package dependencies, see 39 | # https://flutter.dev/assets-and-images/#from-packages 40 | 41 | # To add custom fonts to your application, add a fonts section here, 42 | # in this "flutter" section. Each entry in this list should have a 43 | # "family" key with the font family name, and a "fonts" key with a 44 | # list giving the asset and other descriptors for the font. For 45 | # example: 46 | # fonts: 47 | # - family: Schyler 48 | # fonts: 49 | # - asset: fonts/Schyler-Regular.ttf 50 | # - asset: fonts/Schyler-Italic.ttf 51 | # style: italic 52 | # - family: Trajan Pro 53 | # fonts: 54 | # - asset: fonts/TrajanPro.ttf 55 | # - asset: fonts/TrajanPro_Bold.ttf 56 | # weight: 700 57 | # 58 | # For details regarding fonts from package dependencies, 59 | # see https://flutter.dev/custom-fonts/#from-packages 60 | -------------------------------------------------------------------------------- /example/test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility that Flutter provides. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_test/flutter_test.dart'; 10 | 11 | import 'package:form_validators_example/main.dart'; 12 | 13 | void main() { 14 | testWidgets('Verify Platform version', (WidgetTester tester) async { 15 | // Build our app and trigger a frame. 16 | await tester.pumpWidget(MyApp()); 17 | 18 | // Verify that platform version is retrieved. 19 | expect( 20 | find.byWidgetPredicate( 21 | (Widget widget) => 22 | widget is Text && widget.data?.startsWith('Running on:') == true, 23 | ), 24 | findsOneWidget, 25 | ); 26 | }); 27 | } 28 | -------------------------------------------------------------------------------- /lib/wc_form_validators.dart: -------------------------------------------------------------------------------- 1 | library wc_form_validators; 2 | 3 | import 'package:flutter/widgets.dart' show FormFieldValidator; 4 | 5 | /// Provides a set of built-in validators that can be used by form fields. 6 | /// 7 | /// 8 | /// A validator is a function that processes a `FormField` 9 | /// and returns an error [String] or null. A null [String] means that validation has passed. 10 | class Validators { 11 | /// Validator that requires the field have a non-empty value. 12 | /// 13 | /// 14 | /// ### Validate that the field is non-empty 15 | /// 16 | /// 17 | /// ```dart 18 | /// TextFormField( 19 | /// labelText: 'Required', 20 | /// ), 21 | /// validator: Validators.required('Field is required'), 22 | /// ), 23 | /// ``` 24 | /// 25 | static FormFieldValidator required(String errorMessage) { 26 | return (value) { 27 | if (value == null) { 28 | value = ''; 29 | } 30 | if (value.isEmpty) 31 | return errorMessage; 32 | else 33 | return null; 34 | }; 35 | } 36 | 37 | /// Validator that requires the field's value to be greater than or equal to the provided number. 38 | /// 39 | /// 40 | /// ### Validate against a minimum of 5 41 | /// 42 | /// 43 | /// ```dart 44 | /// TextFormField( 45 | /// keyboardType: TextInputType.numberWithOptions( 46 | /// decimal: true, 47 | /// signed: true, 48 | /// ), 49 | /// decoration: InputDecoration( 50 | /// labelText: 'Minimum 5', 51 | /// ), 52 | /// validator: Validators.min(5, 'Value less than 5 not allowed'), 53 | /// ), 54 | /// ``` 55 | /// 56 | static FormFieldValidator min(double min, String errorMessage) { 57 | return (value) { 58 | if (value == null) { 59 | value = ''; 60 | } 61 | if (value.trim().isEmpty) 62 | return null; 63 | else { 64 | final dValue = _toDouble(value); 65 | if (dValue < min) 66 | return errorMessage; 67 | else 68 | return null; 69 | } 70 | }; 71 | } 72 | 73 | /// Validator that requires the field's value to be less than or equal to the provided number. 74 | /// 75 | /// 76 | /// ### Validate against a maximum of 5 77 | /// 78 | /// ``` dart 79 | /// TextFormField( 80 | /// keyboardType: TextInputType.numberWithOptions( 81 | /// decimal: true, 82 | /// signed: true, 83 | /// ), 84 | /// decoration: InputDecoration( 85 | /// labelText: 'Maximum 5', 86 | /// ), 87 | /// validator: Validators.max(5, 'Value greater than 5 not allowed'), 88 | /// ), 89 | /// ``` 90 | /// 91 | static FormFieldValidator max(double max, String errorMessage) { 92 | return (value) { 93 | if (value == null) { 94 | value = ''; 95 | } 96 | if (value.trim().isEmpty) 97 | return null; 98 | else { 99 | final dValue = _toDouble(value); 100 | if (dValue > max) 101 | return errorMessage; 102 | else 103 | return null; 104 | } 105 | }; 106 | } 107 | 108 | /// Validator that requires the field's value pass an email validation test. 109 | /// 110 | /// This validator uses Regex of HTML5 email validator. 111 | /// 112 | /// ```dart 113 | /// RegExp(r"^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,253}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,253}[a-zA-Z0-9])?)*$"); 114 | /// ``` 115 | /// 116 | /// 117 | /// ### Validate that the field matches a valid email pattern 118 | /// 119 | /// ```dart 120 | /// TextFormField( 121 | /// decoration: InputDecoration( 122 | /// labelText: 'Email', 123 | /// ), 124 | /// validator: Validators.email('Invalid email address'), 125 | /// ), 126 | /// ``` 127 | /// 128 | static FormFieldValidator email(String errorMessage) { 129 | return (value) { 130 | if (value == null) { 131 | value = ''; 132 | } 133 | if (value.isEmpty) 134 | return null; 135 | else { 136 | final emailRegex = RegExp( 137 | r"^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,253}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,253}[a-zA-Z0-9])?)*$"); 138 | if (emailRegex.hasMatch(value)) 139 | return null; 140 | else 141 | return errorMessage; 142 | } 143 | }; 144 | } 145 | 146 | /// Validator that requires the length of the field's value to be greater than or equal 147 | /// to the provided minimum length. 148 | /// 149 | /// 150 | /// ### Validate that the field has a minimum of 5 characters 151 | /// 152 | /// ```dart 153 | /// TextFormField( 154 | /// decoration: InputDecoration( 155 | /// labelText: 'Minimum length 5', 156 | /// ), 157 | /// validator: Validators.minLength(5, 'Characters are less than 5'), 158 | /// ), 159 | /// ``` 160 | /// 161 | static FormFieldValidator minLength( 162 | int minLength, String errorMessage) { 163 | return (value) { 164 | if (value == null) { 165 | value = ''; 166 | } 167 | if (value.isEmpty) return null; 168 | 169 | if (value.length < minLength) 170 | return errorMessage; 171 | else 172 | return null; 173 | }; 174 | } 175 | 176 | /// Validator that requires the length of the field's value to be less than or equal 177 | /// to the provided maximum length. 178 | /// 179 | /// 180 | /// ### Validate that the field has maximum of 5 characters 181 | /// 182 | /// ```dart 183 | /// TextFormField( 184 | /// decoration: InputDecoration( 185 | /// labelText: 'Maximum length 5', 186 | /// ), 187 | /// validator: Validators.maxLength(5, 'Characters are greater than 5'), 188 | /// ), 189 | /// ``` 190 | /// 191 | static FormFieldValidator maxLength( 192 | int maxLength, String errorMessage) { 193 | return (value) { 194 | if (value == null) { 195 | value = ''; 196 | } 197 | if (value.isEmpty) return null; 198 | 199 | if (value.length > maxLength) 200 | return errorMessage; 201 | else 202 | return null; 203 | }; 204 | } 205 | 206 | /// Validator that requires the field's value to match a regex pattern. 207 | /// 208 | /// 209 | /// Note that if a Regexp is provided, the Regexp is used as is to test the values. 210 | /// 211 | /// 212 | /// ### Validate that the field only contains alphabets 213 | /// 214 | /// ```dart 215 | /// TextFormField( 216 | /// decoration: InputDecoration( 217 | /// labelText: 'Pattern r"^[A-Za-z]+\$"', 218 | /// ), 219 | /// validator: Validators.patternString( 220 | /// r"^[A-Za-z]+$", 'Only alphabets are allowed'), 221 | /// ), 222 | /// ``` 223 | /// 224 | static FormFieldValidator patternString( 225 | String pattern, String errorMessage) { 226 | return patternRegExp(RegExp(pattern), errorMessage); 227 | } 228 | 229 | /// Validator that requires the field's value to match another value. 230 | /// 231 | /// This is particularly useful for confirmation fields like password confirmation, 232 | /// email confirmation, etc. 233 | /// 234 | /// ### Validate that the field matches the expected value 235 | /// 236 | /// ```dart 237 | /// TextFormField( 238 | /// decoration: InputDecoration( 239 | /// labelText: 'Confirm Value', 240 | /// ), 241 | /// validator: Validators.compose([ 242 | /// Validators.required('Confirmation is required'), 243 | /// Validators.mustMatch('originalValue', 'Values do not match'), 244 | /// ]), 245 | /// ), 246 | /// ``` 247 | /// 248 | static FormFieldValidator mustMatch( 249 | String expectedValue, String errorMessage) { 250 | return (value) { 251 | if (value == null) { 252 | value = ''; 253 | } 254 | if (value.isEmpty) return null; 255 | 256 | if (value != expectedValue) 257 | return errorMessage; 258 | else 259 | return null; 260 | }; 261 | } 262 | 263 | /// Validator that requires the field's value to match a regex pattern. 264 | /// 265 | /// 266 | /// Note that if a Regexp is provided, the Regexp is used as is to test the values. 267 | /// 268 | /// 269 | /// ### Validate that the field only contains alphabets 270 | /// 271 | /// ```dart 272 | /// TextFormField( 273 | /// decoration: InputDecoration( 274 | /// labelText: 'Pattern r"^[A-Za-z]+\$"', 275 | /// ), 276 | /// validator: Validators.patternRegExp( 277 | /// RegExp(r"^[A-Za-z]+$"), 'Only alphabets are allowed'), 278 | /// ), 279 | /// ``` 280 | /// 281 | static FormFieldValidator patternRegExp( 282 | RegExp pattern, String errorMessage) { 283 | return (value) { 284 | if (value == null) { 285 | value = ''; 286 | } 287 | if (value.isEmpty) return null; 288 | 289 | if (pattern.hasMatch(value)) 290 | return null; 291 | else 292 | return errorMessage; 293 | }; 294 | } 295 | 296 | /// Compose multiple validators into a single validator. 297 | /// 298 | /// ### Validate that the field is non-empty and has character length between 5 and 10 299 | /// 300 | /// ```dart 301 | /// TextFormField( 302 | /// decoration: InputDecoration( 303 | /// labelText: 'Compose', 304 | /// ), 305 | /// validator: Validators.compose([ 306 | /// Validators.required('Compose is required'), 307 | /// Validators.minLength(5, 'Characters cannot be less than 5'), 308 | /// Validators.maxLength(10, 'Characters cannot be greater than 10'), 309 | /// ]), 310 | /// ), 311 | /// ``` 312 | /// 313 | static FormFieldValidator compose( 314 | List> validators) { 315 | return (value) { 316 | for (final validator in validators) { 317 | final result = validator(value); 318 | if (result != null) return result; 319 | } 320 | return null; 321 | }; 322 | } 323 | 324 | // -------------------- private functions ---------------------- // 325 | 326 | static double _toDouble(String value) { 327 | value = value.replaceAll(' ', '').replaceAll(',', ''); 328 | return double.parse(value); 329 | } 330 | } 331 | -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | async: 5 | dependency: transitive 6 | description: 7 | name: async 8 | sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63 9 | url: "https://pub.dev" 10 | source: hosted 11 | version: "2.12.0" 12 | boolean_selector: 13 | dependency: transitive 14 | description: 15 | name: boolean_selector 16 | sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" 17 | url: "https://pub.dev" 18 | source: hosted 19 | version: "2.1.2" 20 | characters: 21 | dependency: transitive 22 | description: 23 | name: characters 24 | sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 25 | url: "https://pub.dev" 26 | source: hosted 27 | version: "1.4.0" 28 | clock: 29 | dependency: transitive 30 | description: 31 | name: clock 32 | sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b 33 | url: "https://pub.dev" 34 | source: hosted 35 | version: "1.1.2" 36 | collection: 37 | dependency: transitive 38 | description: 39 | name: collection 40 | sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" 41 | url: "https://pub.dev" 42 | source: hosted 43 | version: "1.19.1" 44 | fake_async: 45 | dependency: transitive 46 | description: 47 | name: fake_async 48 | sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc" 49 | url: "https://pub.dev" 50 | source: hosted 51 | version: "1.3.2" 52 | flutter: 53 | dependency: "direct main" 54 | description: flutter 55 | source: sdk 56 | version: "0.0.0" 57 | flutter_test: 58 | dependency: "direct dev" 59 | description: flutter 60 | source: sdk 61 | version: "0.0.0" 62 | leak_tracker: 63 | dependency: transitive 64 | description: 65 | name: leak_tracker 66 | sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec 67 | url: "https://pub.dev" 68 | source: hosted 69 | version: "10.0.8" 70 | leak_tracker_flutter_testing: 71 | dependency: transitive 72 | description: 73 | name: leak_tracker_flutter_testing 74 | sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 75 | url: "https://pub.dev" 76 | source: hosted 77 | version: "3.0.9" 78 | leak_tracker_testing: 79 | dependency: transitive 80 | description: 81 | name: leak_tracker_testing 82 | sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" 83 | url: "https://pub.dev" 84 | source: hosted 85 | version: "3.0.1" 86 | matcher: 87 | dependency: transitive 88 | description: 89 | name: matcher 90 | sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 91 | url: "https://pub.dev" 92 | source: hosted 93 | version: "0.12.17" 94 | material_color_utilities: 95 | dependency: transitive 96 | description: 97 | name: material_color_utilities 98 | sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec 99 | url: "https://pub.dev" 100 | source: hosted 101 | version: "0.11.1" 102 | meta: 103 | dependency: transitive 104 | description: 105 | name: meta 106 | sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c 107 | url: "https://pub.dev" 108 | source: hosted 109 | version: "1.16.0" 110 | path: 111 | dependency: transitive 112 | description: 113 | name: path 114 | sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" 115 | url: "https://pub.dev" 116 | source: hosted 117 | version: "1.9.1" 118 | sky_engine: 119 | dependency: transitive 120 | description: flutter 121 | source: sdk 122 | version: "0.0.0" 123 | source_span: 124 | dependency: transitive 125 | description: 126 | name: source_span 127 | sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" 128 | url: "https://pub.dev" 129 | source: hosted 130 | version: "1.10.1" 131 | stack_trace: 132 | dependency: transitive 133 | description: 134 | name: stack_trace 135 | sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" 136 | url: "https://pub.dev" 137 | source: hosted 138 | version: "1.12.1" 139 | stream_channel: 140 | dependency: transitive 141 | description: 142 | name: stream_channel 143 | sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" 144 | url: "https://pub.dev" 145 | source: hosted 146 | version: "2.1.4" 147 | string_scanner: 148 | dependency: transitive 149 | description: 150 | name: string_scanner 151 | sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" 152 | url: "https://pub.dev" 153 | source: hosted 154 | version: "1.4.1" 155 | term_glyph: 156 | dependency: transitive 157 | description: 158 | name: term_glyph 159 | sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" 160 | url: "https://pub.dev" 161 | source: hosted 162 | version: "1.2.2" 163 | test_api: 164 | dependency: transitive 165 | description: 166 | name: test_api 167 | sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd 168 | url: "https://pub.dev" 169 | source: hosted 170 | version: "0.7.4" 171 | vector_math: 172 | dependency: transitive 173 | description: 174 | name: vector_math 175 | sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" 176 | url: "https://pub.dev" 177 | source: hosted 178 | version: "2.1.4" 179 | vm_service: 180 | dependency: transitive 181 | description: 182 | name: vm_service 183 | sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14" 184 | url: "https://pub.dev" 185 | source: hosted 186 | version: "14.3.1" 187 | sdks: 188 | dart: ">=3.7.0-0 <4.0.0" 189 | flutter: ">=3.18.0-18.0.pre.54" 190 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: wc_form_validators 2 | description: A Flutter plugin to validate forms with utility functions. It can validate emails, regex, character limits and many more. 3 | version: 1.1.0 4 | homepage: https://github.com/aarajput/flutter_form_validators 5 | 6 | environment: 7 | sdk: ">=2.12.0 <4.0.0" 8 | 9 | dependencies: 10 | flutter: 11 | sdk: flutter 12 | 13 | dev_dependencies: 14 | flutter_test: 15 | sdk: flutter 16 | -------------------------------------------------------------------------------- /test/form_validators_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | 3 | import 'package:wc_form_validators/wc_form_validators.dart'; 4 | 5 | void main() { 6 | test('Test Validators.required', () { 7 | final errorMessage = 'Field is required'; 8 | expect(Validators.required(errorMessage)(null), errorMessage); 9 | expect(Validators.required(errorMessage)(''), errorMessage); 10 | expect(Validators.required(errorMessage)('any value'), null); 11 | }); 12 | 13 | test('Test Validators.min', () { 14 | final errorMessage = 'Value less than 3 not allowed'; 15 | expect(Validators.min(3, errorMessage)(''), null); 16 | expect(Validators.min(3, errorMessage)(' '), null); 17 | expect(Validators.min(3, errorMessage)(' 2'), errorMessage); 18 | expect(Validators.min(3, errorMessage)('1,000.2'), null); 19 | expect(Validators.min(3, errorMessage)('1,000. 2'), null); 20 | expect(Validators.min(3, errorMessage)('2'), errorMessage); 21 | expect(Validators.min(3, errorMessage)('3'), null); 22 | expect(Validators.min(3, errorMessage)('4'), null); 23 | }); 24 | 25 | test('Test Validators.max', () { 26 | final errorMessage = 'Value greater than 3 not allowed'; 27 | expect(Validators.max(3, errorMessage)(''), null); 28 | expect(Validators.max(3, errorMessage)(' '), null); 29 | expect(Validators.max(3, errorMessage)(' 2'), null); 30 | expect(Validators.max(3, errorMessage)('1,000.2'), errorMessage); 31 | expect(Validators.max(3, errorMessage)('1,000. 2'), errorMessage); 32 | expect(Validators.max(3, errorMessage)('2'), null); 33 | expect(Validators.max(3, errorMessage)('3'), null); 34 | expect(Validators.max(3, errorMessage)('4'), errorMessage); 35 | }); 36 | 37 | test('Test Validators.email', () { 38 | final errorMessage = 'Invalid email address'; 39 | expect(Validators.email(errorMessage)(''), null); 40 | expect(Validators.email(errorMessage)(' '), errorMessage); 41 | expect(Validators.email(errorMessage)('test'), errorMessage); 42 | expect(Validators.email(errorMessage)('test@example'), null); 43 | expect(Validators.email(errorMessage)('@example'), errorMessage); 44 | expect(Validators.email(errorMessage)('text@example.'), errorMessage); 45 | expect(Validators.email(errorMessage)('text@example.com'), null); 46 | expect(Validators.email(errorMessage)('example.com'), errorMessage); 47 | expect(Validators.email(errorMessage)('@example.com'), errorMessage); 48 | }); 49 | 50 | test('Test Validators.minLength', () { 51 | final errorMessage = 'Characters count is less than 3'; 52 | expect(Validators.minLength(3, errorMessage)(''), null); 53 | expect(Validators.minLength(3, errorMessage)('a'), errorMessage); 54 | expect(Validators.minLength(3, errorMessage)('ab'), errorMessage); 55 | expect(Validators.minLength(3, errorMessage)('abc'), null); 56 | expect(Validators.minLength(3, errorMessage)('abcd'), null); 57 | expect(Validators.minLength(3, errorMessage)('abcde'), null); 58 | }); 59 | 60 | test('Test Validators.maxLength', () { 61 | final errorMessage = 'Characters count is greater than 3'; 62 | expect(Validators.maxLength(3, errorMessage)(''), null); 63 | expect(Validators.maxLength(3, errorMessage)('a'), null); 64 | expect(Validators.maxLength(3, errorMessage)('ab'), null); 65 | expect(Validators.maxLength(3, errorMessage)('abc'), null); 66 | expect(Validators.maxLength(3, errorMessage)('abcd'), errorMessage); 67 | expect(Validators.maxLength(3, errorMessage)('abcde'), errorMessage); 68 | }); 69 | 70 | test('Test Validators.patternString', () { 71 | final errorMessage = 'Invalid value'; 72 | final regexString = r"^[A-Za-z]+$"; 73 | expect(Validators.patternString(regexString, errorMessage)(''), null); 74 | expect(Validators.patternString(regexString, errorMessage)(' '), errorMessage); 75 | expect(Validators.patternString(regexString, errorMessage)('a'), null); 76 | expect(Validators.patternString(regexString, errorMessage)('A'), null); 77 | expect(Validators.patternString(regexString, errorMessage)('abc'), null); 78 | expect(Validators.patternString(regexString, errorMessage)('AbC'), null); 79 | expect(Validators.patternString(regexString, errorMessage)('a1'), 80 | errorMessage); 81 | expect(Validators.patternString(regexString, errorMessage)('1a'), 82 | errorMessage); 83 | expect(Validators.patternString(regexString, errorMessage)('1a'), 84 | errorMessage); 85 | expect(Validators.patternString(regexString, errorMessage)('#()%'), 86 | errorMessage); 87 | }); 88 | 89 | test('Test Validators.compose', () { 90 | final errorMessageRequired = 'Field is required'; 91 | final errorMessageMin = 'Value less than 3 not allowed'; 92 | final errorMessageMax = 'Value greater than 6 not allowed'; 93 | final errorMessageEmail = 'Invalid email address'; 94 | final errorMessageMinLength = 'Characters count is less than 3'; 95 | final errorMessageMaxLength = 'Characters count is greater than 6'; 96 | 97 | expect(Validators.compose([])(''), null); 98 | // required + email 99 | expect( 100 | Validators.compose([ 101 | Validators.required(errorMessageRequired), 102 | Validators.email(errorMessageEmail), 103 | ])(''), 104 | errorMessageRequired); 105 | expect( 106 | Validators.compose([ 107 | Validators.required(errorMessageRequired), 108 | Validators.email(errorMessageEmail), 109 | ])('ali@m..'), 110 | errorMessageEmail); 111 | expect( 112 | Validators.compose([ 113 | Validators.required(errorMessageRequired), 114 | Validators.email(errorMessageEmail), 115 | ])('ali@m.c'), 116 | null); 117 | 118 | // required + minLength + maxLength 119 | expect( 120 | Validators.compose([ 121 | Validators.required(errorMessageRequired), 122 | Validators.minLength(3, errorMessageMinLength), 123 | Validators.maxLength(6, errorMessageMaxLength), 124 | ])(''), 125 | errorMessageRequired); 126 | expect( 127 | Validators.compose([ 128 | Validators.required(errorMessageRequired), 129 | Validators.minLength(3, errorMessageMinLength), 130 | Validators.maxLength(6, errorMessageMaxLength), 131 | ])('ab'), 132 | errorMessageMinLength); 133 | expect( 134 | Validators.compose([ 135 | Validators.required(errorMessageRequired), 136 | Validators.minLength(3, errorMessageMinLength), 137 | Validators.maxLength(6, errorMessageMaxLength), 138 | ])('abcdefg'), 139 | errorMessageMaxLength); 140 | expect( 141 | Validators.compose([ 142 | Validators.required(errorMessageRequired), 143 | Validators.minLength(3, errorMessageMinLength), 144 | Validators.maxLength(6, errorMessageMaxLength), 145 | ])('abcde'), 146 | null); 147 | 148 | // optional + min + max 149 | expect( 150 | Validators.compose([ 151 | Validators.min(3, errorMessageMin), 152 | Validators.max(6, errorMessageMax), 153 | ])(''), 154 | null); 155 | expect( 156 | Validators.compose([ 157 | Validators.min(3, errorMessageMin), 158 | Validators.max(6, errorMessageMax), 159 | ])('1'), 160 | errorMessageMin); 161 | expect( 162 | Validators.compose([ 163 | Validators.min(3, errorMessageMin), 164 | Validators.max(6, errorMessageMax), 165 | ])('7'), 166 | errorMessageMax); 167 | expect( 168 | Validators.compose([ 169 | Validators.min(3, errorMessageMin), 170 | Validators.max(6, errorMessageMax), 171 | ])('6'), 172 | null); 173 | }); 174 | } 175 | --------------------------------------------------------------------------------